/*
 * segments.c
 *
 * Copyright (C) 2013 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <segments.h>

static gdt_descriptor_t gdt_table[GDT_MAX_ENTRIES];
static dword_t gdt_entry_count = 0;
static word_t system_code_sel, system_data_sel, user_code_sel, user_data_sel, tss_sel;
static tss_entry_t tss;

extern void init_cpu_gdt(gdt_descriptor_t *table, dword_t size, word_t code_selector, word_t data_selector, word_t tss_selector);

dword_t get_kernel_esp(void)
{
    return tss.esp0;
}

void set_kernel_esp(dword_t esp)
{
    tss.esp0 = esp;
}

word_t get_kernel_code_selector(void)
{
    return system_code_sel;
}

word_t get_kernel_data_selector(void)
{
    return system_data_sel;
}

word_t get_user_code_selector(void)
{
    return user_code_sel;
}

word_t get_user_data_selector(void)
{
    return user_data_sel;
}

word_t gdt_create_segment(dword_t base, dword_t limit, bool_t executable, byte_t priv_level, bool_t readwrite, bool_t dirconf, bool_t large)
{
    word_t selector;
    bool_t granularity = FALSE;

    if (gdt_entry_count == GDT_MAX_ENTRIES) return 0;
    if (priv_level > 3) return 0;
    if (limit > 0xFFFFF)
    {
        limit >>= 12;
        granularity = TRUE;
    }

    gdt_table[gdt_entry_count].base = base & 0x00FFFFFF;
    gdt_table[gdt_entry_count].base_high = base >> 24;
    gdt_table[gdt_entry_count].limit = limit & 0xFFFF;
    gdt_table[gdt_entry_count].limit_high = limit >> 16;
    gdt_table[gdt_entry_count].present =  TRUE;
    gdt_table[gdt_entry_count].accessed = FALSE;
    gdt_table[gdt_entry_count].executable = executable ? TRUE : FALSE;
    gdt_table[gdt_entry_count].readwrite = readwrite ? TRUE : FALSE;
    gdt_table[gdt_entry_count].dirconf = dirconf ? TRUE : FALSE;
    gdt_table[gdt_entry_count].rpl = priv_level;
    gdt_table[gdt_entry_count].size = large ? TRUE : FALSE;
    gdt_table[gdt_entry_count].granularity = granularity;
    gdt_table[gdt_entry_count].standard = 1;
    gdt_table[gdt_entry_count].always_zero = 0;
    selector = (gdt_entry_count << 3) | priv_level;
    gdt_entry_count++;

    return selector;
}

word_t gdt_create_tss(tss_entry_t *base, dword_t limit)
{
    word_t selector;

    if (gdt_entry_count == GDT_MAX_ENTRIES) return 0;
    if (limit < sizeof(tss_entry_t)) return 0;

    gdt_table[gdt_entry_count].base = (dword_t)base & 0x00FFFFFF;
    gdt_table[gdt_entry_count].base_high = (dword_t)base >> 24;
    gdt_table[gdt_entry_count].limit = limit & 0xFFFF;
    gdt_table[gdt_entry_count].limit_high = limit >> 16;
    gdt_table[gdt_entry_count].present =  TRUE;
    gdt_table[gdt_entry_count].accessed = TRUE;
    gdt_table[gdt_entry_count].executable = TRUE;
    gdt_table[gdt_entry_count].readwrite = FALSE;
    gdt_table[gdt_entry_count].dirconf = FALSE;
    gdt_table[gdt_entry_count].rpl = 0;
    gdt_table[gdt_entry_count].size = TRUE;
    gdt_table[gdt_entry_count].granularity = FALSE;
    gdt_table[gdt_entry_count].standard = 0;
    gdt_table[gdt_entry_count].always_zero = 0;
    selector = gdt_entry_count << 3;
    gdt_entry_count++;

    return selector;
}

void segments_init(void)
{
    gdt_entry_count = 0;
    memset(gdt_table, 0, sizeof(gdt_table));
    gdt_create_segment(0, 0, FALSE, 0, FALSE, FALSE, FALSE);
    system_code_sel = gdt_create_segment(0x00000000, 0xFFFFFFFF, TRUE, 0, TRUE, FALSE, TRUE);
    system_data_sel = gdt_create_segment(0x00000000, 0xFFFFFFFF, FALSE, 0, TRUE, FALSE, TRUE);
    user_code_sel = gdt_create_segment(0x00000000, 0xFFFFFFFF, TRUE, 3, TRUE, FALSE, TRUE);
    user_data_sel = gdt_create_segment(0x00000000, 0xFFFFFFFF, FALSE, 3, TRUE, FALSE, TRUE);
    tss_sel = gdt_create_tss(&tss, sizeof(tss));
    memset(&tss, 0, sizeof(tss));
    tss.ss0 = system_data_sel;
    tss.iopb = sizeof(tss_entry_t);
    init_cpu_gdt(gdt_table, sizeof(gdt_table), system_code_sel, system_data_sel, tss_sel);
}
