essence-os/arch/x86_32/kernel.s

592 lines
10 KiB
ArmAsm

; This file is part of the Essence operating system.
; It is released under the terms of the MIT license -- see LICENSE.md.
; Written by: nakst.
[bits 32]
[global ProcessorReset]
[global ProcessorDebugOutputByte]
[global ProcessorIn16]
[global ProcessorIn32]
[global ProcessorIn8]
[global ProcessorOut16]
[global ProcessorOut32]
[global ProcessorOut8]
[global ProcessorHalt]
[global ProcessorDisableInterrupts]
[global ProcessorEnableInterrupts]
[global ProcessorAreInterruptsEnabled]
[global ProcessorSetLocalStorage]
[global ProcessorReadCR3]
[global ProcessorInvalidatePage]
[global ProcessorInvalidateAllPages]
[global ProcessorReadTimeStamp]
[global ProcessorSetThreadStorage]
[global ProcessorFakeTimerInterrupt]
[global ProcessorSetAddressSpace]
[global ProcessorFlushCodeCache]
[global ProcessorAPStartup]
[global GetLocalStorage]
[global GetCurrentThread]
[global ArchSwitchContext]
[global processorGDTR]
[global gdt_data]
[global timeStampCounterSynchronizationValue]
[extern ArchNextTimer]
[extern InterruptHandler]
[extern KThreadTerminate]
[extern KernelInitialise]
[extern PostContextSwitch]
[extern SetupProcessor2]
[extern Syscall]
[extern installationID]
[extern PCSetupCOM1]
[extern PCDisablePIC]
[extern PCProcessMemoryMap]
[extern bootloaderID]
[extern bootloaderInformationOffset]
[section .bss]
%define boot_stack_size 10000
boot_stack: resb boot_stack_size
%define idt_size 2048
idt_data: resb idt_size
%define cpu_local_storage_size (256 * 4 * 4)
cpu_local_storage: resb cpu_local_storage_size
cpu_local_index: resb 4
timeStampCounterSynchronizationValue: resb 8
[section .data]
processorGDTR:
times 8 db 0
idt:
.limit: dw idt_size - 1
.base: dd idt_data
align 4
gdt_data:
.null_entry: dq 0
.code_entry: dd 0xFFFF ; 0x0008
db 0
dw 0xCF9A
db 0
.data_entry: dd 0xFFFF ; 0x0010
db 0
dw 0xCF92
db 0
.user_code: dd 0xFFFF ; 0x001B
db 0
dw 0xCFFA
db 0
.user_data: dd 0xFFFF ; 0x0023
db 0
dw 0xCFF2
db 0
.tss: dd 0x68 ; 0x0028
db 0
dw 0xE9
db 0
dq 0
.local: times 256 dq 0 ; 0x0038 - 0x0838
.gdt: dw (gdt_data.gdt - gdt_data - 1)
.gdt2: dd gdt_data
[section .text]
[global _start]
_start:
; Save the bootloader ID and information offset.
mov [bootloaderID],esi
mov [bootloaderInformationOffset],edi
; The MBR bootloader does not know the address of the RSDP.
cmp edi,0
jne .standard_acpi
mov [0x7FE8],edi
.standard_acpi:
; Install the boot stack.
mov esp,boot_stack + boot_stack_size
; Load the installation ID.
mov eax,[edi + 0x7FF0]
mov [installationID + 0],eax
mov eax,[edi + 0x7FF4]
mov [installationID + 4],eax
mov eax,[edi + 0x7FF8]
mov [installationID + 8],eax
mov eax,[edi + 0x7FFC]
mov [installationID + 12],eax
; Load the new GDT, saving the location of the bootstrap GDT.
lgdt [gdt_data.gdt]
sgdt [processorGDTR]
; Move the identity paging the bootloader used to LOW_MEMORY_MAP_START.
; Then, map the local APIC to LOCAL_APIC_BASE.
mov eax,[0xFFFFF000]
mov [0xFFFFFEC0],eax
xor eax,eax
mov [0xFFFFF000],eax
mov eax,0xFEE00103
mov [0xFFC00000 + (0xEC3FF << 2)],eax
mov eax,cr3
mov cr3,eax
; Install the interrupt handlers
mov ebx,idt_data
%macro INSTALL_INTERRUPT_HANDLER 1
mov edx,InterruptHandler%1
call InstallInterruptHandler
add ebx,8
%endmacro
%assign i 0
%rep 256
INSTALL_INTERRUPT_HANDLER i
%assign i i+1
%endrep
; Setup the remaining things and call KernelInitialise
call SetupProcessor1 ; Need to get SSE up before calling into C code.
call PCSetupCOM1
call PCDisablePIC
call PCProcessMemoryMap
call KernelInitialise
; Fall-through.
ProcessorReady:
; Set the timer and become this CPU's idle thread.
push 1
call ArchNextTimer
; Fall-through.
ProcessorIdle:
sti
hlt
jmp ProcessorIdle
SetupProcessor1:
; x87 FPU.
fninit
fldcw [.cw]
jmp .cwa
.cw: dw 0x037A
.cwa:
; Enable MMX, SSE and SSE2.
; TODO Check these are actually present!
mov eax,cr0
mov ebx,cr4
and eax,~4
or eax,2
or ebx,512 + 1024
mov cr0,eax
mov cr4,ebx
; Setup the local storage.
; This creates a new data segment in the GDT pointing to a unique 16-byte block of cpu_local_storage,
; and updates FS to use the data segment.
mov eax,[cpu_local_index]
mov ebx,eax
shl ebx,4
add ebx,cpu_local_storage
mov edx,ebx
shl ebx,16
or ebx,0x0000FFFF
mov ecx,edx
shr ecx,16
and edx,0xFF000000
or edx,0x00CF9200
or dl,cl
mov dword [gdt_data.local + eax * 8 + 0],ebx
mov dword [gdt_data.local + eax * 8 + 4],edx
lea eax,[0x0038 + eax * 8]
mov fs,ax
inc dword [cpu_local_index]
; Enable global pages.
mov eax,cr4
or eax,1 << 7
mov cr4,eax
; Enable write protect, so copy-on-write works in the kernel, and MMArchSafeCopy will page fault in read-only regions.
mov eax,cr0
or eax,1 << 16
mov cr0,eax
; Load the IDTR.
lidt [idt]
sti
; Enable the APIC.
; TODO Check it is actually present!
mov ecx,0x1B
rdmsr
or eax,0x800
wrmsr
; Set the spurious interrupt vector to 0xFF
mov eax,0xEC3FF0F0
mov ebx,[eax]
or ebx,0x1FF
mov [eax],ebx
; Use the flat processor addressing model
mov eax,0xEC3FF0E0
mov dword [eax],0xFFFFFFFF
; Make sure that no external interrupts are masked
xor eax,eax
mov [0xEC3FF080],eax
; TODO More feature detection and initialisation!
ret
InstallInterruptHandler:
mov word [ebx + 0],dx
mov word [ebx + 2],0x0008
mov word [ebx + 4],0x8E00
shr edx,16
mov word [ebx + 6],dx
ret
%macro INTERRUPT_HANDLER 1
InterruptHandler%1:
push dword 0 ; A fake error code
push dword %1 ; The interrupt number
jmp ASMInterruptHandler
%endmacro
%macro INTERRUPT_HANDLER_EC 1
InterruptHandler%1:
; The CPU already pushed an error code
push dword %1 ; The interrupt number
jmp ASMInterruptHandler
%endmacro
INTERRUPT_HANDLER 0
INTERRUPT_HANDLER 1
INTERRUPT_HANDLER 2
INTERRUPT_HANDLER 3
INTERRUPT_HANDLER 4
INTERRUPT_HANDLER 5
INTERRUPT_HANDLER 6
INTERRUPT_HANDLER 7
INTERRUPT_HANDLER_EC 8
INTERRUPT_HANDLER 9
INTERRUPT_HANDLER_EC 10
INTERRUPT_HANDLER_EC 11
INTERRUPT_HANDLER_EC 12
INTERRUPT_HANDLER_EC 13
INTERRUPT_HANDLER_EC 14
INTERRUPT_HANDLER 15
INTERRUPT_HANDLER 16
INTERRUPT_HANDLER_EC 17
INTERRUPT_HANDLER 18
INTERRUPT_HANDLER 19
INTERRUPT_HANDLER 20
INTERRUPT_HANDLER 21
INTERRUPT_HANDLER 22
INTERRUPT_HANDLER 23
INTERRUPT_HANDLER 24
INTERRUPT_HANDLER 25
INTERRUPT_HANDLER 26
INTERRUPT_HANDLER 27
INTERRUPT_HANDLER 28
INTERRUPT_HANDLER 29
INTERRUPT_HANDLER 30
INTERRUPT_HANDLER 31
%assign i 32
%rep 224
INTERRUPT_HANDLER i
%assign i i+1
%endrep
ASMInterruptHandler:
cld
test byte [esp + 12],3
jnz .have_esp
; When ring 0 is interrupted, ESP and SS aren't pushed.
; We push them ourselves here; we'll fix the order later.
push eax
push esp
mov eax,ss
xchg [esp + 4],eax
push 1
jmp .fixed
.have_esp:
push 0
.fixed:
push eax
push ebx
push ecx
push edx
push esi
push edi
push ebp
mov ebx,esp
and esp,~0xF
fxsave [esp - 512]
mov esp,ebx
sub esp,512 + 16
mov eax,ds
push eax
mov eax,0x10
mov ds,ax
mov eax,cr2
push eax
mov edx,[0xEC3FF080]
push edx
mov edx,0xF0
mov [0xEC3FF080],edx ; Mask all interrupts.
sti ; ...so there's no need to have the interrupt flag clear.
push esp
call InterruptHandler
add esp,4
xor eax,eax
.return:
cli ; Must be done before restoring CR8.
pop edx
mov [0xEC3FF080],edx
add esp,4
pop ebx
mov ds,bx
add esp,512 + 16
mov ebx,esp
and ebx,~0xF
fxrstor [ebx - 512]
or al,al
jz .old_thread
fninit ; New thread - initialise FPU.
.old_thread:
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop eax
test byte [esp],1
jz .need_esp
; When returning to ring 0, ESP and SS aren't popped.
; So we do it manually here.
add esp,8
.need_esp:
add esp,12
iret
ArchSwitchContext:
cli
mov eax,[esp + 16]
mov [fs:8],eax
mov ebx,[esp + 12]
mov [fs:4],ebx
mov edi,[esp + 8]
mov ecx,[edi]
mov edx,cr3
cmp edx,ecx
je .cont
mov cr3,ecx
.cont:
mov eax,[esp + 4]
mov ecx,[esp + 20]
mov esp,eax ; Put the stack just below the context.
push ecx
push eax
call PostContextSwitch
add esp,8
jmp ASMInterruptHandler.return
ProcessorDebugOutputByte:
%ifdef COM_OUTPUT
mov dx,0x3F8 + 5
.WaitRead:
in al,dx
and al,0x20
cmp al,0
je .WaitRead
mov dx,0x3F8 + 0
mov eax,[esp + 4]
out dx,al
%endif
ret
ProcessorOut8:
mov edx,[esp + 4]
mov eax,[esp + 8]
out dx,al
ret
ProcessorIn8:
mov edx,[esp + 4]
xor eax,eax
in al,dx
ret
ProcessorOut16:
mov edx,[esp + 4]
mov eax,[esp + 8]
out dx,ax
ret
ProcessorIn16:
mov edx,[esp + 4]
xor eax,eax
in ax,dx
ret
ProcessorOut32:
mov edx,[esp + 4]
mov eax,[esp + 8]
out dx,eax
ret
ProcessorIn32:
mov edx,[esp + 4]
xor eax,eax
in eax,dx
ret
ProcessorReset:
in al,0x64
test al,2
jne ProcessorReset
mov al,0xFE
out 0x64,al
; Fall-through.
ProcessorHalt:
cli
hlt
jmp ProcessorHalt
ProcessorDisableInterrupts:
mov eax,0xE0 ; Still allow important IPIs to go through.
mov [0xEC3FF080],eax
ret
ProcessorEnableInterrupts:
xor eax,eax
mov [0xEC3FF080],eax
ret
ProcessorAreInterruptsEnabled:
xor al,al
mov edx,[0xEC3FF080]
or edx,edx
jnz .done
mov al,1
.done:
ret
GetLocalStorage:
mov eax,[fs:0]
ret
GetCurrentThread:
mov eax,[fs:8]
ret
ProcessorSetLocalStorage:
mov eax,[esp + 4]
mov [fs:0],eax
ret
ProcessorReadCR3:
mov eax,cr3
ret
ProcessorInvalidatePage:
mov eax,[esp + 4]
invlpg [eax]
ret
ProcessorInvalidateAllPages:
; Toggle CR4.PGE to invalidate all TLB entries, including global entries.
mov eax,cr4
and eax,~(1 << 7)
mov cr4,eax
or eax,1 << 7
mov cr4,eax
ret
ProcessorReadTimeStamp:
rdtsc
ret
ProcessorSetThreadStorage:
; TODO.
ret
ProcessorFakeTimerInterrupt:
int 0x40
ret
ProcessorSetAddressSpace:
mov eax,[esp + 4]
mov edx,[eax]
mov eax,cr3
cmp eax,edx
je .cont
mov cr3,edx
.cont:
ret
ProcessorFlushCodeCache:
wbinvd
ret
SynchronizeTimeStampCounter:
; TODO
ret
[bits 16]
ProcessorAPStartup: ; This function must be less than 4KB in length (see drivers/acpi.cpp)
mov ax,0x1000
mov ds,ax
mov byte [0xFC0],1 ; Indicate we've started.
mov eax,[0xFF0]
mov cr3,eax
lgdt [0x1000 + gdt_data.gdt - gdt_data]
mov eax,cr0
or eax,1
mov cr0,eax
jmp 0x8:dword (.pmode - ProcessorAPStartup + 0x10000)
[bits 32]
.pmode:
mov eax,0x10
mov ds,ax
mov es,ax
mov ss,ax
lgdt [gdt_data.gdt]
mov esp,[0x10FD0]
call SetupProcessor1
call SynchronizeTimeStampCounter
mov edi,[0x10FB0]
push edi
call SetupProcessor2
mov byte [0x10FC0],2 ; Indicate the BSP can start the next processor.
jmp ProcessorReady