;
;
;
%include "arch/x86_64/include/common.inc.asm"
[BITS 64]

[extern Log]
[extern Log_Debug]
[extern gGDTPtr]
[extern gGDT]

%define NUM_IRQ_CALLBACKS	4

MM_LOCALAPIC	equ	0xFFFFFD0000000000

[section .text]
[global Desctab_Init]
Desctab_Init:	
	; Save to make following instructions smaller
	mov rdi, gIDT
	
	; Set an IDT entry to a callback
	%macro SETIDT 2
	mov rax, %2
	mov WORD [rdi + %1*16], ax
	shr rax, 16
	mov WORD [rdi + %1*16 + 6], ax
	shr rax, 16
	mov DWORD [rdi + %1*16 + 8], eax
	; Enable
	mov ax, WORD [rdi + %1*16 + 4]
	or  ax, 0x8000
	mov WORD [rdi + %1*16 + 4], ax
	%endmacro
	
	; Install error handlers
	%macro SETISR 1
	SETIDT %1, Isr%1
	%endmacro
	
	%assign i 0
	%rep 32
	SETISR i
	%assign i i+1
	%endrep
	
	; Install IRQs
	SETIDT	0xF0, PIT_IRQ
	SETIDT	0xF1, Irq1
	SETIDT	0xF2, Irq2
	SETIDT	0xF3, Irq3
	SETIDT	0xF4, Irq4
	SETIDT	0xF5, Irq5
	SETIDT	0xF6, Irq6
	SETIDT	0xF7, Irq7
	SETIDT	0xF8, Irq8
	SETIDT	0xF9, Irq9
	SETIDT	0xFA, Irq10
	SETIDT	0xFB, Irq11
	SETIDT	0xFC, Irq12
	SETIDT	0xFD, Irq13
	SETIDT	0xFE, Irq14
	SETIDT	0xFF, Irq15

	; Remap PIC
	push rdx	; Save RDX
	mov dx, 0x20
	mov al, 0x11
	out dx, al	;	Init Command
	mov dx, 0x21
	mov al, 0xF0
	out dx, al	;	Offset (Start of IDT Range)
	mov al, 0x04
	out dx, al	;	IRQ connected to Slave (00000100b) = IRQ2
	mov al, 0x01
	out dx, al	;	Set Mode
	mov al, 0x00
	out dx, al	;	Set Mode
	
	mov dx, 0xA0
	mov al, 0x11
	out dx, al	;	Init Command
	mov dx, 0xA1
	mov al, 0xF8
	out dx, al	;	Offset (Start of IDT Range)
	mov al, 0x02
	out dx, al	;	IRQ Line connected to master
	mov al, 0x01
	out dx, al	;	Set Mode
	mov dl, 0x00
	out dx, al	;	Set Mode
	pop rdx
	
	
	; Install IDT
	mov rax, gIDTPtr
	lidt [rax]
	
	; Re-install GDT (in higher address space)
	mov rax, gGDTPtr
	mov rcx, gGDT
	mov QWORD [rax+2], rcx
	lgdt [rax]
	
	; Start interrupts
	sti

	; Set IA32_LSTAR (RIP of handler)
	mov ecx, 0xC0000082	; IA32_LSTAR
	mov eax, SyscallStub - 0xFFFFFFFF00000000
	mov edx, 0xFFFFFFFF
	wrmsr
	; Set IA32_FMASK (flags mask)
	mov ecx, 0xC0000084
	rdmsr
	mov eax, ~0x202
	wrmsr
	; Set IA32_STAR (Kernel/User CS)
	mov ecx, 0xC0000081
	rdmsr
	mov edx, 0x8 | (0x1B << 16)	; Kernel CS (and Kernel DS/SS - 8), User CS
	wrmsr
	
	ret

; int IRQ_AddHandler(int IRQ, void (*Handler)(int IRQ), void *Ptr)
; Return Values:
;  0 on Success
; -1 on an invalid IRQ Number
; -2 when no slots are avaliable
[global IRQ_AddHandler]
IRQ_AddHandler:
	; RDI - IRQ Number
	; RSI - Callback
	; RDX - Ptr
	
	; Check for RDI >= 16
	cmp rdi, 16
	jb .numOK
	xor rax, rax
	dec rax
	jmp .ret
.numOK:

	; Get handler base into RAX
	lea rax, [rdi*4]
	mov rcx, gaIRQ_Handlers
	lea rax, [rcx+rax*8]
	
	; Find a free callback slot
	%rep NUM_IRQ_CALLBACKS
	mov rcx, [rax]
	test rcx, rcx
	jz .assign
	add rax, 8
	%endrep
	; None found, return -2
	xor rax, rax
	dec rax
	dec rax
	jmp .ret
	
	; Assign the IRQ Callback
.assign:
	; A little bit of debug
	push rdi
	push rsi
	push rax
	push rdx
	sub rsp, 8
	mov rcx, rsi	; IRQ Number
	mov rdx, rdi	; Callback
	mov rsi, csIRQ_Assigned
	mov rdi, csIRQ_Tag
	call Log_Debug
	add rsp, 8
	pop rdx
	pop rax
	pop rsi
	pop rdi

	; Assign and return
	mov [rax], rsi
	add rax, gaIRQ_DataPtrs - gaIRQ_Handlers
	mov [rax], rdx
	xor rax, rax

.ret:
	ret
	
[section .rodata]
csIRQ_Assigned:
	db	"IRQ %i .= %p",0
csIRQ_Fired:
	db	"IRQ %i fired",0
csIRQ_Tag:
	db	"IRQ",0
[section .text]

%macro ISR_NOERRNO	1
Isr%1:
	push	QWORD 0
	push	QWORD %1
	jmp	ErrorCommon
%endmacro
%macro ISR_ERRNO	1
Isr%1:
	push	QWORD %1
	jmp	ErrorCommon
%endmacro

ISR_NOERRNO	0;  0: Divide By Zero Exception
ISR_NOERRNO	1;  1: Debug Exception
ISR_NOERRNO	2;  2: Non Maskable Interrupt Exception
ISR_NOERRNO	3;  3: Int 3 Exception
ISR_NOERRNO	4;  4: INTO Exception
ISR_NOERRNO	5;  5: Out of Bounds Exception
ISR_NOERRNO	6;  6: Invalid Opcode Exception
ISR_NOERRNO	7;  7: Coprocessor Not Available Exception
ISR_ERRNO	8;  8: Double Fault Exception (With Error Code!)
ISR_NOERRNO	9;  9: Coprocessor Segment Overrun Exception
ISR_ERRNO	10; 10: Bad TSS Exception (With Error Code!)
ISR_ERRNO	11; 11: Segment Not Present Exception (With Error Code!)
ISR_ERRNO	12; 12: Stack Fault Exception (With Error Code!)
ISR_ERRNO	13; 13: General Protection Fault Exception (With Error Code!)
ISR_ERRNO	14; 14: Page Fault Exception (With Error Code!)
ISR_NOERRNO	15; 15: Reserved Exception
ISR_NOERRNO	16; 16: Floating Point Exception
ISR_NOERRNO	17; 17: Alignment Check Exception
ISR_NOERRNO	18; 18: Machine Check Exception
ISR_NOERRNO	19; 19: Reserved
ISR_NOERRNO	20; 20: Reserved
ISR_NOERRNO	21; 21: Reserved
ISR_NOERRNO	22; 22: Reserved
ISR_NOERRNO	23; 23: Reserved
ISR_NOERRNO	24; 24: Reserved
ISR_NOERRNO	25; 25: Reserved
ISR_NOERRNO	26; 26: Reserved
ISR_NOERRNO	27; 27: Reserved
ISR_NOERRNO	28; 28: Reserved
ISR_NOERRNO	29; 29: Reserved
ISR_NOERRNO	30; 30: Reserved
ISR_NOERRNO	31; 31: Reserved

[extern Error_Handler]
[global ErrorCommon]
ErrorCommon:
	PUSH_GPR
	push gs
	push fs
	;PUSH_FPU
	;PUSH_XMM
	
	mov rdi, rsp
;	xchg bx, bx
	call Error_Handler
	
	;POP_XMM
	;POP_FPU
	pop fs
	pop gs
	POP_GPR
	add rsp, 2*8
	iretq

%macro DEFIRQ	1
Irq%1:
	push	0
	push	%1
	jmp	IrqCommon
%endmacro

%assign i 0
%rep 16
DEFIRQ	i
%assign i i+1
%endrep

[extern Proc_int_SetIRQIP]

[global IrqCommon]
IrqCommon:
	PUSH_GPR
	push gs
	push fs
	
	mov rdi, [rsp+(16+2+2)*8]	; 2SReg + GPRs + Int/Errcode = RIP
	call Proc_int_SetIRQIP
	push rax
	
;	mov rdi, csIRQ_Fired
;	mov rsi, [rsp+(16+2)*8]
;	call Log
	
	mov ebx, [rsp+(1+2+16)*8]	; Get interrupt number (16 GPRS + 2 SRs)
	shl ebx, 2	; *4
	mov rax, gaIRQ_Handlers
	lea rbx, [rax+rbx*8]
	
	; Check all callbacks
	sub rsp, 8	; Shadow of argument
	%assign i 0
	%rep NUM_IRQ_CALLBACKS
	; Get callback address
	mov rax, [rbx]
	test rax, rax	; Check if it exists
	jz .skip.%[i]
	; Set RDI to IRQ number
	mov rdi, [rsp+(16+2+1+1)*8]	; Get IRQ number
	mov rsi, [rbx-gaIRQ_Handlers+gaIRQ_DataPtrs]
	call rax	; Call
.skip.%[i]:
	add rbx, 8	; Next!
	%assign i i+1
	%endrep
	add rsp, 8
	
	; ACK
	mov al, 0x20
	mov rdi, [rsp+(16+2+1)*8]	; Get IRQ number
	cmp rdi, 8
	jb .skipAckSecondary
	out 0xA0, al
.skipAckSecondary:
	out 0x20, al

	pop rdi
	call Proc_int_SetIRQIP
	
	pop fs
	pop gs
	POP_GPR
	add rsp, 8*2
	iretq

[extern Time_UpdateTimestamp]

%if USE_MP
[global APIC_Timer_IRQ]
APIC_Timer_IRQ:
	PUSH_GPR
	push gs
	push fs

	; TODO: What to do?

	mov eax, DWORD [gpMP_LocalAPIC]
	mov DWORD [eax+0x0B0], 0

	pop fs
	pop gs
	POP_GPR
	iretq
%endif

[global PIT_IRQ]
PIT_IRQ:
	PUSH_GPR
	;PUSH_FPU
	;PUSH_XMM
	
	call Time_UpdateTimestamp

	%if 0
[section .rodata]
csUserSS:	db	"User SS: 0x%x",0
[section .text]
	mov rdi, csUserSS
	mov rsi, [rsp+0x80+0x20]
	call Log
	%endif

	; Send EOI
	mov al, 0x20
	out 0x20, al		; ACK IRQ
	
	;POP_XMM
	;POP_FPU
	POP_GPR
	iretq

[extern ci_offsetof_tThread_KernelStack]
[extern SyscallHandler]
[global SyscallStub]
SyscallStub:
	mov rbp, dr0
	mov ebx, [rel ci_offsetof_tThread_KernelStack]
	mov rbp, [rbp+rbx]	; Get kernel stack
	xchg rbp, rsp	; Swap stacks

	push rbp	; Save User RSP
	push rcx	; RIP
	push r11	; RFLAGS

	; RDI
	; RSI
	; RDX
	; R10 (RCX for non syscall)
	; R8
	; R9
	sub rsp, (6+2)*8
	mov [rsp+0x00], rax	; Number
;	mov [rsp+0x08], rax	; Errno (output only)
	mov [rsp+0x10], rdi	; Arg1
	mov [rsp+0x18], rsi	; Arg2
	mov [rsp+0x20], rdx	; Arg3
	mov [rsp+0x28], r10	; Arg4
	mov [rsp+0x30], r8	; Arg5
	mov [rsp+0x38], r9	; Arg6

	mov rdi, rcx	
	call Proc_int_SetIRQIP

	mov rdi, rsp
	sub rsp, 8
	call SyscallHandler

	%if 0
[section .rodata]
csSyscallReturn:	db	"Syscall Return: 0x%x",0
[section .text]
	mov rdi, csSyscallReturn
	mov rsi, [rsp+0+8]
	call Log
	%endif

	add rsp, 8
	mov ebx, [rsp+8]	; Get errno
	mov rax, [rsp+0]	; Get return
	add rsp, (6+2)*8

	pop r11
	pop rcx
	pop rsp 	; Change back to user stack
	; TODO: Determine if user is 64 or 32 bit

	db 0x48	; REX, nasm doesn't have a sysretq opcode
	sysret

[section .data]
gIDT:
	; 64-bit Interrupt Gate, CS = 0x8, IST0 (Disabled)
	times 256	dd	0x00080000, 0x00000E00, 0, 0
gIDTPtr:
	dw	256*16-1
	dq	gIDT

gaIRQ_Handlers:
	times	16*NUM_IRQ_CALLBACKS	dq	0
gaIRQ_DataPtrs:
	times	16*NUM_IRQ_CALLBACKS	dq	0

; vim: ft=nasm
