]> git.saurik.com Git - apple/boot.git/blob - i386/boot0/boot0.s
af31d8daebb5d228fbdfd5464c1ba1e0001e32a9
[apple/boot.git] / i386 / boot0 / boot0.s
1 ; Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
2 ;
3 ; @APPLE_LICENSE_HEADER_START@
4 ;
5 ; Portions Copyright (c) 1999-2002 Apple Computer, Inc. All Rights
6 ; Reserved. This file contains Original Code and/or Modifications of
7 ; Original Code as defined in and that are subject to the Apple Public
8 ; Source License Version 1.2 (the "License"). You may not use this file
9 ; except in compliance with the License. Please obtain a copy of the
10 ; License at http://www.apple.com/publicsource and read it before using
11 ; this file.
12 ;
13 ; The Original Code and all software distributed under the License are
14 ; distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 ; EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 ; INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 ; FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
18 ; License for the specific language governing rights and limitations
19 ; under the License.
20 ;
21 ; @APPLE_LICENSE_HEADER_END@
22 ;
23 ; Boot Loader: boot0
24 ;
25 ; A small boot sector program written in x86 assembly whose only
26 ; responsibility is to locate the active partition, load the
27 ; partition booter into memory, and jump to the booter's entry point.
28 ; It leaves the boot drive in DL and a pointer to the partition entry in SI.
29 ;
30 ; This boot loader must be placed in the Master Boot Record.
31 ;
32 ; In order to coexist with a fdisk partition table (64 bytes), and
33 ; leave room for a two byte signature (0xAA55) in the end, boot0 is
34 ; restricted to 446 bytes (512 - 64 - 2). If boot0 did not have to
35 ; live in the MBR, then we would have 510 bytes to work with.
36 ;
37 ; boot0 is always loaded by the BIOS or another booter to 0:7C00h.
38 ;
39 ; This code is written for the NASM assembler.
40 ; nasm boot0.s -o boot0
41
42
43 ;
44 ; Set to 1 to enable obscure debug messages.
45 ;
46 DEBUG EQU 0
47
48 ;
49 ; Set to 1 to support loading the partition booter (boot1) from a
50 ; logical partition.
51 ;
52 EXT_PART_SUPPORT EQU 1
53
54 ;
55 ; Various constants.
56 ;
57 kBoot0Segment EQU 0x0000
58 kBoot0Stack EQU 0xFFF0 ; boot0 stack pointer
59 kBoot0LoadAddr EQU 0x7C00 ; boot0 load address
60 kBoot0RelocAddr EQU 0xE000 ; boot0 relocated address
61
62 kMBRBuffer EQU 0x1000 ; MBR buffer address
63 kExtBuffer EQU 0x1200 ; EXT boot block buffer address
64
65 kPartTableOffset EQU 0x1be
66 kMBRPartTable EQU kMBRBuffer + kPartTableOffset
67 kExtPartTable EQU kExtBuffer + kPartTableOffset
68
69 kSectorBytes EQU 512 ; sector size in bytes
70 kBootSignature EQU 0xAA55 ; boot sector signature
71
72 kPartCount EQU 4 ; number of paritions per table
73 kPartTypeBoot EQU 0xab ; boot2 partition type
74 kPartTypeExtDOS EQU 0x05 ; DOS extended partition type
75 kPartTypeExtWin EQU 0x0f ; Windows extended partition type
76 kPartTypeExtLinux EQU 0x85 ; Linux extended partition type
77
78 kPartActive EQU 0x80
79
80 %ifdef FLOPPY
81 kDriveNumber EQU 0x00
82 %else
83 kDriveNumber EQU 0x80
84 %endif
85
86 ;
87 ; Format of fdisk partition entry.
88 ;
89 ; The symbol 'part_size' is automatically defined as an `EQU'
90 ; giving the size of the structure.
91 ;
92 struc part
93 .bootid: resb 1 ; bootable or not
94 .head: resb 1 ; starting head, sector, cylinder
95 .sect: resb 1 ;
96 .cyl: resb 1 ;
97 .type: resb 1 ; partition type
98 .endhead resb 1 ; ending head, sector, cylinder
99 .endsect: resb 1 ;
100 .endcyl: resb 1 ;
101 .lba: resd 1 ; starting lba
102 .sectors resd 1 ; size in sectors
103 endstruc
104
105 ;
106 ; Macros.
107 ;
108 %macro DebugCharMacro 1
109 mov al, %1
110 call print_char
111 %endmacro
112
113 %if DEBUG
114 %define DebugChar(x) DebugCharMacro x
115 %else
116 %define DebugChar(x)
117 %endif
118
119 ;--------------------------------------------------------------------------
120 ; Start of text segment.
121
122 SEGMENT .text
123
124 ORG 0xE000 ; must match kBoot0RelocAddr
125
126 ;--------------------------------------------------------------------------
127 ; Boot code is loaded at 0:7C00h.
128 ;
129 start
130 ;
131 ; Set up the stack to grow down from kBoot0Segment:kBoot0Stack.
132 ; Interrupts should be off while the stack is being manipulated.
133 ;
134 cli ; interrupts off
135 xor ax, ax ; zero ax
136 mov ss, ax ; ss <- 0
137 mov sp, kBoot0Stack ; sp <- top of stack
138 sti ; reenable interrupts
139
140 mov es, ax ; es <- 0
141 mov ds, ax ; ds <- 0
142
143 ;
144 ; Relocate boot0 code.
145 ;
146 mov si, kBoot0LoadAddr ; si <- source
147 mov di, kBoot0RelocAddr ; di <- destination
148 ;
149 cld ; auto-increment SI and/or DI registers
150 mov cx, kSectorBytes/2 ; copy 256 words
151 repnz movsw ; repeat string move (word) operation
152
153 ; Code relocated, jump to start_reloc in relocated location.
154 ;
155 jmp 0:start_reloc
156
157 ;--------------------------------------------------------------------------
158 ; Start execution from the relocated location.
159 ;
160 start_reloc:
161
162 DebugChar('>')
163
164 mov dl, kDriveNumber ; starting BIOS drive number
165
166 .loop:
167
168 %if DEBUG
169 mov al, dl
170 call print_hex
171 %endif
172
173 ;
174 ; Clear various flags in memory.
175 ;
176 xor eax, eax
177 mov [ebios_lba], eax ; clear EBIOS LBA offset
178 mov [ebios_present], al ; clear EBIOS support flag
179
180 ;
181 ; Check if EBIOS is supported for this hard drive.
182 ;
183 mov ah, 0x41 ; Function 0x41
184 mov bx, 0x55AA ; check signature
185 ; mov dl, kDriveNumber ; Drive number
186 int 0x13
187
188 ;
189 ; If successful, the return values are as follows:
190 ;
191 ; carry = 0
192 ; ah = major version of EBIOS extensions (0x21 = version 1.1)
193 ; al = altered
194 ; bx = 0xAA55
195 ; cx = support bits. bit 0 must be set for function 0x42.
196 ;
197 jc .ebios_check_done
198 cmp bx, 0xAA55 ; check BX = 0xAA55
199 jnz .ebios_check_done
200 test cl, 0x01 ; check enhanced drive read support
201 setnz [ebios_present] ; EBIOS supported, set flag
202 DebugChar('E') ; EBIOS supported
203 .ebios_check_done:
204
205 ;
206 ; Since this code may not always reside in the MBR, always start by
207 ; loading the MBR to kMBRBuffer.
208 ;
209 mov al, 1 ; load one sector
210 xor bx, bx
211 mov es, bx ; MBR load segment = 0
212 mov bx, kMBRBuffer ; MBR load address
213 mov si, bx ; pointer to fake partition entry
214 mov WORD [si], 0x0000 ; CHS DX: head = 0
215 mov WORD [si + 2], 0x0001 ; CHS CX: cylinder = 0, sector = 1
216
217 call load
218 jc .next_drive ; MBR load error
219
220 ;
221 ; Look for the booter partition in the MBR partition table,
222 ; which is at offset kMBRPartTable.
223 ;
224 mov di, kMBRPartTable ; pointer to partition table
225 mov ah, 0 ; initial nesting level is 0
226 call find_boot ; will not return on success
227
228 .next_drive:
229 inc dl ; next drive number
230 test dl, 0x4 ; went through all 4 drives?
231 jz .loop ; not yet, loop again
232
233 mov si, boot_error_str
234 call print_string
235
236 hang:
237 jmp SHORT hang
238
239 ;--------------------------------------------------------------------------
240 ; Find the active (boot) partition and load the booter from the partition.
241 ;
242 ; Arguments:
243 ; AH = recursion nesting level
244 ; DL = drive number (0x80 + unit number)
245 ; DI = pointer to fdisk partition table.
246 ;
247 ; Clobber list:
248 ; AX, BX, EBP
249 ;
250 find_boot:
251 push cx ; preserve CX and SI
252 push si
253
254 ;
255 ; Check for boot block signature 0xAA55 following the 4 partition
256 ; entries.
257 ;
258 cmp WORD [di + part_size * kPartCount], kBootSignature
259 jne .exit ; boot signature not found
260
261 mov si, di ; make SI a pointer to partition table
262 mov cx, kPartCount ; number of partition entries per table
263
264 .loop:
265 ;
266 ; First scan through the partition table looking for the active
267 ; partition. Postpone walking the extended partition chain for
268 ; the second pass. Do not merge the two without changing the
269 ; buffering scheme used to store extended partition tables.
270 ;
271 %if DEBUG
272 mov al, ah ; indent based on nesting level
273 call print_spaces
274 mov al, [si + part.type] ; print partition type
275 call print_hex
276 %endif
277
278 cmp BYTE [si + part.bootid], kPartActive
279 jne .continue
280
281 DebugChar('*')
282
283 ;
284 ; Found boot partition, read boot sector to memory.
285 ;
286 mov al, 1
287 mov bx, kBoot0Segment
288 mov es, bx
289 mov bx, kBoot0LoadAddr
290 call load ; will not return on success
291 jc .continue ; load error, keep looking?
292
293 ;
294 ; Fix up absolute block location in partition record.
295 ;
296 mov eax, [si + part.lba]
297 add eax, [ebios_lba]
298 mov [si + part.lba], eax
299
300 ;
301 ; Jump to partition booter. The drive number is already in register DL.
302 ; SI is pointing to the modified partition entry.
303 ;
304 jmp kBoot0Segment:kBoot0LoadAddr
305
306 .continue:
307 add si, part_size ; advance SI to next partition entry
308 loop .loop ; loop through all partition entries
309
310 %if EXT_PART_SUPPORT
311 ;
312 ; No primary (or logical) boot partition found in the current
313 ; partition table. Restart and look for extended partitions.
314 ;
315 mov si, di ; make SI a pointer to partition table
316 mov cx, kPartCount ; number of partition entries per table
317
318 .ext_loop:
319
320 mov al, [si + part.type] ; AL <- partition type
321
322 cmp al, kPartTypeExtDOS ; Extended DOS
323 je .ext_load
324
325 cmp al, kPartTypeExtWin ; Extended Windows(95)
326 je .ext_load
327
328 cmp al, kPartTypeExtLinux ; Extended Linux
329 je .ext_load
330
331 .ext_continue:
332 ;
333 ; Advance si to the next partition entry in the extended
334 ; partition table.
335 ;
336 add si, part_size ; advance SI to next partition entry
337 loop .ext_loop ; loop through all partition entries
338 jmp .exit ; boot partition not found
339
340 .ext_load:
341 ;
342 ; Setup the arguments for the load function call to bring the
343 ; extended partition table into memory.
344 ; Remember that SI points to the extended partition entry.
345 ;
346 mov al, 1 ; read 1 sector
347 xor bx, bx
348 mov es, bx ; es = 0
349 mov bx, kExtBuffer ; load extended boot sector
350 call load
351 jc .ext_continue ; load error
352
353 ;
354 ; The LBA address of all extended partitions is relative based
355 ; on the LBA address of the extended partition in the MBR, or
356 ; the extended partition at the head of the chain. Thus it is
357 ; necessary to save the LBA address of the first extended partition.
358 ;
359 or ah, ah
360 jnz .ext_find_boot
361 mov ebp, [si + part.lba]
362 mov [ebios_lba], ebp
363
364 .ext_find_boot:
365 ;
366 ; Call find_boot recursively to scan through the extended partition
367 ; table. Load DI with a pointer to the extended table in memory.
368 ;
369 inc ah ; increment recursion level
370 mov di, kExtPartTable ; partition table pointer
371 call find_boot ; recursion...
372 ;dec ah
373
374 ;
375 ; Since there is an "unwritten" rule that limits each partition table
376 ; to have 0 or 1 extended partitions, there is no point in looking for
377 ; any additional extended partition entries at this point. There is no
378 ; boot partition linked beyond the extended partition that was loaded
379 ; above.
380 ;
381
382 %endif ; EXT_PART_SUPPORT
383
384 .exit:
385 ;
386 ; Boot partition not found. Giving up.
387 ;
388 pop si
389 pop cx
390 ret
391
392 ;--------------------------------------------------------------------------
393 ; load - Load one or more sectors from a partition.
394 ;
395 ; Arguments:
396 ; AL = number of 512-byte sectors to read.
397 ; ES:BX = pointer to where the sectors should be stored.
398 ; DL = drive number (0x80 + unit number)
399 ; SI = pointer to the partition entry.
400 ;
401 ; Returns:
402 ; CF = 0 success
403 ; 1 error
404 ;
405 load:
406 push cx
407 test BYTE [ebios_present], 1
408 jz .chs
409
410 .ebios:
411 mov cx, 5 ; load retry count
412 .ebios_loop:
413 call read_lba ; use INT13/F42
414 jnc .exit
415 loop .ebios_loop
416
417 .chs:
418 mov cx, 5 ; load retry count
419 .chs_loop:
420 call read_chs ; use INT13/F2
421 jnc .exit
422 loop .chs_loop
423
424 .exit
425 pop cx
426 ret
427
428 ;--------------------------------------------------------------------------
429 ; read_chs - Read sectors from a partition using CHS addressing.
430 ;
431 ; Arguments:
432 ; AL = number of 512-byte sectors to read.
433 ; ES:BX = pointer to where the sectors should be stored.
434 ; DL = drive number (0x80 + unit number)
435 ; SI = pointer to the partition entry.
436 ;
437 ; Returns:
438 ; CF = 0 success
439 ; 1 error
440 ;
441 read_chs:
442 pusha ; save all registers
443
444 ;
445 ; Read the CHS start values from the partition entry.
446 ;
447 mov dh, [ si + part.head ] ; drive head
448 mov cx, [ si + part.sect ] ; drive sector + cylinder
449
450 ;
451 ; INT13 Func 2 - Read Disk Sectors
452 ;
453 ; Arguments:
454 ; AH = 2
455 ; AL = number of sectors to read
456 ; CH = lowest 8 bits of the 10-bit cylinder number
457 ; CL = bits 6 & 7: cylinder number bits 8 and 9
458 ; bits 0 - 5: starting sector number (1-63)
459 ; DH = starting head number (0 to 255)
460 ; DL = drive number (80h + drive unit)
461 ; es:bx = pointer where to place sectors read from disk
462 ;
463 ; Returns:
464 ; AH = return status (sucess is 0)
465 ; AL = burst error length if ah=0x11 (ECC corrected)
466 ; carry = 0 success
467 ; 1 error
468 ;
469 ; mov dl, kDriveNumber
470 mov ah, 0x02 ; Func 2
471 int 0x13 ; INT 13
472 jnc .exit
473
474 DebugChar('r') ; indicate INT13/F2 error
475
476 ;
477 ; Issue a disk reset on error.
478 ; Should this be changed to Func 0xD to skip the diskette controller
479 ; reset?
480 ;
481 xor ax, ax ; Func 0
482 int 0x13 ; INT 13
483 stc ; set carry to indicate error
484
485 .exit:
486 popa
487 ret
488
489 ;--------------------------------------------------------------------------
490 ; read_lba - Read sectors from a partition using LBA addressing.
491 ;
492 ; Arguments:
493 ; AL = number of 512-byte sectors to read (valid from 1-127).
494 ; ES:BX = pointer to where the sectors should be stored.
495 ; DL = drive number (0x80 + unit number)
496 ; SI = pointer to the partition entry.
497 ;
498 ; Returns:
499 ; CF = 0 success
500 ; 1 error
501 ;
502 read_lba:
503 pusha ; save all registers
504 mov bp, sp ; save current SP
505
506 ;
507 ; Create the Disk Address Packet structure for the
508 ; INT13/F42 (Extended Read Sectors) on the stack.
509 ;
510
511 ; push DWORD 0 ; offset 12, upper 32-bit LBA
512 push ds ; For sake of saving memory,
513 push ds ; push DS register, which is 0.
514
515 mov ecx, [ebios_lba] ; offset 8, lower 32-bit LBA
516 add ecx, [si + part.lba]
517 push ecx
518
519 push es ; offset 6, memory segment
520
521 push bx ; offset 4, memory offset
522
523 xor ah, ah ; offset 3, must be 0
524 push ax ; offset 2, number of sectors
525
526 push WORD 16 ; offset 0-1, packet size
527
528 ;
529 ; INT13 Func 42 - Extended Read Sectors
530 ;
531 ; Arguments:
532 ; AH = 0x42
533 ; DL = drive number (80h + drive unit)
534 ; DS:SI = pointer to Disk Address Packet
535 ;
536 ; Returns:
537 ; AH = return status (sucess is 0)
538 ; carry = 0 success
539 ; 1 error
540 ;
541 ; Packet offset 2 indicates the number of sectors read
542 ; successfully.
543 ;
544 ; mov dl, kDriveNumber
545 mov si, sp
546 mov ah, 0x42
547 int 0x13
548
549 jnc .exit
550
551 DebugChar('R') ; indicate INT13/F42 error
552
553 ;
554 ; Issue a disk reset on error.
555 ; Should this be changed to Func 0xD to skip the diskette controller
556 ; reset?
557 ;
558 xor ax, ax ; Func 0
559 int 0x13 ; INT 13
560 stc ; set carry to indicate error
561
562 .exit:
563 mov sp, bp ; restore SP
564 popa
565 ret
566
567 ;--------------------------------------------------------------------------
568 ; Write a string to the console.
569 ;
570 ; Arguments:
571 ; DS:SI pointer to a NULL terminated string.
572 ;
573 ; Clobber list:
574 ; AX, BX, SI
575 ;
576 print_string
577 mov bx, 1 ; BH=0, BL=1 (blue)
578 cld ; increment SI after each lodsb call
579 .loop
580 lodsb ; load a byte from DS:SI into AL
581 cmp al, 0 ; Is it a NULL?
582 je .exit ; yes, all done
583 mov ah, 0xE ; INT10 Func 0xE
584 int 0x10 ; display byte in tty mode
585 jmp short .loop
586 .exit
587 ret
588
589
590 %if DEBUG
591
592 ;--------------------------------------------------------------------------
593 ; Write a ASCII character to the console.
594 ;
595 ; Arguments:
596 ; AL = ASCII character.
597 ;
598 print_char
599 pusha
600 mov bx, 1 ; BH=0, BL=1 (blue)
601 mov ah, 0x0e ; bios INT 10, Function 0xE
602 int 0x10 ; display byte in tty mode
603 popa
604 ret
605
606 ;--------------------------------------------------------------------------
607 ; Write a variable number of spaces to the console.
608 ;
609 ; Arguments:
610 ; AL = number to spaces.
611 ;
612 print_spaces:
613 pusha
614 xor cx, cx
615 mov cl, al ; use CX as the loop counter
616 mov al, ' ' ; character to print
617 .loop:
618 jcxz .exit
619 call print_char
620 loop .loop
621 .exit:
622 popa
623 ret
624
625 ;--------------------------------------------------------------------------
626 ; Write the byte value to the console in hex.
627 ;
628 ; Arguments:
629 ; AL = Value to be displayed in hex.
630 ;
631 print_hex:
632 push ax
633 ror al, 4
634 call print_nibble ; display upper nibble
635 pop ax
636 call print_nibble ; display lower nibble
637
638 mov al, 10 ; carriage return
639 call print_char
640 mov al, 13
641 call print_char
642 ret
643
644 print_nibble:
645 and al, 0x0f
646 add al, '0'
647 cmp al, '9'
648 jna .print_ascii
649 add al, 'A' - '9' - 1
650 .print_ascii:
651 call print_char
652 ret
653
654 getc:
655 pusha
656 mov ah, 0
657 int 0x16
658 popa
659 ret
660
661 %endif ; DEBUG
662
663 ;--------------------------------------------------------------------------
664 ; NULL terminated strings.
665 ;
666 boot_error_str db 10, 13, 'Error', 0
667
668 ;--------------------------------------------------------------------------
669 ; Pad the rest of the 512 byte sized booter with zeroes. The last
670 ; two bytes is the mandatory boot sector signature.
671 ;
672 ; If the booter code becomes too large, then nasm will complain
673 ; that the 'times' argument is negative.
674
675 ;
676 ; In memory variables.
677 ;
678 ebios_lba dd 0 ; starting LBA of the intial extended partition.
679 ebios_present db 0 ; 1 if EBIOS is supported, 0 otherwise.
680
681 pad_boot
682 times 446-($-$$) db 0
683
684 %ifdef FLOPPY
685 ;--------------------------------------------------------------------------
686 ; Put fake partition entries for the bootable floppy image
687 ;
688 part1bootid db 0x80 ; first partition active
689 part1head db 0x00 ; head #
690 part1sect db 0x02 ; sector # (low 6 bits)
691 part1cyl db 0x00 ; cylinder # (+ high 2 bits of above)
692 part1systid db 0xab ; Apple boot partition
693 times 3 db 0x00 ; ignore head/cyl/sect #'s
694 part1relsect dd 0x00000001 ; start at sector 1
695 part1numsect dd 0x00000080 ; 64K for booter
696 part2bootid db 0x00 ; not active
697 times 3 db 0x00 ; ignore head/cyl/sect #'s
698 part2systid db 0xa8 ; Apple UFS partition
699 times 3 db 0x00 ; ignore head/cyl/sect #'s
700 part2relsect dd 0x00000082 ; start after booter
701 ; part2numsect dd 0x00000abe ; 1.44MB - 65K
702 part2numsect dd 0x000015fe ; 2.88MB - 65K
703 %endif
704
705 pad_table_and_sig
706 times 510-($-$$) db 0
707 dw kBootSignature
708
709 END