; Copyright (c) 2003 Apple Computer, Inc. All rights reserved. ; ; @APPLE_LICENSE_HEADER_START@ ; ; Portions Copyright (c) 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@ ; nasm cdboot.s -o cdboot ; ; This version of cdboot loads 256k of data. ; ; Modifications by Tamás Kosárszky on 2008-10-20 ; ; ; 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 1 ; ; Various constants. ; NULL EQU 0 CR EQU 0x0D LF EQU 0x0A ; ; Macros. ; %macro jmpabs 1 push WORD %1 ret %endmacro %macro DebugCharMacro 1 pushad mov al, %1 call print_char call getc popad %endmacro %macro DebugPauseMacro 0 push ax call getc pop ax %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 DebugPause(x) DebugPauseMacro %define PrintChar(x) PrintCharMacro x %define PutChar(x) PutCharMacro %define PrintHex(x) ;PrintHexMacro x %else %define DebugChar(x) %define DebugPause(x) %define PrintChar(x) %define PutChar(x) %define PrintHex(x) %endif maxSectorCount EQU 8 ; maximum sector count for readSectors CDBootSizeMagic EQU 0xDEADFACE ; indicates if the size field was not specificed ; at build time. kSectorBytes EQU 2048 ; sector size in bytes kBoot2Size EQU 65024 ; default load size for boot2 kBoot2MaxSize EQU (472*1024-512) ;458240 ; max size for boot2 kBoot2Address EQU 0x0200 ; boot2 load address kBoot2Segment EQU 0x2000 ; boot2 load segment kBoot0Stack EQU 0xFFF0 ; boot0 stack pointer kReadBuffer EQU 0x1000 ; disk data buffer address kVolSectorOffset EQU 0x47 ; offset in buffer of sector number ; in volume descriptor kBootSectorOffset EQU 0x28 ; offset in boot catalog ; of sector number to load boot file kBootCountOffset EQU 0x26 ; offset in boot catalog ; of number of sectors to read kCDBootSizeOffset EQU 2048 - 4 ; the file size can be found at the ; last dword of this sector. ;-------------------------------------------------------------------------- ; Start of text segment. SEGMENT .text ORG 0x7C00 ;-------------------------------------------------------------------------- ; Boot code is loaded at 0:7C00h. ; start: cli jmp 0:start1 times 8-($-$$) nop ; Put boot information table at offset 8 ; El Torito boot information table, filled in by the ; mkisofs -boot-info-table option, if used. bi_pvd: dd 0 ; LBA of primary volume descriptor bi_file: dd 0 ; LBA of boot file bi_length: dd 0 ; Length of boot file bi_csum: dd 0 ; Checksum of boot file bi_reserved: times 10 dd 0 ; Reserved start1: xor ax, ax ; zero %ax mov ss, ax ; setup the mov sp, kBoot0Stack ; stack sti cld ; increment SI after each lodsb call mov ds, ax ; setup the mov es, ax ; data segments %if VERBOSE LogString(init_str) %endif ;; BIOS boot drive is in DL mov [gBIOSDriveNumber], dl ; save BIOS drive number DebugChar('!') DebugPause() %if 0 ;DEBUG mov eax, [kBoot2LoadAddr] call print_hex call getc %endif ;; ;; The BIOS likely didn't load the rest of the booter, ;; so we have to fetch it ourselves. ;; mov edx, kReadBuffer mov al, 1 mov ecx, 17 call readLBA jc NEAR error DebugChar('A') mov ecx, [kReadBuffer + kVolSectorOffset] %if 0 ;DEBUG mov eax, ecx call print_hex DebugPause() %endif mov al, 1 call readLBA jc NEAR error ;; Now we have the boot catalog in the buffer. ;; Really we should look at the validation entry, but oh well. DebugChar('B') mov ecx, [kReadBuffer + kBootSectorOffset] mov al, 1 call readLBA ; reading this boot sector inc ecx ; skip the first sector which is what we are in %if 0 ;DEBUG mov eax, ecx call print_hex DebugPause() %endif ; ; Testing cdboot size ; mov eax, [kReadBuffer + kCDBootSizeOffset] or eax, eax jz .useDefaultSize ; use the default size if zero cmp eax, CDBootSizeMagic je .useDefaultSize ; use the default size if equals to magic cmp eax, kBoot2MaxSize jbe .calcSectors ; use the actual size .useDefaultSize: mov eax, kBoot2Size %if VERBOSE LogString(defaultsize_str) %endif .calcSectors: %if VERBOSE LogString(size_str) ; call print_hex %endif add eax, kSectorBytes - 1 ; adjust size before unit conversion shr eax, 11 ; convert file size to CD sector unit %if VERBOSE LogString(read_str) ; call print_hex %endif %if VERBOSE LogString(loading_str) %endif mov edx, (kBoot2Segment << 4) + kBoot2Address call readSectors jc error DebugChar('C') %if 0 ;DEBUG mov eax, [es:kBoot2Address] call print_hex DebugPause() %endif DebugChar('X') DebugPause() ;; Jump to newly-loaded booter %if VERBOSE LogString(done_str) %endif %if UNUSED LogString(keypress_str) call getc %endif mov dl, [gBIOSDriveNumber] ; load BIOS drive number jmp kBoot2Segment:kBoot2Address error: %if VERBOSE LogString(error_str) %endif .loop: hlt jmp .loop ;; ;; Support functions ;; ;-------------------------------------------------------------------------- ; readSectors - Reads more than 127 sectors using LBA addressing. ; ; Arguments: ; AX = number of 2048-byte sectors to read (valid from 1-320). ; 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: mov al, '.' call print_char 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 jc .exit 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 eax, 11 ; convert CD 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 ; [gBIOSDriveNumber] = 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. 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 ; [gBIOSDriveNumber] = 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 jnc .exit DebugChar('R') ; indicate INT13/F42 error ; ; Issue a disk reset on error. ; Should this be changed to Func 0xD to skip the diskette controller ; reset? ; %if VERBOSE LogString(readerror_str) mov eax, ecx ; call print_hex %endif 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 ;-------------------------------------------------------------------------- ; Write a string with 'cdboot: ' 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 ;%if DEBUG ;-------------------------------------------------------------------------- ; Write the 4-byte value to the console in hex. ; ; Arguments: ; EAX = Value to be displayed in hex. ; %if 0 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 %endif ;-------------------------------------------------------------------------- ; 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 ;-------------------------------------------------------------------------- ; Static data. ; %if VERBOSE log_title_str db CR, LF, 'cdboot: ', NULL init_str db 'init', NULL defaultsize_str db 'using default size', NULL size_str db 'file size: ', NULL read_str db 'reading sectors: ', NULL loading_str db 'loading', NULL done_str db 'done', NULL readerror_str db 'BIOS error at: ', NULL error_str db 'error', NULL %endif %if UNUSED keypress_str db 'Press any key to continue...', NULL %endif ;; Pad this file to a size of 2048 bytes (one CD sector). pad: times 2044-($-$$) db 0 CDBootSize dd CDBootSizeMagic ;; Location of loaded boot2 code. kBoot2LoadAddr equ $ ; ; Global variables ; ABSOLUTE kReadBuffer + kSectorBytes gBIOSDriveNumber resw 1 ; END