642 lines
16 KiB
NASM
642 lines
16 KiB
NASM
; =============================================================================
|
|
; Pure64 -- a 64-bit OS loader written in Assembly for x86-64 systems
|
|
; Copyright (C) 2008-2014 Return Infinity -- see LICENSE.TXT
|
|
;
|
|
; Loaded from the first stage. Gather information about the system while
|
|
; in 16-bit mode (BIOS is still accessible), setup a minimal 64-bit
|
|
; environment, copy the 64-bit kernel from the end of the Pure64 binary to
|
|
; the 1MiB memory mark and jump to it!
|
|
;
|
|
; Pure64 requires a payload for execution! The stand-alone pure64.sys file
|
|
; is not sufficient. You must append your kernel or software to the end of
|
|
; the Pure64 binary. The maximum size of the kernel of software is 26KiB.
|
|
;
|
|
; Windows - copy /b pure64.sys + kernel64.sys
|
|
; Unix - cat pure64.sys kernel64.sys > pure64.sys
|
|
; Max size of the resulting pure64.sys is 32768 bytes (32KiB)
|
|
; =============================================================================
|
|
|
|
|
|
USE16
|
|
ORG 0x00008000
|
|
start:
|
|
cli ; Disable all interrupts
|
|
xor eax, eax
|
|
xor ebx, ebx
|
|
xor ecx, ecx
|
|
xor edx, edx
|
|
xor esi, esi
|
|
xor edi, edi
|
|
xor ebp, ebp
|
|
mov ds, ax
|
|
mov es, ax
|
|
mov ss, ax
|
|
mov fs, ax
|
|
mov gs, ax
|
|
mov esp, 0x8000 ; Set a known free location for the stack
|
|
|
|
ap_modify:
|
|
jmp start16 ; This command will be overwritten with 'NOP's before the AP's are started
|
|
nop ; The 'jmp' is only 3 bytes
|
|
|
|
%include "init/smp_ap.asm" ; AP's will start execution at 0x8000 and fall through to this code
|
|
|
|
|
|
;db '_16_' ; Debug
|
|
align 16
|
|
|
|
USE16
|
|
start16:
|
|
jmp 0x0000:clearcs
|
|
|
|
clearcs:
|
|
|
|
; Configure serial port
|
|
xor dx, dx ; First serial port
|
|
mov ax, 0000000011100011b ; 9600 baud, no parity, 1 stop bit, 8 data bits
|
|
int 0x14
|
|
|
|
; Make sure the screen is set to 80x25 color text mode
|
|
mov ax, 0x0003 ; Set to normal (80x25 text) video mode
|
|
int 0x10
|
|
|
|
; Disable blinking
|
|
mov ax, 0x1003
|
|
mov bx, 0x0000
|
|
int 0x10
|
|
|
|
; Print message
|
|
mov si, msg_initializing
|
|
call print_string_16
|
|
|
|
; Check to make sure the CPU supports 64-bit mode... If not then bail out
|
|
mov eax, 0x80000000 ; Extended-function 8000000h.
|
|
cpuid ; Is largest extended function
|
|
cmp eax, 0x80000000 ; any function > 80000000h?
|
|
jbe no_long_mode ; If not, no long mode.
|
|
mov eax, 0x80000001 ; Extended-function 8000001h.
|
|
cpuid ; Now EDX = extended-features flags.
|
|
bt edx, 29 ; Test if long mode is supported.
|
|
jnc no_long_mode ; Exit if not supported.
|
|
|
|
call init_isa ; Setup legacy hardware
|
|
|
|
; Hide the hardware cursor (interferes with print_string_16 if called earlier)
|
|
mov ax, 0x0200 ; VIDEO - SET CURSOR POSITION
|
|
mov bx, 0x0000 ; Page number
|
|
mov dx, 0x2000 ; Row / Column
|
|
int 0x10
|
|
|
|
; At this point we are done with real mode and BIOS interrupts. Jump to 32-bit mode.
|
|
lgdt [cs:GDTR32] ; Load GDT register
|
|
|
|
mov eax, cr0
|
|
or al, 0x01 ; Set protected mode bit
|
|
mov cr0, eax
|
|
|
|
jmp 8:start32 ; Jump to 32-bit protected mode
|
|
|
|
; 16-bit function to print a sting to the screen
|
|
print_string_16: ; Output string in SI to screen
|
|
pusha
|
|
mov ah, 0x0E ; http://www.ctyme.com/intr/rb-0106.htm
|
|
print_string_16_repeat:
|
|
lodsb ; Get char from string
|
|
cmp al, 0
|
|
je print_string_16_done ; If char is zero, end of string
|
|
int 0x10 ; Otherwise, print it
|
|
jmp print_string_16_repeat
|
|
print_string_16_done:
|
|
popa
|
|
ret
|
|
|
|
; Display an error message that the CPU does not support 64-bit mode
|
|
no_long_mode:
|
|
mov si, msg_no64
|
|
call print_string_16
|
|
jmp $
|
|
|
|
%include "init/isa.asm"
|
|
|
|
align 16
|
|
GDTR32: ; Global Descriptors Table Register
|
|
dw gdt32_end - gdt32 - 1 ; limit of GDT (size minus one)
|
|
dq gdt32 ; linear address of GDT
|
|
|
|
align 16
|
|
gdt32:
|
|
dw 0x0000, 0x0000, 0x0000, 0x0000 ; Null desciptor
|
|
dw 0xFFFF, 0x0000, 0x9A00, 0x00CF ; 32-bit code descriptor
|
|
dw 0xFFFF, 0x0000, 0x9200, 0x00CF ; 32-bit data descriptor
|
|
gdt32_end:
|
|
|
|
;db '_32_' ; Debug
|
|
align 16
|
|
|
|
|
|
; =============================================================================
|
|
; 32-bit mode
|
|
USE32
|
|
|
|
start32:
|
|
mov eax, 16 ; load 4 GB data descriptor
|
|
mov ds, ax ; to all data segment registers
|
|
mov es, ax
|
|
mov fs, ax
|
|
mov gs, ax
|
|
mov ss, ax
|
|
xor eax, eax
|
|
xor ebx, ebx
|
|
xor ecx, ecx
|
|
xor edx, edx
|
|
xor esi, esi
|
|
xor edi, edi
|
|
xor ebp, ebp
|
|
mov esp, 0x8000 ; Set a known free location for the stack
|
|
|
|
mov al, '2' ; Now in 32-bit protected mode (0x20 = 32)
|
|
mov [0x000B809C], al
|
|
mov al, '0'
|
|
mov [0x000B809E], al
|
|
|
|
; Clear out the first 4096 bytes of memory. This will store the 64-bit IDT, GDT, PML4, and PDP
|
|
mov ecx, 1024
|
|
xor eax, eax
|
|
mov edi, eax
|
|
rep stosd
|
|
|
|
; Clear memory for the Page Descriptor Entries (0x10000 - 0x4FFFF)
|
|
mov edi, 0x00010000
|
|
mov ecx, 65536
|
|
rep stosd
|
|
|
|
; Copy the GDT to its final location in memory
|
|
mov esi, gdt64
|
|
mov edi, 0x00001000 ; GDT address
|
|
mov ecx, (gdt64_end - gdt64)
|
|
rep movsb ; Move it to final pos.
|
|
|
|
; Create the Level 4 Page Map. (Maps 4GBs of 2MB pages)
|
|
; First create a PML4 entry.
|
|
; PML4 is stored at 0x0000000000002000, create the first entry there
|
|
; A single PML4 entry can map 512GB with 2MB pages.
|
|
cld
|
|
mov edi, 0x00002000 ; Create a PML4 entry for the first 4GB of RAM
|
|
mov eax, 0x00003007
|
|
stosd
|
|
xor eax, eax
|
|
stosd
|
|
|
|
mov edi, 0x00002800 ; Create a PML4 entry for higher half (starting at 0xFFFF800000000000)
|
|
mov eax, 0x00003007 ; The higher half is identity mapped to the lower half
|
|
stosd
|
|
xor eax, eax
|
|
stosd
|
|
|
|
; Create the PDP entries.
|
|
; The first PDP is stored at 0x0000000000003000, create the first entries there
|
|
; A single PDP entry can map 1GB with 2MB pages
|
|
mov ecx, 64 ; number of PDPE's to make.. each PDPE maps 1GB of physical memory
|
|
mov edi, 0x00003000
|
|
mov eax, 0x00010007 ; location of first PD
|
|
create_pdpe:
|
|
stosd
|
|
push eax
|
|
xor eax, eax
|
|
stosd
|
|
pop eax
|
|
add eax, 0x00001000 ; 4K later (512 records x 8 bytes)
|
|
dec ecx
|
|
cmp ecx, 0
|
|
jne create_pdpe
|
|
|
|
; Create the PD entries.
|
|
; PD entries are stored starting at 0x0000000000010000 and ending at 0x000000000004FFFF (256 KiB)
|
|
; This gives us room to map 64 GiB with 2 MiB pages
|
|
mov edi, 0x00010000
|
|
mov eax, 0x0000008F ; Bit 7 must be set to 1 as we have 2 MiB pages
|
|
xor ecx, ecx
|
|
pd_again: ; Create a 2 MiB page
|
|
stosd
|
|
push eax
|
|
xor eax, eax
|
|
stosd
|
|
pop eax
|
|
add eax, 0x00200000
|
|
inc ecx
|
|
cmp ecx, 2048
|
|
jne pd_again ; Create 2048 2 MiB page maps.
|
|
|
|
; Load the GDT
|
|
lgdt [GDTR64]
|
|
|
|
; Enable extended properties
|
|
mov eax, cr4
|
|
or eax, 0x0000000B0 ; PGE (Bit 7), PAE (Bit 5), and PSE (Bit 4)
|
|
mov cr4, eax
|
|
|
|
; Point cr3 at PML4
|
|
mov eax, 0x00002008 ; Write-thru (Bit 3)
|
|
mov cr3, eax
|
|
|
|
; Enable long mode and SYSCALL/SYSRET
|
|
mov ecx, 0xC0000080 ; EFER MSR number
|
|
rdmsr ; Read EFER
|
|
or eax, 0x00000101 ; LME (Bit 8)
|
|
wrmsr ; Write EFER
|
|
|
|
; Debug
|
|
mov al, '1' ; About to make the jump into 64-bit mode
|
|
mov [0x000B809C], al
|
|
mov al, 'E'
|
|
mov [0x000B809E], al
|
|
|
|
; Enable paging to activate long mode
|
|
mov eax, cr0
|
|
or eax, 0x80000000 ; PG (Bit 31)
|
|
mov cr0, eax
|
|
|
|
jmp SYS64_CODE_SEL:start64 ; Jump to 64-bit mode
|
|
|
|
;db '_64_' ; Debug
|
|
align 16
|
|
|
|
|
|
; =============================================================================
|
|
; 64-bit mode
|
|
USE64
|
|
|
|
start64:
|
|
; Debug
|
|
mov al, '4' ; Now in 64-bit mode (0x40 = 64)
|
|
mov [0x000B809C], al
|
|
mov al, '0'
|
|
mov [0x000B809E], al
|
|
|
|
mov al, 2
|
|
mov ah, 22
|
|
call os_move_cursor
|
|
|
|
xor rax, rax ; aka r0
|
|
xor rbx, rbx ; aka r3
|
|
xor rcx, rcx ; aka r1
|
|
xor rdx, rdx ; aka r2
|
|
xor rsi, rsi ; aka r6
|
|
xor rdi, rdi ; aka r7
|
|
xor rbp, rbp ; aka r5
|
|
mov rsp, 0x8000 ; aka r4
|
|
xor r8, r8
|
|
xor r9, r9
|
|
xor r10, r10
|
|
xor r11, r11
|
|
xor r12, r12
|
|
xor r13, r13
|
|
xor r14, r14
|
|
xor r15, r15
|
|
|
|
mov ds, ax ; Clear the legacy segment registers
|
|
mov es, ax
|
|
mov ss, ax
|
|
mov fs, ax
|
|
mov gs, ax
|
|
|
|
mov rax, clearcs64 ; Do a proper 64-bit jump. Should not be needed as the ...
|
|
jmp rax ; jmp SYS64_CODE_SEL:start64 would have sent us ...
|
|
nop ; out of compatibility mode and into 64-bit mode
|
|
clearcs64:
|
|
xor rax, rax
|
|
|
|
lgdt [GDTR64] ; Reload the GDT
|
|
|
|
; Debug
|
|
mov al, '2'
|
|
mov [0x000B809E], al
|
|
|
|
; Patch Pure64 AP code ; The AP's will be told to start execution at 0x8000
|
|
mov edi, ap_modify ; We need to remove the BSP Jump call to get the AP's
|
|
mov eax, 0x90909090 ; to fall through to the AP Init code
|
|
stosd
|
|
|
|
; Build the rest of the page tables (4GiB+)
|
|
mov rcx, 0x0000000000000000
|
|
mov rax, 0x000000010000008F
|
|
mov rdi, 0x0000000000014000
|
|
buildem:
|
|
stosq
|
|
add rax, 0x0000000000200000
|
|
add rcx, 1
|
|
cmp rcx, 30720 ; Another 60 GiB (We already mapped 4 GiB)
|
|
jne buildem
|
|
; We have 64 GiB mapped now
|
|
|
|
; Build a temporary IDT
|
|
xor rdi, rdi ; create the 64-bit IDT (at linear address 0x0000000000000000)
|
|
|
|
mov rcx, 32
|
|
make_exception_gates: ; make gates for exception handlers
|
|
mov rax, exception_gate
|
|
push rax ; save the exception gate to the stack for later use
|
|
stosw ; store the low word (15..0) of the address
|
|
mov ax, SYS64_CODE_SEL
|
|
stosw ; store the segment selector
|
|
mov ax, 0x8E00
|
|
stosw ; store exception gate marker
|
|
pop rax ; get the exception gate back
|
|
shr rax, 16
|
|
stosw ; store the high word (31..16) of the address
|
|
shr rax, 16
|
|
stosd ; store the extra high dword (63..32) of the address.
|
|
xor rax, rax
|
|
stosd ; reserved
|
|
dec rcx
|
|
jnz make_exception_gates
|
|
|
|
mov rcx, 256-32
|
|
make_interrupt_gates: ; make gates for the other interrupts
|
|
mov rax, interrupt_gate
|
|
push rax ; save the interrupt gate to the stack for later use
|
|
stosw ; store the low word (15..0) of the address
|
|
mov ax, SYS64_CODE_SEL
|
|
stosw ; store the segment selector
|
|
mov ax, 0x8F00
|
|
stosw ; store interrupt gate marker
|
|
pop rax ; get the interrupt gate back
|
|
shr rax, 16
|
|
stosw ; store the high word (31..16) of the address
|
|
shr rax, 16
|
|
stosd ; store the extra high dword (63..32) of the address.
|
|
xor rax, rax
|
|
stosd ; reserved
|
|
dec rcx
|
|
jnz make_interrupt_gates
|
|
|
|
; Set up the exception gates for all of the CPU exceptions
|
|
; The following code will be seriously busted if the exception gates are moved above 16MB
|
|
mov word [0x00*16], exception_gate_00
|
|
mov word [0x01*16], exception_gate_01
|
|
mov word [0x02*16], exception_gate_02
|
|
mov word [0x03*16], exception_gate_03
|
|
mov word [0x04*16], exception_gate_04
|
|
mov word [0x05*16], exception_gate_05
|
|
mov word [0x06*16], exception_gate_06
|
|
mov word [0x07*16], exception_gate_07
|
|
mov word [0x08*16], exception_gate_08
|
|
mov word [0x09*16], exception_gate_09
|
|
mov word [0x0A*16], exception_gate_10
|
|
mov word [0x0B*16], exception_gate_11
|
|
mov word [0x0C*16], exception_gate_12
|
|
mov word [0x0D*16], exception_gate_13
|
|
mov word [0x0E*16], exception_gate_14
|
|
mov word [0x0F*16], exception_gate_15
|
|
mov word [0x10*16], exception_gate_16
|
|
mov word [0x11*16], exception_gate_17
|
|
mov word [0x12*16], exception_gate_18
|
|
mov word [0x13*16], exception_gate_19
|
|
|
|
mov rdi, 0x21 ; Set up Keyboard handler
|
|
mov rax, keyboard
|
|
call create_gate
|
|
mov rdi, 0x22 ; Set up Cascade handler
|
|
mov rax, cascade
|
|
call create_gate
|
|
mov rdi, 0x28 ; Set up RTC handler
|
|
mov rax, rtc
|
|
call create_gate
|
|
|
|
lidt [IDTR64] ; load IDT register
|
|
|
|
; Debug
|
|
mov al, '4'
|
|
mov [0x000B809E], al
|
|
|
|
; Clear memory 0xf000 - 0xf7ff for the infomap (2048 bytes)
|
|
xor rax, rax
|
|
mov rcx, 256
|
|
mov rdi, 0x000000000000F000
|
|
clearmapnext:
|
|
stosq
|
|
dec rcx
|
|
cmp rcx, 0
|
|
jne clearmapnext
|
|
|
|
call init_acpi ; Find and process the ACPI tables
|
|
|
|
call init_cpu ; Configure the BSP CPU
|
|
|
|
call init_pic ; Configure the PIC(s), also activate interrupts
|
|
|
|
; Debug
|
|
mov al, '6' ; CPU Init complete
|
|
mov [0x000B809E], al
|
|
|
|
; Make sure exceptions are working.
|
|
; xor rax, rax
|
|
; xor rbx, rbx
|
|
; xor rcx, rcx
|
|
; xor rdx, rdx
|
|
; div rax
|
|
|
|
; Init of SMP
|
|
call init_smp
|
|
|
|
; Reset the stack to the proper location (was set to 0x8000 previously)
|
|
mov rsi, [os_LocalAPICAddress] ; We would call os_smp_get_id here but the stack is not ...
|
|
add rsi, 0x20 ; ... yet defined. It is safer to find the value directly.
|
|
lodsd ; Load a 32-bit value. We only want the high 8 bits
|
|
shr rax, 24 ; Shift to the right and AL now holds the CPU's APIC ID
|
|
shl rax, 10 ; shift left 10 bits for a 1024byte stack
|
|
add rax, 0x0000000000050400 ; stacks decrement when you "push", start at 1024 bytes in
|
|
mov rsp, rax ; Pure64 leaves 0x50000-0x9FFFF free so we use that
|
|
|
|
; Debug
|
|
mov al, '6' ; SMP Init complete
|
|
mov [0x000B809C], al
|
|
mov al, '0'
|
|
mov [0x000B809E], al
|
|
|
|
; Calculate amount of usable RAM from Memory Map
|
|
xor rcx, rcx
|
|
mov rsi, 0x0000000000004000 ; E820 Map location
|
|
readnextrecord:
|
|
lodsq
|
|
lodsq
|
|
lodsd
|
|
cmp eax, 0 ; Are we at the end?
|
|
je endmemcalc
|
|
cmp eax, 1 ; Useable RAM
|
|
je goodmem
|
|
cmp eax, 3 ; ACPI Reclaimable
|
|
je goodmem
|
|
cmp eax, 6 ; BIOS Reclaimable
|
|
je goodmem
|
|
lodsd
|
|
lodsq
|
|
jmp readnextrecord
|
|
goodmem:
|
|
sub rsi, 12
|
|
lodsq
|
|
add rcx, rax
|
|
lodsq
|
|
lodsq
|
|
jmp readnextrecord
|
|
|
|
endmemcalc:
|
|
shr rcx, 20 ; Value is in bytes so do a quick divide by 1048576 to get MiB's
|
|
add ecx, 1 ; The BIOS will usually report actual memory minus 1
|
|
and ecx, 0xFFFFFFFE ; Make sure it is an even number (in case we added 1 to an even number)
|
|
mov dword [mem_amount], ecx
|
|
|
|
; Debug
|
|
mov al, '2'
|
|
mov [0x000B809E], al
|
|
|
|
; Convert CPU speed value to string
|
|
xor rax, rax
|
|
mov ax, [cpu_speed]
|
|
mov rdi, speedtempstring
|
|
call os_int_to_string
|
|
|
|
; Convert CPU amount value to string
|
|
xor rax, rax
|
|
mov ax, [cpu_activated]
|
|
mov rdi, cpu_amount_string
|
|
call os_int_to_string
|
|
|
|
; Convert RAM amount value to string
|
|
xor rax, rax
|
|
mov eax, [mem_amount]
|
|
mov rdi, memtempstring
|
|
call os_int_to_string
|
|
|
|
; Build the infomap
|
|
xor rdi, rdi
|
|
mov di, 0x5000
|
|
mov rax, [os_ACPITableAddress]
|
|
stosq
|
|
mov eax, [os_BSP]
|
|
stosd
|
|
|
|
mov di, 0x5010
|
|
mov ax, [cpu_speed]
|
|
stosw
|
|
mov ax, [cpu_activated]
|
|
stosw
|
|
mov ax, [cpu_detected]
|
|
stosw
|
|
|
|
mov di, 0x5020
|
|
mov ax, [mem_amount]
|
|
stosd
|
|
|
|
mov di, 0x5030
|
|
mov al, [os_IOAPICCount]
|
|
stosb
|
|
|
|
mov di, 0x5040
|
|
mov rax, [os_HPETAddress]
|
|
stosq
|
|
|
|
mov di, 0x5060
|
|
mov rax, [os_LocalAPICAddress]
|
|
stosq
|
|
xor ecx, ecx
|
|
mov cl, [os_IOAPICCount]
|
|
mov rsi, os_IOAPICAddress
|
|
nextIOAPIC:
|
|
lodsq
|
|
stosq
|
|
sub cl, 1
|
|
cmp cl, 0
|
|
jne nextIOAPIC
|
|
|
|
mov di, 0x5080
|
|
mov eax, [VBEModeInfoBlock.PhysBasePtr] ; Base address of video memory (if graphics mode is set)
|
|
stosd
|
|
mov eax, [VBEModeInfoBlock.XResolution] ; X and Y resolution (16-bits each)
|
|
stosd
|
|
mov al, [VBEModeInfoBlock.BitsPerPixel] ; Color depth
|
|
stosb
|
|
|
|
; Initialization is now complete... write a message to the screen
|
|
mov rsi, msg_done
|
|
call os_print_string
|
|
|
|
; Debug
|
|
mov al, '4'
|
|
mov [0x000B809E], al
|
|
|
|
; Print info on CPU and MEM
|
|
mov ax, 0x0004
|
|
call os_move_cursor
|
|
mov rsi, msg_CPU
|
|
call os_print_string
|
|
mov rsi, speedtempstring
|
|
call os_print_string
|
|
mov rsi, msg_mhz
|
|
call os_print_string
|
|
mov rsi, cpu_amount_string
|
|
call os_print_string
|
|
mov rsi, msg_MEM
|
|
call os_print_string
|
|
mov rsi, memtempstring
|
|
call os_print_string
|
|
mov rsi, msg_mb
|
|
call os_print_string
|
|
|
|
; Move the trailing binary to its final location
|
|
mov rsi, 0x60000+6144 ; Memory offset to end of pure64.sys
|
|
mov rdi, 0x100000 ; Destination address at the 1MiB mark
|
|
mov rcx, 0x8000 ; For up to 256KiB kernel (262144 / 8)
|
|
rep movsq ; Copy 8 bytes at a time
|
|
|
|
; Print a message that the kernel is being started
|
|
mov ax, 0x0006
|
|
call os_move_cursor
|
|
mov rsi, msg_startingkernel
|
|
call os_print_string
|
|
|
|
; Debug
|
|
mov rdi, 0x000B8092 ; Clear the debug messages
|
|
mov ax, 0x0720
|
|
mov cx, 7
|
|
clearnext:
|
|
stosw
|
|
sub cx, 1
|
|
cmp cx, 0
|
|
jne clearnext
|
|
|
|
; Clear all registers (skip the stack pointer)
|
|
xor rax, rax
|
|
xor rbx, rbx
|
|
xor rcx, rcx
|
|
xor rdx, rdx
|
|
xor rsi, rsi
|
|
xor rdi, rdi
|
|
xor rbp, rbp
|
|
xor r8, r8
|
|
xor r9, r9
|
|
xor r10, r10
|
|
xor r11, r11
|
|
xor r12, r12
|
|
xor r13, r13
|
|
xor r14, r14
|
|
xor r15, r15
|
|
|
|
jmp 0x0000000000100000 ; Jump to the kernel
|
|
|
|
|
|
%include "init/acpi.asm"
|
|
%include "init/cpu.asm"
|
|
%include "init/pic.asm"
|
|
%include "init/smp.asm"
|
|
%include "syscalls.asm"
|
|
%include "interrupt.asm"
|
|
%include "sysvar.asm"
|
|
|
|
; Pad to an even KB file (6 KiB)
|
|
times 6144-($-$$) db 0x90
|
|
|
|
|
|
; =============================================================================
|
|
; EOF
|