CloverBootloader/BootHFS/boot1f32alt.s

667 lines
15 KiB
ArmAsm
Raw Permalink Normal View History

; Copyright (c) 1999-2003 Apple Computer, Inc. All rights reserved.
;
; @APPLE_LICENSE_HEADER_START@
;
; Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
; Reserved. This file contains Original Code and/or Modifications of
; Original Code as defined in and that are subject to the Apple Public
; Source License Version 2.0 (the "License"). You may not use this file
; except in compliance with the License. Please obtain a copy of the
; License at http://www.apple.com/publicsource and read it before using
; this file.
;
; The Original Code and all software distributed under the License are
; distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
; EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
; INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
; License for the specific language governing rights and limitations
; under the License.
;
; @APPLE_LICENSE_HEADER_END@
;
; Partition Boot Loader: boot1f32
;
; This program is designed to reside in sector 0 of a FAT32 partition.
; It expects that the MBR has left the drive number in DL
; and a pointer to the partition entry in SI.
;
; This version requires a BIOS with EBIOS (LBA) support.
;
; This code is written for the NASM assembler.
; nasm boot1f32.s -o boot1f32
;
; dd if=origbs of=newbs skip=3 seek=3 bs=1 count=87 conv=notrunc
;
;
; This version of boot1f32 tries to find a stage2 boot file in the root folder.
;
; Written by mackerintel on 2009-01-26
;
;
; Set to 1 to enable obscure debug messages.
;
DEBUG EQU 0
;
; Set to 1 to enable unused code.
;
UNUSED EQU 0
;
; Set to 1 to enable verbose mode.
;
VERBOSE EQU 0
;
; Set to 1 to make this stage 1 loader expecting arguments in SI and DL registers.
;
USESIDL EQU 1
;
; Various constants.
;
NULL EQU 0
CR EQU 0x0D
LF EQU 0x0A
maxSectorCount EQU 64 ; maximum sector count for readSectors
kSectorBytes EQU 512 ; sector size in bytes
kBootSignature EQU 0xAA55 ; boot sector signature
kBoot1StackAddress EQU 0xFFF0 ; boot1 stack pointer
kBoot1LoadAddr EQU 0x7C00 ; boot1 load address
kBoot1RelocAddr EQU 0xE000 ; boot1 relocated address
kBoot2Sectors EQU (480 * 1024 - 512) / kSectorBytes ; max size of 'boot' file in sectors
kBoot2Segment EQU 0x2000 ; boot2 load segment
kBoot2Address EQU kSectorBytes ; boot2 load address
FATBUF EQU 0x7000 ; Just place for one sectors
DIRBUFSEG EQU 0x1000 ; Cluster sizes >64KB aren't supported
;
; Format of fdisk partition entry.
;
; The symbol 'part_size' is automatically defined as an `EQU'
; giving the size of the structure.
;
struc part
.bootid resb 1 ; bootable or not
.head resb 1 ; starting head, sector, cylinder
.sect resb 1 ;
.cyl resb 1 ;
.type resb 1 ; partition type
.endhead resb 1 ; ending head, sector, cylinder
.endsect resb 1 ;
.endcyl resb 1 ;
.lba resd 1 ; starting lba
.sectors resd 1 ; size in sectors
endstruc
struc direntry
.nameext resb 11
.attr resb 1
.nused1 resb 8
.highclus resw 1
.nused2 resb 4
.lowclus resw 1
.size resd 1
endstruc
;
; Macros.
;
%macro jmpabs 1
push WORD %1
ret
%endmacro
%macro DebugCharMacro 1
pushad
mov al, %1
call print_char
call getc
popad
%endmacro
%macro PrintCharMacro 1
pushad
mov al, %1
call print_char
popad
%endmacro
%macro PutCharMacro 1
call print_char
%endmacro
%macro PrintHexMacro 1
call print_hex
%endmacro
%macro PrintString 1
mov si, %1
call print_string
%endmacro
%macro LogString 1
mov di, %1
call log_string
%endmacro
%if DEBUG
%define DebugChar(x) DebugCharMacro x
%define PrintChar(x) PrintCharMacro x
%define PutChar(x) PutCharMacro
%define PrintHex(x) PrintHexMacro x
%else
%define DebugChar(x)
%define PrintChar(x)
%define PutChar(x)
%define PrintHex(x)
%endif
;--------------------------------------------------------------------------
; Start of text segment.
SEGMENT .text
ORG kBoot1LoadAddr
jmp start
times 3-($-$$) nop
gOEMName times 8 db 0 ;OEMNAME
gBPS dw 0
gSPC db 0
gReservedSectors dw 0
gNumFats db 0
gCrap1 times 11 db 0
gPartLBA dd 0
gPartSize dd 0
gSectPerFat dd 0
gCrap2 times 4 db 0
gRootCluster dd 0
gCrap3 times 16 db 0
gBIOSDriveNumber db 0
gExtInfo times 25 db 0
gFileName db "BOOT " ; Used as a magic string in boot0
;--------------------------------------------------------------------------
; Boot code is loaded at 0:7C00h.
;
start:
;
; set up the stack to grow down from kBoot1StackSegment:kBoot1StackAddress.
; Interrupts should be off while the stack is being manipulated.
;
cli ; interrupts off
xor eax, eax ; zero ax
mov ss, ax ; ss <- 0
mov sp, kBoot1StackAddress ; sp <- top of stack
sti ; reenable interrupts
mov ds, ax ; ds <- 0
mov es, ax ; es <- 0
;
; Initializing global variables.
;
mov ax, word [gReservedSectors]
%if USESIDL
add eax, [si + part.lba]
%else
add eax, [gPartLBA]
%endif
mov [gPartLBA], eax ; save the current FAT LBA offset
%if USESIDL
mov [gBIOSDriveNumber], dl ; save BIOS drive number
%endif
xor eax,eax
mov al, [gNumFats]
mul dword [gSectPerFat]
mov [gSectPerFat], eax
;--------------------------------------------------------------------------
; Find stage2 boot file in a FAT32 Volume's root folder.
;
findRootBoot:
setBootFile:
mov cx, 2000 ; loop counter = max 2000 miliseconds in total
.loop:
mov ah, 0x01 ; int 0x16, Func 0x01 - get keyboard status/preview key
int 0x16
jz .wait ; no keypress - wait and loop again
xor ah, ah ; read the char from buffer to spend it
int 0x16
; have a key - ASCII is in al - put it to file name /boot<pressed key>
mov BYTE [gFileName + 4], al
jmp SHORT .bootFileSet ; try to boot
.wait:
; waith for 1 ms: int 0x15, Func 0x86 (wait for cx:dx microseconds)
push cx ; save loop counter
xor cx, cx
mov dx, 1000
mov ah, 0x86
int 0x15
pop cx ; restore loop counter
loop .loop
; no keypress so far
; change filename to /boot by putting space as 5th char
; and try to load
; mov BYTE [gFileName + 4], ' '
.bootFileSet:
%if VERBOSE
LogString(init_str)
%endif
mov eax, [gRootCluster]
nextdirclus:
mov edx, DIRBUFSEG<<4
call readCluster
jc error
xor si, si
mov bl, [gSPC]
shl bx, 9
add bx, si
nextdirent:
mov di, gFileName
push ds
push DIRBUFSEG
pop ds
mov cl, [si]
test cl, cl
jz dserror
mov cx, 11
repe cmpsb
jz direntfound
falsealert:
pop ds
add cl, 21
add si, cx
cmp si, bx
jz nextdirclus
jmp nextdirent
direntfound:
; test byte [ds:si+direntry.attr-11], 0x18
lodsb
test al, 0x18
jnz falsealert
push WORD [si + direntry.highclus - 12]
push WORD [si + direntry.lowclus - 12]
pop eax
pop ds
mov edx, (kBoot2Segment << 4) + kBoot2Address
cont_read:
push edx
call readCluster
pop edx
pushf
xor ebx,ebx
mov bl, [gSPC]
shl ebx, 9
add edx, ebx
popf
jnc cont_read
boot2:
%if DEBUG
DebugChar ('!')
%endif
%if UNUSED
;
; Waiting for a key press.
;
mov ah, 0
int 0x16
%endif
mov dl, [gBIOSDriveNumber] ; load BIOS drive number
jmp kBoot2Segment:kBoot2Address
dserror:
pop ds
error:
%if VERBOSE
LogString(error_str)
%endif
hang:
hlt
jmp hang
; readCluster - Reads cluster EAX to (EDX), updates EAX to next cluster
readCluster:
cmp eax, 0x0ffffff8
jb do_read
stc
ret
do_read:
push eax
xor ecx,ecx
dec eax
dec eax
mov cl, [gSPC]
push edx
mul ecx
pop edx
add eax, [gSectPerFat]
mov ecx, eax
xor ah,ah
mov al, [gSPC]
call readSectors
jc clusend
pop ecx
push cx
shr ecx, 7
xor ax, ax
inc ax
mov edx, FATBUF
call readSectors
jc clusend
pop si
and si, 0x7f
shl si, 2
mov eax, [FATBUF + si]
and eax, 0x0fffffff
clc
ret
clusend:
pop eax
ret
;--------------------------------------------------------------------------
; readSectors - Reads more than 127 sectors using LBA addressing.
;
; Arguments:
; AX = number of 512-byte sectors to read (valid from 1-1280).
; EDX = pointer to where the sectors should be stored.
; ECX = sector offset in partition
;
; Returns:
; CF = 0 success
; 1 error
;
readSectors:
pushad
mov bx, ax
.loop:
xor eax, eax ; EAX = 0
mov al, bl ; assume we reached the last block.
cmp bx, maxSectorCount ; check if we really reached the last block
jb .readBlock ; yes, BX < MaxSectorCount
mov al, maxSectorCount ; no, read MaxSectorCount
.readBlock:
call readLBA
sub bx, ax ; decrease remaning sectors with the read amount
jz .exit ; exit if no more sectors left to be loaded
add ecx, eax ; adjust LBA sector offset
shl ax, 9 ; convert sectors to bytes
add edx, eax ; adjust target memory location
jmp .loop ; read remaining sectors
.exit:
popad
ret
;--------------------------------------------------------------------------
; readLBA - Read sectors from a partition using LBA addressing.
;
; Arguments:
; AL = number of 512-byte sectors to read (valid from 1-127).
; EDX = pointer to where the sectors should be stored.
; ECX = sector offset in partition
; [bios_drive_number] = drive number (0x80 + unit number)
;
; Returns:
; CF = 0 success
; 1 error
;
readLBA:
pushad ; save all registers
push es ; save ES
mov bp, sp ; save current SP
;
; Convert EDX to segment:offset model and set ES:BX
;
; Some BIOSes do not like offset to be negative while reading
; from hard drives. This usually leads to "boot1: error" when trying
; to boot from hard drive, while booting normally from USB flash.
; The routines, responsible for this are apparently different.
; Thus we split linear address slightly differently for these
; capricious BIOSes to make sure offset is always positive.
;
mov bx, dx ; save offset to BX
and bh, 0x0f ; keep low 12 bits
shr edx, 4 ; adjust linear address to segment base
xor dl, dl ; mask low 8 bits
mov es, dx ; save segment to ES
;
; Create the Disk Address Packet structure for the
; INT13/F42 (Extended Read Sectors) on the stack.
;
; push DWORD 0 ; offset 12, upper 32-bit LBA
push ds ; For sake of saving memory,
push ds ; push DS register, which is 0.
add ecx, [gPartLBA] ; offset 8, lower 32-bit LBA
push ecx
push es ; offset 6, memory segment
push bx ; offset 4, memory offset
xor ah, ah ; offset 3, must be 0
push ax ; offset 2, number of sectors
push WORD 16 ; offset 0-1, packet size
;
; INT13 Func 42 - Extended Read Sectors
;
; Arguments:
; AH = 0x42
; [bios_drive_number] = drive number (0x80 + unit number)
; DS:SI = pointer to Disk Address Packet
;
; Returns:
; AH = return status (sucess is 0)
; carry = 0 success
; 1 error
;
; Packet offset 2 indicates the number of sectors read
; successfully.
;
mov dl, [gBIOSDriveNumber] ; load BIOS drive number
mov si, sp
mov ah, 0x42
int 0x13
jc error
;
; Issue a disk reset on error.
; Should this be changed to Func 0xD to skip the diskette controller
; reset?
;
; xor ax, ax ; Func 0
; int 0x13 ; INT 13
; stc ; set carry to indicate error
.exit:
mov sp, bp ; restore SP
pop es ; restore ES
popad
ret
%if VERBOSE
;--------------------------------------------------------------------------
; Write a string with 'boot1: ' prefix to the console.
;
; Arguments:
; ES:DI pointer to a NULL terminated string.
;
; Clobber list:
; DI
;
log_string:
pushad
push di
mov si, log_title_str
call print_string
pop si
call print_string
popad
ret
;-------------------------------------------------------------------------
; Write a string to the console.
;
; Arguments:
; DS:SI pointer to a NULL terminated string.
;
; Clobber list:
; AX, BX, SI
;
print_string:
mov bx, 1 ; BH=0, BL=1 (blue)
.loop:
lodsb ; load a byte from DS:SI into AL
cmp al, 0 ; Is it a NULL?
je .exit ; yes, all done
mov ah, 0xE ; INT10 Func 0xE
int 0x10 ; display byte in tty mode
jmp .loop
.exit:
ret
%endif ; VERBOSE
%if DEBUG
;--------------------------------------------------------------------------
; Write the 4-byte value to the console in hex.
;
; Arguments:
; EAX = Value to be displayed in hex.
;
print_hex:
pushad
mov cx, WORD 4
bswap eax
.loop:
push ax
ror al, 4
call print_nibble ; display upper nibble
pop ax
call print_nibble ; display lower nibble
ror eax, 8
loop .loop
%if UNUSED
mov al, 10 ; carriage return
call print_char
mov al, 13
call print_char
%endif ; UNUSED
popad
ret
print_nibble:
and al, 0x0f
add al, '0'
cmp al, '9'
jna .print_ascii
add al, 'A' - '9' - 1
.print_ascii:
call print_char
ret
;--------------------------------------------------------------------------
; getc - wait for a key press
;
getc:
pushad
mov ah, 0
int 0x16
popad
ret
;--------------------------------------------------------------------------
; Write a ASCII character to the console.
;
; Arguments:
; AL = ASCII character.
;
print_char:
pushad
mov bx, 1 ; BH=0, BL=1 (blue)
mov ah, 0x0e ; bios INT 10, Function 0xE
int 0x10 ; display byte in tty mode
popad
ret
%endif ; DEBUG
;--------------------------------------------------------------------------
; Static data.
;
%if VERBOSE
log_title_str db CR, LF, 'b1f: ', NULL
init_str db 'init', NULL
error_str db 'error', NULL
%endif
;--------------------------------------------------------------------------
; Pad the rest of the 512 byte sized sector with zeroes. The last
; two bytes is the mandatory boot sector signature.
;
; If the booter code becomes too large, then nasm will complain
; that the 'times' argument is negative.
pad_table_and_sig:
times 510-($-$$) db 0
dw kBootSignature
ABSOLUTE kBoot1LoadAddr + kSectorBytes
; END