/*
 * cpu.c
 *
 * Copyright (C) 2018 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 <cpu.h>
#include <cpuid.h>

#define CPUID_GET_FEATURE_FLAGS    0x00000001
#define CPUID_GET_MISC_INFORMATION 0x80000008

#define CPUID_FEATURE_FLAG_ECX_SSE3    (1 << 0)
#define CPUID_FEATURE_FLAG_ECX_PCLMUL  (1 << 1)
#define CPUID_FEATURE_FLAG_ECX_DTES64  (1 << 2)
#define CPUID_FEATURE_FLAG_ECX_MON     (1 << 3)
#define CPUID_FEATURE_FLAG_ECX_DSCPL   (1 << 4)
#define CPUID_FEATURE_FLAG_ECX_VMX     (1 << 5)
#define CPUID_FEATURE_FLAG_ECX_SMX     (1 << 6)
#define CPUID_FEATURE_FLAG_ECX_EST     (1 << 7)
#define CPUID_FEATURE_FLAG_ECX_TM2     (1 << 8)
#define CPUID_FEATURE_FLAG_ECX_SSSE3   (1 << 9)
#define CPUID_FEATURE_FLAG_ECX_CID     (1 << 10)
#define CPUID_FEATURE_FLAG_ECX_SDBG    (1 << 11)
#define CPUID_FEATURE_FLAG_ECX_FMA     (1 << 12)
#define CPUID_FEATURE_FLAG_ECX_CX16    (1 << 13)
#define CPUID_FEATURE_FLAG_ECX_ETPRD   (1 << 14)
#define CPUID_FEATURE_FLAG_ECX_PDCM    (1 << 15)
#define CPUID_FEATURE_FLAG_ECX_PCID    (1 << 17)
#define CPUID_FEATURE_FLAG_ECX_DCA     (1 << 18)
#define CPUID_FEATURE_FLAG_ECX_SSE41   (1 << 19)
#define CPUID_FEATURE_FLAG_ECX_SSE42   (1 << 20)
#define CPUID_FEATURE_FLAG_ECX_X2APIC  (1 << 21)
#define CPUID_FEATURE_FLAG_ECX_MOVBE   (1 << 22)
#define CPUID_FEATURE_FLAG_ECX_POPCNT  (1 << 23)
#define CPUID_FEATURE_FLAG_ECX_TSCD    (1 << 24)
#define CPUID_FEATURE_FLAG_ECX_AES     (1 << 25)
#define CPUID_FEATURE_FLAG_ECX_XSAVE   (1 << 26)
#define CPUID_FEATURE_FLAG_ECX_OSXSAVE (1 << 27)
#define CPUID_FEATURE_FLAG_ECX_AVX     (1 << 28)
#define CPUID_FEATURE_FLAG_ECX_F16C    (1 << 29)
#define CPUID_FEATURE_FLAG_ECX_RDRAND  (1 << 30)
#define CPUID_FEATURE_FLAG_ECX_HV      (1 << 31)

#define CPUID_FEATURE_FLAG_EDX_FPU   (1 << 0)
#define CPUID_FEATURE_FLAG_EDX_VME   (1 << 1)
#define CPUID_FEATURE_FLAG_EDX_DE    (1 << 2)
#define CPUID_FEATURE_FLAG_EDX_PSE   (1 << 3)
#define CPUID_FEATURE_FLAG_EDX_TSC   (1 << 4)
#define CPUID_FEATURE_FLAG_EDX_MSR   (1 << 5)
#define CPUID_FEATURE_FLAG_EDX_PAE   (1 << 6)
#define CPUID_FEATURE_FLAG_EDX_MCE   (1 << 7)
#define CPUID_FEATURE_FLAG_EDX_CX8   (1 << 8)
#define CPUID_FEATURE_FLAG_EDX_APIC  (1 << 9)
#define CPUID_FEATURE_FLAG_EDX_SEP   (1 << 11)
#define CPUID_FEATURE_FLAG_EDX_MTRR  (1 << 12)
#define CPUID_FEATURE_FLAG_EDX_PGE   (1 << 13)
#define CPUID_FEATURE_FLAG_EDX_MCA   (1 << 14)
#define CPUID_FEATURE_FLAG_EDX_CMOV  (1 << 15)
#define CPUID_FEATURE_FLAG_EDX_PAT   (1 << 16)
#define CPUID_FEATURE_FLAG_EDX_PSE36 (1 << 17)
#define CPUID_FEATURE_FLAG_EDX_PSN   (1 << 18)
#define CPUID_FEATURE_FLAG_EDX_CLFL  (1 << 19)
#define CPUID_FEATURE_FLAG_EDX_DTES  (1 << 21)
#define CPUID_FEATURE_FLAG_EDX_ACPI  (1 << 22)
#define CPUID_FEATURE_FLAG_EDX_MMX   (1 << 23)
#define CPUID_FEATURE_FLAG_EDX_FXSR  (1 << 24)
#define CPUID_FEATURE_FLAG_EDX_SSE   (1 << 25)
#define CPUID_FEATURE_FLAG_EDX_SSE2  (1 << 26)
#define CPUID_FEATURE_FLAG_EDX_SS    (1 << 27)
#define CPUID_FEATURE_FLAG_EDX_HTT   (1 << 28)
#define CPUID_FEATURE_FLAG_EDX_TM1   (1 << 29)
#define CPUID_FEATURE_FLAG_EDX_IA64  (1 << 30)
#define CPUID_FEATURE_FLAG_EDX_PBE   (1 << 31)

int cpu_fpu_present = FPU_NOT_PRESENT;
byte_t cpu_max_physical_bits = 32;

static void fpu_init(void)
{
    cpu_write_master_control_register(cpu_read_master_control_register() & ~(CPU_CONTROL_FLAG_EM | CPU_CONTROL_FLAG_TS));
    __asm__ volatile ("fninit");
}

static void fpu_probe(void)
{
    word_t fpu_status = 0xCCCC;
    __asm__ volatile ("fnstsw %0"
                        : "=m" (fpu_status) /* output */
                        : /* input */
                        : /* clobber */);

    if (fpu_status == 0)
    {
        cpu_fpu_present = FPU_LEGACY;
    }
    else
    {
        cpu_fpu_present = FPU_NOT_PRESENT;
        cpu_write_master_control_register(cpu_read_master_control_register() | CPU_CONTROL_FLAG_EM);
    }
}

void cpu_init(void)
{
    unsigned int features[5], address_size_info, cpu_count_info, dummy;

    if (!__get_cpuid(CPUID_GET_FEATURE_FLAGS, &features[0], &features[1], &features[2], &features[3]))
    {
        fpu_init();
        fpu_probe();
        return;
    }

    if (features[3] & CPUID_FEATURE_FLAG_EDX_FPU)
    {
        fpu_init();

        if (features[3] & CPUID_FEATURE_FLAG_EDX_FXSR)
        {
            cpu_write_feature_register(cpu_read_feature_register() | CPU_FEATURE_FLAG_OSXFSR);
            cpu_fpu_present = FPU_XFSR;
        }
        else
        {
            cpu_fpu_present = FPU_LEGACY;
        }
    }

    if (features[3] & CPUID_FEATURE_FLAG_EDX_PGE)
    {
        cpu_write_feature_register(cpu_read_feature_register() | CPU_FEATURE_FLAG_PGE);
    }

    if (features[3] & CPUID_FEATURE_FLAG_EDX_PSE)
    {
        cpu_write_feature_register(cpu_read_feature_register() | CPU_FEATURE_FLAG_PSE);
    }

    if ((features[3] & CPUID_FEATURE_FLAG_EDX_PAE) || (features[3] & CPUID_FEATURE_FLAG_EDX_PSE36))
    {
        /* Don't enable PAE here, it might be disabled on the user's request */
        cpu_max_physical_bits = 36;
    }

    if (__get_cpuid(CPUID_GET_MISC_INFORMATION, &address_size_info, &features[4], &cpu_count_info, &dummy))
    {
        byte_t physical_bits = address_size_info & 0xFF;
        if (physical_bits > 36) cpu_max_physical_bits = physical_bits;
    }
}
