; This code runs in 16-bit Real Mode when the computer starts. ; It is responsible for loading the kernel and switching to 32-bit Protected Mode. [org 0x7c00] ; BIOS loads the bootloader at this memory address KERNEL_OFFSET equ 0x1000 ; Memory address where we will load our kernel ; Save the boot drive index provided by BIOS in DL register mov [BOOT_DRIVE], dl ; Initialize the stack pointer to a safe area in memory mov bp, 0x9000 mov sp, bp ; Load the kernel from the disk into memory call load_kernel ; Switch to VGA Mode 13h: 320x200 resolution with 256 colors ; This is a graphical mode where each byte in video memory is a pixel color mov ax, 0x0013 int 0x10 ; Enter 32-bit Protected Mode call switch_to_pm ; Infinite loop in case we ever return (we shouldn't) jmp $ ; Global Descriptor Table (GDT) ; The GDT defines memory segments and their access rights for Protected Mode. gdt_start: dd 0x0 ; The null descriptor is required at the beginning dd 0x0 gdt_code: ; Code segment descriptor dw 0xffff ; Limit (bits 0-15) dw 0x0 ; Base (bits 0-15) db 0x0 ; Base (bits 16-23) db 10011010b ; Access byte: Present, Privilege 0, Code, Executable, Readable db 11001111b ; Flags: 4KB granularity, 32-bit mode, Limit (bits 16-19) db 0x0 ; Base (bits 24-31) gdt_data: ; Data segment descriptor dw 0xffff ; Limit (bits 0-15) dw 0x0 ; Base (bits 0-15) db 0x0 ; Base (bits 16-23) db 10010010b ; Access byte: Present, Privilege 0, Data, Writable db 11001111b ; Flags: 4KB granularity, 32-bit mode, Limit (bits 16-19) db 0x0 ; Base (bits 24-31) gdt_end: ; The GDT descriptor is passed to the LGDT instruction gdt_descriptor: dw gdt_end - gdt_start - 1 ; Size of GDT (minus 1) dd gdt_start ; Starting address of GDT ; Constants for segment selectors (offsets within the GDT) CODE_SEG equ gdt_code - gdt_start DATA_SEG equ gdt_data - gdt_start ; Switch to 32-bit Protected Mode [bits 16] switch_to_pm: cli ; Disable interrupts while switching modes lgdt [gdt_descriptor] ; Load the GDT descriptor mov eax, cr0 ; Enable Protected Mode bit in Control Register 0 or eax, 0x1 mov cr0, eax jmp CODE_SEG:init_pm ; Far jump to flush the CPU pipeline and switch to 32-bit mode [bits 32] init_pm: ; Update all segment registers to use the new Data Segment selector mov ax, DATA_SEG mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax ; Re-initialize the stack for 32-bit mode at a higher memory address mov ebp, 0x90000 mov esp, ebp ; Call the entry point that leads to our kernel call BEGIN_PM ; Routine to load sectors from disk using BIOS interrupts [bits 16] load_kernel: mov bx, KERNEL_OFFSET ; Destination address in memory mov dh, 15 ; Number of sectors to load (adjust if kernel grows) mov dl, [BOOT_DRIVE] ; Drive index to read from call disk_load ret disk_load: push dx mov ah, 0x02 ; BIOS Read Sector function mov al, dh ; Number of sectors mov ch, 0x00 ; Cylinder 0 mov dh, 0x00 ; Head 0 mov cl, 0x02 ; Sector 2 (Sector 1 is the bootloader) int 0x13 ; Call BIOS disk interrupt jc disk_error ; Jump if Carry Flag is set (indicates error) pop dx cmp dh, al ; Verify that we read the expected number of sectors jne disk_error ret disk_error: ; Halt execution if disk read fails jmp $ ; 32-bit entry point to jump into the compiled C kernel [bits 32] BEGIN_PM: call KERNEL_OFFSET ; Jump to the memory location where kernel was loaded jmp $ ; Safety hang ; Boot sector padding and signature BOOT_DRIVE db 0 times 510-($-$$) db 0 ; Pad the rest of the 512 bytes with zeros dw 0xaa55 ; The standard boot signature required by BIOS