/*
 * cpu.h
 *
 * Copyright (C) 2016 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/>.
 */

#ifndef _CPU_H_
#define _CPU_H_

#include <sdk/defs.h>
#include <sdk/cpu.h>

#define CPU_CONTROL_FLAG_PE (1 << 0)
#define CPU_CONTROL_FLAG_MP (1 << 1)
#define CPU_CONTROL_FLAG_EM (1 << 2)
#define CPU_CONTROL_FLAG_TS (1 << 3)
#define CPU_CONTROL_FLAG_ET (1 << 4)
#define CPU_CONTROL_FLAG_NE (1 << 5)
#define CPU_CONTROL_FLAG_WP (1 << 16)
#define CPU_CONTROL_FLAG_AM (1 << 18)
#define CPU_CONTROL_FLAG_NW (1 << 29)
#define CPU_CONTROL_FLAG_CD (1 << 30)
#define CPU_CONTROL_FLAG_PG (1 << 31)

#define CPU_FEATURE_FLAG_VME        (1 << 0)
#define CPU_FEATURE_FLAG_PVI        (1 << 1)
#define CPU_FEATURE_FLAG_TSD        (1 << 2)
#define CPU_FEATURE_FLAG_DE         (1 << 3)
#define CPU_FEATURE_FLAG_PSE        (1 << 4)
#define CPU_FEATURE_FLAG_PAE        (1 << 5)
#define CPU_FEATURE_FLAG_MCE        (1 << 6)
#define CPU_FEATURE_FLAG_PGE        (1 << 7)
#define CPU_FEATURE_FLAG_PCE        (1 << 8)
#define CPU_FEATURE_FLAG_OSXFSR     (1 << 9)
#define CPU_FEATURE_FLAG_OSXMMEXCPT (1 << 10)
#define CPU_FEATURE_FLAG_UMIP       (1 << 11)
#define CPU_FEATURE_FLAG_LA57       (1 << 12)
#define CPU_FEATURE_FLAG_VMXE       (1 << 13)
#define CPU_FEATURE_FLAG_SMXE       (1 << 14)
#define CPU_FEATURE_FLAG_FSGSBASE   (1 << 16)
#define CPU_FEATURE_FLAG_PCIDE      (1 << 17)
#define CPU_FEATURE_FLAG_OSXSAVE    (1 << 18)
#define CPU_FEATURE_FLAG_SMEP       (1 << 20)
#define CPU_FEATURE_FLAG_SMAP       (1 << 21)
#define CPU_FEATURE_FLAG_PKE        (1 << 22)

enum
{
    FPU_NOT_PRESENT = 0,
    FPU_LEGACY,
    FPU_XFSR,
};

typedef word_t port_t;

#define IO_PORT_FUNCTIONS(type, prefix)                                 \
    static inline type##_t cpu_read_port_##type(port_t port)            \
    {                                                                   \
        type##_t value;                                                 \
        __asm__ volatile ("in %1, %0\n" : "=a"(value) : "Nd"(port));    \
        return value;                                                   \
    }                                                                   \
                                                                        \
    static inline void cpu_write_port_##type(port_t port, type##_t value) \
    {                                                                   \
        __asm__ volatile ("out %0, %1\n" :: "a"(value), "Nd"(port));    \
    }                                                                   \
                                                                        \
    static inline void cpu_read_port_buffer_##type(port_t port, type##_t *buffer, size_t size) \
    {                                                                   \
        __asm__ volatile("cld\n"                                        \
                         "rep; ins" prefix "\n"                         \
                         :"+D"(buffer), "+c"(size)                      \
                         : "d"(port)                                    \
                         : "cc");                                       \
    }                                                                   \
                                                                        \
    static inline void cpu_write_port_buffer_##type(port_t port, const type##_t *buffer, size_t size) \
    {                                                                   \
        __asm__ volatile("cld\n"                                        \
                         "rep; outs " prefix "\n"                       \
                         : "+S"(buffer), "+c"(size)                     \
                         : "d"(port)                                    \
                         : "cc");                                       \
    }

IO_PORT_FUNCTIONS(byte, "b")
IO_PORT_FUNCTIONS(word, "w")
IO_PORT_FUNCTIONS(dword, "l")

static inline uintptr_t cpu_read_master_control_register(void)
{
    uintptr_t value;
    __asm__ volatile ("movl %%cr0, %0"
                      : "=r"((dword_t)value) /* output */
                      : /* input */
                      : /* clobber */);
    return value;
}

static inline void cpu_write_master_control_register(uintptr_t value)
{
    __asm__ volatile ("movl %0, %%cr0"
                      : /* output */
                      : "r"((dword_t)value) /* input */
                      : /* clobber */);
}

static inline uintptr_t cpu_read_faulting_address(void)
{
    uintptr_t value;
    __asm__ volatile ("movl %%cr2, %0"
                      : "=r"((dword_t)value) /* output */
                      : /* input */
                      : /* clobber */);
    return value;
}

static inline uintptr_t cpu_read_page_table_register(void)
{
     uintptr_t value;
    __asm__ volatile ("movl %%cr3, %0"
                      : "=r"((dword_t)value) /* output */
                      : /* input */
                      : /* clobber */);
    return value;
}

static inline void cpu_write_page_table_register(uintptr_t value)
{
    __asm__ volatile ("movl %0, %%cr3"
                      : /* output */
                      : "r"((dword_t)value) /* input */
                      : /* clobber */);
}

static inline uintptr_t cpu_read_feature_register(void)
{
    uintptr_t value;
    __asm__ volatile ("movl %%cr4, %0"
                      : "=r"((dword_t)value) /* output */
                      : /* input */
                      : /* clobber */);
    return value;
}

static inline void cpu_write_feature_register(uintptr_t value)
{
    __asm__ volatile ("movl %0, %%cr4"
                      : /* output */
                      : "r"((dword_t)value) /* input */
                      : /* clobber */);
}

static inline bool_t cpu_enable_interrupts(void)
{
    dword_t flags;
    __asm__ volatile ("pushf\n"
                      "sti\n"
                      "pop %0"
                      : "=g"(flags) /* output */
                      : /* input */
                      : /* clobber */);
    return !!(flags & CPU_STATUS_FLAG_IF);
}

static inline bool_t cpu_disable_interrupts(void)
{
    dword_t flags;
    __asm__ volatile ("pushf\n"
                      "cli\n"
                      "pop %0"
                      : "=g"(flags) /* output */
                      : /* input */
                      : /* clobber */);
    return !!(flags & CPU_STATUS_FLAG_IF);
}

static inline void cpu_set_interrupt_table(void *base, word_t size)
{
    struct
    {
        word_t length;
        void *base;
    } __attribute__((__packed__)) idtr = { size - 1, base };

    __asm__ volatile ("lidt %0"
                      : /* output */
                      : "m"(idtr) /* input */
                      : /* clobber */);
}

static inline void cpu_invalidate_tlb(void *address)
{
    __asm__ volatile ("invlpg (%0)"
                      : /* output */
                      : "r"(address) /* input */
                      : "memory" /* clobber */);
}

static inline void cpu_halt(void)
{
    __asm__ volatile ("hlt");
}

extern int cpu_fpu_present;
extern byte_t cpu_max_physical_bits;

static inline void cpu_save_fpu_state(void *state)
{
    switch (cpu_fpu_present)
    {
    case FPU_LEGACY:
        asm volatile ("fsave %0"
                      : "=m"(*(byte_t*)state) /* output */
                      : /* input */
                      : /* clobber */);
        break;

    case FPU_XFSR:
        asm volatile ("fxsave %0"
                      : "=m"(*(byte_t*)state) /* output */
                      : /* input */
                      : /* clobber */);
        break;
    }
}

static inline void cpu_restore_fpu_state(void *state)
{
    switch (cpu_fpu_present)
    {
    case FPU_LEGACY:
        asm volatile ("frstor %0"
                      : /* output */
                      : "m"(*(byte_t*)state) /* input */
                      : /* clobber */);
        break;

    case FPU_XFSR:
        asm volatile ("fxrstor %0"
                      : /* output */
                      : "m"(*(byte_t*)state) /* input */
                      : /* clobber */);
        break;
    }
}

void cpu_init(void);

#endif
