- subl %eax, %eax
- subl %edi, %edi
- movl $0x400, %ecx
- rep stosl
-
-# Set PDEs for 0 and LOADER_PHYS_BASE to point to the page table.
-
- movl $0x11000 | PG_U | PG_W | PG_P, %eax
- movl %eax, %es:0
- movl %eax, %es:LOADER_PHYS_BASE >> 20
-
-# Initialize page table.
-
- movl $PG_U | PG_W | PG_P, %eax
- movl $0x400, %ecx
-1: stosl
- addl $0x1000, %eax
- loop 1b
-
-# Set page directory base register.
-
- movl $0x10000, %eax
- movl %eax, %cr3
-
-#### 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.
-
- movl %cr0, %eax
- orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax
- movl %eax, %cr0
-
-# 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: movw $SEL_KDSEG, %ax
- movw %ax, %ds
- movw %ax, %es
- movw %ax, %fs
- movw %ax, %gs
- movw %ax, %ss
- movl $LOADER_PHYS_BASE + 0x20000, %esp
-
-#### Load kernel starting at physical address LOADER_KERN_BASE by
-#### frobbing the IDE controller directly.
-
- movl $1, %ebx
- movl $LOADER_KERN_BASE + LOADER_PHYS_BASE, %edi
-read_sector:
-
-# Poll status register while controller busy.
-
- movl $0x1f7, %edx
-1: inb %dx, %al
- testb $0x80, %al
- jnz 1b
-
-# Read a single sector.
-
- movl $0x1f2, %edx
- movb $1, %al
- outb %al, %dx
-
-# Sector number to write in low 28 bits.
-# LBA mode, device 0 in top 4 bits.
-
- movl %ebx, %eax
- andl $0x0fffffff, %eax
- orl $0xe0000000, %eax
-
-# Dump %eax to ports 0x1f3...0x1f6.
-
- movl $4, %ecx
-1: incw %dx
- outb %al, %dx
- shrl $8, %eax
- loop 1b
-
-# READ command to command register.
-
- incw %dx
- movb $0x20, %al
- outb %al, %dx
-
-# Poll status register while controller busy.
-
-1: inb %dx, %al
- testb $0x80, %al
- jnz 1b
-
-# Poll status register until data ready.
-
-1: inb %dx, %al
- testb $0x08, %al
- jz 1b
-
-# Transfer sector.
-
- movl $512 / 4, %ecx
- movl $0x1f0, %edx
- rep insl
-
-# Next sector.
-
- incl %ebx
- cmpl $KERNEL_LOAD_PAGES*8 + 1, %ebx
- jnz read_sector
-
-#### Jump to kernel entry point.
-
- movl $LOADER_PHYS_BASE + LOADER_KERN_BASE, %eax
- 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.
- movw $panicmsg, %si
- movb $0xe, %ah
- subb %bh, %bh
-1: lodsb
+ call read_sector
+ jc no_such_drive
+
+ # Print hd[a-z].
+ call puts
+ .string " hd"
+ mov %dl, %al
+ add $'a' - 0x80, %al
+ call putc
+
+ # Check for MBR signature--if not present, it's not a
+ # partitioned hard disk.
+ cmpw $0xaa55, %es:510
+ jne next_drive
+
+ mov $446, %si # Offset of partition table entry 1.
+ mov $'1', %al
+check_partition:
+ # Is it an unused partition?
+ cmpl $0, %es:(%si)
+ je next_partition
+
+ # Print [1-4].
+ call putc
+
+ # Is it a Pintos kernel partition?
+ cmpb $0x20, %es:4(%si)
+ jne next_partition
+
+ # Is it a bootable partition?
+ cmpb $0x80, %es:(%si)
+ je load_kernel
+
+next_partition:
+ # No match for this partition, go on to the next one.
+ add $16, %si # Offset to next partition table entry.
+ inc %al
+ cmp $510, %si
+ jb check_partition
+
+next_drive:
+ # No match on this drive, go on to the next one.
+ inc %dl
+ jnc read_mbr
+
+no_such_drive:
+no_boot_partition:
+ # Didn't find a Pintos kernel partition anywhere, give up.
+ call puts
+ .string "\rNot found\r"
+
+ # Notify BIOS that boot failed. See [IntrList].
+ int $0x18
+
+#### We found a kernel. The kernel's drive is in DL. The partition
+#### table entry for the kernel's partition is at ES:SI. Our job now
+#### is to read the kernel from disk and jump to its start address.
+
+load_kernel:
+ call puts
+ .string "\rLoading"
+
+ # Figure out number of sectors to read. A Pintos kernel is
+ # just an ELF format object, which doesn't have an
+ # easy-to-read field to identify its own size (see [ELF1]).
+ # But we limit Pintos kernels to 512 kB for other reasons, so
+ # it's easy enough to just read the entire contents of the
+ # partition or 512 kB from disk, whichever is smaller.
+ mov %es:12(%si), %ecx # EBP = number of sectors
+ cmp $1024, %ecx # Cap size at 512 kB
+ jbe 1f
+ 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