; AcessOS Microkernel Version
; Start.asm

[bits 32]

%define SAVEFLAG_FPU	0x1

KERNEL_BASE	equ 0xC0000000

KSTACK_USERSTATE_SIZE	equ	(4+8+1+5)*4	; SRegs, GPRegs, CPU, IRET

[section .text]

[global NewTaskHeader]
NewTaskHeader:
	mov eax, [esp]
	mov dr0, eax

	mov eax, [esp+4]
	add esp, 12	; Thread, Function, Arg Count
	call eax
	
	push eax	; Ret val
	push 0  	; 0 = This Thread
	call Threads_Exit

[extern MM_Clone]
[global Proc_CloneInt]
Proc_CloneInt:
	pusha
	; Save RSP
	mov eax, [esp+0x20+4]
	mov [eax], esp
	push DWORD [esp+0x20+12]
	call MM_Clone
	add esp, 4
	; Save CR3
	mov esi, [esp+0x20+8]
	mov [esi], eax
	; Undo the pusha
	add esp, 0x20
	mov eax, .newTask
	ret
.newTask:
	popa
	xor eax, eax
	ret

[global SwitchTasks]
; + 4 = New RSP
; + 8 = Old RSP save loc
; +12 = New RIP
; +16 = Old RIP save loc
; +20 = CR3
SwitchTasks:
	pusha
	
	; Old IP
	mov eax, [esp+0x20+16]
	test eax, eax
	jz .nosave
	mov DWORD [eax], .restore
	; Old SP
	mov eax, [esp+0x20+8]
	mov [eax], esp

.nosave:
	mov ecx, [esp+0x20+12]	; New IP
	mov eax, [esp+0x20+20]	; New CR3
	mov esp, [esp+0x20+ 4]	; New SP
	
	test eax, eax
	jz .setState
	mov cr3, eax
	invlpg [esp]
	invlpg [esp+0x1000]
.setState:
	jmp ecx

.restore:
	popa
	xor eax, eax
	ret

[global Proc_InitialiseSSE]
Proc_InitialiseSSE:
	mov eax, cr4
	or eax, (1 << 9)|(1 << 10)	; Set OSFXSR and OSXMMEXCPT
	mov cr4, eax
	mov eax, cr0
	and ax, ~(1 << 2)	; Clear EM
	or eax, (1 << 1)	; Set MP
	mov eax, cr0
	ret
[global Proc_DisableSSE]
Proc_DisableSSE:
	mov eax, cr0
	or ax, 1 << 3	; Set TS
	mov cr0, eax
	ret
[global Proc_EnableSSE]
Proc_EnableSSE:
	mov eax, cr0
	and ax, ~(1 << 3)	; Clear TS
	mov cr0, eax
	ret

[global Proc_SaveSSE]
Proc_SaveSSE:
	mov eax, [esp+4]
	fxsave [eax]
	ret
[global Proc_RestoreSSE]
Proc_RestoreSSE:
	mov eax, [esp+4]
	fxrstor [eax]
	ret

%if USE_MP
[extern giMP_TimerCount]
[extern gpMP_LocalAPIC]
[extern Isr240.jmp]
[global SetAPICTimerCount]
SetAPICTimerCount:
	pusha
	push ds
	push es
	push fs
	push gs
	
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	
	mov eax, [gpMP_LocalAPIC]
	mov ecx, [eax+0x320]
	test ecx, 0x00010000
	jz .setTime
	mov DWORD [eax+0x380], 0xFFFFFFFF	; Set Initial Count
	mov DWORD [eax+0x320], 0x000000F0	; Enable the timer on IVT#0xEF (One Shot)
	jmp .ret

.setTime:	
	; Get Timer Count
	mov ecx, 0xFFFFFFFF
	sub ecx, [eax+0x390]
	mov DWORD [giMP_TimerCount], ecx
	; Disable APIC Timer
	mov DWORD [eax+0x320], 0x000100EF
	mov DWORD [eax+0x380], 0

	; Update Timer IRQ to the IRQ code
	mov eax, SchedulerBase
	sub eax, Isr240.jmp+5
	mov DWORD [Isr240.jmp+1], eax

	;xchg bx, bx	; MAGIC BREAK
.ret:
	mov dx, 0x20
	mov al, 0x20
	out dx, al		; ACK IRQ
	pop gs
	pop fs
	pop es
	pop ds
	popa
	add esp, 8	; CPU ID / Error Code
	iret
%endif
; --------------
; Task Scheduler
; --------------
[extern Proc_Scheduler]
[global SchedulerBase]
SchedulerBase:
	pusha
	push ds
	push es
	push fs
	push gs
	
	pushf
	and BYTE [esp+1], 0xFE	; Clear Trap Flag
	popf
	
	mov eax, dr0
	push eax	; Debug Register 0, Current Thread
	
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	
	%if USE_MP
	call GetCPUNum
	mov ebx, eax
	push eax	; Push as argument
	%else
	push 0
	%endif
	
	call Proc_Scheduler
[global scheduler_return]
scheduler_return:	; Used by some hackery in Proc_DumpThreadCPUState
	
	add esp, 4	; Remove CPU Number (thread is poped later)

	%if USE_MP
	test ebx, ebx
	jnz .sendEOI
	%endif
	
	mov al, 0x20
	out 0x20, al		; ACK IRQ

	%if USE_MP
	jmp .ret
.sendEOI:
	mov eax, DWORD [gpMP_LocalAPIC]
	mov DWORD [eax+0x0B0], 0
	%endif
.ret:
	pop eax	; Debug Register 0, Current Thread
	mov dr0, eax
	
	jmp ReturnFromInterrupt

;
; Returns after an interrupt, restoring the user state
; - Also handles signal handlers
;
[global ReturnFromInterrupt]
[extern Threads_GetPendingSignal]
[extern Threads_GetSignalHandler]
[extern Proc_CallUser]
ReturnFromInterrupt:
	; Check that we're returning to userland
	test DWORD [esp+(4+8+2+1)*4], 0x07
	jz .ret	; Kernel interrupted, return

	call Threads_GetPendingSignal
	; eax: signal number
	test eax, eax
	jz .ret
	
	; There's a signal pending, funtime
	push eax
	call Threads_GetSignalHandler
	; eax: signal handler
	pop ecx
	test eax, eax
	jz .ret
	cmp eax, -1
	jz .default
	
	; (some stack abuse)
	push User_RestoreState
	push ecx
	mov ecx, esp
	
	push (2+4+8+2+2)*4	; Up to and incl. CS
	push ecx	; User stack data base
	push DWORD [ecx+(2+4+8+2+3)*4]	; User SP
	push eax	; handler
	call Proc_CallUser
	; Oh, ... it failed. Default time?
	add esp, (4+2)*4
.default:
	

	; Fall through to return
.ret:
	pop gs
	pop fs
	pop es
	pop ds
	popa
	add esp, 2*4	; IRQ Num / CPU ID + error code
	iret

[extern Proc_Clone]
[extern Threads_Exit]
[global SpawnTask]
SpawnTask:
	; Call Proc_Clone with Flags=0
	xor eax, eax
;	push eax
	push eax
	call Proc_Clone
	add esp, 8	; Remove arguments from stack
	
	test eax, eax
	jnz .parent
	
	; In child, so now set up stack frame
	mov ebx, [esp+4]	; Child Function
	mov edx, [esp+8]	; Argument
	; Child Function
	push edx	; Argument
	call ebx	; Function
	; Kill thread once done
	push eax	; Exit Code
	push   0	; Kill this thread
	call Threads_Exit	; Kill Thread
	
.parent:
	ret

; void Proc_ReturnToUser(void *Method, Uint Parameter, tVAddr KernelStack)
; Calls a user fault handler
;
[global Proc_ReturnToUser]
[extern Proc_GetCurThread]
Proc_ReturnToUser:
	push ebp
	mov ebp, esp
	; [EBP+8]: handler to use
	; [EBP+12]: parameter
	; [EBP+16]: kernel stack top
	
	; Get kernel stack	
	mov eax, [ebp+16]
	sub eax, KSTACK_USERSTATE_SIZE
	
	;
	; NOTE: This can cause corruption if the signal happens while the user
	;       has called a kernel operation.
	; Good thing this can only be called on a user fault.
	;
	
	; Create data to add to user stack
	push DWORD [ebp+12]
	push User_Syscall_RetAndExit
	mov ecx, esp

	; Call user method	
	push 2*4
	push ecx
	push DWORD [eax+KSTACK_USERSTATE_SIZE-12]	; User ESP is at top of kstack - 3*4
	push DWORD [ebp+8]
	call Proc_CallUser	
	
	; Just kill the bleeding thing
	; (I know it calls int 0xAC in kernel mode, but meh)
.justKillIt:
	xor eax, eax
	xor ebx, ebx
	dec ebx	; EBX = -1
	int 0xAC

[global GetCPUNum]
GetCPUNum:	; TODO: Store in debug registers
	mov eax, dr1
	ret

[extern GetEIP]
[global GetEIP_Sched]
[global GetEIP_Sched_ret]
GetEIP_Sched_ret equ GetEIP_Sched.ret
GetEIP_Sched:
	call GetEIP
GetEIP_Sched.ret:
	ret

; Usermode code exported by the kernel
[section .usertext]
; Export a place for the user to jump to to call a syscall
; - Allows the kernel to change the method easily
User_Syscall:
	xchg bx, bx	; MAGIC BREAKPOINT
	int 0xAC

[global User_Signal_Kill]
User_Signal_Kill:
	xor eax, eax
	mov bl, [esp+4]
	mov bh, 0x02
	int 0xAC
	jmp $

User_RestoreState:
	pop gs
	pop fs
	pop es
	pop ds
	popa
	add esp, 2*4	; Kernel's error code and interrupt number
	retf	; EFLAGS/SS/ESP were not included in the state
	

; A place to return to and exit
User_Syscall_RetAndExit:
	push eax
	call User_Syscall_Exit
User_Syscall_Exit:
	xor eax, eax
	mov ebx, [esp+4]
	int 0xAC

; vim: ft=nasm ts=8
