2 * Copyright (c) 1998 Robert Nordier
5 * Redistribution and use in source and binary forms are freely
6 * permitted provided that the above copyright notice and this
7 * paragraph and the following disclaimer are duplicated in all
10 * This software is provided "AS IS" and without any express or
11 * implied warranties, including, without limitation, the implied
12 * warranties of merchantability and fitness for a particular
18 /* A 1024-byte boot manager. */
20 .set NHRDRV,0x475 # Number of hard drives
21 .set ORIGIN,0x600 # Execution address
22 .set SECTOR_SIZE,0x200 # Length of a sector
23 .set NUM_SECTORS,2 # Total length in sectors
25 .set FAKE,ORIGIN+(SECTOR_SIZE*NUM_SECTORS) # Partition entry
26 .set LOAD,0x7c00 # Load address
28 .set PRT_OFF,0x1be # Partition table
30 .set TBL0SZ,table0_end-table0 # Table 0 size
31 .set TBL1SZ,table1_end-table1 # Table 1 size
33 .set MAGIC,0xaa55 # Magic: bootable
35 .set KEY_ENTER,0x1c # Enter key scan code
36 .set KEY_F1,0x3b # F1 key scan code
37 .set KEY_1,0x02 # #1 key scan code
42 .set FL_PACKET,0x80 # Packet mode
43 .set FL_NOUPDATE,0x40 # Don't save selection
44 .set FL_SETDRV,0x20 # Override drive number
46 * Addresses in the sector of embedded data values.
47 * Accessed with negative offsets from the end of the relocated sectors (%bp).
49 .set _PRT_END,(FAKE-(ORIGIN+SECTOR_SIZE*(NUM_SECTORS-1)))
50 .set _NXTDRV,-(_PRT_END+0x48) # Next drive
51 .set _OPT,-(_PRT_END+0x47) # Default option
52 .set _SETDRV,-(_PRT_END+0x46) # Drive to force
53 .set _FLAGS,-(_PRT_END+0x45) # Flags
54 .set _TICKS,-(_PRT_END+0x44) # Timeout ticks
55 .set _FAKE,0x0 # Fake partition entry
56 .set _MNUOPT,0xc # Menu options
58 .globl start # Entry point
59 .code16 # This runs in real mode
62 * Initialise segments and registers to known values.
63 * segments start at 0.
64 * The stack is immediately below the address we were loaded to.
66 * Note that this section of code is used as the first signature check in
69 start: cld # String ops inc
71 movw %ax,%es # Address
74 movw $LOAD,%sp # stack
80 * Set address for variable space beyond code, and clear it.
81 * Notice that this is also used to point to the values embedded in the block,
82 * by using negative offsets.
84 movw $fake,%bp # Address variables
85 movw %bp,%di # %di used in stosw
86 movw $0x8,%cx # Words to clear
89 incb -0xe(%di) # Sector number 1
91 * Check what flags were loaded with us; specifically, use a predefined Drive.
92 * If what the bios gives us is bad, use the '0' in the block instead, as well.
94 testb $FL_SETDRV,LOAD+flags-start # Set number drive?
96 testb %dl,%dl # Drive number valid?
97 js boot.2 # Possibly (0x80 set)
98 boot.1: movb LOAD+setdrv-start,%dl # Drive number to use
101 * Reload all of boot0 (including the extra sectors) into memory at the
102 * relocation address.
104 push %dx # Save drive number
105 movw $start,%bx # Origin we were linked for
106 movw %bp,%si # Fake PTE
107 movw $0x200+NUM_SECTORS,%ax # Read in all
108 callw intx13 # of boot0
111 * Relocate to the new copy of the code.
113 jmp main+ORIGIN-LOAD # To relocated code
115 * Whatever we decided to use, now store it into the fake
116 * partition entry that lives in the data space above us.
118 main: movb %dl,_FAKE(%bp) # Save drive number
119 callw putn # To new line
120 pushw %dx # Save drive number
122 * Start out with a pointer to the 4th byte of the first table entry
123 * so that after 4 iterations it's beyond the end of the sector.
124 * and beyond a 256 byte boundary and has overflowed 8 bits (see next comment).
125 * (remember that the table starts 2 bytes earlier than you would expect
126 * as the bootable flag is after it in the block)
128 movw $(partbl+0x4),%bx # Partition table (+4)
129 xorw %dx,%dx # Item number
131 * Loop around on the partition table, printing values until we
132 * pass a 256 byte boundary. The end of loop test is at main.5.
134 main.3: movb %ch,-0x4(%bx) # Zero active flag (ch == 0)
135 btw %dx,_FLAGS(%bp) # Entry enabled?
138 * If any of the entries in the table are
139 * the same as the 'type' in the slice table entry,
140 * then this is an empty or non bootable partition. Skip it.
142 movb (%bx),%al # Load type
143 movw $tables,%di # Lookup tables
144 movb $TBL0SZ,%cl # Number of entries
149 * Now scan the table of known types
151 movb $TBL1SZ,%cl # Number of entries
156 * If it matches get the matching element in the
157 * next array. If it doesn't, we are already
158 * pointing at its first element which points to a "?".
160 addw $TBL1SZ,%di # Adjust
161 main.4: movb (%di),%cl # Partition
162 addw %cx,%di # description
163 callw putx # Display it
164 main.5: incw %dx # Next item
165 addb $0x10,%bl # Next entry
166 jnc main.3 # Till done
168 * Passed a 256 byte boundary..
170 * Add one to the drive number and check it is valid,
172 popw %ax # Drive number
173 subb $0x80-0x1,%al # Does next
174 cmpb NHRDRV,%al # drive exist? (from BIOS?)
177 * If not then if there is only one drive,
178 * Don't display drive as an option.
180 decw %ax # Already drive 0?
183 * If it was illegal or we cycled through them,
184 * then go back to drive 0.
186 xorb %al,%al # Drive 0
188 * Whatever drive we selected, make it an ascii digit and save it back
189 * to the "next drive" location in the loaded block in case we
190 * want to save it for next time.
191 * This also is part of the printed drive string so add 0x80 to indicate
194 main.6: addb $'0'|0x80,%al # Save next
195 movb %al,_NXTDRV(%bp) # drive number
196 movw $drive,%di # Display
199 * Now that we've printed the drive (if we needed to), display a prompt.
200 * Get ready for the input by noting the time.
202 main.7: movw $prompt,%si # Display
203 callw putstr # prompt
204 movb _OPT(%bp),%dl # Display
207 xorb %ah,%ah # BIOS: Get
208 int $0x1a # system time
209 movw %dx,%di # Ticks when
210 addw _TICKS(%bp),%di # timeout
212 * Busy loop, looking for keystrokes but
213 * keeping one eye on the time.
215 main.8: movb $0x1,%ah # BIOS: Check
216 int $0x16 # for keypress
217 jnz main.11 # Have one
218 xorb %ah,%ah # BIOS: Get
219 int $0x1a # system time
220 cmpw %di,%dx # Timeout?
223 * If timed out or defaulting, come here.
225 main.9: movb _OPT(%bp),%al # Load default
226 jmp main.12 # Join common code
228 * User's last try was bad, beep in displeasure.
229 * Since nothing was printed, just continue on as if the user
230 * hadn't done anything. This gives the effect of the user getting a beep
231 * for all bad keystrokes but no action until either the timeout
232 * occurs or the user hits a good key.
234 main.10: movb $0x7,%al # Signal
239 main.11: xorb %ah,%ah # BIOS: Get
241 movb %ah,%al # Scan code
243 * If it's CR act as if timed out.
245 cmpb $KEY_ENTER,%al # Enter pressed?
248 * Otherwise check if legal
251 subb $KEY_F1,%al # Less F1 scan code
252 cmpb $0x4,%al # F1..F5?
254 subb $(KEY_1 - KEY_F1),%al # Less #1 scan code
255 cmpb $0x4,%al # #1..#5?
258 * We have a selection.
259 * but if it's a bad selection go back to complain.
260 * The bits in MNUOPT were set when the options were printed.
261 * Anything not printed is not an option.
263 main.12: cbtw # Option
264 btw %ax,_MNUOPT(%bp) # enabled?
267 * Save the info in the original tables
268 * for rewriting to the disk.
270 movb %al,_OPT(%bp) # Save option
271 movw $fake,%si # Partition for write
272 movb (%si),%dl # Drive number
273 movw %si,%bx # Partition for read
274 cmpb $0x4,%al # F5/#5 pressed?
277 shlb $0x4,%al # Point to
278 addw $partbl,%ax # selected
279 xchgw %bx,%ax # partition
280 movb $0x80,(%bx) # Flag active
282 * If not asked to do a write-back (flags 0x40) don't do one.
284 main.13: pushw %bx # Save
285 testb $FL_NOUPDATE,_FLAGS(%bp) # Skip update?
287 movw $start,%bx # Data to write
288 movw $0x301,%ax # Write 1 sector
289 callw intx13 # to disk
290 main.14: popw %si # Restore
293 * If going to next drive, replace drive with selected one.
294 * Remember to un-ascii it. Hey 0x80 is already set, cool!
296 jne main.15 # If not F5/#5
297 movb _NXTDRV(%bp),%dl # Next drive
298 subb $'0',%dl # number
300 * load selected bootsector to the LOAD location in RAM.
301 * If it fails to read or isn't marked bootable, treat it
302 * as a bad selection.
304 main.15: movw $LOAD,%bx # Address for read
305 movw $0x201,%ax # Read 1 sector
306 callw intx13 # from disk
307 jc main.10 # If error
308 cmpw $MAGIC,0x1fe(%bx) # Bootable?
310 callw putn # Leave some space
311 jmp *%bx # Invoke bootstrap
317 putkey: movb $'F',%al # Display
319 movb $'1',%al # Prepare
321 jmp putstr.1 # Display the rest
324 * Display the option and note that it is a valid option.
325 * That last point is a bit tricky..
327 putx: btsw %dx,_MNUOPT(%bp) # Enable menu option
328 movw $item,%si # Display
330 movw %di,%si # Display the rest
332 puts: callw putstr # Display string
334 putn: movw $crlf,%si # To next line
336 putstr: lodsb # Get byte
337 testb $0x80,%al # End of string?
339 putstr.1: callw putchr # Display char
340 jmp putstr # Continue
341 putstr.2: andb $~0x80,%al # Clear MSB
343 putchr: pushw %bx # Save
344 movw $0x7,%bx # Page:attribute
345 movb $0xe,%ah # BIOS: Display
346 int $0x10 # character
351 * One-sector disk I/O routine
353 * Calling conventions: (assumes %si -> partition table entry)
355 * 0x1(%si) - byte - head
356 * 0x2(%si) - word - cylinder/sector
357 * 0x8(%si) - long - LBA to use if needed
358 * %ah - byte - operation, 2 = read, 3 = write
359 * %al - byte - sector count
360 * %dl - byte - drive number
361 * %es:(%bx) - void - buffer to use for transfer
363 * If the head == 0xff, and cylinder/sector == 0xffff, then try
364 * to use the EDD stuff with the LBA offset instead of CHS. However,
365 * use CHS if at all possible.
368 intx13: movb 0x1(%si),%dh # Load head
369 movw 0x2(%si),%cx # Load cylinder:sector
372 cmpb $0xff,%dh # Might we need LBA?
373 jne intx13.2 # No, just use CHS
374 cmpw $0xffff,%cx # Do we need LBA?
376 testb $FL_PACKET,_FLAGS(%bp) # Try the packet interface?
380 movw $0x55aa,%bx # Magic
382 movb $0x41,%ah # BIOS: EDD extensions
385 jc intx13.1 # Not present, use CHS
386 cmpw $0xaa55,%bx # Magic?
387 jne intx13.1 # Not present, use CHS
388 testb $0x1,%cl # Packet mode available?
389 jz intx13.1 # No, use CHS
390 orb $0x40,%ah # Use disk packet
391 intx13.1: popw %bx # Restore
393 testb $0x40,%ah # Using packet mode?
394 jz intx13.2 # No, so skip the rest
396 pushl 0x8(%si) # LBA address
397 pushw %es # Set the transfer
398 pushw %bx # buffer address
399 push $0x0 # Punch a hole in the stack
400 push $0x10 # Packet size
401 movw %sp,%si # Packet pointer
402 xchgb %al,0x2(%si) # Set the block count in the
403 # packet and zero %al,
404 # turning verify off for writes
405 intx13.2: int $0x13 # BIOS: Disk I/O
406 movw %di,%sp # Restore
410 .org PRT_OFF-0xe,0x90
412 * These values are sometimes changed before writing back to the drive
413 * Be especially careful that nxtdrv: must come after drive:, as it
414 * is part of the same string.
416 * Note that the 'drive' string variable is used as the second signature
417 * check in boot0cfg(8).
419 version_minor: .byte 0x1 # minor version
420 version_major: .byte 0x1 # major version
421 drive: .ascii "Drive "
422 nxtdrv: .byte 0x0 # Next drive number
423 opt: .byte 0x0 # Option
424 setdrv: .byte 0x80 # Drive to force
425 flags: .byte FLAGS # Flags
426 ticks: .word TICKS # Delay
429 * here is the 64 byte partition table that fdisk would fiddle with.
431 partbl: .fill 0x40,0x1,0x0 # Partition table
432 .word MAGIC # Magic number
435 * start of sector two.. ugh
437 .org SECTOR_SIZE,0x90
441 item: .ascii " "; .byte ' '|0x80
442 prompt: .ascii "\nDefault:"; .byte ' '|0x80
443 crlf: .ascii "\r"; .byte '\n'|0x80
445 /* Partition type tables */
449 * These entries identify invalid or NON BOOT types and partitions.
451 table0: .byte 0x0, 0x5, 0xf
454 * These values indicate bootable types we know the names of
456 table1: .byte 0x1, 0x4, 0x6, 0x7, 0xb, 0xc, 0xe, 0x42, 0x63, 0x83
457 .byte 0x9f, 0xa5, 0xa6, 0xa9
460 * These are offsets that match the known names above and point to the strings
461 * that will be printed.
463 .byte os_misc-. # Unknown
467 .byte os_nt-. # NT/XP or OS/2
468 .byte os_windows-. # Windows 32-bit FAT
469 .byte os_windows-. # Windows 32-bit FAT ext int 13
470 .byte os_windows-. # Windows
471 .byte os_windows-. # Windows 2000 dyn ext
472 .byte os_unix-. # UNIX
473 .byte os_linux-. # Linux
474 .byte os_bsdos-. # BSD/OS
475 .byte os_freebsd-. # FreeBSD
476 .byte os_openbsd-. # OpenBSD
477 .byte os_netbsd-. # NetBSD
479 * And here are the strings themselves. 0x80 or'd into a byte indicates
480 * the end of the string. (not so great for Russians but...)
482 os_misc: .ascii "Unknow"; .byte 'n'|0x80
483 os_dos: .ascii "DO"; .byte 'S'|0x80
484 os_nt: .ascii "Windows NT/X"; .byte 'P'|0x80
485 os_windows: .ascii "Window"; .byte 's'|0x80
486 os_unix: .ascii "UNI"; .byte 'X'|0x80
487 os_linux: .ascii "Linu"; .byte 'x'|0x80
488 os_freebsd: .ascii "FreeBS"; .byte 'D'|0x80
489 os_openbsd: .ascii "OpenBS"; .byte 'D'|0x80
490 os_netbsd: .ascii "NetBS"; .byte 'D'|0x80
491 os_bsdos: .ascii "BSD/O"; .byte 'S'|0x80
494 * Fake partition entry created at the end of the table used when loading
495 * boot0 at the very beginning and when loading an MBR from another disk when
498 .org SECTOR_SIZE*NUM_SECTORS, 0x0