- mov eax, 0x10000
-1: shr eax, 2 # Total 4 kB pages
- mov ram_pages, eax
-
-#### Create temporary page directory and page table and set page
-#### directory base register.
-
-# Create page directory at 64 kB and fill with zeroes.
- mov ax, 0x1000
- mov es, ax
- sub eax, eax
- sub edi, edi
- mov ecx, 0x400
- rep stosd
-
-# Add PDEs to point to PTEs for the first 64 MB of RAM.
-# Also add identical PDEs starting at LOADER_PHYS_BASE.
-# See [IA32-v3] section 3.7.6 for a description of the bits in eax.
-
- mov eax, 0x11007
- mov ecx, 0x11
- sub edi, edi
-1: mov es:[di], eax
- mov es:LOADER_PHYS_BASE / 1024 / 1024[di], eax
- add di, 4
- add eax, 0x1000
- loop 1b
-
-# Set up one-to-map linear to physical map for the first 64 MB of RAM.
-# See [IA32-v3] section 3.7.6 for a description of the bits in eax.
-
- mov ax, 0x1100
- mov es, ax
- mov eax, 0x7
- mov ecx, 0x4000
- sub edi, edi
-1: mov es:[di], eax
- add di, 4
- add eax, 0x1000
- loop 1b
-
-# Set page directory base register.
-
- mov eax, 0x10000
- mov cr3, eax
-
-#### Switch to protected mode.
-
-# First we turn off interrupts because we don't set up an IDT.
-
- cli
-
-# Then we point the GDTR to our GDT. Protected mode requires a GDT.
-# We need a data32 prefix to ensure that all 32 bits of the GDT
-# descriptor are loaded (default is to load only 24 bits).
-
- data32 lgdt gdtdesc
-
-# Then we turn on the following bits in CR0:
-# PE (Protect Enable): this turns on protected mode.
-# PG (Paging): turns on paging.
-# WP (Write Protect): if unset, ring 0 code ignores
-# write-protect bits in page tables (!).
-# EM (Emulation): forces floating-point instructions to trap.
-# We don't support floating point.
-
- mov eax, cr0
- or eax, CR0_PE + CR0_PG + CR0_WP + CR0_EM
- mov cr0, eax
-
-# We're now in protected mode in a 16-bit segment. The CPU still has
-# the real-mode code segment cached in cs's segment descriptor. We
-# need to reload cs, and the easiest way is to use a far jump.
-# Because we're not in a 32-bit segment the data32 prefix is needed to
-# jump to a 32-bit offset.
-
- data32 ljmp SEL_KCSEG, 1f + LOADER_PHYS_BASE
-
-# We're now in protected mode in a 32-bit segment.
-
- .code32
-
-# Reload all the other segment registers and the stack pointer to
-# point into our new GDT.
-
-1: mov ax, SEL_KDSEG
- mov ds, ax
- mov es, ax
- mov fs, ax
- mov gs, ax
- mov ss, ax
- mov esp, LOADER_PHYS_BASE + 0x30000
-
-#### Load kernel starting at physical address LOADER_KERN_BASE by
-#### frobbing the IDE controller directly.
-
- mov ebx, 1
- mov edi, LOADER_KERN_BASE + LOADER_PHYS_BASE
-read_sector:
-
-# Poll status register while controller busy.
-
- mov edx, 0x1f7
-1: in al, dx
- test al, 0x80
- jnz 1b
-
-# Read a single sector.
-
- mov edx, 0x1f2
- mov al, 1
- out dx, al
-
-# Sector number to write in low 28 bits.
-# LBA mode, device 0 in top 4 bits.
-
- mov eax, ebx
- and eax, 0x0fffffff
- or eax, 0xe0000000
-
-# Dump eax to ports 0x1f3...0x1f6.
-
- mov ecx, 4
-1: inc dx
- out dx, al
- shr eax, 8
- loop 1b
-
-# READ command to command register.
-
- inc dx
- mov al, 0x20
- out dx, al
-
-# Poll status register while controller busy.
-
-1: in al, dx
- test al, 0x80
- jnz 1b
-
-# Poll status register until data ready.
-
-1: in al, dx
- test al, 0x08
- jz 1b
-
-# Transfer sector.
-
- mov ecx, 256
- mov edx, 0x1f0
- rep insw
-
-# Next sector.
-
- inc ebx
- cmp ebx, KERNEL_LOAD_PAGES*8 + 1
- jnz read_sector
-
-#### Jump to kernel entry point.
-
- mov eax, LOADER_PHYS_BASE + LOADER_KERN_BASE
- call eax
- jmp panic
-
-#### GDT
-
-gdt:
- .quad 0x0000000000000000 # null seg
- .quad 0x00cf9a000000ffff # code seg
- .quad 0x00cf92000000ffff # data seg
-
-gdtdesc:
- .word 0x17 # sizeof (gdt) - 1
- .long gdt + LOADER_PHYS_BASE # address gdt
-
-#### Fatal error.
-#### Print panicmsg (with help from the BIOS) and spin.
-
-panic: .code16 # We only panic in real mode.
- mov si, offset panicmsg
- mov ah, 0xe
- sub bh, bh
-1: lodsb
- test al, al
-2: jz 2b # Spin.
- int 0x10
+ mov $1024, %cx
+1:
+
+ mov %es:8(%si), %ebx # EBX = first sector
+ mov $0x2000, %ax # Start load address: 0x20000
+
+next_sector:
+ # Read one sector into memory.
+ mov %ax, %es # ES:0000 -> load address
+ call read_sector
+ jc read_failed
+
+ # Print '.' as progress indicator once every 16 sectors == 8 kB.
+ test $15, %bl
+ jnz 1f
+ call puts
+ .string "."
+1:
+
+ # Advance memory pointer and disk sector.
+ add $0x20, %ax
+ inc %bx
+ loop next_sector
+
+ call puts
+ .string "\r"
+
+#### Transfer control to the kernel that we loaded. We read the start
+#### address out of the ELF header (see [ELF1]) and convert it from a
+#### 32-bit linear address into a 16:16 segment:offset address for
+#### real mode, then jump to the converted address. The 80x86 doesn't
+#### have an instruction to jump to an absolute segment:offset kept in
+#### registers, so in fact we store the address in a temporary memory
+#### location, then jump indirectly through that location. To save 4
+#### bytes in the loader, we reuse 4 bytes of the loader's code for
+#### this temporary pointer.
+
+ mov $0x2000, %ax
+ mov %ax, %es
+ mov %es:0x18, %dx
+ mov %dx, start
+ movw $0x2000, start + 2
+ ljmp *start
+
+read_failed:
+start:
+ # Disk sector read failed.
+ call puts
+1: .string "\rBad read\r"
+
+ # Notify BIOS that boot failed. See [IntrList].
+ int $0x18
+
+#### Print string subroutine. To save space in the loader, this
+#### subroutine takes its null-terminated string argument from the
+#### code stream just after the call, and then returns to the byte
+#### just after the terminating null. This subroutine preserves all
+#### general-purpose registers.
+
+puts: xchg %si, %ss:(%esp)
+ push %ax
+next_char:
+ mov %cs:(%si), %al
+ inc %si
+ test %al, %al
+ jz 1f
+ call putc
+ jmp next_char
+1: pop %ax
+ xchg %si, %ss:(%esp)
+ ret
+
+#### Character output subroutine. Prints the character in AL to the
+#### VGA display and serial port 0, using BIOS services (see
+#### [IntrList]). Preserves all general-purpose registers.
+####
+#### If called upon to output a carriage return, this subroutine
+#### automatically supplies the following line feed.
+
+putc: pusha
+
+1: sub %bh, %bh # Page 0.
+ mov $0x0e, %ah # Teletype output service.
+ int $0x10
+
+ mov $0x01, %ah # Serial port output service.
+ sub %dx, %dx # Serial port 0.
+2: int $0x14 # Destroys AH.
+ test $0x80, %ah # Output timed out?
+ jz 3f
+ movw $0x9090, 2b # Turn "int $0x14" above into NOPs.
+
+3:
+ cmp $'\r', %al
+ jne popa_ret
+ mov $'\n', %al