/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   x86 architecture-dependant initialization
 * @license GPL-3.0-only
 */

#include <arch/gdt.h>
#include <stdint.h>
#include <arch/x86.h>
#include <kernel.h>
#include <mem/utils.h>

/*
 * x86_overwrite_pat - Set PAT to WC
 *
 * This function overwrites the 1st Page Attribute
 * Table entry, to enable the Write-Combining property
 * when we map memory regions later on.
 * The framebuffer will be mapped with WC, which makes
 * memory access significantly faster by using burst
 * operations. 
*/

static void x86_overwrite_pat()
{
    uint64_t pat = rdmsr(0x277);
    pat &= ~(0xFFULL << 8); // Clear PAT1
    pat |= (0x01ULL << 8); // PAT1 = 0x01 (WC)
    wrmsr(0x277, pat);
}

/*
 * x86_enable_fpu - Enable Floating Point Unit
 *
 * This function enables the Floating Point Unit,
 * which allows the CPU to do floating point
 * operations.
 *
 * Here we do not check for FPU support but we
 * should. However most processors support it.
 */
static void x86_enable_fpu()
{
    size_t cr4;
	__asm__ volatile("mov %%cr4, %0" : "=r"(cr4));
	cr4 |= 0x200;
	__asm__ volatile("mov %0, %%cr4" :: "r"(cr4));
    uint16_t cw = 0x37F; // control word
    asm volatile("fldcw %0" :: "m"(cw));
}

/*
 * x86_arch_init - Initialize x86 CPU structures
 *
 * This function is responsible for overriding a PAT entry
 * (to put the framebuffer area in WC mode) only.
 *
 * Later, all architecture-dependant init (GDT, IDT, TSS, ...)
 * should be initialized here, and separate function pointers
 * should be set up for each arch.
 */
void x86_arch_init()
{
    x86_overwrite_pat();
    x86_enable_fpu();
    x86_cpu_identification();
    idt_init();
    gdt_init();
}

/*
 * cpu_supports_brandstring - Does the CPU support brand strings?
 *
 * Return:
 * true  - if it does
 * false - if it doesn't
*/
bool cpu_supports_brandstring() {
    uint32_t eax, ebx, ecx, edx;
    cpuid(0x80000000, &eax, &ebx, &ecx, &edx);

    if (eax < 0x80000004) {
        return false;
    } else {
        return true;
    }
}

/*
 * x86_cpu_idenfitication - get info about the CPU
 *
 * This function displays the CPU vendor name or the
 * extended "brand string" if it's supported, on
 * debug output.
*/
void x86_cpu_identification()
{
    if (cpu_supports_brandstring()) {
        uint32_t regs[12];
        // Some CPUs don't return null-terminated values so we do it as a failsafe default
        char str[sizeof(regs)+1] = {0};
        cpuid(0x80000002, &regs[0], &regs[1], &regs[2], &regs[3]);
        cpuid(0x80000003, &regs[4], &regs[5], &regs[6], &regs[7]);
        cpuid(0x80000004, &regs[8], &regs[9], &regs[10], &regs[11]);

        memcpy(str, regs, sizeof(regs));
        str[sizeof(regs)] = '\0';
        DEBUG("CPU: %s", str);
    } else {
        char vendor_string[13] = {0};
        cpuid_get_vendor_string(vendor_string);
        DEBUG("CPU vendor is: %s", vendor_string);
    }
}