- org 7c00h ; boot sector is always loaded at 0x7c00
-
- mov al, 1 ; update cursor after write
- mov bh, 0 ; write to page 0
- mov bl, 00000111b ; attribute: high nibble is foreground (light gray),
- ; low nibble is background (black)
- ; NOTE: in the binary, these will be inverted, so low nibble here will become high nibble in binary and vice versa!
- mov cx, msg1end - msg1 ; size of message
- mov dl, 0 ; column to write to
- mov dh, 0 ; row to write to
- push cs ; push code segment to stack
- pop es ; pop pushed code segment to extra segment, this serves as the base to the string
- mov bp, msg1 ; offset where the string is
- mov ah, 13h ; write string subservice
- int 10h ; call BIOS interrupt 0x10
- jmp msg1end ; jmp to endless loop
- msg1 db "Gotta start somewhere"
- msg1end: jmp msg1end
-
- times 510-($-$$) db 0 ; fill with 0 until 0x1fc is reached
- dw 0xaa55 ; BIOS MBR signature
+ org 7C00h ; BIOS loads us at 0x7c00
+
+KERNEL_OFFSET equ 1000h ; where we will load our kernel
+
+ mov [BOOT_DRIVE], dl ; BIOS puts the number of our boot drive in dl, so we will want to remember this
+
+ mov bp, 9000h ; initialize stack at a spot sufficiently distanced from this code
+ mov sp, bp
+
+ mov di, 0
+
+ xor ax, ax ; clear accumulator
+ int 13h ; BIOS disk services, with a cleared accumulator this will reset the disk controller
+ jc reset_error ; halt if controller reset fails
+
+ mov ah, 2 ; instruct BIOS's interrupt 13h to read sectors
+ mov al, 10 ; load 10 sectors, might be excessive (for now) but it's still good to do so
+ xor ch, ch ; read from 1st cylinder
+ mov cl, 2 ; start reading from 2nd sector, right after the boot sector
+ xor dh, dh ; read from 1st head
+ xor bx, bx ; clear B register
+ mov es, bx ; clear extended segment
+ mov bx, KERNEL_OFFSET ; put the sectors in our desired offset
+ ; dl holds the number of the drive to read from, but BIOS already filled this in
+ int 13h ; do the read
+ jc read_error ; halt if read fails
+ cmp al, 10 ; make sure we actually read 10 sectors
+ jl read_error ; halt if we didn't
+ jmp switch_to_pm ; if all is good, begin the switch to 32-bit protected mode
+
+reset_error
+ mov bx, 0B800h
+ mov es, bx
+ mov byte [es:di], '1'
+ jmp halt
+
+read_error
+ mov bx, 0B800h
+ mov es, bx
+ mov byte [es:di], '2'
+ jmp halt
+
+ call switch_to_pm
+
+halt
+ jmp $
+
+switch_to_pm
+ mov bx, 0B800h
+ mov es, bx ; set extra segment to starting address of video RAM
+ mov byte [es:0], 'L' ; print an L to screen, to let us know that we actually got here
+
+ cli ; disable interrupts
+ xor ax, ax ; clear accumulator
+ mov ds, ax ; clear data segment, this makes sure the next instruction reads from the right place
+ lgdt [gdt_desc] ; load the Global Descriptor Table
+
+ mov eax, cr0 ; move Control Register 0 to accumulator
+ or eax, 1 ; flip the bit which controls memory protection
+ mov cr0, eax ; move the accumulator back to CR0
+ jmp CODE_SEG:protected ; not quite there yet, need to do a far jump to clean the pipeline from any 16-bit instructions
+ ; note that the jump does not have to be physically far away, it just needs to use a segmented address
+
+ bits 32 ; we are finally in 32-bit mode
+protected
+ mov ax, DATA_SEG ; put the selector for the data segment in the accumulator
+ ; in real mode, segmentation works by taking one of these segment registers, shifting it right by 4 bits or 1 hex digit
+ ; and then adding a 16-bit offset to form a 20-bit address
+ ; example: 1234h:5678h
+ ; 1234h is shifted to 12340h, 12340h + 5678h is 179B8h
+ ; in 32-bit protected mode, these segment registers do not hold the segment address itself, but a selector in the GDT
+ ; so we have to update the segment registers accordingly
+ mov ds, ax
+ mov ss, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+
+ ; reinitialize the stack at a safe location
+ mov ebp, 090000h
+ mov esp, ebp
+
+ ; print a string to let us know that we survived the switch
+ mov ebx, pm_success
+ call pmprint
+
+ ; transfer control to the kernel
+ call KERNEL_OFFSET
+
+ ; above call should not return in normal circumstances, but if it does hang forever
+ jmp $
+
+pmprint
+ pusha ; save registers to stack
+ mov edx, video_memory ; initialize dx with location of VRAM
+
+.loop
+ mov al, [ebx] ; read next char and put it in al
+ mov ah, 0Fh ; puts the VGA text mode color white on black into ah
+
+ cmp al, 0 ; if the next character is null, we reached end of string
+ je .done ; so return the instruction
+
+ mov [edx], al ; otherwise put the next character in the video memory
+ mov [edx+1], ah ; do the same for its color
+
+ inc ebx ; point to next character in string
+ add edx, 2 ; point to next character in VRAM
+
+ jmp .loop ; go back to the loop
+
+.done
+ popa ; restore registers from stack
+ ret ; return
+
+pm_success db "Now in protected mode", 0
+video_memory equ 0B8000h
+
+; the actual Global Descriptor Table
+; refer to Volume 3, Chapter 2, 2.1.1 of Intel's 64 and IA-32 Software Developer's Manual for more info
+gdt_start
+null_seg
+ dq 0
+code_seg
+ dw 0FFFFh
+ dw 0
+ db 0
+ db 10011010b
+ db 11001111b
+ db 0
+data_seg
+ dw 0FFFFh
+ dw 0
+ db 0
+ db 10010010b
+ db 11001111b
+ db 0
+gdt_end
+gdt_desc
+ dw gdt_end - gdt_start - 1
+ dd gdt_start
+CODE_SEG equ code_seg - gdt_start
+DATA_SEG equ data_seg - gdt_start
+
+BOOT_DRIVE db 0 ; reserve a spot in RAM for our boot drive variable
+
+ times 510-($-$$) db 0
+ dw 0AA55h ; MBR signature