#ifndef __X86_H__
#define __X86_H__

// CPU rings
#define DPL_KERN       0x0     // Kernel (ring 0)
#define DPL_USER       0x3     // User (ring 3)

// Application segment type bits
#define STA_X          0x8     // Executable segment
#define STA_W          0x2     // Writeable (non-executable segments)
#define STA_R          0x2     // Readable (executable segments)

// System segment type bits
#define STS_T32A       0x9     // Available 32-bit TSS
#define STS_IG32       0xE     // 32-bit Interrupt Gate
#define STS_TG32       0xF     // 32-bit Trap Gate

// Eflags register
#define FL_TF          0x00000100  // Trap Flag
#define FL_IF          0x00000200  // Interrupt Enable

// Control Register flags
#define CR0_PE         0x00000001  // Protection Enable
#define CR0_PG         0x80000000  // Paging

// Page directory and page table constants
#define PGSIZE         4096    // Bytes mapped by a page
#define PGMASK         4095    // Mask for bit ops
#define NR_PDE         1024    // # directory entries per page directory
#define NR_PTE         1024    // # PTEs per page table
#define PGSHFT         12      // log2(PGSIZE)
#define PTXSHFT        12      // Offset of PTX in a linear address
#define PDXSHFT        22      // Offset of PDX in a linear address

// Page table/directory entry flags
#define PTE_P          0x001   // Present
#define PTE_W          0x002   // Writeable
#define PTE_U          0x004   // User
#define PTE_PWT        0x008   // Write-Through
#define PTE_PCD        0x010   // Cache-Disable
#define PTE_A          0x020   // Accessed
#define PTE_D          0x040   // Dirty

// GDT entries
#define NR_SEG         6       // GDT size
#define SEG_KCODE      1       // Kernel code
#define SEG_KDATA      2       // Kernel data/stack
#define SEG_UCODE      3       // User code
#define SEG_UDATA      4       // User data/stack
#define SEG_TSS        5       // Global unique task state segement
#define KSEL(desc)     (((desc) << 3) | DPL_KERN)
#define USEL(desc)     (((desc) << 3) | DPL_USER)

// IDT size
#define NR_IRQ         256     // IDT size

// Interrupts and exceptions
#define T_IRQ0         32
#define IRQ_TIMER      0
#define IRQ_KBD        1
#define IRQ_ERROR      19
#define IRQ_SPURIOUS   31
#define EX_DIV         0
#define EX_UD          6
#define EX_NM          7
#define EX_DF          8
#define EX_TS          10
#define EX_NP          11
#define EX_SS          12
#define EX_GP          13
#define EX_PF          14
#define EX_MF          15
#define EX_SYSCALL     0x80

#define IRQS(_) \
  _(  0, KERN, NOERR) _(  1, KERN, NOERR) \
  _(  2, KERN, NOERR) _(  3, KERN, NOERR) \
  _(  4, KERN, NOERR) _(  5, KERN, NOERR) \
  _(  6, KERN, NOERR) _(  7, KERN, NOERR) \
  _(  8, KERN,   ERR) _(  9, KERN, NOERR) \
  _( 10, KERN,   ERR) _( 11, KERN,   ERR) \
  _( 12, KERN,   ERR) _( 13, KERN,   ERR) \
  _( 14, KERN,   ERR) _( 15, KERN, NOERR) \
  _( 16, KERN, NOERR) _( 19, KERN, NOERR) \
  _( 31, KERN, NOERR) \
  _( 32, KERN, NOERR) _( 33, KERN, NOERR) \
  _( 34, KERN, NOERR) _( 35, KERN, NOERR) \
  _( 36, KERN, NOERR) _( 37, KERN, NOERR) \
  _( 38, KERN, NOERR) _( 39, KERN, NOERR) \
  _( 40, KERN, NOERR) _( 41, KERN, NOERR) \
  _( 42, KERN, NOERR) _( 43, KERN, NOERR) \
  _( 44, KERN, NOERR) _( 45, KERN, NOERR) \
  _( 46, KERN, NOERR) _( 47, KERN, NOERR) \
  _(128, USER, NOERR)

// Below are only defined for c/cpp files
#ifndef __ASSEMBLER__

// +--------10------+-------10-------+---------12----------+
// | Page Directory |   Page Table   | Offset within Page  |
// |      Index     |      Index     |                     |
// +----------------+----------------+---------------------+
//  \--- PDX(va) --/ \--- PTX(va) --/\------ OFF(va) ------/
typedef uint32_t PTE;
typedef uint32_t PDE;
#define PDX(va)          (((uint32_t)(va) >> PDXSHFT) & 0x3ff)
#define PTX(va)          (((uint32_t)(va) >> PTXSHFT) & 0x3ff)
#define OFF(va)          ((uint32_t)(va) & 0xfff)
#define ROUNDUP(a, sz)   ((((uintptr_t)a)+(sz)-1) & ~((sz)-1))
#define ROUNDDOWN(a, sz) ((((uintptr_t)a)) & ~((sz)-1))
#define PTE_ADDR(pte)    ((uint32_t)(pte) & ~0xfff)
#define PGADDR(d, t, o)  ((uint32_t)((d) << PDXSHFT | (t) << PTXSHFT | (o)))

// Segment Descriptor
typedef struct SegDesc {
  uint32_t lim_15_0 : 16;  // Low bits of segment limit
  uint32_t base_15_0 : 16; // Low bits of segment base address
  uint32_t base_23_16 : 8; // Middle bits of segment base address
  uint32_t type : 4  ;     // Segment type (see STS_ constants)
  uint32_t s : 1;          // 0 = system, 1 = application
  uint32_t dpl : 2;        // Descriptor Privilege Level
  uint32_t p : 1;          // Present
  uint32_t lim_19_16 : 4;  // High bits of segment limit
  uint32_t avl : 1;        // Unused (available for software use)
  uint32_t rsv1 : 1;       // Reserved
  uint32_t db : 1;         // 0 = 16-bit segment, 1 = 32-bit segment
  uint32_t g : 1;          // Granularity: limit scaled by 4K when set
  uint32_t base_31_24 : 8; // High bits of segment base address
} SegDesc;

#define SEG(type, base, lim, dpl) (SegDesc)             \
{  ((lim) >> 12) & 0xffff, (uint32_t)(base) & 0xffff,   \
  ((uint32_t)(base) >> 16) & 0xff, type, 1, dpl, 1,     \
  (uint32_t)(lim) >> 28, 0, 0, 1, 1, (uint32_t)(base) >> 24 }

#define SEG16(type, base, lim, dpl) (SegDesc)           \
{  (lim) & 0xffff, (uint32_t)(base) & 0xffff,           \
  ((uint32_t)(base) >> 16) & 0xff, type, 0, dpl, 1,     \
  (uint32_t)(lim) >> 16, 0, 0, 1, 0, (uint32_t)(base) >> 24 }

// Gate descriptors for interrupts and traps
typedef struct GateDesc {
  uint32_t off_15_0 : 16;   // Low 16 bits of offset in segment
  uint32_t cs : 16;         // Code segment selector
  uint32_t args : 5;        // # args, 0 for interrupt/trap gates
  uint32_t rsv1 : 3;        // Reserved(should be zero I guess)
  uint32_t type : 4;        // Type(STS_{TG,IG32,TG32})
  uint32_t s : 1;           // Must be 0 (system)
  uint32_t dpl : 2;         // Descriptor(meaning new) privilege level
  uint32_t p : 1;           // Present
  uint32_t off_31_16 : 16;  // High bits of offset in segment
} GateDesc;

#define GATE(type, cs, entry, dpl) (GateDesc)                \
{  (uint32_t)(entry) & 0xffff, (cs), 0, 0, (type), 0, (dpl), \
  1, (uint32_t)(entry) >> 16 }


// Task state segment format
typedef struct TSS {
  uint32_t link;     // Unused
  uint32_t esp0;     // Stack pointers and segment selectors
  uint32_t ss0;      //   after an increase in privilege level
  char     padding[88];
} TSS;

// Interrupt and exception frame
typedef struct TrapFrame {
  uint32_t edi, esi, ebp, esp_;
  uint32_t ebx, edx, ecx, eax;   // Register saved by pushal
  uint32_t es, ds;               // Segment register
  int32_t  irq;                  // # of irq
  uint32_t err, eip, cs, eflags; // Execution state before trap 
  uint32_t esp, ss;              // Used only when returning to DPL=3
} TrapFrame;

// Multiprocesor configuration
typedef struct MPConf {    // configuration table header
  uint8_t  signature[4];    // "PCMP"
  uint16_t length;         // total table length
  uint8_t  version;         // [14]
  uint8_t  checksum;        // all bytes must add up to 0
  uint8_t  product[20];     // product id
  uint32_t *oemtable;      // OEM table pointer
  uint16_t oemlength;      // OEM table length
  uint16_t entry;          // entry count
  uint32_t *lapicaddr;     // address of local APIC
  uint16_t xlength;        // extended table length
  uint8_t  xchecksum;       // extended table checksum
  uint8_t  reserved;
} MPConf;

typedef struct MPDesc {
  int     magic;
  MPConf  *conf;     // MP config table addr
  uint8_t length;   // 1
  uint8_t specrev;  // [14]
  uint8_t checksum; // all bytes add to 0
  uint8_t type;     // config type
  uint8_t imcrp;
  uint8_t reserved[3];
} MPDesc;

#define asm  __asm__

static inline uint8_t inb(int port) {
  uint8_t data;
  asm volatile ("inb %1, %0" : "=a"(data) : "d"((uint16_t)port));
  return data;
}

static inline uint32_t inw(int port) {
  uint16_t data;
  asm volatile ("inw %1, %0" : "=a"(data) : "d"((uint16_t)port));
  return data;
}

static inline uint32_t inl(int port) {
  uint32_t data;
  asm volatile ("inl %1, %0" : "=a"(data) : "d"((uint16_t)port));
  return data;
}

static inline void outb(int port, uint8_t data) {
  asm volatile ("outb %%al, %%dx" : : "a"(data), "d"((uint16_t)port));
}

static inline void outw(int port, uint16_t data) {
  asm volatile ("outw %%ax, %%dx" : : "a"(data), "d"((uint16_t)port));
}

static inline void outl(int port, uint32_t data) {
  asm volatile ("outl %%eax, %%dx" : : "a"(data), "d"((uint16_t)port));
}

static inline void cli() {
  asm volatile ("cli");
}

static inline void sti() {
  asm volatile ("sti");
}

static inline void hlt() {
  asm volatile ("hlt");
}

static inline void pause() {
  asm volatile ("pause");
}

static inline uint32_t get_efl() {
  volatile uint32_t efl;
  asm volatile ("pushf; pop %0": "=r"(efl));
  return efl;
}

static inline uint32_t get_cr0(void) {
  volatile uint32_t val;
  asm volatile ("movl %%cr0, %0" : "=r"(val));
  return val;
}

static inline void set_cr0(uint32_t cr0) {
  asm volatile ("movl %0, %%cr0" : : "r"(cr0));
}


static inline void set_idt(GateDesc *idt, int size) {
  volatile static uint16_t data[3];
  data[0] = size - 1;
  data[1] = (uint32_t)idt;
  data[2] = (uint32_t)idt >> 16;
  asm volatile ("lidt (%0)" : : "r"(data));
}

static inline void set_gdt(SegDesc *gdt, int size) {
  volatile static uint16_t data[3];
  data[0] = size - 1;
  data[1] = (uint32_t)gdt;
  data[2] = (uint32_t)gdt >> 16;
  asm volatile ("lgdt (%0)" : : "r"(data));
}

static inline void set_tr(int selector) {
  asm volatile ("ltr %0" : : "r"((uint16_t)selector));
}

static inline uint32_t get_cr2() {
  volatile uint32_t val;
  asm volatile ("movl %%cr2, %0" : "=r"(val));
  return val;
}

static inline void set_cr3(void *pdir) {
  asm volatile ("movl %0, %%cr3" : : "r"(pdir));
}

#endif

#endif
