FreeDOS/boot/boot.asm
C. Masloch 858934f152 boot, oemboot: fix to abort properly on file not found
Prior to this commit the first cluster specified from the last
compared directory entry would be used if the file is not found.
Fix the wrong (boot.asm) or missing (oemboot.asm) jump to the
error handler so that on file not found an error is displayed.

Reference: https://sourceforge.net/p/freedos/bugs/97/
"Bugs in FAT12/16 boot sector loaders if file not found"
This commit fixes the most crucial bug of this 2012-09-27 report.
2021-07-29 17:19:45 -04:00

573 lines
20 KiB
NASM

;
; File:
; boot.asm
; Description:
; DOS-C boot
;
; Copyright (c) 1997;
; Svante Frey
; All Rights Reserved
;
; This file is part of DOS-C.
;
; DOS-C is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License
; as published by the Free Software Foundation; either version
; 2, or (at your option) any later version.
;
; DOS-C is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
; the GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public
; License along with DOS-C; see the file COPYING. If not,
; write to the Free Software Foundation, 675 Mass Ave,
; Cambridge, MA 02139, USA.
;
;
; +--------+ 1FE0:7E00
; |BOOT SEC|
; |RELOCATE|
; |--------| 1FE0:7C00
; |LBA PKT |
; |--------| 1FE0:7BC0
; |--------| 1FE0:7BA0
; |BS STACK|
; |--------|
; |4KBRDBUF| used to avoid crossing 64KB DMA boundary
; |--------| 1FE0:63A0
; | |
; |--------| 1FE0:3000
; | CLUSTER|
; | LIST |
; |--------| 1FE0:2000
; | |
; |--------| 0000:7E00
; |BOOT SEC| overwritten by max 128k FAT buffer
; |ORIGIN | and later by max 134k loaded kernel
; |--------| 0000:7C00
; | |
; |--------|
; |KERNEL | also used as max 128k FAT buffer
; |LOADED | before kernel loading starts
; |--------| 0060:0000
; | |
; +--------+
;%define ISFAT12 1
;%define ISFAT16 1
;verify one and only one of ISFAT12 or ISFAT16 is defined
%ifdef ISFAT12
%ifdef ISFAT16
%error Must select one FS
%endif
%elifndef ISFAT16
%error Must select one FS
%endif
segment .text
%define BASE 0x7c00
org BASE
Entry: jmp short real_start
nop
; bp is initialized to 7c00h
%define bsOemName bp+0x03 ; OEM label
%define bsBytesPerSec bp+0x0b ; bytes/sector
%define bsSecPerClust bp+0x0d ; sectors/allocation unit
%define bsResSectors bp+0x0e ; # reserved sectors
%define bsFATs bp+0x10 ; # of fats
%define bsRootDirEnts bp+0x11 ; # of root dir entries
%define bsSectors bp+0x13 ; # sectors total in image
%define bsMedia bp+0x15 ; media descrip: fd=2side9sec, etc...
%define sectPerFat bp+0x16 ; # sectors in a fat
%define sectPerTrack bp+0x18 ; # sectors/track
%define nHeads bp+0x1a ; # heads
%define nHidden bp+0x1c ; # hidden sectors
%define nSectorHuge bp+0x20 ; # sectors if > 65536
%define drive bp+0x24 ; drive number
%define extBoot bp+0x26 ; extended boot signature
%define volid bp+0x27
%define vollabel bp+0x2b
%define filesys bp+0x36
%define LOADSEG 0x0060
%define FATBUF 0x2000 ; offset of temporary buffer for FAT
; chain
; Some extra variables
;%define StoreSI bp+3h ;temp store
;-----------------------------------------------------------------------
times 36h - ($ - $$) db 0
; The filesystem ID is used by lDOS's instsect (by ecm)
; by default to validate that the filesystem matches.
%ifdef ISFAT12
%define FATFS "FAT12"
%elifdef ISFAT16
%define FATFS "FAT16"
%endif
db FATFS
times 3Eh - ($ - $$) db 32
; using bp-Entry+loadseg_xxx generates smaller code than using just
; loadseg_xxx, where bp is initialized to Entry, so bp-Entry equals 0
%define loadsegoff_60 bp-Entry+loadseg_off
%define loadseg_60 bp-Entry+loadseg_seg
%define LBA_PACKET bp-0x40
%define LBA_SIZE word [LBA_PACKET] ; size of packet, should be 10h
%define LBA_SECNUM word [LBA_PACKET+2] ; number of sectors to read
%define LBA_OFF LBA_PACKET+4 ; buffer to read/write to
%define LBA_SEG LBA_PACKET+6
%define LBA_SECTOR_0 word [LBA_PACKET+8 ] ; LBA starting sector #
%define LBA_SECTOR_16 word [LBA_PACKET+10]
%define LBA_SECTOR_32 word [LBA_PACKET+12]
%define LBA_SECTOR_48 word [LBA_PACKET+14]
%define READBUF 0x63A0 ; max 4KB buffer (min 2KB stack), == stacktop-0x1800
%define READADDR_OFF BP-0x60-0x1804 ; pointer within user buffer
%define READADDR_SEG BP-0x60-0x1802
%define PARAMS LBA_PACKET+0x10
;%define RootDirSecs PARAMS+0x0 ; # of sectors root dir uses
%define fat_start PARAMS+0x2 ; first FAT sector
%define root_dir_start PARAMS+0x6 ; first root directory sector
%define data_start PARAMS+0x0a ; first data sector
;-----------------------------------------------------------------------
; ENTRY
;-----------------------------------------------------------------------
real_start:
cli
cld
xor ax, ax
mov ds, ax
mov bp, BASE
; a reset should not be needed here
; int 0x13 ; reset drive
; int 0x12 ; get memory available in AX
; mov ax, 0x01e0
; mov cl, 6 ; move boot sector to higher memory
; shl ax, cl
; sub ax, 0x07e0
mov ax, 0x1FE0
mov es, ax
mov si, bp
mov di, bp
mov cx, 0x0100
rep movsw
jmp word 0x1FE0:cont
loadseg_off dw 0
loadseg_seg dw LOADSEG
cont:
mov ds, ax
mov ss, ax
lea sp, [bp-0x60]
sti
;
; Note: some BIOS implementations may not correctly pass drive number
; in DL, however we work around this in SYS.COM by NOP'ing out the use of DL
; (formerly we checked for [drive]==0xff; update sys.c if code moves)
;
mov [drive], dl ; rely on BIOS drive number in DL
mov LBA_SIZE, 10h
mov LBA_SECNUM,1 ; initialise LBA packet constants
mov word [LBA_SEG],ds
mov word [LBA_OFF],READBUF
; GETDRIVEPARMS: Calculate start of some disk areas.
;
mov si, word [nHidden]
mov di, word [nHidden+2]
add si, word [bsResSectors]
adc di, byte 0 ; DI:SI = first FAT sector
mov word [fat_start], si
mov word [fat_start+2], di
mov al, [bsFATs]
cbw
mul word [sectPerFat] ; DX:AX = total number of FAT sectors
add si, ax
adc di, dx ; DI:SI = first root directory sector
mov word [root_dir_start], si
mov word [root_dir_start+2], di
; Calculate how many sectors the root directory occupies.
mov bx, [bsBytesPerSec]
mov cl, 5 ; divide BX by 32
shr bx, cl ; BX = directory entries per sector
mov ax, [bsRootDirEnts]
xor dx, dx
div bx
; mov word [RootDirSecs], ax ; AX = sectors per root directory
push ax
add si, ax
adc di, byte 0 ; DI:SI = first data sector
mov [data_start], si
mov [data_start+2], di
; FINDFILE: Searches for the file in the root directory.
;
; Returns:
; AX = first cluster of file
; First, read the whole root directory
; into the temporary buffer.
mov ax, word [root_dir_start]
mov dx, word [root_dir_start+2]
pop di ; mov di, word [RootDirSecs]
les bx, [loadsegoff_60] ; es:bx = 60:0
call readDisk
les di, [loadsegoff_60] ; es:di = 60:0
; Search for KERNEL.SYS file name, and find start cluster.
next_entry: mov cx, 11
mov si, filename
push di
repe cmpsb
pop di
mov ax, [es:di+0x1A]; get cluster number from directory entry
je ffDone
add di, byte 0x20 ; go to next directory entry
cmp byte [es:di], 0 ; if the first byte of the name is 0,
jnz next_entry ; there is no more files in the directory
jmp boot_error ; fail if not found
ffDone:
push ax ; store first cluster number
; GETFATCHAIN:
;
; Reads the FAT chain and stores it in a temporary buffer in the first
; 64 kb. The FAT chain is stored an array of 16-bit cluster numbers,
; ending with 0.
;
; The file must fit in conventional memory, so it can't be larger than
; 640 kb. The sector size must be at least 512 bytes, so the FAT chain
; can't be larger than 2.5 KB (655360 / 512 * 2 = 2560).
;
; Call with: AX = first cluster in chain
les bx, [loadsegoff_60] ; es:bx=60:0
mov di, [sectPerFat]
mov ax, word [fat_start]
mov dx, word [fat_start+2]
call readDisk
pop ax ; restore first cluster number
; Set ES:DI to the temporary storage for the FAT chain.
push ds
pop es
mov ds, [loadseg_60]
mov di, FATBUF
next_clust: stosw ; store cluster number
mov si, ax ; SI = cluster number
%ifdef ISFAT12
; This is a FAT-12 disk.
fat_12: add si, si ; multiply cluster number by 3...
add si, ax
shr si, 1 ; ...and divide by 2
lodsw
; If the cluster number was even, the cluster value is now in
; bits 0-11 of AX. If the cluster number was odd, the cluster
; value is in bits 4-15, and must be shifted right 4 bits. If
; the number was odd, CF was set in the last shift instruction.
jnc fat_even
mov cl, 4
shr ax, cl
fat_even: and ah, 0x0f ; mask off the highest 4 bits
cmp ax, 0x0ff8 ; check for EOF
jb next_clust ; continue if not EOF
%endif
%ifdef ISFAT16
; This is a FAT-16 disk. The maximal size of a 16-bit FAT
; is 128 kb, so it may not fit within a single 64 kb segment.
fat_16: mov dx, [loadseg_60]
add si, si ; multiply cluster number by two
jnc first_half ; if overflow...
add dh, 0x10 ; ...add 64 kb to segment value
first_half: mov ds, dx ; DS:SI = pointer to next cluster
lodsw ; AX = next cluster
cmp ax, 0xfff8 ; >= FFF8 = 16-bit EOF
jb next_clust ; continue if not EOF
%endif
finished: ; Mark end of FAT chain with 0, so we have a single
; EOF marker for both FAT-12 and FAT-16 systems.
xor ax, ax
stosw
push cs
pop ds
; loadFile: Loads the file into memory, one cluster at a time.
les bx, [loadsegoff_60] ; set ES:BX to load address 60:0
mov si, FATBUF ; set DS:SI to the FAT chain
cluster_next: lodsw ; AX = next cluster to read
or ax, ax ; EOF?
jne load_next ; no, continue
mov bl,dl ; drive (left from readDisk)
jmp far [loadsegoff_60] ; yes, pass control to kernel
load_next: dec ax ; cluster numbers start with 2
dec ax
mov di, word [bsSecPerClust]
and di, 0xff ; DI = sectors per cluster
mul di
add ax, [data_start]
adc dx, [data_start+2] ; DX:AX = first sector to read
call readDisk
jmp short cluster_next
; shows text after the call to this function.
show.do_show:
mov ah, 0Eh ; show character
int 10h ; via "TTY" mode
show: pop si
lodsb ; get character
push si ; stack up potential return address
cmp al, 0 ; end of string?
jne .do_show ; until done
ret
boot_error: call show
; db "Error! Hit a key to reboot.",0
db "Error!",0
xor ah,ah
int 0x13 ; reset floppy
int 0x16 ; wait for a key
int 0x19 ; reboot the machine
; readDisk: Reads a number of sectors into memory.
;
; Call with: DX:AX = 32-bit DOS sector number
; DI = number of sectors to read
; ES:BX = destination buffer
;
; Returns: CF set on error
; ES:BX points one byte after the last byte read.
readDisk: push si
mov LBA_SECTOR_0,ax
mov LBA_SECTOR_16,dx
mov word [READADDR_SEG], es
mov word [READADDR_OFF], bx
call show
db ".",0
read_next:
;******************** LBA_READ *******************************
; check for LBA support
mov ah,041h ;
mov bx,055aah ;
mov dl, [drive]
; NOTE: sys must be updated if location changes!!!
%ifdef ISFAT12
%define LBA_TEST_OFFSET 179h
%elifdef ISFAT16
%define LBA_TEST_OFFSET 176h
%else
%define LBA_TEST_OFFSET 0
; Just a placeholder, so the proper error message
; will be shown when assembling without either
; of the ISFATx defines.
%endif
%if ($ - Entry) != LBA_TEST_OFFSET
%error Must update constant offset (LBA_TEST_OFFSET) to test dl,dl here and in sys.c for FATFS
%endif
test dl,dl ; don't use LBA addressing on A:
jz read_normal_BIOS ; might be a (buggy)
; CDROM-BOOT floppy emulation
int 0x13
jc read_normal_BIOS
shr cx,1 ; CX must have 1 bit set
sbb bx,0aa55h - 1 ; tests for carry (from shr) too!
jne read_normal_BIOS
; OK, drive seems to support LBA addressing
lea si,[LBA_PACKET]
; setup LBA disk block
mov LBA_SECTOR_32,bx ; bx is 0 if extended 13h mode supported
mov LBA_SECTOR_48,bx
mov ah,042h
jmp short do_int13_read
read_normal_BIOS:
;******************** END OF LBA_READ ************************
mov cx,LBA_SECTOR_0
mov dx,LBA_SECTOR_16
;
; translate sector number to BIOS parameters
;
;
; abs = sector offset in track
; + head * sectPerTrack offset in cylinder
; + track * sectPerTrack * nHeads offset in platter
;
mov al, [sectPerTrack]
mul byte [nHeads]
xchg ax, cx
; cx = nHeads * sectPerTrack <= 255*63
; dx:ax = abs
div cx
; ax = track, dx = sector + head * sectPertrack
xchg ax, dx
; dx = track, ax = sector + head * sectPertrack
div byte [sectPerTrack]
; dx = track, al = head, ah = sector
mov cx, dx
; cx = track, al = head, ah = sector
; the following manipulations are necessary in order to
; properly place parameters into registers.
; ch = cylinder number low 8 bits
; cl = 7-6: cylinder high two bits
; 5-0: sector
mov dh, al ; save head into dh for bios
xchg ch, cl ; set cyl no low 8 bits
ror cl, 1 ; move track high bits into
ror cl, 1 ; bits 7-6 (assumes top = 0)
or cl, ah ; merge sector into cylinder
inc cx ; make sector 1-based (1-63)
les bx,[LBA_OFF]
mov ax, 0x0201
do_int13_read:
mov dl, [drive]
int 0x13
jc boot_error ; exit on error
mov ax, word [bsBytesPerSec]
push di
mov si,READBUF ; copy read in sector data to
les di,[READADDR_OFF] ; user provided buffer
mov cx, ax
; shr cx, 1 ; convert bytes to word count
; rep movsw
rep movsb
pop di
; div byte[LBA_PACKET] ; luckily 16 !!
mov cl, 4
shr ax, cl ; adjust segment pointer by increasing
add word [READADDR_SEG], ax ; by paragraphs read in (per sector)
add LBA_SECTOR_0, byte 1
adc LBA_SECTOR_16, byte 0 ; DX:AX = next sector to read
dec di ; if there is anything left to read,
jnz read_next ; continue
les bx, [READADDR_OFF]
; clear carry: unnecessary since adc clears it
pop si
ret
times 0x01f1-$+$$ db 0
filename db "KERNEL SYS",0,0
sign dw 0xAA55
%ifdef DBGPRNNUM
; DEBUG print hex digit routines
PrintLowNibble: ; Prints low nibble of AL, AX is destroyed
and AL, 0Fh ; ignore upper nibble
cmp AL, 09h ; if greater than 9, then don't base on '0', base on 'A'
jbe .printme
add AL, 7 ; convert to character A-F
.printme:
add AL, '0' ; convert to character 0-9
mov AH,0x0E ; show character
int 0x10 ; via "TTY" mode
retn
PrintAL: ; Prints AL, AX is preserved
push AX ; store value so we can process a nibble at a time
shr AL, 4 ; move upper nibble into lower nibble
call PrintLowNibble
pop AX ; restore for other nibble
push AX ; but save so we can restore original AX
call PrintLowNibble
pop AX ; restore for other nibble
retn
PrintNumber: ; Prints (in Hex) value in AX, AX is preserved
xchg AH, AL ; high byte 1st
call PrintAL
xchg AH, AL ; now low byte
call PrintAL
retn
%endif