diff options
Diffstat (limited to 'pintos-progos/threads/loader.S')
| -rw-r--r-- | pintos-progos/threads/loader.S | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/pintos-progos/threads/loader.S b/pintos-progos/threads/loader.S new file mode 100644 index 0000000..dd87ea1 --- /dev/null +++ b/pintos-progos/threads/loader.S | |||
| @@ -0,0 +1,263 @@ | |||
| 1 | #include "threads/loader.h" | ||
| 2 | |||
| 3 | #### Kernel loader. | ||
| 4 | |||
| 5 | #### This code should be stored in the first sector of a hard disk. | ||
| 6 | #### When the BIOS runs, it loads this code at physical address | ||
| 7 | #### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it, | ||
| 8 | #### in real mode. The loader loads the kernel into memory and jumps | ||
| 9 | #### to its entry point, which is the start function in start.S. | ||
| 10 | #### | ||
| 11 | #### The BIOS passes in the drive that the loader was read from as | ||
| 12 | #### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives | ||
| 13 | #### numbered 0x80, 0x81, ... We want to support booting a kernel on | ||
| 14 | #### a different drive from the loader, so we don't take advantage of | ||
| 15 | #### this. | ||
| 16 | |||
| 17 | # Runs in real mode, which is a 16-bit segment. | ||
| 18 | .code16 | ||
| 19 | |||
| 20 | # Set up segment registers. | ||
| 21 | # Set stack to grow downward from 60 kB (after boot, the kernel | ||
| 22 | # continues to use this stack for its initial thread). | ||
| 23 | |||
| 24 | sub %ax, %ax | ||
| 25 | mov %ax, %ds | ||
| 26 | mov %ax, %ss | ||
| 27 | mov $0xf000, %esp | ||
| 28 | |||
| 29 | # Configure serial port so we can report progress without connected VGA. | ||
| 30 | # See [IntrList] for details. | ||
| 31 | sub %dx, %dx # Serial port 0. | ||
| 32 | mov $0xe3, %al # 9600 bps, N-8-1. | ||
| 33 | # AH is already 0 (Initialize Port). | ||
| 34 | int $0x14 # Destroys AX. | ||
| 35 | |||
| 36 | call puts | ||
| 37 | .string "PiLo" | ||
| 38 | |||
| 39 | #### Read the partition table on each system hard disk and scan for a | ||
| 40 | #### partition of type 0x20, which is the type that we use for a | ||
| 41 | #### Pintos kernel. | ||
| 42 | #### | ||
| 43 | #### Read [Partitions] for a description of the partition table format | ||
| 44 | #### that we parse. | ||
| 45 | #### | ||
| 46 | #### We print out status messages to show the disk and partition being | ||
| 47 | #### scanned, e.g. hda1234 as we scan four partitions on the first | ||
| 48 | #### hard disk. | ||
| 49 | |||
| 50 | mov $0x80, %dl # Hard disk 0. | ||
| 51 | read_mbr: | ||
| 52 | sub %ebx, %ebx # Sector 0. | ||
| 53 | mov $0x2000, %ax # Use 0x20000 for buffer. | ||
| 54 | mov %ax, %es | ||
| 55 | call read_sector | ||
| 56 | jc no_such_drive | ||
| 57 | |||
| 58 | # Print hd[a-z]. | ||
| 59 | call puts | ||
| 60 | .string " hd" | ||
| 61 | mov %dl, %al | ||
| 62 | add $'a' - 0x80, %al | ||
| 63 | call putc | ||
| 64 | |||
| 65 | # Check for MBR signature--if not present, it's not a | ||
| 66 | # partitioned hard disk. | ||
| 67 | cmpw $0xaa55, %es:510 | ||
| 68 | jne next_drive | ||
| 69 | |||
| 70 | mov $446, %si # Offset of partition table entry 1. | ||
| 71 | mov $'1', %al | ||
| 72 | check_partition: | ||
| 73 | # Is it an unused partition? | ||
| 74 | cmpl $0, %es:(%si) | ||
| 75 | je next_partition | ||
| 76 | |||
| 77 | # Print [1-4]. | ||
| 78 | call putc | ||
| 79 | |||
| 80 | # Is it a Pintos kernel partition? | ||
| 81 | cmpb $0x20, %es:4(%si) | ||
| 82 | jne next_partition | ||
| 83 | |||
| 84 | # Is it a bootable partition? | ||
| 85 | cmpb $0x80, %es:(%si) | ||
| 86 | je load_kernel | ||
| 87 | |||
| 88 | next_partition: | ||
| 89 | # No match for this partition, go on to the next one. | ||
| 90 | add $16, %si # Offset to next partition table entry. | ||
| 91 | inc %al | ||
| 92 | cmp $510, %si | ||
| 93 | jb check_partition | ||
| 94 | |||
| 95 | next_drive: | ||
| 96 | # No match on this drive, go on to the next one. | ||
| 97 | inc %dl | ||
| 98 | jnc read_mbr | ||
| 99 | |||
| 100 | no_such_drive: | ||
| 101 | no_boot_partition: | ||
| 102 | # Didn't find a Pintos kernel partition anywhere, give up. | ||
| 103 | call puts | ||
| 104 | .string "\rNot found\r" | ||
| 105 | |||
| 106 | # Notify BIOS that boot failed. See [IntrList]. | ||
| 107 | int $0x18 | ||
| 108 | |||
| 109 | #### We found a kernel. The kernel's drive is in DL. The partition | ||
| 110 | #### table entry for the kernel's partition is at ES:SI. Our job now | ||
| 111 | #### is to read the kernel from disk and jump to its start address. | ||
| 112 | |||
| 113 | load_kernel: | ||
| 114 | call puts | ||
| 115 | .string "\rLoading" | ||
| 116 | |||
| 117 | # Figure out number of sectors to read. A Pintos kernel is | ||
| 118 | # just an ELF format object, which doesn't have an | ||
| 119 | # easy-to-read field to identify its own size (see [ELF1]). | ||
| 120 | # But we limit Pintos kernels to 512 kB for other reasons, so | ||
| 121 | # it's easy enough to just read the entire contents of the | ||
| 122 | # partition or 512 kB from disk, whichever is smaller. | ||
| 123 | mov %es:12(%si), %ecx # EBP = number of sectors | ||
| 124 | cmp $1024, %ecx # Cap size at 512 kB | ||
| 125 | jbe 1f | ||
| 126 | mov $1024, %cx | ||
| 127 | 1: | ||
| 128 | |||
| 129 | mov %es:8(%si), %ebx # EBX = first sector | ||
| 130 | mov $0x2000, %ax # Start load address: 0x20000 | ||
| 131 | |||
| 132 | next_sector: | ||
| 133 | # Read one sector into memory. | ||
| 134 | mov %ax, %es # ES:0000 -> load address | ||
| 135 | call read_sector | ||
| 136 | jc read_failed | ||
| 137 | |||
| 138 | # Print '.' as progress indicator once every 16 sectors == 8 kB. | ||
| 139 | test $15, %bl | ||
| 140 | jnz 1f | ||
| 141 | call puts | ||
| 142 | .string "." | ||
| 143 | 1: | ||
| 144 | |||
| 145 | # Advance memory pointer and disk sector. | ||
| 146 | add $0x20, %ax | ||
| 147 | inc %bx | ||
| 148 | loop next_sector | ||
| 149 | |||
| 150 | call puts | ||
| 151 | .string "\r" | ||
| 152 | |||
| 153 | #### Transfer control to the kernel that we loaded. We read the start | ||
| 154 | #### address out of the ELF header (see [ELF1]) and convert it from a | ||
| 155 | #### 32-bit linear address into a 16:16 segment:offset address for | ||
| 156 | #### real mode, then jump to the converted address. The 80x86 doesn't | ||
| 157 | #### have an instruction to jump to an absolute segment:offset kept in | ||
| 158 | #### registers, so in fact we store the address in a temporary memory | ||
| 159 | #### location, then jump indirectly through that location. To save 4 | ||
| 160 | #### bytes in the loader, we reuse 4 bytes of the loader's code for | ||
| 161 | #### this temporary pointer. | ||
| 162 | |||
| 163 | mov $0x2000, %ax | ||
| 164 | mov %ax, %es | ||
| 165 | mov %es:0x18, %dx | ||
| 166 | mov %dx, start | ||
| 167 | movw $0x2000, start + 2 | ||
| 168 | ljmp *start | ||
| 169 | |||
| 170 | read_failed: | ||
| 171 | start: | ||
| 172 | # Disk sector read failed. | ||
| 173 | call puts | ||
| 174 | 1: .string "\rBad read\r" | ||
| 175 | |||
| 176 | # Notify BIOS that boot failed. See [IntrList]. | ||
| 177 | int $0x18 | ||
| 178 | |||
| 179 | #### Print string subroutine. To save space in the loader, this | ||
| 180 | #### subroutine takes its null-terminated string argument from the | ||
| 181 | #### code stream just after the call, and then returns to the byte | ||
| 182 | #### just after the terminating null. This subroutine preserves all | ||
| 183 | #### general-purpose registers. | ||
| 184 | |||
| 185 | puts: xchg %si, %ss:(%esp) | ||
| 186 | push %ax | ||
| 187 | next_char: | ||
| 188 | mov %cs:(%si), %al | ||
| 189 | inc %si | ||
| 190 | test %al, %al | ||
| 191 | jz 1f | ||
| 192 | call putc | ||
| 193 | jmp next_char | ||
| 194 | 1: pop %ax | ||
| 195 | xchg %si, %ss:(%esp) | ||
| 196 | ret | ||
| 197 | |||
| 198 | #### Character output subroutine. Prints the character in AL to the | ||
| 199 | #### VGA display and serial port 0, using BIOS services (see | ||
| 200 | #### [IntrList]). Preserves all general-purpose registers. | ||
| 201 | #### | ||
| 202 | #### If called upon to output a carriage return, this subroutine | ||
| 203 | #### automatically supplies the following line feed. | ||
| 204 | |||
| 205 | putc: pusha | ||
| 206 | |||
| 207 | 1: sub %bh, %bh # Page 0. | ||
| 208 | mov $0x0e, %ah # Teletype output service. | ||
| 209 | int $0x10 | ||
| 210 | |||
| 211 | mov $0x01, %ah # Serial port output service. | ||
| 212 | sub %dx, %dx # Serial port 0. | ||
| 213 | 2: int $0x14 # Destroys AH. | ||
| 214 | test $0x80, %ah # Output timed out? | ||
| 215 | jz 3f | ||
| 216 | movw $0x9090, 2b # Turn "int $0x14" above into NOPs. | ||
| 217 | |||
| 218 | 3: | ||
| 219 | cmp $'\r', %al | ||
| 220 | jne popa_ret | ||
| 221 | mov $'\n', %al | ||
| 222 | jmp 1b | ||
| 223 | |||
| 224 | #### Sector read subroutine. Takes a drive number in DL (0x80 = hard | ||
| 225 | #### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and | ||
| 226 | #### reads the specified sector into memory at ES:0000. Returns with | ||
| 227 | #### carry set on error, clear otherwise. Preserves all | ||
| 228 | #### general-purpose registers. | ||
| 229 | |||
| 230 | read_sector: | ||
| 231 | pusha | ||
| 232 | sub %ax, %ax | ||
| 233 | push %ax # LBA sector number [48:63] | ||
| 234 | push %ax # LBA sector number [32:47] | ||
| 235 | push %ebx # LBA sector number [0:31] | ||
| 236 | push %es # Buffer segment | ||
| 237 | push %ax # Buffer offset (always 0) | ||
| 238 | push $1 # Number of sectors to read | ||
| 239 | push $16 # Packet size | ||
| 240 | mov $0x42, %ah # Extended read | ||
| 241 | mov %sp, %si # DS:SI -> packet | ||
| 242 | int $0x13 # Error code in CF | ||
| 243 | popa # Pop 16 bytes, preserve flags | ||
| 244 | popa_ret: | ||
| 245 | popa | ||
| 246 | ret # Error code still in CF | ||
| 247 | |||
| 248 | #### Command-line arguments and their count. | ||
| 249 | #### This is written by the `pintos' utility and read by the kernel. | ||
| 250 | #### The loader itself does not do anything with the command line. | ||
| 251 | .org LOADER_ARG_CNT - LOADER_BASE | ||
| 252 | .fill LOADER_ARG_CNT_LEN, 1, 0 | ||
| 253 | |||
| 254 | .org LOADER_ARGS - LOADER_BASE | ||
| 255 | .fill LOADER_ARGS_LEN, 1, 0 | ||
| 256 | |||
| 257 | #### Partition table. | ||
| 258 | .org LOADER_PARTS - LOADER_BASE | ||
| 259 | .fill LOADER_PARTS_LEN, 1, 0 | ||
| 260 | |||
| 261 | #### Boot-sector signature for BIOS inspection. | ||
| 262 | .org LOADER_SIG - LOADER_BASE | ||
| 263 | .word 0xaa55 | ||
