forked from BSB-WS23/mpstubs
246 lines
7.1 KiB
NASM
246 lines
7.1 KiB
NASM
; The stony path to Long Mode (64-bit)...
|
|
; ... begins in 32-bit Protected Mode
|
|
[BITS 32]
|
|
|
|
; Pointer to Long Mode Global Descriptor Table (GDT, machine/gdt.cc)
|
|
[EXTERN gdt_long_mode_pointer]
|
|
|
|
[GLOBAL long_mode]
|
|
long_mode:
|
|
|
|
; You can check if the CPU supports Long Mode by using the `cpuid` command.
|
|
; Problem: You first have to figure out if the `cpuid` command itself is
|
|
; supported. Therefore, you have to try to reverse the 21st bit in the EFLAGS
|
|
; register -- if it works, then there is the 'cpuid' instruction.
|
|
CPUID_BIT_MASK equ 1 << 21
|
|
|
|
check_cpuid:
|
|
; Save EFLAGS on stack
|
|
pushfd
|
|
|
|
; Copy stored EFLAGS from stack to EAX register
|
|
mov eax, [esp]
|
|
|
|
; Flip the 21st bit (ID) in EAX
|
|
xor eax, CPUID_BIT_MASK
|
|
|
|
; Copy EAX to EFLAGS (using the stack)
|
|
push eax
|
|
popfd
|
|
|
|
; And reverse: copy EFLAGS to EAX (using the stack)
|
|
; (but the 21st bit should now still be flipped, if `cpuid` is supported)
|
|
pushfd
|
|
pop eax
|
|
|
|
; Compare the new EFLAGS copy (residing in EAX) with the EFLAGS stored at
|
|
; the beginning of this function by using an exclusive OR -- all different
|
|
; (flipped) bits will be stored in EAX.
|
|
xor eax, [esp]
|
|
|
|
; Restore original EFLAGS
|
|
popfd
|
|
|
|
; If 21st Bit in EAX is set, `cpuid` is supported -- continue at check_long_mode
|
|
and eax, CPUID_BIT_MASK
|
|
jnz check_long_mode
|
|
|
|
; Show error message "No CPUID" and stop CPU
|
|
mov dword [0xb8000], 0xcf6fcf4e
|
|
mov dword [0xb8004], 0xcf43cf20
|
|
mov dword [0xb8008], 0xcf55cf50
|
|
mov dword [0xb800c], 0xcf44cf49
|
|
hlt
|
|
|
|
; Now you are able to use the `cpuid` instruction to check if Long Mode is
|
|
; available -- after you've checked if the `cpuid` is able to perform the
|
|
; check itself (since it is an extended `cpuid` function)...
|
|
|
|
CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER equ 0x80000000
|
|
CPUID_GET_EXTENDED_PROCESSOR_FEATURES equ 0x80000001
|
|
CPUID_HAS_LONGMODE equ 1 << 29
|
|
|
|
check_long_mode:
|
|
; Set argument for `cpuid` to check the availability of extended functions
|
|
; and call cpuid
|
|
mov eax, CPUID_GET_LARGEST_EXTENDED_FUNCTION_NUMBER
|
|
cpuid
|
|
; The return value contains the maximum function number supported by `cpuid`,
|
|
; You'll need the function number for extended processor features
|
|
cmp eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
|
; If not present, the CPU is definitely too old to support long mode
|
|
jb no_long_mode
|
|
|
|
; Finally, you are able to check the Long Mode support itself
|
|
mov eax, CPUID_GET_EXTENDED_PROCESSOR_FEATURES
|
|
cpuid
|
|
; If the return value in the EDX register has set the 29th bit,
|
|
; then long mode is supported -- continue with setup_paging
|
|
test edx, CPUID_HAS_LONGMODE
|
|
jnz setup_paging
|
|
|
|
no_long_mode:
|
|
; Show error message "No 64bit" and stop CPU
|
|
mov dword [0xb8000], 0xcf6fcf4e
|
|
mov dword [0xb8004], 0xcf36cf20
|
|
mov dword [0xb8008], 0xcf62cf34
|
|
mov dword [0xb800c], 0xcf74cf69
|
|
hlt
|
|
|
|
; Paging is required for Long Mode.
|
|
; Since an extensive page manager might be a bit of an overkill to start with,
|
|
; the following code creates an identity mapping for the first four gigabytes
|
|
; (using huge pages): each virtual address will point to the same physical one.
|
|
; This area (up to 4 GiB) is important for some memory mapped devices (APIC)
|
|
; and you don't want to remap them yet for simplicity reasons.
|
|
; In the advanced operating systems lecture, this topic is covered in detail,
|
|
; however, if you want a quick overview, have a look at
|
|
; https://wiki.osdev.org/Page_Tables#2_MiB_pages_2
|
|
|
|
PAGE_SIZE equ 4096
|
|
PAGE_FLAGS_PRESENT equ 1 << 0
|
|
PAGE_FLAGS_WRITEABLE equ 1 << 1
|
|
PAGE_FLAGS_USER equ 1 << 2
|
|
PAGE_FLAGS_HUGE equ 1 << 7
|
|
|
|
setup_paging:
|
|
; Unlike in Protected Mode, an entry in the page table has a size of 8 bytes
|
|
; (vs 4 bytes), so there are only 512 (and not 1024) entries per table.
|
|
; Structure of the 3-level PAE paging: One entry in the
|
|
; - lv2: Page-Directory-Table (PDT) covers 2 MiB (1 Huge Page)
|
|
; - lv3: Page-Directory-Pointer-Table (PDPT) covers 1 GiB (512 * 2 MiB)
|
|
; - lv4: Page-Map-Level-4-Table (PML4) covers 512 GiB (512 * 1 GiB)
|
|
|
|
; To address 4 GiB only four level-2 tables are required.
|
|
; All entries of the level-2 tables should be marked as writeable (attributes)
|
|
; and map (point to) the corresponding physical memory.
|
|
|
|
; This is done in a loop using ECX as counter
|
|
mov ecx, 0
|
|
|
|
.identitymap_level2:
|
|
; Calculate physical address in EAX (2 MiB multiplied by the counter)
|
|
mov eax, 0x200000
|
|
mul ecx
|
|
; Configure page attributes
|
|
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_HUGE | PAGE_FLAGS_USER
|
|
; Write (8 byte) entry in the level-2 table
|
|
mov [paging_level2_tables + ecx * 8], eax
|
|
|
|
; Increment counter...
|
|
inc ecx
|
|
; ... until all four level-2 tables are filled
|
|
cmp ecx, 512 * 4
|
|
jne .identitymap_level2
|
|
|
|
; The first four entries of the level-3 table should point to the
|
|
; four level-2 tables (and be writeable as well).
|
|
; Again, ECX acts as counter for the loop
|
|
mov ecx, 0
|
|
|
|
.identitymap_level3:
|
|
; Calculate the address: ECX * PAGE_SIZE + paging_level2_tables
|
|
mov eax, ecx
|
|
; The size of a page is stored in the EDX register
|
|
mov edx, PAGE_SIZE
|
|
mul edx
|
|
add eax, paging_level2_tables
|
|
; Configure attributes
|
|
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
|
; Write (8 byte) entry in the level-3 table
|
|
mov [paging_level3_table + ecx * 8], eax
|
|
|
|
; Increment counter...
|
|
inc ecx
|
|
; ... until all four entries of the table are written
|
|
cmp ecx, 4
|
|
jne .identitymap_level3
|
|
|
|
mov eax, paging_level2_tables
|
|
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
|
mov [paging_level3_table], eax
|
|
|
|
; The first entry of the level-4 table should point to to the level-3 table
|
|
mov eax, paging_level3_table
|
|
or eax, PAGE_FLAGS_PRESENT | PAGE_FLAGS_WRITEABLE | PAGE_FLAGS_USER
|
|
mov [paging_level4_table], eax
|
|
|
|
; Time to activate paging
|
|
paging_enable:
|
|
; First setup the control registers
|
|
|
|
; Write the address of the level-4 table into the CR3 register
|
|
mov eax, paging_level4_table
|
|
mov cr3, eax
|
|
|
|
; Activate Physical Address Extension (PAE)
|
|
; by setting the 5th bits in the CR4 register
|
|
mov eax, cr4
|
|
or eax, 1 << 5
|
|
mov cr4, eax
|
|
|
|
; Set the Long Mode Enable Bit in the EFER MSR
|
|
; (Extended Feature Enable Register Model Specific Register)
|
|
mov ecx, 0xC0000080
|
|
rdmsr
|
|
or eax, 1 << 8
|
|
wrmsr
|
|
|
|
; Finally, the 31st bit in CR0 is set to enable Paging
|
|
mov eax, cr0
|
|
or eax, 1 << 31
|
|
mov cr0, eax
|
|
|
|
; Load Long Mode Global Descriptor Table
|
|
lgdt [gdt_long_mode_pointer]
|
|
|
|
; Far jump to the 64-bit start code
|
|
jmp 0x8:long_mode_start
|
|
|
|
; print `KO` to screen
|
|
mov dword [0xb8000], 0x3f4f3f4b
|
|
hlt
|
|
|
|
; Memory reserved for page tables
|
|
[SECTION .bss]
|
|
|
|
ALIGN 4096
|
|
|
|
[GLOBAL paging_level4_table]
|
|
[GLOBAL paging_level3_table]
|
|
[GLOBAL paging_level2_tables]
|
|
; 1x Level-4 Table (Page Map Level 4)
|
|
paging_level4_table:
|
|
resb PAGE_SIZE
|
|
|
|
; 1x Level-3 Table (Page Directory Pointer Table)
|
|
paging_level3_table:
|
|
resb PAGE_SIZE
|
|
|
|
; 4x Level-2 Table (Page Directory)
|
|
paging_level2_tables:
|
|
resb PAGE_SIZE * 4
|
|
|
|
[SECTION .text]
|
|
[EXTERN kernel_init] ; C++ entry function
|
|
|
|
; Continue with 64 bit code
|
|
[BITS 64]
|
|
|
|
long_mode_start:
|
|
; Set data segment registers to SEGMENT_KERNEL_DATA (machine/gdt.h)
|
|
mov ax, 0x10
|
|
mov ss, ax
|
|
mov ds, ax
|
|
mov es, ax
|
|
mov fs, ax
|
|
mov gs, ax
|
|
|
|
; Call high-level (C++) kernel initialization function
|
|
call kernel_init
|
|
|
|
; Print `STOP` to screen and stop
|
|
mov rax, 0x2f502f4f2f544f53
|
|
mov qword [0xb8000], rax
|
|
hlt
|