summaryrefslogtreecommitdiff
path: root/src/boot
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2017-06-10 20:55:56 +0200
committerAndreas Baumann <mail@andreasbaumann.cc>2017-06-10 20:55:56 +0200
commiteea5bf4b859eb56c5772c58ca54937a90a10e7ee (patch)
tree391b31a28a3740d3fd52a9a9d1c651bc9f12cc2b /src/boot
parent1625a4752545e54c439a38f3393ab9d72bfee721 (diff)
downloadabaos-eea5bf4b859eb56c5772c58ca54937a90a10e7ee.tar.gz
abaos-eea5bf4b859eb56c5772c58ca54937a90a10e7ee.tar.bz2
moved bootloader to subdirectory
Diffstat (limited to 'src/boot')
-rw-r--r--src/boot/boot.asm227
-rw-r--r--src/boot/boot_gdt.asm43
-rw-r--r--src/boot/magic.asm9
-rw-r--r--src/boot/stage1_functions.asm121
-rw-r--r--src/boot/stage2_a20.asm157
-rw-r--r--src/boot/stage2_check_magic.asm48
-rw-r--r--src/boot/stage2_pm_functions.asm177
-rw-r--r--src/boot/stage2_real_functions.asm169
-rw-r--r--src/boot/stage2_switch_mode.asm40
9 files changed, 991 insertions, 0 deletions
diff --git a/src/boot/boot.asm b/src/boot/boot.asm
new file mode 100644
index 0000000..c3cb57e
--- /dev/null
+++ b/src/boot/boot.asm
@@ -0,0 +1,227 @@
+; legacy BIOS PC-boot code, starts in 16-bit real mode
+
+; BIOS always loads us to this location
+ [org 0x7c00]
+
+; 16-bit real-mode
+ [bits 16]
+
+; export a map of boot loader symbols for debugging
+ [map symbols boot.map]
+
+; initialize segment registers
+ mov ax, 0
+ mov ds, ax
+ mov es, ax
+ mov ss, ax
+
+; dl contains the boot drive, primarily because that's the last function
+; called by the BIOS MBR loader, we remember that for loading additional
+; blocks from the boot medium
+ mov [BOOT_DRIVE], dl
+
+; make sure we know the location of the stack by setting it on our own
+; this stack is only used in real mode in stage 1, so it's growing from
+; 0xffff down
+ mov bp, 0xFFFF
+ mov sp, bp
+
+; print out some information about CPU mode and BIOS boot drive
+ mov si, MESSAGE_REAL_MODE
+ call print_string
+
+ mov si, MESSAGE_BOOT_DRIVE
+ call print_string
+ mov ax, dx
+ call print_hex
+ call print_newline
+
+; print we are going to load stage 2 of the boot blocks
+ mov si, MESSAGE_LOADING_STAGE_2
+ call print_string
+
+; load stage 2 with simple load method to 0x7e00 (directly
+; after the boot sector)
+ mov dl, [BOOT_DRIVE]
+ call read_stage2_from_disk
+ call print_newline
+
+; jump over variables and subroutines of stage 1 and execute stage 2
+ jmp stage2
+
+%include "boot/stage1_functions.asm"
+
+DISK_ERROR:
+ db "DISK ERROR ", 0
+
+SHORT_READ:
+ db "DISK SHORT READ", 0
+
+MESSAGE_REAL_MODE:
+ db "Started in 16-bit Real Mode", 13, 10, 0
+
+MESSAGE_BOOT_DRIVE:
+ db "Booting from drive ", 0
+
+MESSAGE_LOADING_STAGE_2:
+ db "Loading stage 2 boot loader", 0
+
+MESSAGE_DETECTING_DISK_GEOMETRY:
+ db "Detecting disk geometry ", 0
+
+MESSAGE_LOADING_KERNEL:
+ db "Loading kernel", 0
+
+BOOT_DRIVE:
+ db 0
+
+; pad rest of sector with zeroes so we get 512 bytes in the end
+times 510-($-$$) db 0
+
+; magic number of a boot sector
+dw 0xaa55
+
+stage2:
+
+; print a message that we are indeed in stage 2 now
+ mov si, MESSAGE_STAGE_2_LOADED
+ call print_string
+
+; detect disk geometry (works for real floppies only?)
+ mov cl, [BOOT_DRIVE]
+ and cl, 0x80
+ jnz .not_a_floppy
+
+ mov si, MESSAGE_DETECTING_DISK_GEOMETRY
+ call print_string
+ mov dl, [BOOT_DRIVE]
+ call detect_disk_geometry
+ mov dl, [BOOT_DRIVE]
+ call probe_and_fix_disk_geometry
+ call print_newline
+.not_a_floppy:
+
+; print a message we are now loading the kernel
+ mov si, MESSAGE_LOADING_KERNEL
+ call print_string
+
+
+; load kernel to 0x8400 (directly after stage 2
+; of the boot loader)
+ mov dl, [BOOT_DRIVE]
+ call read_from_disk
+ call print_newline
+
+; turn off disk motor
+ call kill_motor
+
+; check A20 gate
+ mov si, MESSAGE_STAGE_2_CHECKING_A20
+ call print_string
+ call check_and_enable_A20
+ cmp ax, 1
+ je .A20_enabled
+ mov si, MESSAGE_STAGE_2_A20_DISABLED
+ call print_string
+; jmp .halt_os_realmode
+
+.A20_enabled:
+ mov si, MESSAGE_STAGE_2_A20_ENABLED
+ call print_string
+
+; remember current position on screen, otherwise we loose this
+; information after switching to protected mode
+ call current_row
+ mov [CURSOR_Y], dh
+
+; enter protected mode
+ call switch_to_protected_mode
+
+; we should never get here, but just in case
+ jmp $
+
+; stop loading in real mode
+.halt_os_realmode:
+ mov si, MESSAGE_HALTED_REAL
+ call print_string
+.loop: cli
+ hlt
+ jmp .loop
+
+MESSAGE_STAGE_2_LOADED:
+ db "Stage 2 boot sectors loaded", 13, 10, 0
+
+MESSAGE_STAGE_2_CHECKING_A20:
+ db "Checking A20 address gate... ", 0
+
+MESSAGE_STAGE_2_A20_ENABLED:
+ db " ...enabled", 13, 10, 0
+
+MESSAGE_STAGE_2_A20_DISABLED:
+ db " ...disabled", 13, 10, 0
+
+MESSAGE_HALTED_REAL:
+ db "Operating system halted in real mode", 13, 10, 0
+
+%include "boot/stage2_real_functions.asm"
+%include "boot/stage2_switch_mode.asm"
+%include "boot/stage2_a20.asm"
+
+[bits 32]
+
+BEGIN_PROTECTED_MODE:
+
+; we switched to protected mode
+ mov si, MESSAGE_PROTECTED_MODE
+ call pm_print_string
+ call pm_print_newline
+
+; check sanity of kernel by searching for MAGIC string at a given
+; position
+ call check_magic
+ cmp ax, 1
+ jnz .halt_os_pm
+
+; print a message before we call the C level kernel
+ mov si, MESSAGE_CALL_C_ENTRY
+ call pm_print_string
+ call pm_print_newline
+
+; call our kernel
+ call kernel_main
+
+; restore cursor variables from VGA hardware
+ call read_hardware_vga_cursor
+
+; "kernel halted" message, when we terminate the C kernel
+.halt_os_pm:
+ mov si, MESSAGE_HALTED
+ call pm_print_string
+ call pm_print_newline
+
+; end of C, disable interupts again, NMIs can still happen
+; make sure we loop endlessly here but without burning too
+; much CPU
+ cli
+.loop:
+ hlt
+ jmp .loop
+
+MESSAGE_PROTECTED_MODE:
+ db "Switched to 32-bit Protected Mode", 0
+
+MESSAGE_CALL_C_ENTRY:
+ db "Calling C entry function", 0
+
+MESSAGE_HALTED:
+ db "Operating system halted", 0
+
+%include "boot/stage2_pm_functions.asm"
+%include "boot/stage2_check_magic.asm"
+
+; make sure we have full sectors, stage 1 is 512 bytes, so we
+; have to will up 3 sectors
+times 2048-($-$$) db 0
+
+; position is 0x8400 now for the C entry
+kernel_main:
diff --git a/src/boot/boot_gdt.asm b/src/boot/boot_gdt.asm
new file mode 100644
index 0000000..87e1585
--- /dev/null
+++ b/src/boot/boot_gdt.asm
@@ -0,0 +1,43 @@
+; GDT global descriptor table
+
+gdt_start:
+
+; mandatory null entry
+gdt_null:
+ dd 0x0
+ dd 0x0
+
+; code segment
+; base=0x0, limit=0xfffff
+; flags: present (not paged), ring 0, executable, direction bit
+; conforming, writable, not accessed
+; granularity: 4kb pages, 32-bit mode, no 64-bit segment, AVL would
+; be for our own extensions
+gdt_code:
+ dw 0xffff ; limit (bits 0-15)
+ dw 0x0 ; base (bits 0-15)
+ db 0x0 ; base (bits 16-23)
+ db 10011010b ; flags
+ db 11001111b ; flags, limit (bits 16-19)
+ db 0x0 ; base (bit 24-31)
+
+; flat model, same as code segment, but flags are
+; flags: ring 0,
+gdt_data:
+ dw 0xffff ; limit (bits 0-15)
+ dw 0x0 ; base (bits 0-15)
+ db 0x0 ; base (bits 16-23)
+ db 10010010b ; flags
+ db 11001111b ; flags, limit (bits 16-19)
+ db 0x0 ; base (bit 24-31)
+
+gdt_end:
+
+gdt_descriptor:
+ dw gdt_end -gdt_start - 1 ; size
+ dd gdt_start ; start address of the GDT
+
+; constants representing the segment bases
+CODE_SEGMENT equ gdt_code - gdt_start
+DATA_SEGMENT equ gdt_data - gdt_start
+
diff --git a/src/boot/magic.asm b/src/boot/magic.asm
new file mode 100644
index 0000000..f7aaf0d
--- /dev/null
+++ b/src/boot/magic.asm
@@ -0,0 +1,9 @@
+; pad rest of sector with zeroes so we get 512 bytes in the end
+times 512-14-($-$$) db 0
+
+; the magic string we search for in stage 2 to ensure we don't read
+; a truncated kernel image
+; we make it unique per compilation to avoid funny problems if
+; the size of the image differs between compilation steps and the
+; host system doens't initialize or randomize the RAM.
+db "ABAOS", %[MAGIC], 0
diff --git a/src/boot/stage1_functions.asm b/src/boot/stage1_functions.asm
new file mode 100644
index 0000000..bdb2aac
--- /dev/null
+++ b/src/boot/stage1_functions.asm
@@ -0,0 +1,121 @@
+; IN dx: hex value to print
+print_hex:
+ push bx
+ push si
+ mov si, HEX_TEMPLATE
+ mov bx, dx
+ shr bx, 12
+ mov bx, [HEXABET+bx]
+ mov [HEX_TEMPLATE+2], bl
+ mov bx, dx
+ and bx, 0x0FFF
+ shr bx, 8
+ mov bx, [HEXABET+bx]
+ mov [HEX_TEMPLATE+3], bl
+ mov bx, dx
+ and bx, 0x00FF
+ shr bx, 4
+ mov bx, [HEXABET+bx]
+ mov [HEX_TEMPLATE+4], bl
+ mov bx, dx
+ and bx, 0x000F
+ mov bx, [HEXABET+bx]
+ mov [HEX_TEMPLATE+5], bl
+ call print_string
+ pop si
+ pop bx
+ ret
+
+HEX_TEMPLATE:
+ db '0x???? ', 0
+
+HEXABET:
+ db '0123456789ABCDEF'
+
+print_newline:
+ push ax
+ mov al, 10
+ call print_char
+ mov al, 13
+ call print_char
+ pop ax
+ ret
+
+; IN si
+print_string:
+ push ax
+.loop:
+ lodsb
+ cmp al, 0
+ je .fini
+ call print_char
+ jmp .loop
+.fini:
+ pop ax
+ ret
+
+; IN al: character to print
+; MOD ah
+print_char:
+ mov ah, 0x0e
+ int 0x10
+ ret
+
+fat_cursor:
+ push ax
+ push cx
+ mov ah, 0x01
+ mov cx, 0x0007
+ int 0x10
+ pop cx
+ pop ax
+ ret
+
+; OUT: current row
+current_row:
+ push ax
+ push bx
+ push cx
+ mov ah, 0x03
+ mov bh, 0
+ int 0x10
+ pop cx
+ pop bx
+ pop ax
+ ret
+
+; IN dl: drive to read from
+read_stage2_from_disk:
+ mov ah, 0x02 ; read sectors from drive
+
+ mov al, 3 ; read 3 sectors of stage 2
+ mov ch, 0 ; select first cylinder
+ mov dh, 0 ; first head
+ mov cl, 2 ; second sector after boot sector
+
+ mov bx, 0 ; where to store the data
+ mov es, bx
+ mov bx, 0x7e00 ; 512 bytes after first sector
+
+ int 0x13
+
+ jc .read_error
+
+ cmp al, 3 ; 3 sectors read?
+ jne .short_read ; if not, short read
+
+ ret
+
+.read_error:
+ mov si, DISK_ERROR
+ call print_string
+ mov dh, 0
+ mov dl, ah
+ call print_hex
+ jmp $
+
+.short_read:
+ mov si, SHORT_READ
+ call print_string
+ jmp $
+
diff --git a/src/boot/stage2_a20.asm b/src/boot/stage2_a20.asm
new file mode 100644
index 0000000..c0951ad
--- /dev/null
+++ b/src/boot/stage2_a20.asm
@@ -0,0 +1,157 @@
+; functions to handle A20 address line status and switching
+
+; returns 0 if not A20_ENABLED, 1 if A20_ENABLED in AX
+check_A20_enabled:
+ pushf
+ push ds
+ push es
+ push di
+ push si
+
+ cli
+
+ xor ax, ax
+ mov es, ax
+ mov di, 0x0500 ; es:di = 0000:0500
+
+ mov ax, 0xffff
+ mov ds, ax
+ mov si, 0x0510 ; ds:si = ffff:0510
+
+ mov al, byte [es:di] ; preserve values in memory
+ push ax ; on stack
+ mov al, byte [ds:si]
+ push ax
+
+ mov byte [es:di], 0x00 ; now the test: write 0x00 to 0000:0500
+ mov byte [ds:si], 0xFF ; write 0xff to ffff:0510
+
+ cmp byte [es:di], 0xFF ; memory wrap? A20 notA20_ENABLED
+
+ pop ax ; restore original memory contents
+ mov byte [ds:si], al
+ pop ax
+ mov byte [es:di], al
+
+ je .enabled
+ jmp .disabled
+
+.enabled:
+ mov al, '+'
+ call print_char
+ mov ax, 1 ; not wrapped around
+ jmp .exit
+
+.disabled:
+ mov al, '-'
+ call print_char
+ mov ax, 0 ; wrapped around (last cmp)
+ jmp .exit
+
+.exit:
+ pop si
+ pop di
+ pop es
+ pop ds
+ popf
+
+ ret
+
+check_and_enable_A20:
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_ENABLE_KBD_PORT:
+ mov al, 'K'
+ call print_char
+ mov al, 0xdd
+ out 0x64, al
+
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_ENABLE_KBD_OUT:
+
+ mov al, 'k'
+ call print_char
+
+ cli ; disable interrupts, we talk directly to the keyboard ports
+
+ call .wait_input
+ mov al,0xAD
+ out 0x64,al ; disable keyboard
+ call .wait_input
+
+ mov al,0xD0
+ out 0x64,al ; tell controller to read output port
+ call .wait_output
+
+ in al,0x60
+ push eax ; get output port data and store it
+ call .wait_input
+
+ mov al,0xD1
+ out 0x64,al ; tell controller to write output port
+ call .wait_input
+
+ pop eax
+ or al,2 ; set bit 1 (enable a20)
+ out 0x60,al ; write out data back to the output port
+
+ call .wait_input
+ mov al,0xAE ; enable keyboard
+ out 0x64,al
+
+ call .wait_input
+
+ sti
+
+ jmp .retest
+
+; wait for input buffer to be clear
+.wait_input:
+ in al,0x64
+ test al,2
+ jnz .wait_input
+ ret
+
+; wait for output buffer to be clear
+.wait_output:
+ in al,0x64
+ test al,1
+ jz .wait_output
+ ret
+
+.retest:
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_FAST_SPECIAL_PORT:
+
+ mov al, 'F'
+ call print_char
+
+ in al, 0x92
+ or al, 2
+ out 0x92, al
+
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+ ret
+
+A20_ENABLE_VIA_BIOS:
+ mov al, 'B'
+ call print_char
+ mov ax, 0x2401
+ int 0x15
+
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_ENABLED:
+ ret
diff --git a/src/boot/stage2_check_magic.asm b/src/boot/stage2_check_magic.asm
new file mode 100644
index 0000000..1deaf05
--- /dev/null
+++ b/src/boot/stage2_check_magic.asm
@@ -0,0 +1,48 @@
+; check whether the end of the loaded image contains in fact the magic
+; string (avoid truncation of image)
+check_magic:
+ push ebx
+ push ecx
+ push edx
+ push esi
+ push edi
+ mov eax, NOF_LOAD_SECTORS ; number of 512-byte sectors
+ shl eax, 9 ; 512 bytes per sector
+ mov edx, 0x8400 ; offset of kernel
+ add edx, eax
+ sub edx, MAGICLEN ; subtract the length of the magic string
+ mov esi, edx ; now use edx as first string address to compare to
+ mov edi, COMPARE_MAGIC ; position of second string
+ mov ecx, MAGICLEN ; length of the magic string
+ repe cmpsb
+ jne .ok
+ jmp .mismatch
+.ok:
+ mov si, MAGIC_OK_MSG
+ call pm_print_string
+ call pm_print_newline
+ xor eax, eax
+ jmp .end
+.mismatch:
+ mov si, MAGIC_NOT_OK_MSG
+ call pm_print_string
+ call pm_print_newline
+ xor eax, eax
+ mov eax, 1
+.end:
+ pop edi
+ pop esi
+ pop edx
+ pop ecx
+ pop ebx
+ ret
+
+COMPARE_MAGIC:
+db "ABAOS", %[MAGIC], 0
+MAGICLEN equ $ - COMPARE_MAGIC
+
+MAGIC_NOT_OK_MSG:
+db "Magic signature found", 0
+
+MAGIC_OK_MSG:
+db "Magic signature not found!", 0
diff --git a/src/boot/stage2_pm_functions.asm b/src/boot/stage2_pm_functions.asm
new file mode 100644
index 0000000..f3c348b
--- /dev/null
+++ b/src/boot/stage2_pm_functions.asm
@@ -0,0 +1,177 @@
+VIDEO_MEMORY equ 0xb8000
+
+VIDEO_COLS equ 80
+VIDEO_ROWS equ 25
+
+CURSOR_X:
+ dw 0
+
+CURSOR_Y:
+ dw 0
+
+pm_print_newline:
+ push eax
+ mov [CURSOR_X], word 0
+ mov ax, [CURSOR_Y]
+ inc ax
+ mov [CURSOR_Y], ax
+ pop eax
+ call update_vga_cursor
+ ret
+
+; IN si
+pm_print_string:
+ push eax
+.loop:
+ lodsb
+ cmp al, 0
+ je .fini
+ call pm_print_char
+ jmp .loop
+.fini:
+ pop eax
+ ret
+
+; IN al: character to print
+pm_print_char:
+ push edx
+ push ecx
+ push ebx
+ push eax
+ mov ax, [CURSOR_Y]
+ mov cl, VIDEO_COLS
+ mul cl
+ mov bx, [CURSOR_X]
+ add ax, bx
+ shl ax, 1
+ mov edx, VIDEO_MEMORY
+ add dx, ax
+ pop eax
+ mov ah, 0x07
+ mov [edx], ax
+ pop ebx
+ pop ecx
+ pop edx
+ call inc_cursor
+ ret
+
+; IN dx: hex value to print
+pm_print_hex:
+ push bx
+ push si
+ mov si, PM_HEX_TEMPLATE
+ mov bx, dx
+ shr bx, 12
+ mov bx, [PM_HEXABET+bx]
+ mov [PM_HEX_TEMPLATE+2], bl
+ mov bx, dx
+ and bx, 0x0FFF
+ shr bx, 8
+ mov bx, [PM_HEXABET+bx]
+ mov [PM_HEX_TEMPLATE+3], bl
+ mov bx, dx
+ and bx, 0x00FF
+ shr bx, 4
+ mov bx, [PM_HEXABET+bx]
+ mov [PM_HEX_TEMPLATE+4], bl
+ mov bx, dx
+ and bx, 0x000F
+ mov bx, [PM_HEXABET+bx]
+ mov [PM_HEX_TEMPLATE+5], bl
+ call pm_print_string
+ pop si
+ pop bx
+ ret
+
+inc_cursor:
+ push eax
+ mov ax, [CURSOR_X]
+ inc ax
+ mov [CURSOR_X], ax
+ cmp ax, VIDEO_COLS
+ jl .fini
+ mov [CURSOR_X], word 1
+ mov ax, [CURSOR_Y]
+ inc ax
+ mov [CURSOR_Y], ax
+ cmp ax, VIDEO_ROWS
+ jl .fini
+ ; TODO: scoll one line, for now restart on top
+ mov [CURSOR_Y], word 0
+.fini:
+ call update_vga_cursor
+ pop eax
+ ret
+
+; update the VGA cursor on screen
+update_vga_cursor:
+ push eax
+ push ebx
+ push ecx
+ push edx
+ mov al, [CURSOR_Y]
+ mov cl, VIDEO_COLS
+ mul cl
+ mov bx, ax
+ movzx ax, [CURSOR_X]
+ add bx, ax
+ mov cx, bx
+ ;cursor LOW port to vga INDEX register
+ mov al, 0fh ;Cursor Location Low Register --
+ mov dx, 3d4h ;VGA port 3D4h
+ out dx, al
+ mov ax, cx ;restore 'postion' back to AX
+ mov dx, 3d5h ;VGA port 3D5h
+ out dx, al ;send to VGA hardware
+ ;cursor HIGH port to vga INDEX register
+ mov al, 0eh
+ mov dx, 3d4h ;VGA port 3D4h
+ out dx, al
+ mov ax, cx ;restore 'position' back to AX
+ shr ax, 8 ;get high byte in 'position'
+ mov dx, 3d5h ;VGA port 3D5h
+ out dx, al ;send to VGA hardware
+ pop edx
+ pop ecx
+ pop ebx
+ pop eax
+ ret
+
+; read cursor from hardware
+read_hardware_vga_cursor:
+ push eax
+ push ebx
+ push ecx
+ push edx
+ xor eax, eax
+ xor ebx, ebx
+ mov al, 0eh
+ mov dx, 3d4h
+ out dx, al
+ mov dx, 3d5h
+ in al, dx
+ mov bh, al
+ mov al, 0fh
+ mov dx, 3d4h
+ out dx, al
+ mov dx, 3d5h
+ in al, dx
+ mov bl, al
+ mov ax, bx
+ mov edx, 0
+ mov cx, VIDEO_COLS
+ div cx
+ mov [CURSOR_X], dx
+ mov [CURSOR_Y], ax
+ pop edx
+ pop ecx
+ pop ebx
+ pop eax
+ ret
+
+PM_HEX_TEMPLATE:
+ db '0x???? ', 0
+
+PM_HEXABET:
+ db '0123456789ABCDEF'
+
diff --git a/src/boot/stage2_real_functions.asm b/src/boot/stage2_real_functions.asm
new file mode 100644
index 0000000..d49bfce
--- /dev/null
+++ b/src/boot/stage2_real_functions.asm
@@ -0,0 +1,169 @@
+; number of sectors to be read for the kernel itself
+; (note: the first sector gets loaded by the BIOS, the
+; next 3 sectors are read by the simple stage 1 loader,
+; so subtract 3 here!)
+NOF_LOAD_SECTORS equ 37
+
+; data sections used for reading the kernel from disk
+SECTORS_PER_CYLINDER:
+ db 0x3F ; detect parameters enters the correct value here (sectors + 1)
+ ; if detection fails, force int13 to read ahead
+NOF_HEADS:
+ db 0x01 ; number of heads + 1
+SECTORS_TO_LOAD:
+ db NOF_LOAD_SECTORS ; load NOF_LOAD_SECTORS sectors in total
+CURRENT_SECTOR:
+ db 5 ; first sector after stage 2
+CURRENT_CYLINDER:
+ db 0
+CURRENT_HEAD:
+ db 0
+
+; detect disk geometry
+; IN dl: drive
+detect_disk_geometry:
+ mov dx, 0
+ mov es, dx
+ mov ah, 0x08
+ int 0x13
+ jc .error
+ jmp .ok
+
+.error:
+ mov si, DISK_ERROR
+ call print_string
+ mov dh, 0
+ mov dl, ah
+ call print_hex
+ ret
+
+.ok:
+ add dh, 1
+ mov [NOF_HEADS], BYTE dh
+ call print_hex
+ mov dx, cx
+ call print_hex
+ and cl, 0x3F
+ add cl, 1
+ mov [SECTORS_PER_CYLINDER], BYTE cl
+ ret
+
+probe_and_fix_disk_geometry:
+ mov cl, 0x1 ; from 1 to 63
+ mov BYTE [SECTORS_PER_CYLINDER], cl
+
+.loop:
+ mov dl, [BOOT_DRIVE]
+ mov ah, 0x02 ; read sectors from drive
+ mov al, 1 ; read 1 sector
+ mov cl, BYTE [SECTORS_PER_CYLINDER]
+ mov ch, BYTE 0
+ mov dh, BYTE 0
+
+ int 0x13
+
+ jnc .next_sector ; on error, remeber cl being the highest sector
+
+.fix_heads:
+ cmp [NOF_HEADS], BYTE 0 ; 0 heads, the BIOS is playing tricks to us!
+ je .illegal_heads
+ jmp .end
+
+.illegal_heads:
+ mov [NOF_HEADS], BYTE 1
+ jmp .end
+
+.next_sector:
+ mov cl, BYTE [SECTORS_PER_CYLINDER]
+ inc cl ; next sector
+ mov BYTE [SECTORS_PER_CYLINDER], cl
+ cmp cl, 0x40 ; to a max of 63, if so, this is the default
+ jbe .loop
+ jmp .fix_heads
+
+.end:
+ ret
+
+; read the whole stage2 and kernel from the disk
+; IN dl: drive to read from
+read_from_disk:
+
+ mov bx, 0 ; where to store the data
+ mov es, bx
+ mov bx, 0x8400 ; 2'048 bytes after first sector
+
+.read_next_sector:
+
+ call read_one_sector_from_disk
+
+ sub [SECTORS_TO_LOAD], BYTE 1 ; one less to load
+ cmp [SECTORS_TO_LOAD], BYTE 0 ; finished?
+ je .finished
+
+ add bx, 0x200 ; advance write buffer position
+ add [CURRENT_SECTOR], BYTE 1 ; next sector
+ mov ch, [SECTORS_PER_CYLINDER]
+ cmp [CURRENT_SECTOR], ch ; after the end of the current track?
+ je .next_head
+ jmp .read_next_sector
+
+.next_head:
+ mov [CURRENT_SECTOR], BYTE 1 ; start from first sector again
+ add [CURRENT_HEAD], BYTE 1 ; advance head
+ mov ch, [NOF_HEADS]
+ cmp [CURRENT_HEAD], ch ; after the number of heads?
+ je .next_track
+ jmp .read_next_sector
+
+.next_track:
+ mov [CURRENT_HEAD], BYTE 0 ; start from head 0 again
+ add [CURRENT_CYLINDER], BYTE 1 ; advance track
+ jmp .read_next_sector
+
+.finished:
+ ret
+
+; Read one sector after the other, if we get illegal parameter
+; errors we assume we have gone over the limit of number of
+; sectors per cylinder. This avoids to probe for this number with
+; shacky BIOS functions or with probing (as LILO does). It's not
+; very efficient though..
+; IN dl: drive to read from
+read_one_sector_from_disk:
+
+ mov ah, 0x02 ; read sectors from drive
+ mov al, 1 ; read 1 sector
+ mov ch, BYTE [CURRENT_CYLINDER]
+ mov dh, BYTE [CURRENT_HEAD]
+ mov cl, BYTE [CURRENT_SECTOR]
+
+ int 0x13
+
+ jc .read_error
+
+ cmp al, 1 ; 1 sector read?
+ jne .short_read ; if not, short read
+
+ mov al, '.'
+ call print_char
+
+ ret
+
+.read_error:
+ mov si, DISK_ERROR
+ call print_string
+ mov dh, 0
+ mov dl, ah
+ call print_hex
+ jmp $
+
+.short_read:
+ mov si, SHORT_READ
+ call print_string
+ jmp $
+
+kill_motor:
+ mov dx, 0x3F2
+ mov al, 0x00
+ out dx, al
+ ret
diff --git a/src/boot/stage2_switch_mode.asm b/src/boot/stage2_switch_mode.asm
new file mode 100644
index 0000000..9259e08
--- /dev/null
+++ b/src/boot/stage2_switch_mode.asm
@@ -0,0 +1,40 @@
+[bits 16]
+
+%include "boot/boot_gdt.asm"
+
+switch_to_protected_mode:
+ ; switch off interrupts for now, we don't
+ ; have a valid IDT installed yet
+ cli
+
+ ; load GDT (global descriptor table)
+ lgdt [gdt_descriptor]
+
+ ; do the actual switch
+ mov eax, cr0
+ or eax, 0x1
+ mov cr0, eax
+
+ ; unconditional far jump into code segment,
+ ; wipes the instruction prefetch pipeline
+ jmp CODE_SEGMENT:init_pm
+
+[bits 32]
+
+; initialize registers and stack for protected mode
+init_pm:
+ mov ax, DATA_SEGMENT
+ mov ds, ax
+ mov ss, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+
+ ; a new stack
+ mov ebp, 0x90000
+ mov esp, ebp
+
+ call BEGIN_PROTECTED_MODE
+
+[bits 16]
+