Greg Hartman | 76d05dc | 2016-11-23 15:51:27 -0800 | [diff] [blame] | 1 | /* NOTE: this boot sector contains instructions that need at least an 80186. |
| 2 | * Yes, as86 has a bug somewhere in the valid instruction set checks. |
| 3 | * |
| 4 | */ |
| 5 | |
| 6 | /* floppyload.S Copyright (C) 1991, 1992 Linus Torvalds |
| 7 | * modified by Drew Eckhardt |
| 8 | * modified by Bruce Evans (bde) |
| 9 | * |
| 10 | * floppyprefix.S is loaded at 0x0000:0x7c00 by the bios-startup routines. |
| 11 | * |
| 12 | * It then loads the system at SYSSEG<<4, using BIOS interrupts. |
| 13 | * |
| 14 | * The loader has been made as simple as possible, and continuous read errors |
| 15 | * will result in a unbreakable loop. Reboot by hand. It loads pretty fast by |
| 16 | * getting whole tracks at a time whenever possible. |
| 17 | */ |
| 18 | |
| 19 | FILE_LICENCE ( GPL2_ONLY ) |
| 20 | |
| 21 | .equ BOOTSEG, 0x07C0 /* original address of boot-sector */ |
| 22 | |
| 23 | .equ SYSSEG, 0x1000 /* system loaded at SYSSEG<<4 */ |
| 24 | |
| 25 | .org 0 |
| 26 | .arch i386 |
| 27 | .text |
| 28 | .section ".prefix", "ax", @progbits |
| 29 | .code16 |
| 30 | |
| 31 | jmp $BOOTSEG, $go /* reload cs:ip to match relocation addr */ |
| 32 | go: |
| 33 | movw $0x2000-12, %di /* 0x2000 is arbitrary value >= length */ |
| 34 | /* of bootsect + room for stack + 12 for */ |
| 35 | /* saved disk parm block */ |
| 36 | |
| 37 | movw $BOOTSEG, %ax |
| 38 | movw %ax,%ds |
| 39 | movw %ax,%es |
| 40 | movw %ax,%ss /* put stack at BOOTSEG:0x4000-12. */ |
| 41 | movw %di,%sp |
| 42 | |
| 43 | /* Many BIOS's default disk parameter tables will not recognize multi-sector |
| 44 | * reads beyond the maximum sector number specified in the default diskette |
| 45 | * parameter tables - this may mean 7 sectors in some cases. |
| 46 | * |
| 47 | * Since single sector reads are slow and out of the question, we must take care |
| 48 | * of this by creating new parameter tables (for the first disk) in RAM. We |
| 49 | * will set the maximum sector count to 36 - the most we will encounter on an |
| 50 | * ED 2.88. High doesn't hurt. Low does. |
| 51 | * |
| 52 | * Segments are as follows: ds=es=ss=cs - BOOTSEG |
| 53 | */ |
| 54 | |
| 55 | xorw %cx,%cx |
| 56 | movw %cx,%es /* access segment 0 */ |
| 57 | movw $0x78, %bx /* 0:bx is parameter table address */ |
| 58 | pushw %ds /* save ds */ |
| 59 | /* 0:bx is parameter table address */ |
| 60 | ldsw %es:(%bx),%si /* loads ds and si */ |
| 61 | |
| 62 | movw %ax,%es /* ax is BOOTSECT (loaded above) */ |
| 63 | movb $6, %cl /* copy 12 bytes */ |
| 64 | cld |
| 65 | pushw %di /* keep a copy for later */ |
| 66 | rep |
| 67 | movsw /* ds:si is source, es:di is dest */ |
| 68 | popw %di |
| 69 | |
| 70 | movb $36,%es:4(%di) |
| 71 | |
| 72 | movw %cx,%ds /* access segment 0 */ |
| 73 | xchgw %di,(%bx) |
| 74 | movw %es,%si |
| 75 | xchgw %si,2(%bx) |
| 76 | popw %ds /* restore ds */ |
| 77 | movw %di, dpoff /* save old parameters */ |
| 78 | movw %si, dpseg /* to restore just before finishing */ |
| 79 | pushw %ds |
| 80 | popw %es /* reload es */ |
| 81 | |
| 82 | /* Note that es is already set up. Also cx is 0 from rep movsw above. */ |
| 83 | |
| 84 | xorb %ah,%ah /* reset FDC */ |
| 85 | xorb %dl,%dl |
| 86 | int $0x13 |
| 87 | |
| 88 | /* Get disk drive parameters, specifically number of sectors/track. |
| 89 | * |
| 90 | * It seems that there is no BIOS call to get the number of sectors. Guess |
| 91 | * 36 sectors if sector 36 can be read, 18 sectors if sector 18 can be read, |
| 92 | * 15 if sector 15 can be read. Otherwise guess 9. |
| 93 | */ |
| 94 | |
| 95 | movw $disksizes, %si /* table of sizes to try */ |
| 96 | |
| 97 | probe_loop: |
| 98 | lodsb |
| 99 | cbtw /* extend to word */ |
| 100 | movw %ax, sectors |
| 101 | cmpw $disksizes+4, %si |
| 102 | jae got_sectors /* if all else fails, try 9 */ |
| 103 | xchgw %cx,%ax /* cx = track and sector */ |
| 104 | xorw %dx,%dx /* drive 0, head 0 */ |
| 105 | movw $0x0200, %bx /* address after boot sector */ |
| 106 | /* (512 bytes from origin, es = cs) */ |
| 107 | movw $0x0201, %ax /* service 2, 1 sector */ |
| 108 | int $0x13 |
| 109 | jc probe_loop /* try next value */ |
| 110 | |
| 111 | got_sectors: |
| 112 | movw $msg1end-msg1, %cx |
| 113 | movw $msg1, %si |
| 114 | call print_str |
| 115 | |
| 116 | /* ok, we've written the Loading... message, now we want to load the system */ |
| 117 | |
| 118 | movw $SYSSEG, %ax |
| 119 | movw %ax,%es /* segment of SYSSEG<<4 */ |
| 120 | pushw %es |
| 121 | call read_it |
| 122 | |
| 123 | /* This turns off the floppy drive motor, so that we enter the kernel in a |
| 124 | * known state, and don't have to worry about it later. |
| 125 | */ |
| 126 | movw $0x3f2, %dx |
| 127 | xorb %al,%al |
| 128 | outb %al,%dx |
| 129 | |
| 130 | call print_nl |
| 131 | pop %es /* = SYSSEG */ |
| 132 | |
| 133 | /* Restore original disk parameters */ |
| 134 | movw $0x78, %bx |
| 135 | movw dpoff, %di |
| 136 | movw dpseg, %si |
| 137 | xorw %ax,%ax |
| 138 | movw %ax,%ds |
| 139 | movw %di,(%bx) |
| 140 | movw %si,2(%bx) |
| 141 | |
| 142 | /* Everything now loaded. %es = SYSSEG, so %es:0000 points to |
| 143 | * start of loaded image. |
| 144 | */ |
| 145 | |
| 146 | /* Jump to loaded copy */ |
| 147 | ljmp $SYSSEG, $start_runtime |
| 148 | |
| 149 | endseg: .word SYSSEG |
| 150 | .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */ |
| 151 | .ascii "ADDW" |
| 152 | .long endseg |
| 153 | .long 16 |
| 154 | .long 0 |
| 155 | .previous |
| 156 | |
| 157 | /* This routine loads the system at address SYSSEG<<4, making sure no 64kB |
| 158 | * boundaries are crossed. We try to load it as fast as possible, loading whole |
| 159 | * tracks whenever we can. |
| 160 | * |
| 161 | * in: es - starting address segment (normally SYSSEG) |
| 162 | */ |
| 163 | read_it: |
| 164 | movw $0,sread /* load whole image including prefix */ |
| 165 | movw %es,%ax |
| 166 | testw $0x0fff, %ax |
| 167 | die: jne die /* es must be at 64kB boundary */ |
| 168 | xorw %bx,%bx /* bx is starting address within segment */ |
| 169 | rp_read: |
| 170 | movw %es,%ax |
| 171 | movw %bx,%dx |
| 172 | movb $4, %cl |
| 173 | shrw %cl,%dx /* bx is always divisible by 16 */ |
| 174 | addw %dx,%ax |
| 175 | cmpw endseg, %ax /* have we loaded all yet? */ |
| 176 | jb ok1_read |
| 177 | ret |
| 178 | ok1_read: |
| 179 | movw sectors, %ax |
| 180 | subw sread, %ax |
| 181 | movw %ax,%cx |
| 182 | shlw $9, %cx |
| 183 | addw %bx,%cx |
| 184 | jnc ok2_read |
| 185 | je ok2_read |
| 186 | xorw %ax,%ax |
| 187 | subw %bx,%ax |
| 188 | shrw $9, %ax |
| 189 | ok2_read: |
| 190 | call read_track |
| 191 | movw %ax,%cx |
| 192 | addw sread, %ax |
| 193 | cmpw sectors, %ax |
| 194 | jne ok3_read |
| 195 | movw $1, %ax |
| 196 | subw head, %ax |
| 197 | jne ok4_read |
| 198 | incw track |
| 199 | ok4_read: |
| 200 | movw %ax, head |
| 201 | xorw %ax,%ax |
| 202 | ok3_read: |
| 203 | movw %ax, sread |
| 204 | shlw $9, %cx |
| 205 | addw %cx,%bx |
| 206 | jnc rp_read |
| 207 | movw %es,%ax |
| 208 | addb $0x10, %ah |
| 209 | movw %ax,%es |
| 210 | xorw %bx,%bx |
| 211 | jmp rp_read |
| 212 | |
| 213 | read_track: |
| 214 | pusha |
| 215 | pushw %ax |
| 216 | pushw %bx |
| 217 | pushw %bp /* just in case the BIOS is buggy */ |
| 218 | movw $0x0e2e, %ax /* 0x2e = . */ |
| 219 | movw $0x0007, %bx |
| 220 | int $0x10 |
| 221 | popw %bp |
| 222 | popw %bx |
| 223 | popw %ax |
| 224 | |
| 225 | movw track, %dx |
| 226 | movw sread, %cx |
| 227 | incw %cx |
| 228 | movb %dl,%ch |
| 229 | movw head, %dx |
| 230 | movb %dl,%dh |
| 231 | andw $0x0100, %dx |
| 232 | movb $2, %ah |
| 233 | |
| 234 | pushw %dx /* save for error dump */ |
| 235 | pushw %cx |
| 236 | pushw %bx |
| 237 | pushw %ax |
| 238 | |
| 239 | int $0x13 |
| 240 | jc bad_rt |
| 241 | addw $8, %sp |
| 242 | popa |
| 243 | ret |
| 244 | |
| 245 | bad_rt: pushw %ax /* save error code */ |
| 246 | call print_all /* ah = error, al = read */ |
| 247 | |
| 248 | xorb %ah,%ah |
| 249 | xorb %dl,%dl |
| 250 | int $0x13 |
| 251 | |
| 252 | addw $10, %sp |
| 253 | popa |
| 254 | jmp read_track |
| 255 | |
| 256 | /* print_all is for debugging purposes. It will print out all of the registers. |
| 257 | * The assumption is that this is called from a routine, with a stack frame like |
| 258 | * dx |
| 259 | * cx |
| 260 | * bx |
| 261 | * ax |
| 262 | * error |
| 263 | * ret <- sp |
| 264 | */ |
| 265 | |
| 266 | print_all: |
| 267 | call print_nl /* nl for readability */ |
| 268 | movw $5, %cx /* error code + 4 registers */ |
| 269 | movw %sp,%bp |
| 270 | |
| 271 | print_loop: |
| 272 | pushw %cx /* save count left */ |
| 273 | |
| 274 | cmpb $5, %cl |
| 275 | jae no_reg /* see if register name is needed */ |
| 276 | |
| 277 | movw $0x0007, %bx /* page 0, attribute 7 (normal) */ |
| 278 | movw $0xe05+0x41-1, %ax |
| 279 | subb %cl,%al |
| 280 | int $0x10 |
| 281 | |
| 282 | movb $0x58, %al /* 'X' */ |
| 283 | int $0x10 |
| 284 | |
| 285 | movb $0x3A, %al /* ':' */ |
| 286 | int $0x10 |
| 287 | |
| 288 | no_reg: |
| 289 | addw $2, %bp /* next register */ |
| 290 | call print_hex /* print it */ |
| 291 | movb $0x20, %al /* print a space */ |
| 292 | int $0x10 |
| 293 | popw %cx |
| 294 | loop print_loop |
| 295 | call print_nl /* nl for readability */ |
| 296 | ret |
| 297 | |
| 298 | print_str: |
| 299 | movw $0x0007, %bx /* page 0, attribute 7 (normal) */ |
| 300 | movb $0x0e, %ah /* write char, tty mode */ |
| 301 | prloop: |
| 302 | lodsb |
| 303 | int $0x10 |
| 304 | loop prloop |
| 305 | ret |
| 306 | |
| 307 | print_nl: |
| 308 | movw $0x0007, %bx /* page 0, attribute 7 (normal) */ |
| 309 | movw $0xe0d, %ax /* CR */ |
| 310 | int $0x10 |
| 311 | movb $0xa, %al /* LF */ |
| 312 | int $0x10 |
| 313 | ret |
| 314 | |
| 315 | /* print_hex prints the word pointed to by ss:bp in hexadecimal. */ |
| 316 | |
| 317 | print_hex: |
| 318 | movw (%bp),%dx /* load word into dx */ |
| 319 | movb $4, %cl |
| 320 | movb $0x0e, %ah /* write char, tty mode */ |
| 321 | movw $0x0007, %bx /* page 0, attribute 7 (normal) */ |
| 322 | call print_digit |
| 323 | call print_digit |
| 324 | call print_digit |
| 325 | /* fall through */ |
| 326 | print_digit: |
| 327 | rol %cl,%dx /* rotate so that lowest 4 bits are used */ |
| 328 | movb $0x0f, %al /* mask for nybble */ |
| 329 | andb %dl,%al |
| 330 | addb $0x90, %al /* convert al to ascii hex (four instructions) */ |
| 331 | daa |
| 332 | adcb $0x40, %al |
| 333 | daa |
| 334 | int $0x10 |
| 335 | ret |
| 336 | |
| 337 | sread: .word 0 /* sectors read of current track */ |
| 338 | head: .word 0 /* current head */ |
| 339 | track: .word 0 /* current track */ |
| 340 | |
| 341 | sectors: |
| 342 | .word 0 |
| 343 | |
| 344 | dpseg: .word 0 |
| 345 | dpoff: .word 0 |
| 346 | |
| 347 | disksizes: |
| 348 | .byte 36,18,15,9 |
| 349 | |
| 350 | msg1: |
| 351 | .ascii "Loading ROM image" |
| 352 | msg1end: |
| 353 | |
| 354 | .org 510, 0 |
| 355 | .word 0xAA55 |
| 356 | |
| 357 | start_runtime: |
| 358 | /* Install gPXE */ |
| 359 | call install |
| 360 | |
| 361 | /* Set up real-mode stack */ |
| 362 | movw %bx, %ss |
| 363 | movw $_estack16, %sp |
| 364 | |
| 365 | /* Jump to .text16 segment */ |
| 366 | pushw %ax |
| 367 | pushw $1f |
| 368 | lret |
| 369 | .section ".text16", "awx", @progbits |
| 370 | 1: |
| 371 | pushl $main |
| 372 | pushw %cs |
| 373 | call prot_call |
| 374 | popl %ecx /* discard */ |
| 375 | |
| 376 | /* Uninstall gPXE */ |
| 377 | call uninstall |
| 378 | |
| 379 | /* Boot next device */ |
| 380 | int $0x18 |
| 381 | |