1 /* $OpenBSD: cdbr.S,v 1.2 2004/08/24 15:24:05 tom Exp $ */
2
3 /*
4 * Copyright (c) 2004 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
5 * Copyright (c) 2001 John Baldwin <jhb@FreeBSD.org>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the author nor the names of any co-contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 .file "cdbr.S"
34
35 /* #include <machine/asm.h> */
36 /* #include <assym.h> */
37
38 /*
39 * This program is a CD boot sector, similar to the partition boot record
40 * (pbr, also called biosboot) used by hard disks. It is implemented as a
41 * "no-emulation" boot sector, as described in the "El Torito" Bootable
42 * CD-ROM Format Specification.
43 *
44 * The function of this boot sector is to load and start the next-stage
45 * cdboot program, which will load the kernel.
46 *
47 * The El Torito standard allows us to specify where we want to be loaded,
48 * but for maximum compatibility we choose the default load address of
49 * 0x07C00.
50 *
51 * Memory layout:
52 *
53 * 0x00000 -> 0x003FF real mode interrupt vector table
54 * 0x00400 -> 0x00500 BIOS data segment
55 *
56 * 0x00000 -> 0x073FF our stack (grows down) (from 29k)
57 * 0x07400 -> 0x07BFF we relocate to here (at 29k)
58 * 0x07C00 -> 0x08400 BIOS loads us here (at 31k, for 2k)
59 * 0x07C00 -> ... /cdboot
60 *
61 * The BIOS loads us at physical address 0x07C00. We then relocate to
62 * 0x07400, seg:offset 0740:0000. We then load /cdboot at seg:offset
63 * 07C0:0000.
64 */
65 #define BOOTSEG 0x7c0 /* segment we're loaded to */
66 #define BOOTSECTSIZE 0x800 /* our size in bytes */
67 #define BOOTRELOCSEG 0x740 /* segment we relocate to */
68 #define BOOTSTACKOFF ((BOOTRELOCSEG << 4) - 4) /* starts here, grows down */
69
70 /* Constants for reading from the CD */
71 #define ERROR_TIMEOUT 0x80 /* BIOS timeout on read */
72 #define NUM_RETRIES 3 /* Num times to retry */
73 #define SECTOR_SIZE 0x800 /* size of a sector */
74 #define SECTOR_SHIFT 11 /* number of place to shift */
75 #define BUFFER_LEN 0x100 /* number of sectors in buffr */
76 #define MAX_READ 0x10000 /* max we can read at a time */
77 #define MAX_READ_PARAS MAX_READ >> 4
78 #define MAX_READ_SEC MAX_READ >> SECTOR_SHIFT
79 #define MEM_READ_BUFFER 0x9000 /* buffer to read from CD */
80 #define MEM_VOLDESC MEM_READ_BUFFER /* volume descriptor */
81 #define MEM_DIR MEM_VOLDESC+SECTOR_SIZE /* Lookup buffer */
82 #define VOLDESC_LBA 0x10 /* LBA of vol descriptor */
83 #define VD_PRIMARY 1 /* Primary VD */
84 #define VD_END 255 /* VD Terminator */
85 #define VD_ROOTDIR 156 /* Offset of Root Dir Record */
86 #define DIR_LEN 0 /* Offset of Dir Rec length */
87 #define DIR_EA_LEN 1 /* Offset of EA length */
88 #define DIR_EXTENT 2 /* Offset of 64-bit LBA */
89 #define DIR_SIZE 10 /* Offset of 64-bit length */
90 #define DIR_NAMELEN 32 /* Offset of 8-bit name len */
91 #define DIR_NAME 33 /* Offset of dir name */
92
93 .text
94 .code16
95
96 .globl start
97 start:
98 /* Set up stack */
99 xorw %ax, %ax
100 movw %ax, %ss
101 movw $BOOTSTACKOFF, %sp
102
103 /* Relocate so we can load cdboot where we were */
104 movw $BOOTSEG, %ax
105 movw %ax, %ds
106 movw $BOOTRELOCSEG, %ax
107 movw %ax, %es
108 xorw %si, %si
109 xorw %di, %di
110 movw $BOOTSECTSIZE, %cx /* Bytes in cdbr, relocate it all */
111 cld
112 rep
113 movsb
114
115 /* Jump to relocated self */
116 ljmp $BOOTRELOCSEG, $reloc
117 reloc:
118
119 /*
120 * Set up %ds and %es: %ds is our data segment (= %cs), %es is
121 * used to specify the segment address of the destination buffer
122 * for cd reads. We initially have %es = %ds.
123 */
124 movw %cs, %ax
125 movw %ax, %ds
126 movw %ax, %es
127
128 movb %dl, drive /* Store the boot drive number */
129
130 movw $signon, %si /* Say "hi", and give boot drive */
131 call display_string
132 movb drive, %al
133 call hex_byte
134 movw $crlf, %si
135 call display_string
136
137 /*
138 * Load Volume Descriptor
139 */
140 movl $VOLDESC_LBA, %eax /* Get the sector # for vol desc */
141 load_vd:
142 pushl %eax
143 movb $1, %dh /* One sector */
144 movw $MEM_VOLDESC, %bx /* Destination */
145 call read /* Read it in */
146 popl %eax
147 cmpb $VD_PRIMARY, (%bx) /* Primary vol descriptor? */
148 je have_vd /* Yes */
149 inc %eax /* Try the next one */
150 cmpb $VD_END, (%bx) /* Is it the last one? */
151 jne load_vd /* No, so go try the next one */
152 movw $msg_novd, %si /* No pri vol descriptor */
153 jmp err_stop /* Panic */
154 have_vd: /* Have Primary VD */
155
156 /*
157 * Look for the next-stage loader binary at pre-defined paths (loader_paths)
158 */
159 movw $loader_paths, %si /* Point to start of array */
160 lookup_path:
161 movw %si, loader /* remember the one we're looking for */
162 pushw %si /* Save file name pointer */
163 call lookup /* Try to find file */
164 popw %di /* Restore file name pointer */
165 jnc lookup_found /* Found this file */
166 xorb %al, %al /* Look for next */
167 movw $0xffff, %cx /* path name by */
168 repnz /* scanning for */
169 scasb /* nul char */
170 movw %di, %si /* Point %si at next path */
171 movb (%si), %al /* Get first char of next path */
172 orb %al, %al /* Is it double nul? */
173 jnz lookup_path /* No, try it */
174 movw $msg_failed, %si /* Failed message */
175 jmp err_stop /* Print it and halt */
176
177 lookup_found: /* Found a loader file */
178
179 /*
180 * Load the binary into the buffer. Due to real mode addressing limitations
181 * we have to read it in in 64k chunks.
182 */
183 movl DIR_SIZE(%bx), %eax /* Read file length */
184 add $SECTOR_SIZE-1, %eax /* Convert length to sectors */
185 shr $SECTOR_SHIFT, %eax
186 cmp $BUFFER_LEN, %eax
187 jbe load_sizeok
188 movw $msg_load2big, %si /* Error message */
189 jmp err_stop
190 load_sizeok:
191 movzbw %al, %cx /* Num sectors to read */
192 movl DIR_EXTENT(%bx), %eax /* Load extent */
193 xorl %edx, %edx
194 movb DIR_EA_LEN(%bx), %dl
195 addl %edx, %eax /* Skip extended */
196
197 /* Use %bx to hold the segment (para) number */
198 movw $BOOTSEG, %bx /* We put cdboot here too */
199 load_loop:
200 movb %cl, %dh
201 cmpb $MAX_READ_SEC, %cl /* Truncate to max read size */
202 jbe load_notrunc
203 movb $MAX_READ_SEC, %dh
204 load_notrunc:
205 subb %dh, %cl /* Update count */
206 pushl %eax /* Save */
207 movw %bx, %es /* %bx had the segment (para) number */
208 xorw %bx, %bx /* %es:0000 for destination */
209 call read /* Read it in */
210 popl %eax /* Restore */
211 addl $MAX_READ_SEC, %eax /* Update LBA */
212 addw $MAX_READ_PARAS, %bx /* Update dest addr */
213 jcxz load_done /* Done? */
214 jmp load_loop /* Keep going */
215 load_done:
216
217 /* Now we can start the loaded program */
218
219 movw loader, %cx /* Tell cdboot where it is */
220 /* (Older versions of cdbr have */
221 /* %cx == 0 from the jcxz load_done) */
222 movb drive, %dl /* Get the boot drive number */
223 ljmp $BOOTSEG, $0 /* Go run cdboot */
224
225 /*
226 * Lookup the file in the path at [SI] from the root directory.
227 *
228 * Trashes: All but BX
229 * Returns: CF = 0 (success), BX = pointer to record
230 * CF = 1 (not found)
231 */
232 lookup:
233 movw $VD_ROOTDIR + MEM_VOLDESC, %bx /* Root directory record */
234
235 lookup_dir:
236 lodsb /* Get first char of path */
237 cmpb $0, %al /* Are we done? */
238 je lookup_done /* Yes */
239 cmpb $'/', %al /* Skip path separator */
240 je lookup_dir
241 decw %si /* Undo lodsb side effect */
242 call find_file /* Lookup first path item */
243 jnc lookup_dir /* Try next component */
244 ret
245 lookup_done:
246 movw $msg_loading, %si /* Success message - say which file */
247 call display_string
248 mov loader, %si
249 call display_string
250 mov $crlf, %si
251 call display_string
252 clc /* Clear carry */
253 ret
254
255 /*
256 * Lookup file at [SI] in directory whose record is at [BX].
257 *
258 * Trashes: All but returns
259 * Returns: CF = 0 (success), BX = pointer to record, SI = next path item
260 * CF = 1 (not found), SI = preserved
261 */
262 find_file:
263 mov DIR_EXTENT(%bx), %eax /* Load extent */
264 xor %edx, %edx
265 mov DIR_EA_LEN(%bx), %dl
266 add %edx, %eax /* Skip extended attributes */
267 mov %eax, rec_lba /* Save LBA */
268 mov DIR_SIZE(%bx), %eax /* Save size */
269 mov %eax, rec_size
270 xor %cl, %cl /* Zero length */
271 push %si /* Save */
272 ff_namelen:
273 inc %cl /* Update length */
274 lodsb /* Read char */
275 cmp $0, %al /* Nul? */
276 je ff_namedone /* Yes */
277 cmp $'/', %al /* Path separator? */
278 jnz ff_namelen /* No, keep going */
279 ff_namedone:
280 dec %cl /* Adjust length and save */
281 mov %cl, name_len
282 pop %si /* Restore */
283 ff_load:
284 mov rec_lba, %eax /* Load LBA */
285 mov $MEM_DIR, %ebx /* Address buffer */
286 mov $1, %dh /* One sector */
287 call read /* Read directory block */
288 incl rec_lba /* Update LBA to next block */
289 ff_scan:
290 mov %ebx, %edx /* Check for EOF */
291 sub $MEM_DIR, %edx
292 cmp %edx, rec_size
293 ja ff_scan_1
294 stc /* EOF reached */
295 ret
296 ff_scan_1:
297 cmpb $0, DIR_LEN(%bx) /* Last record in block? */
298 je ff_nextblock
299 push %si /* Save */
300 movzbw DIR_NAMELEN(%bx), %si /* Find end of string */
301 ff_checkver:
302 cmpb $'0', DIR_NAME-1(%bx,%si) /* Less than '0'? */
303 jb ff_checkver_1
304 cmpb $'9', DIR_NAME-1(%bx,%si) /* Greater than '9'? */
305 ja ff_checkver_1
306 dec %si /* Next char */
307 jnz ff_checkver
308 jmp ff_checklen /* All numbers in name, so */
309 /* no version */
310 ff_checkver_1:
311 movzbw DIR_NAMELEN(%bx), %cx
312 cmp %cx, %si /* Did we find any digits? */
313 je ff_checkdot /* No */
314 cmpb $';', DIR_NAME-1(%bx,%si) /* Check for semicolon */
315 jne ff_checkver_2
316 dec %si /* Skip semicolon */
317 mov %si, %cx
318 mov %cl, DIR_NAMELEN(%bx) /* Adjust length */
319 jmp ff_checkdot
320 ff_checkver_2:
321 mov %cx, %si /* Restore %si to end of string */
322 ff_checkdot:
323 cmpb $'.', DIR_NAME-1(%bx,%si) /* Trailing dot? */
324 jne ff_checklen /* No */
325 decb DIR_NAMELEN(%bx) /* Adjust length */
326 ff_checklen:
327 pop %si /* Restore */
328 movzbw name_len, %cx /* Load length of name */
329 cmp %cl, DIR_NAMELEN(%bx) /* Does length match? */
330 je ff_checkname /* Yes, check name */
331 ff_nextrec:
332 add DIR_LEN(%bx), %bl /* Next record */
333 adc $0, %bh
334 jmp ff_scan
335 ff_nextblock:
336 subl $SECTOR_SIZE, rec_size /* Adjust size */
337 jnc ff_load /* If subtract ok, keep going */
338 ret /* End of file, so not found */
339 ff_checkname:
340 lea DIR_NAME(%bx), %di /* Address name in record */
341 push %si /* Save */
342 repe cmpsb /* Compare name */
343 jcxz ff_match /* We have a winner! */
344 pop %si /* Restore */
345 jmp ff_nextrec /* Keep looking */
346 ff_match:
347 add $2, %sp /* Discard saved %si */
348 clc /* Clear carry */
349 ret
350
351 /*
352 * Load DH sectors starting at LBA %eax into address %es:%bx.
353 *
354 * Preserves %bx, %cx, %dx, %si, %es
355 * Trashes %eax
356 */
357 read:
358 pushw %si /* Save */
359 pushw %cx /* Save since some BIOSs trash */
360 movl %eax, edd_lba /* LBA to read from */
361 movw %es, %ax /* Get the segment */
362 movw %ax, edd_addr + 2 /* and store */
363 movw %bx, edd_addr /* Store offset too */
364 read_retry:
365 call twiddle /* Entertain the user */
366 pushw %dx /* Save */
367 movw $edd_packet, %si /* Address Packet */
368 movb %dh, edd_len /* Set length */
369 movb drive, %dl /* BIOS Device */
370 movb $0x42, %ah /* BIOS: Extended Read */
371 int $0x13 /* Call BIOS */
372 popw %dx /* Restore */
373 jc read_fail /* Worked? */
374 popw %cx /* Restore */
375 popw %si
376 ret /* Return */
377 read_fail:
378 cmpb $ERROR_TIMEOUT, %ah /* Timeout? */
379 je read_retry /* Yes, Retry */
380 read_error:
381 pushw %ax /* Save error */
382 movw $msg_badread, %si /* "Read error: 0x" */
383 call display_string
384 popw %ax /* Retrieve error code */
385 movb %ah, %al /* Into %al */
386 call hex_byte /* Display error code */
387 jmp stay_stopped /* ... then hang */
388
389 /*
390 * Display the ASCIZ error message in %esi then halt
391 */
392 err_stop:
393 call display_string
394
395 stay_stopped:
396 sti /* Ensure Ctl-Alt-Del will work */
397 hlt /* (don't require power cycle) */
398 jmp stay_stopped /* (Just to make sure) */
399
400 /*
401 * Output the "twiddle"
402 */
403 twiddle:
404 push %ax /* Save */
405 push %bx /* Save */
406 mov twiddle_index, %al /* Load index */
407 mov twiddle_chars, %bx /* Address table */
408 inc %al /* Next */
409 and $3, %al /* char */
410 mov %al, twiddle_index /* Save index for next call */
411 xlat /* Get char */
412 call display_char /* Output it */
413 mov $8, %al /* Backspace */
414 call display_char /* Output it */
415 pop %bx /* Restore */
416 pop %ax /* Restore */
417 ret
418
419 /*
420 * Display the ASCIZ string pointed to by %si.
421 *
422 * Destroys %si, possibly others.
423 */
424 display_string:
425 pushw %ax
426 cld
427 1:
428 lodsb /* %al = *%si++ */
429 testb %al, %al
430 jz 1f
431 call display_char
432 jmp 1b
433
434 /*
435 * Write out value in %eax in hex
436 */
437 hex_long:
438 pushl %eax
439 shrl $16, %eax
440 call hex_word
441 popl %eax
442 /* fall thru */
443
444 /*
445 * Write out value in %ax in hex
446 */
447 hex_word:
448 pushw %ax
449 mov %ah, %al
450 call hex_byte
451 popw %ax
452 /* fall thru */
453 /*
454 * Write out value in %al in hex
455 */
456 hex_byte:
457 pushw %ax
458 shrb $4, %al
459 call hex_nibble
460 popw %ax
461 /* fall thru */
462
463 /* Write out nibble in %al */
464 hex_nibble:
465 and $0x0F, %al
466 add $'0', %al
467 cmpb $'9', %al
468 jbe display_char
469 addb $'A'-'9'-1, %al
470 /* fall thru to display_char */
471
472 /*
473 * Display the character in %al
474 */
475 display_char:
476 pushw %ax
477
478 pushw %bx
479 movb $0x0e, %ah
480 movw $1, %bx
481 int $0x10
482 popw %bx
483 1: popw %ax
484 ret
485
486 /*
487 * Data
488 */
489 drive: .byte 0 /* Given to us by the BIOS */
490 signon: .asciz "CD-ROM: "
491 crlf: .asciz "\r\n"
492 msg_load2big: .asciz "File too big"
493 msg_badread: .asciz "Read error: 0x"
494 msg_novd: .asciz "No Primary Volume Descriptor"
495 msg_loading: .asciz "Loading "
496
497 /* State for searching dir */
498 rec_lba: .long 0x0 /* LBA (adjusted for EA) */
499 rec_size: .long 0x0 /* File size */
500 name_len: .byte 0x0 /* Length of current name */
501
502 twiddle_index: .byte 0x0
503 twiddle_chars: .ascii "|/-\\"
504
505 /* Packet for LBA (CD) read */
506 edd_packet: .byte 0x10 /* Length */
507 .byte 0 /* Reserved */
508 edd_len: .byte 0x0 /* Num to read */
509 .byte 0 /* Reserved */
510 edd_addr: .word 0x0, 0x0 /* Seg:Off */
511 edd_lba: .quad 0x0 /* LBA */
512
513 /* The data from here must be last in the file, only followed by 0x00 bytes */
514
515 loader: .word 0 /* The path we end up using */
516
517 msg_failed: .ascii "Can't find " /* This string runs into... */
518
519 /* loader_paths is a list of ASCIZ strings followed by a term NUL byte */
520 loader_paths: .asciz "/cdboot"
521 .asciz "/CDBOOT"
522 .ascii "/", OSREV, "/", MACH, "/cdboot"
523 .byte 0 /* NUL-term line above */
524 .ascii "/", OSREV, "/", MACH_U, "/CDBOOT"
525 .byte 0 /* NUL-term line above */
526 .byte 0 /* Terminate the list */
527
528 . = BOOTSECTSIZE
529
530 .end