1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

Switch to a 64-bit version of the kernel and rt0 code

The switch to 64-bit mode allows us to use 48-bit addressing and to
relocate the kernel to virtual address 0xffff800000000000 + 1M. The
actual kernel is loaded by the bootloader at physical address 1M.

The rt0 code has been split in two parts. The 32-bit part provides the
entrypoint that the bootloader jumps to after loading the kernel. Its
purpose is to make sure that:
- the kernel was booted by a multiboot-compliant bootloader
- the multiboot info structures are copied to a reserved memory block
  where they can be accessed after enabling paging
- the CPU meets the minimum requirements for the kernel (CPUID, SSE,
  support for long-mode)

Since paging is not enabled when the 32-bit code runs, it needs to
translate all memory addresses it accesses to physical memory addresses
by subtracting PAGE_OFFSET. The 32-bit rt0 code will set up a page table
that identity-maps region: 0 to 8M and region: PAGE_OFFSET to
PAGE_OFFSET+8M. This ensures that when paging gets enabled, we will still
be able to access the kernel using both physical and virtual memory
addresses. After enabling paging, the 32-bit rt0 will jump to a small
64-bit trampoline function that updates the stack pointer to use the
proper virtual address and jumps to the virtual address of the 64-bit
entry point.

The 64-bit entrypoint sets up the minimal g0 structure required by the
go function prologue for stack checks and sets up the FS register to
point to it. The principle is the same as with 32-bit code (a segment
register has the address of a pointer to the active g) with the
difference that in 64-bit mode, the FS register is used instead of GS
and that in order to set its value we need to write to a MSR.
This commit is contained in:
Achilleas Anagnostopoulos
2017-04-26 08:30:38 +01:00
parent 4829115647
commit 2558f79fbf
14 changed files with 560 additions and 288 deletions

View File

@@ -0,0 +1,24 @@
; vim: set ft=nasm :
section .text
bits 64
global x_cgo_callers
global x_cgo_init
global x_cgo_mmap
global x_cgo_notify_runtime_init_done
global x_cgo_sigaction
global x_cgo_thread_start
global x_cgo_setenv
global x_cgo_unsetenv
; Stubs for missing cgo functions to keep the linker happy
x_cgo_callers:
x_cgo_init:
x_cgo_mmap:
x_cgo_notify_runtime_init_done:
x_cgo_sigaction:
x_cgo_thread_start:
x_cgo_setenv:
x_cgo_unsetenv:
ret

View File

@@ -0,0 +1,12 @@
; vim: set ft=nasm :
; The bootloader load the kernel at LOAD_ADDRESS and jumps to the rt0_32 entrypoint
; at this address.
LOAD_ADDRESS equ 0x100000
; Page offset is the start of the 48-bit upper half canonical memory region
; The kernel is compiled with a VMA equal to PAGE_OFFSET + LOAD_ADDRESS but
; loaded at physical address LOAD_ADDRESS.
PAGE_OFFSET equ 0xffff800000000000

0
arch/x86_64/asm/data.s Normal file
View File

View File

@@ -0,0 +1,41 @@
; vim: set ft=nasm :
section .multiboot_header
MAGIC equ 0xe85250d6
ARCH equ 0x0
; Define the multiboot header (multiboot 1.6)
; http://nongnu.askapache.com/grub/phcoder/multiboot.pdf
header_start:
dd MAGIC ; magic number
dd ARCH ; i386 protected mode
dd header_end - header_start ; header length
; The field checksum is a 32-bit unsigned value which, when added to the other
; magic fields (i.e. magic, architecture and header_length), must have a
; 32-bit unsigned sum of zero.
dd (1 << 32) - (MAGIC + ARCH + (header_end - header_start))
; Console flags tag
align 8 ; tags should be 64-bit aligned
dw 4 ; type
dw 0 ; flags
dd 12 ; size
dd 0x3 ; kernel supports EGA console
; Define graphics mode tag
;align 8 ; tags should be 64-bit aligned
;dw 5 ; type
;dw 0 ; flags
;dd 20 ; size
;dd 80 ; width (pixels or chars)
;dd 25 ; height (pixels or chars)
;dd 0 ; bpp (0 for text mode
; According to page 6 of the spec, the tag list is terminated by a tag with
; type 0 and size 8
align 8 ; tags should be 64-bit aligned
dd 0 ; type & flag = 0
dd 8 ; size
header_end:

356
arch/x86_64/asm/rt0_32.s Normal file
View File

@@ -0,0 +1,356 @@
; vim: set ft=nasm :
%include "constants.inc"
section .data
align 4
; GDT definition
gdt0:
gdt0_nil_seg: dw 0 ; Limit (low)
dw 0 ; Base (low)
db 0 ; Base (middle)
db 0 ; Access (exec/read)
db 0 ; Granularity
db 0 ; Base (high)
gdt0_cs_seg: dw 0 ; Limit (low)
dw 0 ; Base (low)
db 0 ; Base (middle)
db 10011010b ; Access (exec/read)
db 00100000b ; Granularity
db 0 ; Base (high)
gdt0_ds_seg: dw 0 ; Limit (low)
dw 0 ; Base (low)
db 0 ; Base (middle)
db 10010010b ; Access (read/write)
db 00000000b ; Granularity
db 0 ; Base (high)
gdt0_desc:
dw $ - gdt0 - 1 ; gdt size should be 1 byte less than actual length
dq gdt0 - PAGE_OFFSET
NULL_SEG equ gdt0_nil_seg - gdt0
CS_SEG equ gdt0_cs_seg - gdt0
DS_SEG equ gdt0_ds_seg - gdt0
;------------------------------------------------------------------------------
; Error messages
;------------------------------------------------------------------------------
err_unsupported_bootloader db '[rt0_32] kernel not loaded by multiboot-compliant bootloader', 0
err_multiboot_data_too_big db '[rt0_32] multiboot information data length exceeds local buffer size', 0
err_cpuid_not_supported db '[rt0_32] the processor does not support the CPUID instruction', 0
err_longmode_not_supported db '[rt0_32] the processor does not support longmode which is required by this kernel', 0
err_sse_not_supported db '[rt0_32] the processor does not support SSE instructions which are required by this kernel', 0
section .bss
align 4096
; Reserve 3 pages for the initial page tables
page_table_l4: resb 4096
page_table_l3: resb 4096
page_table_l2: resb 4096
; Reserve 16K for storing multiboot data and for the kernel stack
global multiboot_data ; Make this available to the 64-bit entrypoint
global stack_bottom
global stack_top
multiboot_data: resb 16384
stack_bottom: resb 16384
stack_top:
section .rt0
bits 32
align 4
;------------------------------------------------------------------------------
; Kernel 32-bit entry point
;
; The boot loader will jump to this symbol after setting up the CPU according
; to the multiboot standard. At this point:
; - A20 is enabled
; - The CPU is using 32-bit protected mode
; - Interrupts are disabled
; - Paging is disabled
; - EAX contains the magic value 0x36d76289; the presence of this value indicates
; to the operating system that it was loaded by a Multiboot-compliant boot loader
; - EBX contains the 32-bit physical address of the Multiboot information structure
;------------------------------------------------------------------------------
global _rt0_32_entry
_rt0_32_entry:
; Provide a stack
mov esp, stack_top - PAGE_OFFSET
; Ensure we were booted by a bootloader supporting multiboot
cmp eax, 0x36d76289
jne _rt0_32_entry.unsupported_bootloader
; Copy multiboot struct to our own buffer
call _rt0_copy_multiboot_data
; Check processor features
call _rt0_check_cpuid_support
call _rt0_check_longmode_support
call _rt0_check_sse_support
; Setup initial page tables, enable paging and enter longmode
call _rt0_populate_initial_page_tables
call _rt0_enter_long_mode
call _rt0_64_entry_trampoline
.unsupported_bootloader:
mov edi, err_unsupported_bootloader - PAGE_OFFSET
call write_string
jmp _rt0_32_entry.halt
.halt:
cli
hlt
;------------------------------------------------------------------------------
; Copy multiboot information blocks from the address pointed to by ebx into a
; local buffer. This enables the kernel code to access them once paging is enabled.
;------------------------------------------------------------------------------
_rt0_copy_multiboot_data:
mov esi, ebx
mov edi, multiboot_data - PAGE_OFFSET
mov ecx, dword [esi]
cmp ecx, 16384
jle _rt0_copy_multiboot_data.copy
mov edi, err_multiboot_data_too_big - PAGE_OFFSET
call write_string
jmp _rt0_32_entry.halt
.copy:
test ecx, ecx
jz _rt0_copy_multiboot_data.done
mov eax, dword[esi]
mov dword [edi], eax
add esi, 4
add edi, 4
sub ecx, 4
jmp _rt0_copy_multiboot_data.copy
.done:
ret
;------------------------------------------------------------------------------
; Check that the processor supports the CPUID instruction.
;
; To check if CPUID is supported, we need to attempt to flip the ID bit (bit 21)
; in the FLAGS register. If that works, CPUID is available.
;
; Code taken from: http://wiki.osdev.org/Setting_Up_Long_Mode#x86_or_x86-64
;------------------------------------------------------------------------------
_rt0_check_cpuid_support:
; Copy FLAGS in to EAX via stack
pushfd
pop eax
; Copy to ECX as well for comparing later on
mov ecx, eax
; Flip the ID bit
xor eax, 1 << 21
; Copy EAX to FLAGS via the stack
push eax
popfd
; Copy FLAGS back to EAX (with the flipped bit if CPUID is supported)
pushfd
pop eax
; Restore FLAGS from the old version stored in ECX (i.e. flipping the
; ID bit back if it was ever flipped).
push ecx
popfd
; Compare EAX and ECX. If they are equal then that means the bit
; wasn't flipped, and CPUID isn't supported.
cmp eax, ecx
je _rt0_check_cpuid_support.no_cpuid
ret
.no_cpuid:
mov edi, err_cpuid_not_supported - PAGE_OFFSET
call write_string
jmp _rt0_32_entry.halt
;------------------------------------------------------------------------------
; Check that the processor supports long mode
; Code taken from: http://wiki.osdev.org/Setting_Up_Long_Mode#x86_or_x86-64
;------------------------------------------------------------------------------
_rt0_check_longmode_support:
; To check for longmode support we need to ensure that the CPUID instruction
; can report it. To do this we need to query it first.
mov eax, 0x80000000 ; Set the A-register to 0x80000000.
cpuid
cmp eax, 0x80000001 ; We need at least 0x80000001 to check for long mode.
jb _rt0_check_longmode_support.no_long_mode
mov eax, 0x80000001 ; Set the A-register to 0x80000001.
cpuid
test edx, 1 << 29 ; Test if the LM-bit, which is bit 29, is set in the D-register.
jz _rt0_check_longmode_support.no_long_mode
ret
.no_long_mode:
mov edi, err_longmode_not_supported - PAGE_OFFSET
call write_string
jmp _rt0_32_entry.halt
;------------------------------------------------------------------------------
; Check for and enabl SSE support. Code taken from:
; http://wiki.osdev.org/SSE#Checking_for_SSE
;------------------------------------------------------------------------------
_rt0_check_sse_support:
; check for SSE
mov eax, 0x1
cpuid
test edx, 1<<25
jz _rt0_check_sse_support.no_sse
; Enable SSE
mov eax, cr0
and ax, 0xfffb ; Clear coprocessor emulation CR0.EM
or ax, 0x2 ; Set coprocessor monitoring CR0.MP
mov cr0, eax
mov eax, cr4
or ax, 3 << 9 ; Set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
mov cr4, eax
ret
.no_sse:
mov edi, err_sse_not_supported - PAGE_OFFSET
call write_string
jmp _rt0_32_entry.halt
;------------------------------------------------------------------------------
; Setup minimal page tables to allow access to the following regions:
; - 0 to 8M
; - PAGE_OFFSET to PAGE_OFFSET + 8M
;
; The second region mapping allows us to access the kernel at its VMA when
; paging is enabled.
;------------------------------------------------------------------------------
PAGE_PRESENT equ (1 << 0)
PAGE_WRITABLE equ (1 << 1)
PAGE_2MB equ (1 << 7)
_rt0_populate_initial_page_tables:
; The CPU uses bits 39-47 of the virtual address as an index to the P4 table.
mov eax, page_table_l3 - PAGE_OFFSET
or eax, PAGE_PRESENT | PAGE_WRITABLE
mov ebx, page_table_l4 - PAGE_OFFSET
mov [ebx], eax
; Also map the addresses starting at PAGE_OFFSET to the same P3 table.
; To find the P4 index for PAGE_OFFSET we need to extract bits 39-47
; of its address.
mov ecx, (PAGE_OFFSET >> 39) & 511
mov [ebx + ecx*8], eax
; The CPU uses bits 30-38 as an index to the P3 table. We just need to map
; entry 0 from the P3 table to point to the P2 table .
mov eax, page_table_l2 - PAGE_OFFSET
or eax, PAGE_PRESENT | PAGE_WRITABLE
mov ebx, page_table_l3 - PAGE_OFFSET
mov [ebx], eax
; For the L2 table we enable the huge page bit which allows us to specify
; 2M pages without needing to use the L1 table. To cover the required
; 0-8M region we need to provide 4 2M page entries at indices 0 to 4.
mov ecx, 0
mov ebx, page_table_l2 - PAGE_OFFSET
.next_page:
mov eax, 1 << 21 ; 2M
mul ecx ; eax *= ecx
or eax, PAGE_PRESENT | PAGE_WRITABLE | PAGE_2MB
mov [ebx + ecx*8], eax
inc ecx
cmp ecx, 4
jne _rt0_populate_initial_page_tables.next_page
ret
;------------------------------------------------------------------------------
; Load P4 table, enable PAE, enter long mode and finally enable paging
;------------------------------------------------------------------------------
_rt0_enter_long_mode:
; Load page table map pointer to cr3
mov eax, page_table_l4 - PAGE_OFFSET
mov cr3, eax
; Enable PAE support
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
; Now enable long mode by modifying the EFER MSR
mov ecx, 0xc0000080
rdmsr ; read msr value to eax
or eax, 1 << 8
wrmsr
; Finally enable paging
mov eax, cr0
or eax, 1 << 31
mov cr0, eax
; We are in 32-bit compatibility submode. We need to load a 64bit GDT
; and perform a far jmp to switch to long mode
mov eax, gdt0_desc - PAGE_OFFSET
lgdt [eax]
; set ds and es segments
; to set the cs segment we need to perform a far jmp
mov ax, DS_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
jmp CS_SEG:.flush_gdt - PAGE_OFFSET
.flush_gdt:
ret
;------------------------------------------------------------------------------
; Write the NULL-terminated string contained in edi to the screen using white
; text on red background. Assumes that text-mode is enabled and that its
; physical address is 0xb8000.
;------------------------------------------------------------------------------
write_string:
mov ebx,0xb8000
mov ah, 0x4F
.next_char:
mov al, byte[edi]
test al, al
jz write_string.done
mov word [ebx], ax
add ebx, 2
inc edi
jmp write_string.next_char
.done:
ret
;------------------------------------------------------------------------------
; Set up the stack pointer to the virtual address of the stack and jump to the
; 64-bit entrypoint.
;------------------------------------------------------------------------------
bits 64
_rt0_64_entry_trampoline:
mov rsp, stack_top ; now that paging is enabled we can load the stack
; with the virtual address of the allocated stack.
; Jump to 64-bit entry
extern _rt0_64_entry
mov rax, _rt0_64_entry
jmp rax

95
arch/x86_64/asm/rt0_64.s Normal file
View File

@@ -0,0 +1,95 @@
; vim: set ft=nasm :
section .bss
align 8
r0_g_ptr: resq 1 ; fs:0x00 is a pointer to the current g struct
r0_g:
r0_g_stack_lo: resq 1
r0_g_stack_hi: resq 1
r0_g_stackguard0: resq 1 ; rsp compared to this value in go stack growth prologue
r0_g_stackguard1: resq 1 ; rsp compared to this value in C stack growth prologue
section .text
bits 64
;------------------------------------------------------------------------------
; Kernel 64-bit entry point
;
; The 32-bit entrypoint code jumps to this entrypoint after:
; - it has entered long mode and enabled paging
; - it has loaded a 64bit GDT
; - it has set up identity paging for the physical 0-8M region and the
; PAGE_OFFSET to PAGE_OFFSET+8M region.
;------------------------------------------------------------------------------
global _rt0_64_entry
_rt0_64_entry:
; According to the x86_64 ABI, the fs:0 should point to the address of
; the user-space thread structure. The actual TLS structure is located
; just before that (aligned). Go code tries to fetch the address to the
; active go-routine's g struct by accessing fs:-8. What we need to do
; is to setup a mock g0 struct, populate its stack_lo/hi/guard fields
; and then use wrmsr to update the FS register
extern stack_top
extern stack_bottom
; Setup r0_g
mov rax, stack_bottom
mov rbx, stack_top
mov rsi, r0_g
mov qword [rsi+0], rax ; stack_lo
mov qword [rsi+8], rbx ; stack_hi
mov qword [rsi+16], rax ; stackguard0
mov rax, r0_g_ptr
mov qword [rax], rsi
; Load 64-bit FS register address
; rax -> lower 32 bits
; rdx -> upper 32 bits
mov ecx, 0xc0000100 ; fs_base
mov rax, rsi ; lower 32 bits
shr rsi, 32
mov rdx, rsi ; high 32 bits
wrmsr
; Call the kernel entry point passing a pointer to the multiboot data
; copied by the 32-bit entry code
extern multiboot_data
extern kernel.Kmain
mov rax, multiboot_data
push rax
call kernel.Kmain
; Main should never return; halt the CPU
mov rdi, err_kmain_returned
call write_string
cli
hlt
;------------------------------------------------------------------------------
; Error messages
;------------------------------------------------------------------------------
err_kmain_returned db '[rt0_64] kmain returned', 0
;------------------------------------------------------------------------------
; Write the NULL-terminated string contained in rdi to the screen using white
; text on red background. Assumes that text-mode is enabled and that its
; physical address is 0xb8000.
;------------------------------------------------------------------------------
write_string:
mov rbx,0xb8000
mov ah, 0x4F
.next_char:
mov al, byte[rdi]
test al, al
jz write_string.done
mov word [rbx], ax
add rbx, 2
inc rdi
jmp write_string.next_char
.done:
ret