#include "threads/loader.h" #### Kernel loader. #### This code should be stored in the first sector of a hard disk. #### When the BIOS runs, it loads this code at physical address #### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it, #### in real mode. The loader loads the kernel into memory and jumps #### to its entry point, which is the start function in start.S. #### #### The BIOS passes in the drive that the loader was read from as #### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives #### numbered 0x80, 0x81, ... We want to support booting a kernel on #### a different drive from the loader, so we don't take advantage of #### this. # Runs in real mode, which is a 16-bit segment. .code16 # Set up segment registers. # Set stack to grow downward from 60 kB (after boot, the kernel # continues to use this stack for its initial thread). sub %ax, %ax mov %ax, %ds mov %ax, %ss mov $0xf000, %esp # Configure serial port so we can report progress without connected VGA. # See [IntrList] for details. sub %dx, %dx # Serial port 0. mov $0xe3, %al # 9600 bps, N-8-1. # AH is already 0 (Initialize Port). int $0x14 # Destroys AX. call puts .string "PiLo" #### Read the partition table on each system hard disk and scan for a #### partition of type 0x20, which is the type that we use for a #### Pintos kernel. #### #### Read [Partitions] for a description of the partition table format #### that we parse. #### #### We print out status messages to show the disk and partition being #### scanned, e.g. hda1234 as we scan four partitions on the first #### hard disk. mov $0x80, %dl # Hard disk 0. read_mbr: sub %ebx, %ebx # Sector 0. mov $0x2000, %ax # Use 0x20000 for buffer. mov %ax, %es 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 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 jmp 1b #### Sector read subroutine. Takes a drive number in DL (0x80 = hard #### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and #### reads the specified sector into memory at ES:0000. Returns with #### carry set on error, clear otherwise. Preserves all #### general-purpose registers. read_sector: pusha sub %ax, %ax push %ax # LBA sector number [48:63] push %ax # LBA sector number [32:47] push %ebx # LBA sector number [0:31] push %es # Buffer segment push %ax # Buffer offset (always 0) push $1 # Number of sectors to read push $16 # Packet size mov $0x42, %ah # Extended read mov %sp, %si # DS:SI -> packet int $0x13 # Error code in CF popa # Pop 16 bytes, preserve flags popa_ret: popa ret # Error code still in CF #### Command-line arguments and their count. #### This is written by the `pintos' utility and read by the kernel. #### The loader itself does not do anything with the command line. .org LOADER_ARG_CNT - LOADER_BASE .fill LOADER_ARG_CNT_LEN, 1, 0 .org LOADER_ARGS - LOADER_BASE .fill LOADER_ARGS_LEN, 1, 0 #### Partition table. .org LOADER_PARTS - LOADER_BASE .fill LOADER_PARTS_LEN, 1, 0 #### Boot-sector signature for BIOS inspection. .org LOADER_SIG - LOADER_BASE .word 0xaa55