os/arch/i686/bootstrap.s
2024-11-27 21:57:18 +01:00

134 lines
4.3 KiB
ArmAsm

# Declare constants for the multiboot header.
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
.set MAGIC, 0x1BADB002 # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot
# Declare a multiboot header that marks the program as a kernel.
.section .multiboot.data, "aw"
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
/*
The multiboot standard does not define the value of the stack pointer register
(esp) and it is up to the kernel to provide a stack. This allocates room for a
small stack by creating a symbol at the bottom of it, then allocating 16384
bytes for it, and finally creating a symbol at the top. The stack grows
downwards on x86. The stack is in its own section so it can be marked nobits,
which means the kernel file is smaller because it does not contain an
uninitialized stack. The stack on x86 must be 16-byte aligned according to the
System V ABI standard and de-facto extensions. The compiler will assume the
stack is properly aligned and failure to align the stack will result in
undefined behavior.
*/
.section .bss, "aw", @nobits
.align 16
stack_bottom:
.skip 16384 # 16 KiB
stack_top:
.global boot_page_directory
.align 4096
boot_page_directory:
.skip 4096
boot_page_table1:
.skip 4096
/*
The linker script specifies _start as the entry point to the kernel and the
bootloader will jump to this position once the kernel has been loaded. It
doesn't make sense to return from this function as the bootloader is gone.
*/
.section .multiboot.text, "a"
.global _start
.type _start, @function
_start:
# Physical address of boot_page_table1.
# TODO: I recall seeing some assembly that used a macro to do the
# conversions to and from physical. Maybe this should be done in this
# code as well?
movl $(boot_page_table1 - 0xC0000000), %edi
# First address to map is address 0.
# TODO: Start at the first kernel page instead. Alternatively map the first
# 1 MiB as it can be generally useful, and there's no need to
# specially map the VGA buffer.
movl $0, %esi
# Map 1023 pages. The 1024th will be the VGA text buffer.
movl $1023, %ecx
1:
# Only map the kernel.
cmpl $_kernel_start, %esi
jl 2f
cmpl $(_kernel_end - 0xC0000000), %esi
jge 3f
# Map physical address as "present, writable". Note that this maps
# .text and .rodata as writable. Mind security and map them as non-writable.
movl %esi, %edx
orl $0x003, %edx
movl %edx, (%edi)
2:
# Size of page is 4096 bytes.
addl $4096, %esi
# Size of entries in boot_page_table1 is 4 bytes.
addl $4, %edi
# Loop to the next entry if we haven't finished.
loop 1b
3:
# Map VGA video memory to 0xC03FF000 as "present, writable".
movl $(0x000B8000 | 0x003), boot_page_table1 - 0xC0000000 + 1023 * 4
# The page table is used at both page directory entry 0 (virtually from 0x0
# to 0x3FFFFF) (thus identity mapping the kernel) and page directory entry
# 768 (virtually from 0xC0000000 to 0xC03FFFFF) (thus mapping it in the
# higher half). The kernel is identity mapped because enabling paging does
# not change the next instruction, which continues to be physical. The CPU
# would instead page fault if there was no identity mapping.
# Map the page table to both virtual addresses 0x00000000 and 0xC0000000.
movl $(boot_page_table1 - 0xC0000000 + 0x003), boot_page_directory - 0xC0000000 + 0
movl $(boot_page_table1 - 0xC0000000 + 0x003), boot_page_directory - 0xC0000000 + 768 * 4
# Set cr3 to the address of the boot_page_directory.
movl $(boot_page_directory - 0xC0000000), %ecx
movl %ecx, %cr3
# Enable paging and the write-protect bit.
movl %cr0, %ecx
orl $0x80010000, %ecx
movl %ecx, %cr0
# Jump to higher half with an absolute jump.
lea 4f, %ecx
jmp *%ecx
.section .text
4:
# At this point, paging is fully set up and enabled.
# Unmap the identity mapping as it is now unnecessary.
movl $0, boot_page_directory + 0
# Reload crc3 to force a TLB flush so the changes to take effect.
movl %cr3, %ecx
movl %ecx, %cr3
# Set up the stack.
mov $stack_top, %esp
# Enter the high-level kernel.
call kernel_main
# Infinite loop if the system has nothing more to do.
cli
1: hlt
jmp 1b