diff --git a/boot/linker.ld b/boot/linker.ld index a4a7dce..890b534 100644 --- a/boot/linker.ld +++ b/boot/linker.ld @@ -1,29 +1,41 @@ -ENTRY(_start) +ENTRY (_start) SECTIONS { - . = 2M; + . = 0x00100000; + /* The kernel will live at 3GB + 1MB in the virtual address space, */ + /* which will be mapped to 1MB in the physical address space. */ + /* Note that we page-align the sections. */ - .text BLOCK(4K) : ALIGN(4K) + _kernel_start = .; + .multiboot.data : { + *(.multiboot.data) + } + + .multiboot.text : { + *(.multiboot.text) + } + + . += 0xC0000000; + /* Add a symbol that indicates the start address of the kernel. */ + .text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000) { - *(.multiboot) *(.text) } - - .rodata BLOCK(4K) : ALIGN(4K) + .rodata ALIGN (4K) : AT (ADDR (.rodata) - 0xC0000000) { *(.rodata) } - - .data BLOCK(4K) : ALIGN(4K) + .data ALIGN (4K) : AT (ADDR (.data) - 0xC0000000) { *(.data) } - - .bss BLOCK(4K) : ALIGN(4K) + .bss ALIGN (4K) : AT (ADDR (.bss) - 0xC0000000) { *(COMMON) *(.bss) - end_kernel = .; + *(.bootstrap_stack) } -} + /* Add a symbol that indicates the end address of the kernel. */ + _kernel_end = .; +} \ No newline at end of file diff --git a/headers/terminal.h b/headers/terminal.h index b7aed9c..5a7bca0 100644 --- a/headers/terminal.h +++ b/headers/terminal.h @@ -8,7 +8,7 @@ #define VGA_WIDTH 80 #define VGA_HEIGHT 25 -#define TERM_BUF ((uint16_t *)0xB8000) +#define TERM_BUF ((uint16_t *)0xC03FF000) #define TERM_COUNT 10 struct screen { diff --git a/src/boot.s b/src/boot.s index 520e9d8..6295a58 100644 --- a/src/boot.s +++ b/src/boot.s @@ -1,110 +1,120 @@ -/* 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 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. These are magic -values that are documented in the multiboot standard. The bootloader will -search for this signature in the first 8 KiB of the kernel file, aligned at a -32-bit boundary. The signature is in its own section so the header can be -forced to be within the first 8 KiB of the kernel file. -*/ -.section .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 -.align 16 +# Allocate the initial stack. +.section .bootstrap_stack, "aw", @nobits stack_bottom: .skip 16384 # 16 KiB stack_top: -/* -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 .text +# Preallocate pages used for paging. Don't hard-code addresses and assume they +# are available, as the bootloader might have loaded its multiboot structures or +# modules there. This lets the bootloader know it must avoid the addresses. +.section .bss, "aw", @nobits + .align 4096 +boot_page_directory: + .skip 4096 +boot_page_table1: + .skip 4096 +# Further page tables may be required if the kernel grows beyond 3 MiB. + +# The kernel entry point. +.section .multiboot.text, "a" .global _start .type _start, @function _start: - /* - The bootloader has loaded us into 32-bit protected mode on a x86 - machine. Interrupts are disabled. Paging is disabled. The processor - state is as defined in the multiboot standard. The kernel has full - control of the CPU. The kernel can only make use of hardware features - and any code it provides as part of itself. There's no printf - function, unless the kernel provides its own header and a - printf implementation. There are no security restrictions, no - safeguards, no debugging mechanisms, only what the kernel provides - itself. It has absolute and complete power over the - machine. - */ + # 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 - /* - To set up a stack, we set the esp register to point to the top of the - stack (as it grows downwards on x86 systems). This is necessarily done - in assembly as languages such as C cannot function without a stack. - */ +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 - /* - This is a good place to initialize crucial processor state before the - high-level kernel is entered. It's best to minimize the early - environment where crucial features are offline. Note that the - processor is not fully initialized yet: Features such as floating - point instructions and instruction set extensions are not initialized - yet. The GDT should be loaded here. Paging should be enabled here. - C++ features such as global constructors and exceptions will require - runtime support to work as well. - */ - - /* - Enter the high-level kernel. The ABI requires the stack is 16-byte - aligned at the time of the call instruction (which afterwards pushes - the return pointer of size 4 bytes). The stack was originally 16-byte - aligned above and we've pushed a multiple of 16 bytes to the - stack since (pushed 0 bytes so far), so the alignment has thus been - preserved and the call is well defined. - */ + # Enter the high-level kernel. call kernel_main - /* - If the system has nothing more to do, put the computer into an - infinite loop. To do that: - 1) Disable interrupts with cli (clear interrupt enable in eflags). - They are already disabled by the bootloader, so this is not needed. - Mind that you might later enable interrupts and return from - kernel_main (which is sort of nonsensical to do). - 2) Wait for the next interrupt to arrive with hlt (halt instruction). - Since they are disabled, this will lock up the computer. - 3) Jump to the hlt instruction if it ever wakes up due to a - non-maskable interrupt occurring or due to system management mode. - */ + # Infinite loop if the system has nothing more to do. cli 1: hlt - jmp 1b - -/* -Set the size of the _start symbol to the current location '.' minus its start. -This is useful when debugging or when you implement call tracing. -*/ -.size _start, . - _start - + jmp 1b \ No newline at end of file