// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "gdt.h"
#include <stdint.h>
#include <stddef.h>
#include "memory_manager.h"

static void *gdt_memset(void *s, int c, size_t n) {
    uint8_t *p = (uint8_t *)s;
    while (n--) {
        *p++ = (uint8_t)c;
    }
    return s;
}

#define GDT_ENTRIES 7

struct gdt_entry gdt[GDT_ENTRIES];
struct gdt_ptr gdtr;
struct tss_entry tss;

extern void gdt_flush(uint64_t);
extern void tss_flush(void);

static void gdt_set_gate(int num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) {
    gdt[num].base_low = (base & 0xFFFF);
    gdt[num].base_middle = (base >> 16) & 0xFF;
    gdt[num].base_high = (base >> 24) & 0xFF;

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

    gdt[num].granularity |= (gran & 0xF0);
    gdt[num].access = access;
}

static void gdt_set_tss_gate(int num, uint64_t base, uint32_t limit, uint8_t access, uint8_t gran) {
    // A TSS entry in x86_64 is 16 bytes (takes up 2 adjacent GDT entries)
    struct {
        uint16_t limit_low;
        uint16_t base_low;
        uint8_t  base_middle;
        uint8_t  access;
        uint8_t  granularity;
        uint8_t  base_high;
        uint32_t base_upper;
        uint32_t reserved;
    } __attribute__((packed)) *tss_desc = (void*)&gdt[num];

    tss_desc->base_low = (base & 0xFFFF);
    tss_desc->base_middle = (base >> 16) & 0xFF;
    tss_desc->base_high = (base >> 24) & 0xFF;
    tss_desc->base_upper = (base >> 32);

    tss_desc->limit_low = (limit & 0xFFFF);
    tss_desc->granularity = ((limit >> 16) & 0x0F);

    tss_desc->granularity |= (gran & 0xF0);
    tss_desc->access = access;
    tss_desc->reserved = 0;
}

void tss_set_stack(uint64_t kernel_stack) {
    tss.rsp0 = kernel_stack;
}

void gdt_init(void) {
    gdtr.limit = (sizeof(struct gdt_entry) * GDT_ENTRIES) - 1;
    gdtr.base = (uint64_t)&gdt;

    // NULL segment
    gdt_set_gate(0, 0, 0, 0, 0);

    // Kernel Code segment (Ring 0, 64-bit)
    // 0x9A: Present(1), Ring(0), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
    // 0xAF: Long Mode (64-bit) (L=1, DB=0)
    gdt_set_gate(1, 0, 0, 0x9A, 0xAF);

    // Kernel Data segment (Ring 0)
    // 0x92: Present(1), Ring(0), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
    gdt_set_gate(2, 0, 0, 0x92, 0xAF);

    // User Data segment (Ring 3)
    // 0xF2: Present(1), Ring(3), System(1), Executable(0), DirExpandDown(0), Writable(1), Accessed(0)
    gdt_set_gate(3, 0, 0, 0xF2, 0xAF);

    // User Code segment (Ring 3, 64-bit)
    // 0xFA: Present(1), Ring(3), System(1), Executable(1), DirConforming(0), Readable(1), Accessed(0)
    // 0xAF: Long Mode (64-bit)
    gdt_set_gate(4, 0, 0, 0xFA, 0xAF);

    // TSS segment (takes entries 5 and 6 technically because it's 16 bytes)
    gdt_memset(&tss, 0, sizeof(struct tss_entry));
    tss.iopb_offset = sizeof(struct tss_entry);
    
    // Allocate a default Ring 0 interrupt stack in case an interrupt fires early or 
    // the scheduler hasn't set one up yet for a task.
    void* initial_tss_stack = kmalloc_aligned(4096, 4096);
    if (initial_tss_stack) {
        tss.rsp0 = (uint64_t)initial_tss_stack + 4096;
    }
    
    gdt_set_tss_gate(5, (uint64_t)&tss, sizeof(struct tss_entry) - 1, 0x89, 0x00);

    gdt_flush((uint64_t)&gdtr);
    tss_flush();
}
