1024 lines
21 KiB
NASM
1024 lines
21 KiB
NASM
|
|
%if 0
|
|
|
|
Write success indicator after file system booting
|
|
by C. Masloch, 2020
|
|
|
|
Usage of the works is permitted provided that this
|
|
instrument is retained with the works, so that any entity
|
|
that uses the works is notified of this instrument.
|
|
|
|
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
|
|
|
|
%endif
|
|
|
|
|
|
%assign __lMACROS1_MAC__DEBUG_DEFAULTS 1
|
|
%include "lmacros3.mac"
|
|
|
|
struc BS
|
|
bsJump: resb 3
|
|
bsOEM: resb 8
|
|
bsBPB:
|
|
endstruc
|
|
|
|
struc EBPB ; BPB sec
|
|
bpbBytesPerSector: resw 1 ; offset 00h 0Bh
|
|
bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh
|
|
bpbReservedSectors: resw 1 ; offset 03h 0Eh
|
|
bpbNumFATs: resb 1 ; offset 05h 10h
|
|
bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32
|
|
bpbTotalSectors: resw 1 ; offset 08h 13h
|
|
bpbMediaID: resb 1 ; offset 0Ah 15h
|
|
bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32
|
|
bpbCHSSectors: resw 1 ; offset 0Dh 18h
|
|
bpbCHSHeads: resw 1 ; offset 0Fh 1Ah
|
|
bpbHiddenSectors: resd 1 ; offset 11h 1Ch
|
|
bpbTotalSectorsLarge: resd 1 ; offset 15h 20h
|
|
bpbNew: ; offset 19h 24h
|
|
|
|
ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h
|
|
ebpbFSFlags: resw 1 ; offset 1Dh 28h
|
|
ebpbFSVersion: resw 1 ; offset 1Fh 2Ah
|
|
ebpbRootCluster: resd 1 ; offset 21h 2Ch
|
|
ebpbFSINFOSector: resw 1 ; offset 25h 30h
|
|
ebpbBackupSector: resw 1 ; offset 27h 32h
|
|
ebpbReserved: resb 12 ; offset 29h 34h
|
|
ebpbNew: ; offset 35h 40h
|
|
endstruc
|
|
|
|
struc BPBN ; ofs B16 S16 B32 S32
|
|
bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h
|
|
resb 1 ; 01h 1Ah 25h 36h 41h
|
|
bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN
|
|
bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h
|
|
bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h
|
|
bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h
|
|
endstruc ; 1Ah 33h 3Eh 4Fh 5Ah
|
|
|
|
struc LOADSTACKVARS, -10h
|
|
lsvFirstCluster: resd 1
|
|
lsvFATSector: resd 1
|
|
lsvFATSeg: resw 1
|
|
lsvLoadSeg: resw 1
|
|
lsvDataStart: resd 1
|
|
endstruc
|
|
|
|
lsvclSignature equ "CL"
|
|
lsvclBufferLength equ 256
|
|
|
|
struc LOADDATA, LOADSTACKVARS - 10h
|
|
ldMemoryTop: resw 1
|
|
ldLoadTop: resw 1
|
|
ldSectorSeg: resw 1
|
|
ldFATType: resb 1
|
|
ldHasLBA: resb 1
|
|
ldClusterSize: resw 1
|
|
ldParaPerSector:resw 1
|
|
ldLoadingSeg: resw 1
|
|
ldLoadUntilSeg: resw 1
|
|
endstruc
|
|
|
|
struc LOADCMDLINE, LOADDATA - lsvclBufferLength
|
|
ldCommandLine:
|
|
.start: resb lsvclBufferLength
|
|
endstruc
|
|
|
|
struc LBAPACKET
|
|
lpSize: resw 1
|
|
lpCount: resw 1
|
|
lpBuffer: resd 1
|
|
lpSector: resq 1
|
|
endstruc
|
|
|
|
struc DIRENTRY
|
|
deName: resb 8
|
|
deExt: resb 3
|
|
deAttrib: resb 1
|
|
resb 8
|
|
deClusterHigh: resw 1
|
|
deTime: resw 1
|
|
deDate: resw 1
|
|
deClusterLow: resw 1
|
|
deSize: resd 1
|
|
endstruc
|
|
|
|
ATTR_READONLY equ 1
|
|
ATTR_HIDDEN equ 2
|
|
ATTR_SYSTEM equ 4
|
|
ATTR_VOLLABEL equ 8
|
|
ATTR_DIRECTORY equ 10h
|
|
ATTR_ARCHIVE equ 20h
|
|
|
|
|
|
%ifndef _MAP
|
|
%elifempty _MAP
|
|
%else ; defined non-empty, str or non-str
|
|
[map all _MAP]
|
|
%endif
|
|
|
|
defaulting
|
|
|
|
numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access)
|
|
numdef CHS, 1 ; support CHS (if it fits)
|
|
numdef LBA, 1 ; support LBA (if available)
|
|
numdef LBA_33_BIT, 1 ; support 33-bit LBA
|
|
numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry
|
|
numdef LBA_SKIP_CHECK, 0 ; don't use proper LBA extensions check
|
|
numdef LBA_RETRY, 1 ; retry LBA reads
|
|
numdef CHS_RETRY, 1 ; retry CHS reads
|
|
|
|
strdef FILE_NAME, "RESULT"
|
|
strdef FILE_EXT, "TXT" ; name of file to write
|
|
strdef FILE_SUCCESS_MSG,"success"
|
|
|
|
|
|
cpu 8086
|
|
org 0
|
|
start:
|
|
push ss
|
|
pop ds
|
|
mov word [cs:dirsp], sp
|
|
|
|
mov ax, cs
|
|
cmp ax, 200h + 200h ; loaded high ?
|
|
mov dx, 200h ; yes, use low buffer
|
|
jae @F
|
|
add ax, paras(end - $$) ; => behind used load image
|
|
mov dx, ax
|
|
neg ax ; - behind used
|
|
add ax, word [bp + ldLoadTop] ; top - used = length of free
|
|
cmp ax, 200h ; enough ?
|
|
jb error_outofmemory ; no -->
|
|
; (For simplicity we do not attempt to relocate our load image.)
|
|
@@:
|
|
mov word [cs:dirbuf], dx
|
|
|
|
%if _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
|
|
call query_geometry
|
|
; The ebpbNew BPBN needs to be initialised
|
|
; to use this function. It must be called
|
|
; before using read_sector.
|
|
%endif
|
|
|
|
cmp byte [bp + ldFATType], 16
|
|
ja search32
|
|
cmp byte [bp + ldFATType], 0
|
|
ja search1216
|
|
call error
|
|
asciz "Unknown file system (ldFATType = 0)."
|
|
|
|
search1216:
|
|
xor cx, cx
|
|
mov bx, word [bp + ldParaPerSector]
|
|
shr bx, 1 ; = entries per sector
|
|
|
|
; number of sectors used for root directory (store in CX)
|
|
mov si, [bp + bsBPB + bpbNumRootDirEnts]
|
|
mov ax, bx
|
|
; The ax value here is the last value of bx, which is set
|
|
; by the shr instruction. Therefore, it cannot be higher
|
|
; than 7FFFh, so this cwd instruction always zeros dx.
|
|
cwd
|
|
dec ax ; rounding up
|
|
add ax, si ; from BPB
|
|
adc dx, dx ; account for overflow (dx was zero)
|
|
div bx ; get number of root sectors
|
|
xchg ax, cx ; cx = number of root secs, ! ah = 0
|
|
|
|
; first sector of root directory
|
|
mov al, [bp + bsBPB + bpbNumFATs]; ! ah = 0, hence ax = number of FATs
|
|
mul word [bp + bsBPB + bpbSectorsPerFAT]
|
|
add ax, [bp + bsBPB + bpbReservedSectors]
|
|
adc dl, dh ; account for overflow (dh was and is 0)
|
|
|
|
; Scan root directory for file. We don't bother to check for deleted
|
|
; entries (E5h) or entries that mark the end of the directory (00h).
|
|
; number of root entries in si here
|
|
.next_sect:
|
|
mov cx, bx ; entries per sector as loop counter
|
|
call read_sector_to_dirbuf
|
|
mov bx, cx ; restore bx for next iteration later
|
|
|
|
xor di, di ; es:di-> first entry in this sector
|
|
.next_ent:
|
|
call check_entry
|
|
dec si ; count down entire root's entries
|
|
loopnz .next_ent ; count down sector's entries (jumps iff si >0 && cx >0)
|
|
jnz .next_sect ; (jumps iff si >0 && cx ==0)
|
|
; ends up here iff si ==0
|
|
; ie all root entries checked unsuccessfully
|
|
jmp error_filenotfound
|
|
|
|
|
|
search32:
|
|
mov ax, [bp + bsBPB + ebpbRootCluster]
|
|
mov dx, [bp + bsBPB + ebpbRootCluster + 2]
|
|
mov si, word [bp + lsvFATSector + 2]
|
|
mov di, word [bp + lsvFATSector]
|
|
call check_clust
|
|
jc error_filenotfound
|
|
|
|
.next_root_clust:
|
|
call clust_to_first_sector
|
|
push cx
|
|
push bx
|
|
mov cx, [bp + ldClusterSize]
|
|
.next_root_sect:
|
|
push cx
|
|
mov cx, [bp + ldParaPerSector]
|
|
shr cx, 1
|
|
|
|
; Scan root directory for file. We don't bother to check for deleted
|
|
; entries (E5h) or entries that mark the end of the directory (00h).
|
|
call read_sector_to_dirbuf
|
|
|
|
push di
|
|
xor di, di ; es:di-> first entry in this sector
|
|
.next_ent:
|
|
call check_entry
|
|
loop .next_ent ; count down sector's entries (jumps iff cx >0)
|
|
pop di
|
|
pop cx
|
|
loop .next_root_sect
|
|
pop bx
|
|
pop cx
|
|
call clust_next
|
|
jnc .next_root_clust
|
|
jmp error_filenotfound
|
|
|
|
|
|
read_sector_to_dirbuf:
|
|
mov bx, word [cs:dirbuf]
|
|
mov word [cs:dirsec], ax
|
|
mov word [cs:dirsec + 2], dx
|
|
jmp read_sector
|
|
|
|
check_entry:
|
|
test byte [es:di + deAttrib], ATTR_DIRECTORY | ATTR_VOLLABEL
|
|
jnz @F ; directory, label, or LFN entry --> (NZ)
|
|
push si
|
|
push di
|
|
push cx
|
|
push ds
|
|
push cs
|
|
pop ds
|
|
mov si, msg.filename ; ds:si-> name to match
|
|
mov cx, 11 ; length of padded 8.3 FAT filename
|
|
repe cmpsb ; check entry
|
|
pop ds
|
|
pop cx
|
|
pop di
|
|
pop si
|
|
@@: ; ZR = match, NZ = mismatch
|
|
je found_it ; found entry -->
|
|
lea di, [di + DIRENTRY_size]
|
|
retn
|
|
|
|
found_it:
|
|
mov sp, word [cs:dirsp]
|
|
mov word [cs:dirofs], di
|
|
|
|
cmp word [es:di + deSize], 0
|
|
jne @F
|
|
cmp word [es:di + deSize + 2], 0
|
|
jne @F
|
|
|
|
error_emptyfile: equ $
|
|
call error
|
|
asciz "File is empty."
|
|
|
|
@@:
|
|
; get starting cluster of file
|
|
mov ax, [es:di + deClusterLow]
|
|
mov dx, [es:di + deClusterHigh] ; dx:ax = first cluster
|
|
cmp byte [bp + ldFATType], 16
|
|
ja @F
|
|
xor dx, dx
|
|
@@:
|
|
|
|
mov di, [bp + lsvFATSector]
|
|
mov si, [bp + lsvFATSector + 2]
|
|
call check_clust
|
|
jc error_emptyfile
|
|
|
|
call clust_to_first_sector
|
|
mov bx, cs
|
|
add bx, paras(filbuf - $$)
|
|
call write_sector
|
|
|
|
mov es, word [cs:dirbuf]
|
|
mov bx, word [cs:dirofs]
|
|
mov word [es:bx + deSize], filbuf.size
|
|
and word [es:bx + deSize + 2], 0
|
|
mov bx, es
|
|
mov ax, word [cs:dirsec]
|
|
mov dx, word [cs:dirsec + 2]
|
|
call write_sector
|
|
|
|
push cs
|
|
pop ds
|
|
mov si, msg.success
|
|
call disp_error
|
|
|
|
quit:
|
|
int3
|
|
|
|
mov ax, 0F000h
|
|
mov es, ax
|
|
push cs
|
|
pop ds ; avoid "repe cs cmpsw" (8086 bug)
|
|
mov di, 0FFF5h
|
|
mov si, msg.dosemudate
|
|
mov cx, 4
|
|
repe cmpsw ; running in DosEmu?
|
|
jne .quit_not_dosemu
|
|
|
|
xor bx, bx
|
|
mov ax, -1
|
|
int 0E6h ; dosemu quit
|
|
|
|
.quit_not_dosemu:
|
|
|
|
; from https://stackoverflow.com/a/5240330/738287
|
|
mov ax, 5301h
|
|
xor bx, bx
|
|
int 15h ; connect to APM API
|
|
|
|
mov ax, 530Eh
|
|
xor bx, bx
|
|
mov cx, 0102h
|
|
int 15h ; set APM version to 1.02
|
|
|
|
mov ax, 5307h
|
|
mov bx, 1
|
|
mov cx, 3
|
|
int 15h ; shut down system
|
|
|
|
; setopt [cs:quitrecurse], 1
|
|
call error
|
|
asciz "Quit failed."
|
|
|
|
|
|
error:
|
|
push cs
|
|
pop ds
|
|
mov si, msg.error
|
|
call disp_error
|
|
pop si
|
|
call disp_error
|
|
mov si, msg.error.trailer
|
|
call disp_error
|
|
%if 0
|
|
testopt [cs:quitrecurse], 1
|
|
jz quit
|
|
%endif
|
|
int3
|
|
xor ax, ax
|
|
int 16h
|
|
int 19h
|
|
|
|
disp_error:
|
|
.:
|
|
lodsb
|
|
test al, al
|
|
jz .ret
|
|
mov ah, 0Eh
|
|
mov bx, 7
|
|
push bp
|
|
; (call may change bp)
|
|
int 10h
|
|
pop bp
|
|
jmp short .
|
|
|
|
|
|
align 4
|
|
dirsec: dd 0
|
|
dirofs: dw 0
|
|
dirbuf: dw 0
|
|
dirsp: dw 0
|
|
; quitrecurse: db 0
|
|
|
|
msg:
|
|
.error: asciz "Load error: "
|
|
.success: db "Test writer loaded successfully. Quitting the machine."
|
|
.error.trailer: asciz 13,10
|
|
.filename: fill 8, 32, db _FILE_NAME
|
|
fill 3, 32, db _FILE_EXT
|
|
align 4
|
|
.dosemudate: db "02/25/93"
|
|
|
|
align 16, db 38
|
|
filbuf:
|
|
.:
|
|
db _FILE_SUCCESS_MSG,13,10
|
|
.size: equ $ - .
|
|
%if .size > 32
|
|
%error File success message is too long
|
|
%endif
|
|
_fill 8192, 38, .
|
|
|
|
|
|
query_geometry:
|
|
%if _QUERY_GEOMETRY ; +30 bytes
|
|
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
|
|
%if !_LBA_SKIP_CHECK
|
|
push dx
|
|
%endif
|
|
; test dl, dl ; floppy?
|
|
; jns @F ; don't attempt query, might fail -->
|
|
; Note that while the original PC BIOS doesn't support this function
|
|
; (for its diskettes), it does properly return the error code 01h.
|
|
; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1)
|
|
mov ah, 08h
|
|
xor cx, cx ; initialise cl to 0
|
|
stc ; initialise to CY
|
|
int 13h ; query drive geometry
|
|
jc @F ; apparently failed -->
|
|
and cx, 3Fh ; get sectors
|
|
jz @F ; invalid (S is 1-based), don't use -->
|
|
mov [bp + bsBPB + bpbCHSSectors], cx
|
|
mov cl, dh ; cx = maximum head number
|
|
inc cx ; cx = number of heads (H is 0-based)
|
|
mov [bp + bsBPB + bpbCHSHeads], cx
|
|
@@:
|
|
%endif
|
|
|
|
%if !_LBA_SKIP_CHECK
|
|
mov ah, 41h
|
|
%if _QUERY_GEOMETRY
|
|
pop dx
|
|
%else
|
|
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
|
|
%endif
|
|
mov bx, 55AAh
|
|
stc
|
|
int 13h ; 13.41.bx=55AA extensions installation check
|
|
mov al, 0 ; zero in case of no LBA support
|
|
jc .no_lba
|
|
cmp bx, 0AA55h
|
|
jne .no_lba
|
|
test cl, 1 ; support bitmap bit 0
|
|
jz .no_lba
|
|
inc ax ; al = 1 to indicate LBA support
|
|
.no_lba:
|
|
mov byte [bp + ldHasLBA], al
|
|
%endif
|
|
|
|
%if 1 || _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
|
|
disp_error.ret:
|
|
retn
|
|
%endif
|
|
|
|
|
|
; Read a sector using Int13.02 or Int13.42
|
|
;
|
|
; INP: dx:ax = sector number within partition
|
|
; bx:0-> buffer
|
|
; (_LBA) ds = ss
|
|
; OUT: If unable to read,
|
|
; ! jumps to error instead of returning
|
|
; If sector has been read,
|
|
; dx:ax = next sector number (has been incremented)
|
|
; bx:0-> next buffer (bx = es+word[para_per_sector])
|
|
; es = input bx
|
|
; CHG: -
|
|
; STT: ds = ss
|
|
;
|
|
; Note: If error 09h (data boundary error) is returned,
|
|
; the read is done into the ldSectorSeg buffer,
|
|
; then copied into the user buffer.
|
|
read_sector:
|
|
clropt [bp + ldHasLBA], 2
|
|
jmp @F
|
|
|
|
write_sector:
|
|
setopt [bp + ldHasLBA], 2
|
|
@@:
|
|
|
|
push dx
|
|
push cx
|
|
push ax
|
|
push si
|
|
|
|
push bx
|
|
|
|
; DX:AX==LBA sector number
|
|
; add partition start (= number of hidden sectors)
|
|
add ax,[bp + bsBPB + bpbHiddenSectors + 0]
|
|
adc dx,[bp + bsBPB + bpbHiddenSectors + 2]
|
|
|
|
%if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33
|
|
jc .err_CY
|
|
%endif
|
|
%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS)
|
|
%if _LBA_33_BIT
|
|
sbb si, si ; -1 if was CY, 0 else
|
|
neg si ; 1 if was CY, 0 else
|
|
%endif
|
|
xor cx, cx ; cx = 0 (needed if jumping to .no_lba_checked)
|
|
%if !_LBA_SKIP_CHECK
|
|
test byte [bp + ldHasLBA], 1
|
|
jz .no_lba_checked
|
|
%endif
|
|
push cx
|
|
%if _LBA_33_BIT
|
|
push si ; bit 32 = 1 if operating in 33-bit space
|
|
%else
|
|
push cx ; second highest word = 0
|
|
%endif
|
|
push dx
|
|
push ax ; qword sector number (lpSector)
|
|
push bx
|
|
push cx ; bx:0 -> buffer (lpBuffer)
|
|
inc cx
|
|
push cx ; word number of sectors to read (lpCount)
|
|
mov cl, 10h
|
|
push cx ; word size of disk address packet (lpSize)
|
|
mov si, sp ; ds:si -> disk address packet (on stack)
|
|
|
|
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
|
|
call .get_ah_3_write_2_read
|
|
or ah, 40h ; 42h extensions read or 43h extensions write
|
|
%if _LBA_RETRY
|
|
call .int13_retry
|
|
%else
|
|
call .int13_preserve_lpcount
|
|
%endif
|
|
jnc .lba_done
|
|
|
|
%if _LBA_SKIP_CHECK
|
|
cmp ah, 1 ; invalid function?
|
|
je .no_lba_skip ; try CHS instead -->
|
|
%endif
|
|
cmp ah, 9 ; data boundary error?
|
|
jne .lba_error
|
|
|
|
testopt [bp + ldHasLBA], 2
|
|
jz @F
|
|
|
|
mov es, word [si + 4 + 2] ; user buffer
|
|
call .sectorseg_helper_write
|
|
mov word [si + 4 + 2], es ; => sector buffer
|
|
mov ah, 43h
|
|
%if _LBA_RETRY
|
|
call .int13_retry
|
|
%else
|
|
int 13h
|
|
; (don't need .int13_preserve_lpcount as no further call)
|
|
%endif
|
|
jc .lba_error
|
|
jmp .lba_done
|
|
|
|
@@:
|
|
; push word [si + 4 + 0]
|
|
push word [si + 4 + 2] ; user buffer
|
|
push word [bp + ldSectorSeg]
|
|
pop word [si + 4 + 2]
|
|
; and word [si + 4 + 0], byte 0
|
|
|
|
mov ah, 42h
|
|
%if _LBA_RETRY
|
|
call .int13_retry
|
|
%else
|
|
int 13h
|
|
; (don't need .int13_preserve_lpcount as no further call)
|
|
%endif
|
|
jc .lba_error
|
|
|
|
pop es
|
|
; pop cx
|
|
call .sectorseg_helper_read
|
|
|
|
.lba_done:
|
|
add sp, 10h
|
|
pop bx
|
|
jmp short .chs_done
|
|
|
|
.lba_error: equ .err
|
|
|
|
%if !_CHS
|
|
.no_lba_skip: equ .err
|
|
.no_lba_checked: equ .err
|
|
%elif _LBA_SKIP_CHECK
|
|
.no_lba_skip:
|
|
; si == sp
|
|
add sp, 8
|
|
pop ax
|
|
pop dx
|
|
%if _LBA_33_BIT
|
|
pop si
|
|
pop cx ; cx = 0 (needed as input for next cwd instruction)
|
|
test si, si
|
|
mov si, sp ; si == sp
|
|
%else
|
|
pop cx
|
|
pop cx
|
|
; si == sp - 16
|
|
%endif
|
|
%else
|
|
.no_lba_checked:
|
|
%if _LBA_33_BIT
|
|
test si, si
|
|
%endif
|
|
mov si, sp ; si == sp
|
|
%endif
|
|
%endif
|
|
|
|
%if _CHS ; +70 bytes
|
|
%if _LBA && _LBA_33_BIT
|
|
jnz .err
|
|
%endif
|
|
; dx:ax = LBA sector number, (if _LBA) cx = 0
|
|
; divide by number of sectors per track to get sector number
|
|
; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability
|
|
; Use two-step 32:16 divide to avoid overflow
|
|
%if !_LBA
|
|
xchg cx, ax ; cx = low word of sector, clobbers ax
|
|
xchg ax, dx ; ax = high word of sector, clobbers dx
|
|
xor dx, dx ; dx:ax = high word of sector
|
|
%else
|
|
xchg cx, ax ; cx = low word of sector, ax = 0
|
|
push dx ; stack = high word of sector
|
|
cwd ; dx = 0 (because ax was 0)
|
|
pop ax ; ax = high word of sector
|
|
; dx:ax = high word of sector
|
|
%endif
|
|
div word [bp + bsBPB + bpbCHSSectors]
|
|
xchg cx,ax
|
|
div word [bp + bsBPB + bpbCHSSectors]
|
|
xchg cx,dx
|
|
|
|
; DX:AX=quotient, CX=remainder=sector (S) - 1
|
|
; divide quotient by number of heads
|
|
xchg bx, ax ; bx = low word of quotient, clobbers ax
|
|
xchg ax, dx ; ax = high word of quotient, clobbers dx
|
|
xor dx, dx ; dx = 0
|
|
div word [bp + bsBPB + bpbCHSHeads]
|
|
; ax = high / heads, dx = high % heads
|
|
xchg bx, ax ; bx = high / heads, ax = low quotient
|
|
div word [bp + bsBPB + bpbCHSHeads]
|
|
|
|
; bx:ax=quotient=cylinder (C), dx=remainder=head (H)
|
|
; move variables into registers for INT 13h AH=02h
|
|
mov dh, dl ; dh = head
|
|
inc cx ; cl5:0 = sector
|
|
xchg ch, al ; ch = cylinder 7:0, al = 0
|
|
shr ax, 1
|
|
shr ax, 1 ; al7:6 = cylinder 9:8
|
|
; bx has bits set iff it's > 0, indicating a cylinder >= 65536.
|
|
or bl, bh ; collect set bits from bh
|
|
or cl, al ; cl7:6 = cylinder 9:8
|
|
; ah has bits set iff it was >= 4, indicating a cylinder >= 1024.
|
|
or bl, ah ; collect set bits from ah
|
|
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
|
|
; dl = drive
|
|
jnz .err ; error if cylinder >= 1024 -->
|
|
; ! bx = 0 (for 13.02 call)
|
|
|
|
; we call INT 13h AH=02h once for each sector. Multi-sector reads
|
|
; may fail if we cross a track or 64K boundary
|
|
pop es
|
|
|
|
call .get_ah_3_write_2_read
|
|
mov al, 01h ; access one sector
|
|
|
|
%if _CHS_RETRY
|
|
call .int13_retry
|
|
%else
|
|
int 13h
|
|
%endif
|
|
jnc .done
|
|
|
|
cmp ah, 9 ; data boundary error?
|
|
jne .err
|
|
|
|
testopt [bp + ldHasLBA], 2
|
|
jz @F
|
|
|
|
push es ; user buffer
|
|
call .sectorseg_helper_write
|
|
mov ax, 0301h
|
|
%if _LBA_RETRY
|
|
call .int13_retry
|
|
%else
|
|
int 13h
|
|
%endif
|
|
jc .err
|
|
pop es
|
|
jmp .done
|
|
|
|
@@:
|
|
push es ; user buffer
|
|
mov es, word [bp + ldSectorSeg]
|
|
|
|
mov ax, 0201h
|
|
%if _CHS_RETRY
|
|
call .int13_retry
|
|
%else
|
|
int 13h
|
|
%endif
|
|
jc .err
|
|
|
|
pop es
|
|
call .sectorseg_helper_read
|
|
|
|
.done:
|
|
; increment segment
|
|
mov bx, es
|
|
%endif
|
|
|
|
.chs_done:
|
|
mov es, bx
|
|
add bx, word [bp + ldParaPerSector]
|
|
|
|
pop si
|
|
pop ax
|
|
pop cx
|
|
pop dx
|
|
; increment LBA sector number
|
|
inc ax
|
|
jne @F
|
|
inc dx
|
|
@@:
|
|
retn
|
|
|
|
|
|
%if (_LBA && _LBA_RETRY) || (_CHS && _CHS_RETRY)
|
|
.int13_retry:
|
|
push ax
|
|
%if _LBA
|
|
call .int13_preserve_lpcount
|
|
%else
|
|
int 13h ; first try
|
|
%endif
|
|
jnc @F ; NC, success on first attempt -->
|
|
|
|
; reset drive
|
|
xor ax, ax
|
|
int 13h
|
|
jc @F ; CY, reset failed, error in ah -->
|
|
|
|
; try read again
|
|
pop ax ; restore function number
|
|
%if _LBA
|
|
call .int13_preserve_lpcount
|
|
%else
|
|
int 13h ; retry, CF error status, ah error number
|
|
%endif
|
|
retn
|
|
|
|
@@: ; NC or CY, stack has function number
|
|
inc sp
|
|
inc sp ; discard word on stack, preserve CF
|
|
retn
|
|
%endif
|
|
|
|
%if _LBA
|
|
; have to reset the LBAPACKET's lpCount, as the handler may
|
|
; set it to "the number of blocks successfully transferred".
|
|
|
|
; hack: si points into unclaimed stack space
|
|
; when this is called from the CHS handler.
|
|
; this should not cause any issues however.
|
|
; actually, if !_LBA_SKIP_CHECK, then si is set
|
|
; to point to claimed stack space. also legal.
|
|
.int13_preserve_lpcount:
|
|
push word [si + lpCount]
|
|
int 13h
|
|
pop word [si + lpCount]
|
|
retn
|
|
%endif
|
|
|
|
; INP: word [bp + ldSectorSeg] => source buffer with read data
|
|
; es => destination buffer
|
|
; CHG: si, cx
|
|
; OUT: ss = ds
|
|
; data copied to destination buffer
|
|
; STT: UP
|
|
.sectorseg_helper_read:
|
|
xor si, si
|
|
mov ds, word [bp + ldSectorSeg]
|
|
push di
|
|
; mov di, cx
|
|
xor di, di
|
|
mov cx, word [bp + bsBPB + bpbBytesPerSector]
|
|
rep movsb
|
|
pop di
|
|
|
|
push ss
|
|
pop ds
|
|
retn
|
|
|
|
; INP: es => source buffer with data to read
|
|
; word [bp + ldSectorSeg] => destination buffer
|
|
; CHG: -
|
|
; OUT: ss = ds
|
|
; es => destination buffer
|
|
; data copied to destination buffer
|
|
; STT: UP
|
|
.sectorseg_helper_write:
|
|
push es
|
|
pop ds
|
|
push si
|
|
push di
|
|
push cx
|
|
xor di, di
|
|
mov es, word [bp + ldSectorSeg]
|
|
xor si, si
|
|
mov cx, word [bp + bsBPB + bpbBytesPerSector]
|
|
rep movsb
|
|
pop cx
|
|
pop di
|
|
pop si
|
|
|
|
push ss
|
|
pop ds
|
|
retn
|
|
|
|
.get_ah_3_write_2_read:
|
|
mov ah, 02h ; read
|
|
testopt [bp + ldHasLBA], 2
|
|
jz @F
|
|
mov ah, 03h ; write
|
|
@@:
|
|
retn
|
|
|
|
.err:
|
|
error_diskaccess:
|
|
testopt [bp + ldHasLBA], 2
|
|
jz @F
|
|
call error
|
|
db "Disk write error.", 0
|
|
|
|
@@:
|
|
call error
|
|
db "Disk read error.", 0
|
|
|
|
error_badchain:
|
|
call error
|
|
db "Bad cluster chain.", 0
|
|
|
|
error_badclusters:
|
|
call error
|
|
db "Bad amount of clusters.", 0
|
|
|
|
error_outofmemory:
|
|
call error
|
|
db "Out of memory.", 0
|
|
|
|
error_filenotfound:
|
|
call error
|
|
db "File not found.", 0
|
|
|
|
|
|
; INP: dx:ax = cluster - 2 (0-based cluster)
|
|
; OUT: cx:bx = input dx:ax
|
|
; dx:ax = first sector of that cluster
|
|
; CHG: -
|
|
clust_to_first_sector:
|
|
push dx
|
|
push ax
|
|
push dx
|
|
mul word [bp + ldClusterSize]
|
|
xchg bx, ax
|
|
xchg cx, dx
|
|
pop ax
|
|
mul word [bp + ldClusterSize]
|
|
test dx, dx
|
|
jnz short error_badchain
|
|
xchg dx, ax
|
|
add dx, cx
|
|
.cy_error_badchain:
|
|
jc short error_badchain
|
|
xchg ax, bx
|
|
|
|
add ax, [bp + lsvDataStart]
|
|
adc dx, [bp + lsvDataStart + 2]
|
|
jc short .cy_error_badchain
|
|
; dx:ax = first sector in cluster
|
|
pop bx
|
|
pop cx ; cx:bx = cluster
|
|
retn
|
|
|
|
|
|
; INP: cx:bx = cluster (0-based)
|
|
; si:di = loaded FAT sector, -1 if none
|
|
; OUT: CY if no next cluster
|
|
; NC if next cluster found,
|
|
; dx:ax = next cluster value (0-based)
|
|
; si:di = loaded FAT sector
|
|
; CHG: cx, bx
|
|
clust_next:
|
|
mov ax, bx
|
|
mov dx, cx
|
|
add ax, 2
|
|
adc dx, 0
|
|
|
|
push es
|
|
cmp byte [bp + ldFATType], 16
|
|
je .fat16
|
|
ja .fat32
|
|
|
|
.fat12:
|
|
; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2,
|
|
; so multiply cluster number by 3 first, then divide by 2.
|
|
; ax = cluster number (up to 12 bits set)
|
|
mov dx, ax
|
|
shl ax, 1 ; = 2n (up to 13 bits set)
|
|
add ax, dx ; = 2n+n = 3n (up to 14 bits set)
|
|
shr ax, 1 ; ax = byte offset into FAT (0..6129)
|
|
; CF = whether to use high 12 bits
|
|
sbb cx, cx ; = -1 iff CY, else 0
|
|
|
|
; Use the calculated byte offset as an offset into the FAT
|
|
; buffer, which holds all of the FAT's relevant data.
|
|
mov es, [bp + lsvFATSeg]
|
|
xchg bx, ax ; bx -> 16-bit word in FAT to load
|
|
|
|
; get 16 bits from FAT
|
|
mov ax, [es:bx]
|
|
|
|
and cl, 4 ; = 4 iff CY after shift, else 0
|
|
shr ax, cl ; shift down iff odd entry, else unchanged
|
|
and ax, 0FFFh ; insure it's only 12 bits
|
|
jmp short .gotvalue_zero_dx
|
|
|
|
.fat32:
|
|
; * 4 = byte offset into FAT (0--4000_0000h)
|
|
add ax, ax
|
|
adc dx, dx
|
|
.fat16:
|
|
; * 2 = byte offset into FAT (0--2_0000h)
|
|
add ax, ax
|
|
adc dx, dx
|
|
|
|
push ax
|
|
xchg ax, dx
|
|
xor dx, dx ; dx:ax = high word
|
|
div word [bp + bsBPB + bpbBytesPerSector]
|
|
xchg bx, ax ; bx = high word / divisor
|
|
pop ax ; dx = remainder, ax = low word
|
|
div word [bp + bsBPB + bpbBytesPerSector]
|
|
xchg dx, bx ; dx:ax = result, bx = remainder
|
|
; dx:ax = sector offset into FAT (0--200_0000h)
|
|
; bx = byte offset into FAT sector (0--8190)
|
|
cmp dx, si
|
|
jne @F ; read sector
|
|
cmp ax, di
|
|
je @FF ; sector is already buffered
|
|
@@:
|
|
mov si, dx
|
|
mov di, ax
|
|
mov word [bp + lsvFATSector + 2], dx
|
|
mov word [bp + lsvFATSector + 0], ax
|
|
|
|
push bx
|
|
add ax, [bp + bsBPB + bpbReservedSectors]
|
|
adc dx, 0
|
|
mov bx, [bp + lsvFATSeg]
|
|
call read_sector
|
|
pop bx
|
|
@@:
|
|
mov es, [bp + lsvFATSeg]
|
|
mov dx, [es:bx + 2]
|
|
mov ax, [es:bx] ; dx:ax = FAT32 entry
|
|
|
|
cmp byte [bp + ldFATType], 16 ; is it FAT32 ?
|
|
jne @F ; yes -->
|
|
.gotvalue_zero_dx:
|
|
xor dx, dx ; no, clear high word
|
|
@@:
|
|
pop es
|
|
|
|
; INP: dx:ax = cluster value, 2-based
|
|
; OUT: dx:ax -= 2 (makes it 0-based)
|
|
; CY iff invalid cluster
|
|
check_clust:
|
|
and dh, 0Fh
|
|
sub ax, 2
|
|
sbb dx, 0
|
|
|
|
cmp byte [bp + ldFATType], 16
|
|
ja .fat32
|
|
je .fat16
|
|
|
|
.fat12:
|
|
cmp ax, 0FF7h - 2
|
|
jmp short .common
|
|
|
|
.fat32:
|
|
cmp dx, 0FFFh
|
|
jb @F ; CY here means valid ...-
|
|
|
|
.fat16:
|
|
cmp ax, 0FFF7h - 2
|
|
@@: ; -... or if NC first, CY here also
|
|
.common:
|
|
cmc ; NC if valid
|
|
retn
|
|
|
|
|
|
align 16, db 38
|
|
end:
|