#include "gdt.h"

extern void gdt_flush();

gdt_entry_t gdt_entries[6];
gdt_ptr_t   gdt_ptr;
tss_entry_t tss_entry;
uint64_t rsp[256];

void init_gdt()
{
    gdt_ptr.limit = (sizeof(gdt_entry_t) * 6) - 1;
    gdt_ptr.base  = (uint64_t)&gdt_entries;

    gdt_set_gate(0, 0, 0, 0, 0);                // Null segment
    //0xAF instead of 0xCF because we are in 64bit
    gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xAF); // Code segment
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xAF); // Data segment
    gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xAF); // User mode code segment
    gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xAF); // User mode data segment
    //write_tss(5, &rsp);

    gdt_flush();
    //tss_flush();
}

// Set the value of one GDT entry.
void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran)
{
    gdt_entries[num].base_low    = (base & 0xFFFF);
    gdt_entries[num].base_middle = (base >> 16) & 0xFF;
    gdt_entries[num].base_high   = (base >> 24) & 0xFF;

    gdt_entries[num].limit_low   = (limit & 0xFFFF);
    gdt_entries[num].granularity = (limit >> 16) & 0x0F;

    gdt_entries[num].granularity |= gran & 0xF0;
    gdt_entries[num].access      = access;
}

// Initialise our task state segment structure.
void write_tss(int32_t num, uint64_t rsp0)
{
    // Firstly, let's compute the base and limit of our entry into the GDT.
    uint32_t base = (uint32_t) ((uint32_t)(&tss_entry) & 0xFFFFFFFF);
    uint32_t limit = base + sizeof(tss_entry);

    // Now, add our TSS descriptor's address to the GDT.
    gdt_set_gate(num, base, limit, 0xE9, 0x00);

    // Ensure the descriptor is initially zero.
    memset(&tss_entry, 0, sizeof(tss_entry));

    tss_entry.rsp0_low = (uint32_t) (rsp0 & 0xFFFFFFFF); // Set the kernel stack pointer.
    tss_entry.rsp0_high = (uint32_t) (rsp0 & 0x00000000FFFFFFFF);

}

void set_kernel_stack(uint64_t stack)
{
    tss_entry.rsp0_low = stack & 0xFFFFFFFF; // Set the kernel stack pointer.
    tss_entry.rsp0_high = stack & 0x00000000FFFFFFFF;
}
