+ 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