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