FreeDOS/test/ldosboot/iniload.asm

2478 lines
56 KiB
NASM

%if 0
Loader for finishing file system booting
by C. Masloch, 2017
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"
numdef DEBUG5
%idefine d5 _d 5,
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: ; word
lsvCommandLine: ; word
.start: equ $ - lsvclBufferLength
.signature: resw 1
ldLoadUntilSeg: ; word
lsvExtra: ; word
.partition: resb 1 ; byte
.flags: resb 1 ; byte
endstruc
lsvefNoDataStart equ 1
lsvefPartitionNumber equ 2
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 PARTINFO
piBoot: resb 1
piStartCHS: resb 3
piType: resb 1
piEndCHS: resb 3
piStart: resd 1
piLength: resd 1
endstruc
ptEmpty: equ 0
ptFAT12: equ 1
ptFAT16_16BIT_CHS: equ 4
ptExtendedCHS: equ 5
ptFAT16_CHS: equ 6
ptFAT32_CHS: equ 0Bh
ptFAT32: equ 0Ch
ptFAT16: equ 0Eh
ptExtended: equ 0Fh
ptLinux: equ 83h
ptExtendedLinux: equ 85h
%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 RPL, 1 ; support RPL and do not overwrite it
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 MULTIBOOT1, 1 ; use Multiboot specification loader
numdef MULTIBOOT2, 1 ; use Multiboot2 specification loader
numdef LSVEXTRA, 1 ; use lsvExtra field
; (needed if to use partition scanner)
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
numdef STACKSIZE, 2048
%if _STACKSIZE < 256
%error Too small stack size
%elif _STACKSIZE > 3 * 1024
; Note that we use 8 KiB for SectorSeg, 8 KiB for FATSeg,
; 512 bytes + (ebpbNew - bpbNew) for the boot sector,
; and a few paragraphs left for MCBs and headers. As the
; protocol is implemented with a 20 KiB reserved area (below
; EBDA / RPL / end of low memory), this results in a maximum
; stack size around 3 KiB (substantially below 4 KiB).
%error Too large stack size
%endif
numdef CHECKSUM, 0 ; include checksumming of kernel image
%if _CHECKSUM
%include "inicheck.mac"
%endif
strdef PAYLOAD_FILE, "lDOSLOAD.BIN"
numdef EXEC_OFFSET, 0
numdef EXEC_SEGMENT, 0
strdef INILOAD_SIGNATURE, "XX"
numdef IMAGE_EXE, 0
numdef IMAGE_EXE_CS, -16 ; relative-segment for CS
numdef IMAGE_EXE_IP, 256 +64 ; value for IP
; The next two are only used if _IMAGE_EXE_AUTO_STACK is 0.
numdef IMAGE_EXE_SS, -16 ; relative-segment for SS
numdef IMAGE_EXE_SP, 0FFFEh ; value for SP (0 underflows)
numdef IMAGE_EXE_AUTO_STACK, 0, 2048 ; allocate stack behind image
numdef IMAGE_EXE_MIN, 65536 ; how much to allocate for the process
%ifndef _IMAGE_EXE_MIN_CALC
%define _IMAGE_EXE_MIN_CALC \
(((_IMAGE_EXE_MIN \
- (payload.actual_end - payload) \
- 256 \
+ _IMAGE_EXE_AUTO_STACK) + 15) & ~15)
%endif
numdef IMAGE_EXE_MAX, -1
numdef SECOND_PAYLOAD_EXE, 0
numdef SECOND_PAYLOAD_EXE_CS, -16
numdef SECOND_PAYLOAD_EXE_IP, 256 +64
numdef SECOND_PAYLOAD_EXE_SS, -16
numdef SECOND_PAYLOAD_EXE_SP, 0FFFEh
numdef SECOND_PAYLOAD_EXE_AUTO_STACK, 0, 2048
numdef SECOND_PAYLOAD_EXE_MIN, 65536
%ifndef _SECOND_PAYLOAD_EXE_MIN_CALC
%define _SECOND_PAYLOAD_EXE_MIN_CALC \
(((_SECOND_PAYLOAD_EXE_MIN \
- (second_payload.actual_end - second_payload) \
- 256 \
+ _SECOND_PAYLOAD_EXE_AUTO_STACK) + 15) & ~15)
%endif
numdef SECOND_PAYLOAD_EXE_MAX, -1
strdef SECOND_PAYLOAD_FILE, "lDOSEXEC.COM"
strdef INILOAD_CFG, ""
%ifnidn _INILOAD_CFG, ""
%include _INILOAD_CFG
%endif
%if _IMAGE_EXE && _SECOND_PAYLOAD_EXE
%error Cannot use both of these.
%endif
%push
%define %$string _INILOAD_SIGNATURE
%strlen %$length %$string
%if %$length != 2
%error Invalid signature
%endif
%substr %$letter %$string 1
%if %$letter <= 32 || %$letter >= 127
%error Invalid signature
%endif
%substr %$letter %$string 2
%if %$letter <= 32 || %$letter >= 127
%error Invalid signature
%endif
%pop
cpu 8086
org 0
start:
db "MZ" ; exeSignature
; dec bp, pop dx
jmp strict short ms6_entry ; exeExtraBytes
; db 0EBh, 16h ; dw 16EBh
%if _IMAGE_EXE
; For now hardcoded to carry a .COM-like executable.
; Note: With _IMAGE_EXE_AUTO_STACK, the
; stack segment will be behind the image.
dw (payload.end - $$ + 511) / 512 ; exePages
dw 0 ; exeRelocItems
dw (payload -$$+0) >> 4 ; exeHeaderSize
dw (_IMAGE_EXE_MIN_CALC + 15) >> 4 ; exeMinAlloc
%if _IMAGE_EXE_MAX
dw _IMAGE_EXE_MAX ; exeMaxAlloc
%else
dw (_IMAGE_EXE_MIN_CALC + 15) >> 4 ; exeMaxAlloc
%endif
%if _IMAGE_EXE_AUTO_STACK
dw ((payload.actual_end - payload) \
+ _IMAGE_EXE_MIN_CALC \
- _IMAGE_EXE_AUTO_STACK + 15) >> 4 ; exeInitSS
; ss: payload size minus 512 (conservative, assume DOS
; treats bogus exeExtraBytes as below 512 bytes.)
; + exeMinAlloc
; - auto stack size
dw _IMAGE_EXE_AUTO_STACK ; exeInitSP
; sp = auto stack size (eg 800h)
%else
dw _IMAGE_EXE_SS ; exeInitSS
dw _IMAGE_EXE_SP ; exeInitSP
%endif
dw 0 ; exeChecksum
dw _IMAGE_EXE_IP, _IMAGE_EXE_CS ; exeInitCSIP
dw 0 ; exeRelocTable
%elif _SECOND_PAYLOAD_EXE
; For now hardcoded to carry a .COM-like executable.
; Note: With _SECOND_PAYLOAD_EXE_AUTO_STACK, the
; stack segment will be behind the image.
dw (second_payload.end - $$ + 511) / 512 ; exePages
dw 0 ; exeRelocItems
dw (second_payload -$$+0) >> 4 ; exeHeaderSize
dw (_SECOND_PAYLOAD_EXE_MIN_CALC + 15) >> 4 ; exeMinAlloc
%if _SECOND_PAYLOAD_EXE_MAX
dw _SECOND_PAYLOAD_EXE_MAX ; exeMaxAlloc
%else
dw (_SECOND_PAYLOAD_EXE_MIN_CALC + 15) >> 4 ; exeMaxAlloc
%endif
%if _SECOND_PAYLOAD_EXE_AUTO_STACK
dw ((second_payload.actual_end - second_payload) \
+ _SECOND_PAYLOAD_EXE_MIN_CALC \
- _SECOND_PAYLOAD_EXE_AUTO_STACK + 15) >> 4 ; exeInitSS
dw _SECOND_PAYLOAD_EXE_AUTO_STACK ; exeInitSP
%else
dw _SECOND_PAYLOAD_EXE_SS ; exeInitSS
dw _SECOND_PAYLOAD_EXE_SP ; exeInitSP
%endif
dw 0 ; exeChecksum
dw _SECOND_PAYLOAD_EXE_IP, _SECOND_PAYLOAD_EXE_CS ; exeInitCSIP
dw 0 ; exeRelocTable
%else
dw -1 ; exePages
dw 0 ; exeRelocItems
dw 0 ; exeHeaderSize
dw -1 ; exeMinAlloc
dw -1 ; exeMaxAlloc
dw -16, 0 ; exeInitSS, exeInitSP
dw 0 ; exeChecksum
dw 100h, -16 ; exeInitCSIP
dw 0 ; exeRelocTable
%endif
ms6_entry:
; This is the MS-DOS 6 / IBMDOS compatible entry point.
; Note that this supports FAT32 for PC-DOS 7.10!
; cs:ip = 70h:0
; ax:bx = first data sector of first cluster,
; including hidden sectors
; 0:7C00h-> boot sector with BPB,
; load unit field set, hidden sectors set
; (actually boot unit in dl; because the "MZ" signature
; destroys dl we assume it's in the BPB too)
; Either:
; dword [ss:sp] = 0:78h = 1Eh * 4 (IVT entry of int 1Eh)
; dword [ss:sp + 4] = old int 1Eh address
; Or:
; ds:si = old int 1Eh address
; 0:500h-> directory entry for BIO file
cli
cld
push dx
inc bp ; undo signature instructions
d3 call d3_display_two_characters
d3 test ax, "00"
; test dx, dx
; jnz @FF
; Actual DOS will always put a zero word on top of
; the stack. But when the debugger loads us as
; a flat format binary it may set up another
; stack segment or not initialise the stack slot.
; (So as to avoid corrupting the binary.)
; The offset check should suffice anyway.
call @F
@@:
pop cx
cmp cx, @B + 100h
je msdos1_com_entry
@@:
mov cx, cs
cmp cx, 60h
je freedos_entry
xor cx, cx
; Note: It has been observed that some IBMBIO.COM / IO.SYS
; boot sector loaders pass the int 1Eh address on the
; stack (like MS-DOS 7 loading does). So we detect
; whether the first dword (far pointer to IVT entry)
; matches and then assume that the second dword has
; the original int 1Eh address. Else, ds:si is used.
mov di, 1Eh * 4 ; -> IVT entry of int 1Eh
cmp dx, di ; int 1Eh address on stack ?
jne .dssi ; no -->
mov bp, sp
cmp word [bp + 2], cx ; segment 0 in next word ?
jne .dssi ; no -->
pop si
pop ds ; discard
pop si
pop ds ; get old int 1Eh address from stack
.dssi:
jmp ms6_continue1
error:
push cs
pop ds
mov si, msg.error
call disp_error
pop si
call disp_error
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, but it is not used here any longer.)
int 10h
; pop bp
jmp short .
msg:
.error: db "Load error: ", 0
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
%else
mov byte [bp + ldHasLBA], 0
%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:
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_2
%if !_LBA
.err_CY_2: equ .err_CY_1
%endif
%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]
mov ah, 42h ; 13.42 extensions read
%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
; 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
.err_CY_2:
jc .err_CY_1
%ifn _CHS
.err_CY_1: equ .err
%endif
pop es
; pop cx
call .sectorseg_helper
.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:
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_NZ_2
%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
.err_NZ_2:
jnz .err_NZ_1 ; 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
mov ax, 0201h ; read one sector
%if _CHS_RETRY
call .int13_retry
%else
int 13h
%endif
jnc .done
cmp ah, 9 ; data boundary error?
.err_NZ_1:
jne .err
push es ; user buffer
mov es, word [bp + ldSectorSeg]
mov ax, 0201h
%if _CHS_RETRY
call .int13_retry
%else
int 13h
%endif
.err_CY_1:
jc .err
pop es
call .sectorseg_helper
.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
.sectorseg_helper:
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
.err:
error_diskaccess:
call error
db "Disk read error.", 0
error_shortfile:
call error
db "File is too short.", 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
%assign num 512-($-$$)
%if num >= 3
%assign num num - 3
%warning num bytes in front of ms7_entry
_fill 512 - 3,38,start
error_outofmemory_j1:
jmp error_outofmemory
%else
error_outofmemory_j1: equ error_outofmemory
%warning num bytes in front of ms7_entry
%endif
_fill 512,38,start
ms7_entry:
; This is the MS-DOS 7 compatible entry point.
; Supports FAT32 too.
; cs:ip = 70h:200h
; (si:)di = first cluster of load file
; dwo [ss:bp - 4] = first data sector (with hidden sectors)
; dwo [ss:sp] = 0:78h (IVT entry of int 1Eh)
; dwo [ss:sp + 4] = old int 1Eh address
; ss:bp -> boot sector with (E)BPB,
; load unit field set, hidden sectors set
inc dx
dec dx ; "BJ" signature (apparently not about FAT32 support)
cli
cld
jmp .continue ; jump to handler above 600h (sector loads 800h bytes)
.ms6_common:
mov ax, cs
add ax, (3 * 512) >> 4
.continue2_set_extra_and_empty_cmdline:
%if _LSVEXTRA
and word [bp + lsvExtra], 0
%endif
and word [bp + lsvCommandLine], 0
.continue2:
mov word [bp + lsvLoadSeg], ax
xor ax, ax
mov word [bp + lsvFATSeg], ax ; initialise to zero (for FAT12)
dec ax
mov word [bp + lsvFATSector + 0], ax
mov word [bp + lsvFATSector + 2], ax ; initialise to -1
; Actually it seems that the MS-DOS 7 loaders load 4 sectors
; instead of only three (as the MS-DOS 6 loaders do).
; We use this to store specific handling in that last sector.
jmp ldos_entry.ms7_common
finish_continue:
add ax, bx ; = cs + rounded up length
sub ax, word [bp + ldLoadTop] ; = paras to move down
jbe short finish_load
push ax
neg ax
add ax, bx ; ax = cs - paras to move down
jnc short error_outofmemory_j1
mov di, relocate_to
push ax
push di ; dword on stack: relocate_to
cmp ax, 60h + 1
jb short error_outofmemory_j1
dec ax ; one less to allow relocator
mov es, ax
finish_relocation:
xor di, di ; es:di -> where to put relocator
push es
push di ; dword on stack: relocator destination
mov ds, bx ; ds => unrelocated cs
inc ax ; ax => where to relocate to
mov si, relocator ; ds:si -> relocator
relocator_size equ relocator.end - relocator
%rep (relocator_size + 1) / 2
movsw ; place relocator
%endrep
mov es, ax
xor di, di ; -> where to relocate to
xor si, si ; ds:si = cs:0
mov cx, word [bp + lsvLoadSeg]
sub cx, bx ; length of currently loaded fragment
mov bx, 1000h
mov ax, cx
cmp ax, bx ; > 64 KiB ?
jbe @F
mov cx, bx ; first relocate the first 64 KiB
@@:
sub ax, cx ; how much to relocate later
shl cx, 1
shl cx, 1
shl cx, 1 ; how much to relocate first,
; << 3 == convert paragraphs to words
retf ; jump to relocator
; ds => first chunk of to be relocated data
; es => first chunk of relocated data
; bx = 1000h (64 KiB >> 4)
; ax = number of paragraphs after first chunk (in next chunk)
relocate_to:
@@:
mov dx, es
add dx, bx
mov es, dx ; next segment
mov dx, ds
add dx, bx
mov ds, dx ; next segment
sub ax, bx ; = how much to relocate after this round
mov cx, 1000h << 3 ; in case another full 64 KiB to relocate
jae @F ; another full 64 KiB to relocate -->
add ax, bx ; restore
shl ax, 1
shl ax, 1
shl ax, 1 ; convert paragraphs to words
xchg cx, ax ; cx = that many words
xor ax, ax ; no more to relocate after this round
@@:
xor si, si
xor di, di
rep movsw ; relocate next chunk
test ax, ax ; another round needed?
jnz @BB ; yes -->
pop ax
sub word [bp + lsvLoadSeg], ax
push ss
pop ds
; ds = ss
; cs = low enough to complete load
; lsvLoadSeg => after last loaded fragment
; ldLoadTop => after last available memory
; ldParaPerSector = initialised
; word [ss:sp] = payload.actual_end in paras
finish_load:
pop ax
mov bx, cs
add ax, bx
mov word [bp + ldLoadUntilSeg], ax
; ldLoadUntilSeg => after last to-be-loaded paragraph
mov bx, word [bp + lsvLoadSeg]
mov word [bp + ldLoadingSeg], bx
cmp bx, ax
jae short loaded_all_if_ae ; (for FreeDOS entrypoint) already loaded -->
mov word [bp + ldLoadingSeg], cs
mov ax, [bp + lsvFirstCluster]
mov dx, [bp + lsvFirstCluster + 2]
mov di, [bp + lsvFATSector]
mov si, [bp + lsvFATSector + 2]
call check_clust
jc short error_badchain_j
skip_next_clust:
call clust_to_first_sector
push cx
push bx
mov cx, [bp + ldClusterSize]
skip_next_sect:
push cx
mov bx, [bp + ldLoadingSeg]
cmp bx, [bp + ldLoadUntilSeg]
jae loaded_all.3stack
mov cx, bx
add cx, [bp + ldParaPerSector]
cmp cx, [bp + lsvLoadSeg]
ja skipped_all
inc ax ; emulate read_sector:
jnz @F
inc dx ; dx:ax += 1
@@:
mov bx, cx ; bx += paras per sector
mov [bp + ldLoadingSeg], bx
pop cx
loop skip_next_sect
pop bx
pop cx
call clust_next
jnc skip_next_clust
end_of_chain:
inc ax
inc ax
test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh,
; clear in 0, 1, and 0FFF_FFF7h
jz short error_badchain_j
mov bx, [bp + ldLoadingSeg]
cmp bx, [bp + ldLoadUntilSeg]
loaded_all_if_ae:
jae loaded_all
jmp error_shortfile
skipped_all:
call read_sector
; we can depend on the fact that at least
; up to end was already loaded, so this
; (successful) read_sector call loaded
; at least 32 bytes starting at end.
; therefore, we can put part of the
; remaining handler into these 32 bytes.
jmp skipped_all_continue
error_badchain_j:
jmp error_badchain
; ds => first chunk of to be relocated data
; es => first chunk of relocation destination
; cx = number of words in first chunk
relocator:
rep movsw
retf ; jump to relocated relocate_to
.end:
; 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_j
xchg dx, ax
add dx, cx
.cy_error_badchain:
jc short error_badchain_j
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
ms6_continue1:
mov es, cx
mov bp, 7C00h
mov word [es:di], si
mov word [es:di + 2], ds ; restore old int 1Eh address
mov ss, cx
mov sp, 7C00h + lsvCommandLine
mov dx, word [es:500h + 26]
mov cx, word [es:500h + 20]
mov word [bp + lsvFirstCluster + 0], dx
mov word [bp + lsvFirstCluster + 2], cx
sub bx, word [bp + bsBPB + bpbHiddenSectors + 0]
sbb ax, word [bp + bsBPB + bpbHiddenSectors + 2]
mov word [bp + lsvDataStart + 0], bx
mov word [bp + lsvDataStart + 2], ax
jmp ms7_entry.ms6_common
%assign num 1020-($-$$)
%warning num bytes in front of ldos_entry
_fill 1020,38,start
dw "lD" ; always this signature (word [1020] == 446Ch)
dw _INILOAD_SIGNATURE
; two printable non-blank ASCII characters
; (ie both bytes in the range 21h..7Eh)
; Rx = RxDOS kernel
; FD = FreeDOS kernel
; TP = TestPL
; (lD)eb = lDebug
; (lD)Db = lDDebug
%if ($ - $$) != 1024
%error Invalid signature
%endif
ldos_entry:
cli
cld
; cs:ip = 70h:400h
; dwo [ss:bp - 4] = first data sector (without hidden sectors)
; wo [ss:bp - 6] = load_seg, => after last loaded data
; wo [ss:bp - 8] = fat_seg, 0 if invalid
; initialised to 0 by MS-DOS 6, 7, FreeDOS entrypoints
; fat_sector is not used for FAT12 !
; wo [ss:bp - 12] = fat_sector, -1 if none (FAT16)
; dwo [ss:bp - 12] = fat_sector, -1 if none (FAT32)
; initialised to -1 by MS-DOS 6, 7, FreeDOS entrypoints
; wo [ss:bp - 16] = first_cluster (FAT16, FAT12)
; dwo [ss:bp - 16] = first_cluster (FAT32)
; initialised to 0 by FreeDOS entrypoint
;
; Extension 1:
; lsvExtra (word [ss:bp - 18]) may be set,
; not sure about interface yet. allows
; to not initialise data start, or to specify
; a partition number instead of offset
;
; Extension 2:
; word [ss:bp - 20] = signature "CL" if valid
; 256bytes [ss:bp - 20 - 256] = ASCIZ command line string
xor ax, ax
push ax ; push into lsvExtra if sp -> LSV
%if _LSVEXTRA
mov word [bp + lsvExtra], ax
; byte [ss:bp - 18] = partition number
; byte [ss:bp - 17] = flags for initialisation
%endif
push ax ; push into lsvCommandLine if sp -> LSV
.ms7_common:
mov ax, cs
mov cx, word [bp + lsvLoadSeg]
sub cx, ax
cmp cx, (end -$$+0) >> 4
jae @F
error_notfullyloaded:
call error
db "Initial loader not fully loaded.", 0
@@:
mov bx, (payload.actual_end -$$+0 +15) >> 4
cmp cx, bx
jbe @F
add bx, ax
mov word [bp + lsvLoadSeg], bx
@@:
init_memory:
; Get conventional memory size and store it
int 12h
mov cl, 6
shl ax, cl
%if _RPL
xor si, si
xchg dx, ax
mov ds, si
lds si, [4 * 2Fh]
add si, 3
lodsb
cmp al, 'R'
jne .no_rpl
lodsb
cmp al, 'P'
jne .no_rpl
lodsb
cmp al, 'L'
jne .no_rpl
mov ax, 4A06h
int 2Fh
.no_rpl:
xchg ax, dx
%endif
push ax
; sub ax, 32 >> 4 ; make space for two MCBs: top MCB, RPL MCB
dec ax
dec ax
mov cx, ax
sub ax, (8192 + 16) >> 4
dec cx ; => last paragraph of higher buffer (16-byte trailer)
mov dx, ax ; => first paragraph of higher buffer
mov bx, cx
and dx, 0F000h ; 64 KiB chunk of first paragraph of higher buffer
and bx, 0F000h ; 64 KiB chunk of last paragraph of higher buffer
cmp bx, dx ; in same chunk?
mov bx, ax
je .gotsectorseg ; yes, use higher buffer as sector buffer ->
; bx = use higher buffer as FAT buffer
inc bx ; => 8 KiB buffer (no 16-byte trailer)
sub ax, (8192 + 32) >> 4
; 32 = leave space for higher buffer MCB + header
; +16 from the above calcs for 16-byte trailer
mov cx, ax ; use lower buffer as sector buffer
jmp short .gotsegs
.gotsectorseg:
; ax = use higher buffer as sector buffer
sub bx, (8192 + 32) >> 4 ; use lower buffer as FAT buffer
; 32 = leave space for higher buffer MCB + header
mov cx, bx
; ax = sector seg
; bx = FAT seg
; cx = the lower of the two
.gotsegs:
sub cx, (+_STACKSIZE -LOADCMDLINE + 512 + (ebpbNew - bpbNew) + 32 + 15) >> 4
; +_STACKSIZE = stack space
; -LOADCMDLINE = load cmd line + data + lsv space
; 512 = boot sector (allows finding filename)
; (ebpbNew - bpbNew) = additional space for BPBN moving
; 32 = leave space for lower buffer MCB + header
; cx = stack seg
dec cx ; leave space for stack + BPB buffer MCB
cmp cx, word [bp + lsvLoadSeg]
jnb @F
.error_outofmemory:
jmp error_outofmemory
@@:
push ax
mov dx, ss
mov ax, bp
add ax, 512 + 15
jnc @F
mov ax, 1_0000h >> 1
db __TEST_IMM16 ; (skip one shr)
@@:
shr ax, 1
shr ax, 1
shr ax, 1
shr ax, 1
add dx, ax
cmp dx, cx
ja .error_outofmemory
; note that the next conditional doesn't jump for lsvFATSeg = 0
mov dx, word [bp + lsvFATSeg]
add dx, (8192) >> 4
cmp dx, cx
ja .error_outofmemory
pop ax
pop dx ; top of memory (=> start of RPL, EBDA, video memory)
inc cx ; => stack + BPB buffer
push ss
pop ds
lea si, [bp + lsvCommandLine.start]
mov es, cx
mov di, _STACKSIZE - LOADCMDLINE + ldCommandLine.start
; -> cmd line target
push cx ; top of memory below buffers
mov cx, (LOADCMDLINE_size + 1) >> 1
rep movsw ; copy cmd line
%if lsvCommandLine.start + fromwords(words(LOADCMDLINE_size)) != lsvCommandLine.signature
%error Unexpected structure layout
%endif
cmp word [si], lsvclSignature
je @F ; if command line given -->
mov byte [es: _STACKSIZE - LOADCMDLINE + ldCommandLine.start ], cl
; truncate as if empty line given
dec cx ; cl = 0FFh
@@:
mov byte [es:di - 1], cl
; remember whether command line given
; = 0 if given (also truncates if too long)
; = 0FFh if not given
push ax
%if lsvCommandLine.signature + 2 != lsvExtra
%error Unexpected structure layout
%endif
lodsw
; lea si, [bp + lsvExtra]
; ds:si -> lsv + BPB
mov di, _STACKSIZE - LOADCMDLINE + lsvExtra
; es:di -> where to place lsv
mov cx, (- lsvExtra + 512 + 1) >> 1
rep movsw ; copy lsv (including lsvExtra) and BPB
xor ax, ax
mov cx, ((ebpbNew - bpbNew + 15) & ~15) >> 1
rep stosw ; initialise area behind sector (left so for FAT32)
pop ax
pop cx
mov ss, cx
mov sp, _STACKSIZE
; -> above end of stack space
mov bp, _STACKSIZE - LOADCMDLINE
; -> BPB, above end of lsv
dec cx ; => space for stack + BPB buffer MCB
sti
; ax => sector buffer
; bx => FAT buffer
; cx => above end of memory available for load
; dx => above end of memory used by us
mov word [bp + ldMemoryTop], dx
mov word [bp + ldLoadTop], cx
mov word [bp + ldSectorSeg], ax
mov ds, word [bp + lsvFATSeg]
xor si, si ; ds:si -> FAT buffer
mov es, bx
xor di, di ; es:di -> where to move
mov cx, 8192 >> 1
rep movsw
mov word [bp + lsvFATSeg], bx
push ds ; to check for word [lsvFATSeg] == zero later on
push ss
pop es
push ss
pop ds
mov bx, [bp + bsBPB + bpbSectorsPerFAT]
test bx, bx
jz .is_fat32
lea si, [bp + 510] ; -> last source word
lea di, [si + (ebpbNew - bpbNew)] ; -> last dest word
mov cx, (512 - bsBPB - bpbNew + 1) >> 1
; move sector up, except common BPB start part
%if ((512 - bsBPB - bpbNew + 1) >> 1) <= 20
%fatal Need AMD erratum 109 workaround
%endif
std ; AMD erratum 109 handling not needed
rep movsw
cld
mov word [bp + lsvFirstCluster + 2], cx
mov word [bp + lsvFATSector + 2], cx
mov word [bp + bsBPB + ebpbSectorsPerFATLarge], bx
mov word [bp + bsBPB + ebpbSectorsPerFATLarge + 2], cx
mov word [bp + bsBPB + ebpbFSFlags], cx
; FSVersion, RootCluster, FSINFOSector, BackupSector, Reserved:
; uninitialised here (initialised by loaded_all later)
.is_fat32:
%if 1 || _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 (used by the FAT12
; FAT loader, or by finish_load later).
%endif
%if _LSVEXTRA
test byte [bp + lsvExtra.flags], -1
jz @F
mov cx, cs
mov ax, word [bp + lsvLoadSeg]
sub ax, cx
cmp ax, (end_of_handle_lsv_extra_flags + 15 -$$+0) >> 4
jb error_notfullyloaded
call handle_lsv_extra_flags
@@:
%endif
; adjusted sectors per cluster (store in a word,
; and decode EDR-DOS's special value 0 meaning 256)
xor ax, ax
mov al, [bp + bsBPB + bpbSectorsPerCluster]
dec al
inc ax
mov [bp + ldClusterSize], ax
; 16-byte paragraphs per sector
mov ax, [bp + bsBPB + bpbBytesPerSector]
mov cl, 4
shr ax, cl
mov [bp + ldParaPerSector], ax
; total sectors
; After the prior shr instruction, ax is < 8000h,
; so the following cwd always zeros dx.
cwd
mov ax, [bp + bsBPB + bpbTotalSectors]
test ax, ax
jnz @F
mov dx, [bp + bsBPB + bpbTotalSectorsLarge + 2]
mov ax, [bp + bsBPB + bpbTotalSectorsLarge]
; fall through and let it overwrite the field with the
; already current contents. saves a jump.
@@:
mov [bp + bsBPB + bpbTotalSectorsLarge + 2], dx
mov [bp + bsBPB + bpbTotalSectorsLarge], ax
; dx:ax = total sectors
cmp word [bp + bsBPB + bpbSectorsPerFAT], 0
mov byte [bp + ldFATType], 32
je .got_fat_type
; dx:ax = total amount of sectors
sub ax, word [bp + lsvDataStart]
sbb dx, word [bp + lsvDataStart + 2]
; dx:ax = total amount of data sectors
mov bx, ax
xchg ax, dx
xor dx, dx
div word [bp + ldClusterSize]
xchg bx, ax
div word [bp + ldClusterSize]
; bx:ax = quotient, dx = remainder
; bx:ax = number of clusters
test bx, bx
jz @F
.badclusters:
jmp error_badclusters
@@:
cmp ax, 0FFF7h - 2
ja .badclusters
mov byte [bp + ldFATType], 16
cmp ax, 0FF7h - 2
ja .got_fat_type
mov byte [bp + ldFATType], 12
pop ax
test ax, ax
jnz .got_fat12
; lsvFATSeg was zero! This means the FAT isn't loaded yet.
; Load the entire FAT into memory. This is easily feasible for FAT12,
; as the FAT can only contain at most 4096 entries.
; (The exact condition should be "at most 4087 entries", or with a
; specific FF7h semantic, "at most 4088 entries"; the more reliable
; and portable alternative would be "at most 4080 entries".)
; Thus, no more than 6 KiB need to be read, even though the FAT size
; as indicated by word[sectors_per_fat] could be much higher. The
; first loop condition below is to correctly handle the latter case.
; (Sector size is assumed to be a power of two between 32 and 8192
; bytes, inclusive. An 8 KiB buffer is necessary if the sector size
; is 4 or 8 KiB, because reading the FAT can or will write to 8 KiB
; of memory instead of only the relevant 6 KiB. This is always true
; if the sector size is 8 KiB, and with 4 KiB sector size it is true
; iff word[sectors_per_fat] is higher than one.)
mov di, 6 << 10 ; maximum size of FAT12 to load
mov cx, [bp + bsBPB + bpbSectorsPerFAT]
; maximum size of this FS's FAT
; If we're here, then ax = 0 (jnz jumped if not),
; so this cwd always zeros dx.
cwd
mov ax, [bp + bsBPB + bpbReservedSectors]; = first FAT sector
mov bx, [bp + lsvFATSeg]
@@:
call read_sector ; read next FAT sector
sub di, [bp + bsBPB + bpbBytesPerSector]
; di = bytes still left to read
jbe @F ; if none -->
; (jbe means jump if CF || ZF)
loop @B ; if any FAT sector still remains -->
@@: ; one of the limits reached; FAT read
.got_fat12:
db __TEST_IMM8 ; skip pop ax
.got_fat_type:
pop ax
mov ax, (payload.actual_end -$$+0 +15) >> 4
push ax
; on stack: payload.actual_end in paragraphs
mov bx, [bp + ldParaPerSector]
dec bx ; para per sector - 1
add ax, bx ; round up
not bx ; ~ (para per sector - 1)
and ax, bx ; rounded up,
; ((payload.actual_end -$$+0 +15) >> 4 + pps - 1) & ~ (pps - 1)
mov bx, cs
jmp finish_continue
%assign num 1024+512-4-($-$$)
%warning num bytes in front of end
_fill 1024+512-4,38,start
; -4 is for the following two instructions.
; they want execution to fall through to
; load_next_clust_continue. placing them
; at the very end of the 3 sectors allows
; not to use a jump here.
load_next_clust:
call clust_to_first_sector
push cx
align 16, nop
_fill 1024+512,90h,start ; check that we are at 3 sectors end
end:
load_next_clust_continue:
push bx
mov cx, [bp + ldClusterSize]
load_next_sect:
push cx
mov bx, [bp + ldLoadingSeg]
cmp bx, [bp + ldLoadUntilSeg]
jae loaded_all.3stack_j
call read_sector
skipped_all_continue:
mov [bp + ldLoadingSeg], bx
pop cx
loop load_next_sect
pop bx
pop cx
call clust_next
jnc load_next_clust
jmp end_of_chain
%if ($ - end) > 32
%error load_next part exceeds end+32
%endif
; if we jump to here, then the whole file has
; been loaded, so this jump doesn't have to
; stay in the 32 bytes after the end label.
loaded_all.3stack_j:
jmp loaded_all.3stack
ms7_entry.continue:
pop bx
pop es
pop word [es:bx]
pop word [es:bx + 2]
lea bx, [bp + lsvCommandLine]
cmp sp, bx
jbe @F
mov sp, bx
@@:
mov word [bp + lsvFirstCluster + 0], di
mov word [bp + lsvFirstCluster + 2], si
mov ax, word [bp + bsBPB + bpbHiddenSectors + 0]
mov dx, word [bp + bsBPB + bpbHiddenSectors + 2]
sub word [bp + lsvDataStart + 0], ax
sbb word [bp + lsvDataStart + 2], dx
mov ax, cs
add ax, (4 * 512) >> 4
jmp ms7_entry.continue2_set_extra_and_empty_cmdline
%assign num 2046-($-$$)
%warning num bytes in front of end2
_fill 2046,38,start
dw "MS" ; signature of MS-DOS 7 load
align 16, db 38
end2:
; This handling is in the second header part,
; behind the needed part to finish loading.
; It is only used when the file is completely loaded.
loaded_all.3stack:
pop ax
pop ax
pop ax
loaded_all:
mov ax, word [bp + bsBPB + bpbSectorsPerFAT]
test ax, ax
jz .fat32
xor ax, ax
push ss
pop es
lea di, [bp + bsBPB + ebpbFSFlags]
mov cx, (EBPB_size - ebpbFSFlags) / 2
rep stosw
; initialise ebpbFSFlags (reinit), ebpbFSVersion,
; ebpbRootCluster, ebpbFSINFOSector, ebpbBackupSector,
; ebpbReserved
.fat32:
%if _CHECKSUM
push cs
pop ds
mov si, checksumheader
mov cx, CHECKSUMHEADER_size / 2
xor bx, bx
@@:
cmp si, ..@checksumfield
lodsw
jne @F
xor ax, ax
@@:
add bx, ax
loop @BB
test bx, bx
jnz error_header_checksum_failed
testopt [..@checksumtype], 8000h
jnz @F
call checksum_crc16_6_paragraphs_start_cs
int3
push cs
pop ds
cmp ax, word [..@checksumfield]
jne error_data_checksum_failed
..@data_checksum_ignore_failure_debugger:
@@:
%endif
push ss
pop es
lea di, [bp + ldCommandLine.start]
mov cx, lsvclBufferLength
xor ax, ax
push word [bp + ldCommandLine.start + lsvclBufferLength - 1]
; get sentinel (whether command line given)
repne scasb ; scan for terminator
pop ax ; al = 0FFh if no command line given
; al = 0 else
rep stosb ; clear remainder of buffer
mov ax, cs
add ax, ((payload -$$+0) >> 4) + _EXEC_SEGMENT
push ax
%if _EXEC_OFFSET
mov ax, _EXEC_OFFSET
%else
xor ax, ax
%endif
push ax
; cs:ip = xxxxh:_EXEC_OFFSET
; entire payload loaded (payload -- payload.actual_end)
; LOADSTACKVARS and LOADDATA and EBPB and ebpbNew BPBN set
; LOADCMDLINE set (ASCIZ, up to 255 bytes + 1 byte terminator)
; word [ldCommandLine.start] = 0FF00h if had invalid signature
retf
%if _CHECKSUM
error_header_checksum_failed:
call error
db "Header checksum failed.", 0
error_data_checksum_failed:
stc
int3
jnc ..@data_checksum_ignore_failure_debugger
call error
db "Data checksum failed.", 0
%endif
freedos_entry:
; This is the FreeDOS compatible entry point.
; Supports FAT32 too.
; cs:ip = 60h:0
; whole load file loaded
; first cluster of load file: not given!
; first data sector: not given!
; int 1Eh not modified, original address: not given!
; bl = load unit (not used by us)
; ss:bp -> boot sector with (E)BPB,
; load unit field set, hidden sectors set
; (usually at 1FE0h:7C00h)
; NEW: word [ss:bp - 14h] = "CL" to indicate command line
; then ss:bp - 114h -> 256 byte ASCIZ string
lea bx, [bp + lsvCommandLine.start]
; ss:bx -> command line buffer, if any
cmp bp, - lsvCommandLine.start
; enough data below bp to hold buffer ?
jb @F ; no -->
cmp sp, bx ; sp below-or-equal would-be buffer ?
jbe .canbevalid ; yes, can be valid --> (and word access valid)
@@:
cmp bp, - lsvCommandLine.signature
; enough data below bp to hold our lsv ?
jae @F ; yes -->
test bp, 1 ; valid to access even-aligned words ?
jnz .error ; maybe not -->
@@:
and word [bp + lsvCommandLine.signature], 0
; invalidate signature
.canbevalid:
cmp word [bp + lsvCommandLine.signature], "CL"
; valid signature ?
je @F ; yes, keep bx pointing at buffer
lea bx, [bp + lsvCommandLine.signature]
; no, ss:bx -> lsv with signature
@@:
cmp sp, bx ; sp below-or-equal needed stack frame ?
jbe @F ; yes -->
and bl, ~1 ; make even-aligned stack (rounding down)
mov sp, bx ; change sp
@@:
d3 call d3_display_two_characters
d3 test ax, "F0"
xor cx, cx
mov word [bp + lsvFirstCluster + 0], cx
mov word [bp + lsvFirstCluster + 2], cx
%if _LSVEXTRA
mov word [bp + lsvExtra], lsvefNoDataStart << 8
%else
call calculate_data_start
%endif
.multiboot_entry:
mov ax, cs
add ax, (payload.actual_end -$$+0 +15) >> 4
jmp ms7_entry.continue2
.error:
call error
asciz "Invalid base pointer in FreeDOS entrypoint."
%if _LSVEXTRA
handle_lsv_extra_flags:
test byte [bp + lsvExtra.flags], lsvefPartitionNumber
jz @F
call parse_partition_number
@@:
test byte [bp + lsvExtra.flags], lsvefNoDataStart
jz @F
call calculate_data_start
@@:
retn
parse_partition_number:
xor ax, ax
mov word [bp + bsBPB + bpbHiddenSectors], ax
mov word [bp + bsBPB + bpbHiddenSectors + 2], ax
cmp byte [bp + bsBPB + ebpbNew + bpbnBootUnit], -1
jne @F
mov byte [bp + bsBPB + ebpbNew + bpbnBootUnit], 80h
mov word [bp + bsBPB + bpbCHSSectors], ax
mov word [bp + bsBPB + bpbCHSHeads], ax
call query_geometry
@@:
%if !_LBA_SKIP_CHECK
test byte [bp + ldHasLBA], 1
jnz @F
%endif
mov ax, word [bp + bsBPB + bpbCHSSectors]
mov dx, word [bp + bsBPB + bpbCHSHeads]
; following is from lDebug 0c0930773929 boot.asm
overridedef DEBUG5, 0
%define load_unit (bp + bsBPB + ebpbNew + bpbnBootUnit)
%define load_sectorsize (bp + bsBPB + bpbBytesPerSector)
%define load_sectorsizepara (bp + ldParaPerSector)
test ax, ax
jz .invalid_sectors
cmp ax, 63
ja .invalid_sectors
test dx, dx
jz .invalid_heads
cmp dx, 100h
ja .invalid_heads
@@:
mov ax, word [bp + ldSectorSeg] ; ax => sector seg
dec ax ; ax => sector seg - 16
mov es, ax
xor ax, ax
mov bx, 16
d5 call d5dumpregs
d5 call d5message
d5 asciz 13,10,"In query_geometry 0",13,10
mov di, bx
mov cx, (8192 + 2) >> 1
; es:bx -> auxbuff, es:di = same
rep stosw ; fill buffer, di -> behind (auxbuff+8192+2)
mov ax, 0201h ; read sector, 1 sector
inc cx ; sector 1 (1-based!), cylinder 0 (0-based)
mov dh, 0 ; head 0 (0-based)
mov dl, [load_unit]
stc
call .int13_retry
jc .access_error
std ; _AMD_ERRATUM_109_WORKAROUND does not apply
mov word [es:bx - 2], 5E5Eh ; may overwrite last 2 bytes at line_out_end
scasw ; -> auxbuff+8192 (at last word to sca)
d5 call d5dumpregs
d5 call d5message
d5 asciz 13,10,"In query_geometry 1",13,10
mov cx, (8192 + 2) >> 1
xor ax, ax
repe scasw
add di, 4 ; di -> first differing byte (from top)
cld
push di
mov di, bx
mov cx, (8192 + 2) >> 1
dec ax ; = FFFFh
rep stosw
mov ax, 0201h
inc cx
mov dh, 0
mov dl, [load_unit]
stc
call .int13_retry
jc .access_error
std ; _AMD_ERRATUM_109_WORKAROUND does not apply
scasw ; di -> auxbuff+8192 (last word to sca)
d5 call d5dumpregs
d5 call d5message
d5 asciz 13,10,"In query_geometry 2",13,10
pop dx
mov ax, -1
mov cx, (8192 + 2) >> 1
repe scasw
%if 0
AAAB
^
sca B, match
^
sca B, mismatch
^
stop
%endif
add di, 4 ; di -> first differing byte (from top)
cld
%if 0
0000000000000
AAAAAAAA00000
^
FFFFFFFFFFFFF
AAAAAAAA00FFF
^
%endif
cmp dx, di ; choose the higher one
jae @F
mov dx, di
@@:
sub dx, bx ; dx = sector size
d5 call d5dumpregs
d5 call d5message
d5 asciz 13,10,"In query_geometry 3",13,10
cmp dx, 8192 + 2
jae .sector_too_large
mov ax, 32
cmp dx, ax
jb .sector_too_small
@@:
cmp dx, ax
je .got_match
cmp ax, 8192
jae .sector_not_power
shl ax, 1
jmp @B
.got_match:
mov word [load_sectorsize], ax
mov cl, 4
shr ax, cl
mov word [load_sectorsizepara], ax
resetdef
push cs
pop ds
push cs
pop es
mov cx, .per_partition
call scan_partitions
mov di, partition_offset
mov dx, word [di + 2]
mov ax, word [di]
cmp ax, -1
jne @F
cmp dx, -1
jne @F
push ss
pop es
mov di, bp
xor ax, ax
mov cx, 512 / 2 + (((ebpbNew - bpbNew + 15) & ~15) >> 1)
push word [bp + bsBPB + ebpbNew + bpbnBootUnit]
push word [bp + bsBPB + bpbCHSSectors]
push word [bp + bsBPB + bpbCHSHeads]
rep stosw
pop word [bp + bsBPB + bpbCHSHeads]
pop word [bp + bsBPB + bpbCHSSectors]
pop bx
mov byte [bp + bsBPB + ebpbNew + bpbnBootUnit], bl
mov word [bp + bsBPB + bpbHiddenSectors], dx
mov word [bp + bsBPB + bpbHiddenSectors + 2], dx
jmp .invalid_return
@@:
push dx
push ax
mov bx, [bp + ldSectorSeg]
call read_ae_512_bytes
push es
pop ds
xor si, si
push ss
pop es
mov di, bp
mov cx, 512 / 2
pop ax
pop dx
push word [bp + bsBPB + ebpbNew + bpbnBootUnit]
push word [bp + bsBPB + bpbCHSSectors]
push word [bp + bsBPB + bpbCHSHeads]
rep movsw
push ax
xor ax, ax
mov cx, ((ebpbNew - bpbNew + 15) & ~15) >> 1
rep stosw ; initialise area behind sector (left so for FAT32)
pop ax
pop word [bp + bsBPB + bpbCHSHeads]
pop word [bp + bsBPB + bpbCHSSectors]
mov word [bp + bsBPB + bpbHiddenSectors], ax
mov word [bp + bsBPB + bpbHiddenSectors + 2], dx
push ss
pop ds
push ss
pop es
mov bx, [bp + bsBPB + bpbSectorsPerFAT]
test bx, bx
jz .not_fat32
lea si, [bp + 510] ; -> last source word
lea di, [si + (ebpbNew - bpbNew)] ; -> last dest word
mov cx, (512 - bsBPB - bpbNew + 1) >> 1
; move sector up, except common BPB start part
%if ((512 - bsBPB - bpbNew + 1) >> 1) <= 20
%fatal Need AMD erratum 109 workaround
%endif
std ; AMD erratum 109 handling not needed
rep movsw
cld
mov word [bp + lsvFirstCluster + 2], cx
mov word [bp + lsvFATSector + 2], cx
mov word [bp + bsBPB + ebpbSectorsPerFATLarge], bx
mov word [bp + bsBPB + ebpbSectorsPerFATLarge + 2], cx
mov word [bp + bsBPB + ebpbFSFlags], cx
; FSVersion, RootCluster, FSINFOSector, BackupSector, Reserved:
; uninitialised here (initialised by loaded_all later)
.not_fat32:
pop bx
mov byte [bp + bsBPB + ebpbNew + bpbnBootUnit], bl
mov ah, lsvefNoDataStart
mov al, byte [cs:partition_type]
cmp al, ptFAT12
je @F
cmp al, ptFAT16_16BIT_CHS
je @F
cmp al, ptFAT16_CHS
je @F
cmp al, ptFAT32_CHS
je @F
cmp al, ptFAT32
je @F
cmp al, ptFAT16
je @F
.invalid_return:
xor ax, ax
mov word [bp + lsvDataStart], ax
mov word [bp + lsvDataStart + 2], ax
@@:
mov byte [bp + lsvExtra.flags], ah
%if _CHECKSUM
push cs
pop es
mov di, scanparttab_variables_start
mov cx, scanparttab_variables_length_w
xor ax, ax
rep stosw ; clear variables for eventual checksum
dec ax
mov di, partition_offset
stosw
stosw ; reset this variable too
%endif
push ss
pop es
push ss
pop ds
retn
.per_partition:
push cx
push si
push di
push bx
mov ax, [es:si + piStart]
mov dx, [es:si + piStart + 2]
add ax, [ss:bx + di - 8]
adc dx, [ss:bx + di - 8 + 2] ; = partition start
mov cx, -1
mov di, partition_offset
cmp word [di], cx ; first one encountered ?
jne @F
cmp word [di + 2], cx
jne @F ; no -->
mov cl, byte [es:si + piType]
mov byte [di - partition_offset + partition_type], cl
; save type
mov word [di], ax
mov word [di + 2], dx ; yes, save offset
@@:
mov cl, byte [load_current_partition]
; which one ?
cmp cl, byte [bp + lsvExtra.partition]
jne @F ; not the sought one
mov cl, byte [es:si + piType]
mov byte [di - partition_offset + partition_type], cl
; save type
mov word [di], ax
mov word [di + 2], dx ; save offset
pop bx ; bx = base
mov sp, bx ; reset sp
pop ax ; pop dummy bp
retn ; return to caller
@@: ; not yet found, continue
pop bx
pop di
pop si
pop cx
retn
.int13_retry:
pushf
push ax
int 13h ; first try
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
popf
int 13h ; retry, CF error status, ah error number
retn
@@: ; NC or CY, stack has function number
inc sp
inc sp
inc sp
inc sp ; discard two words on stack, preserve CF
retn
.access_error:
jmp error_diskaccess
.sector_too_large:
.sector_too_small:
.sector_not_power:
call error
asciz "Invalid sector size."
.invalid_sectors:
.invalid_heads:
call error
asciz "Invalid geometry."
scan_logical.got_partition_cycle:
call error
asciz "Partition cycle detected."
scan_logical.error_too_many_partitions:
call error
asciz "Too many partitions detected."
read_partition_table.signature_fail:
call error
asciz "Invalid partition table detected."
; INP: dx:ax = first sector
; bx:0 -> buffer
; OUT: dx:ax = sector number after last read
; es = input bx
; bx:0 -> buffer after last written
; CHG: -
read_ae_512_bytes:
push ds
push cx
push bx
push ss
pop ds
mov cx, 512
.loop:
call read_sector
sub cx, word [bp + bsBPB + bpbBytesPerSector]
ja .loop
pop es
pop cx
pop ds
retn
%assign _PARTITION_TABLE_IN_CS 1
%assign _BOOTCMD_FAIL_ERROR 0
%define _SCANPTAB_PREFIX
%define _SCANPTAB_DEBUG4_PREFIX
overridedef DEBUG4, 0
%include "scanptab.asm"
resetdef
align 16
scanparttab_variables_start:
partition_table:
times 16 * 4 db 0
.end:
partition_offset:
dd -1
load_partition_cycle:
dw 0
load_current_partition:
db 0
partition_type:
db 0
align 2
scanparttab_variables_length_w: equ ($ - scanparttab_variables_start) / 2
%endif
; INP: ss:bp -> BPB
; ss:bp - LOADSTACKVARS -> lsv
; OUT: lsvDataStart set
; CHG: ax, bx, cx, dx, si, di
calculate_data_start:
xor cx, cx ; ! ch = 0
; Although this currently is unused, we calculate the
; first data sector (including root directory size)
; here to complete the LOADSTACKVARS.
; 32-byte FAT directory entries per sector
mov ax, [bp + bsBPB + bpbBytesPerSector]
mov cl, 5 ; ! ch = 0
shr ax, cl
; number of sectors used for root directory (store in CX)
; After the prior shr instruction, ax is always < 8000h,
; so this cwd instruction always zeros dx.
cwd
mov si, [bp + bsBPB + bpbNumRootDirEnts] ; (0 iff FAT32)
mov bx, ax
dec ax ; rounding up
js .error_badchain ; if >= 8000h (ie, 0FFFFh while bx = 0)
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
push cx ; number of root secs
; first sector of root directory
mov al, [bp + bsBPB + bpbNumFATs]
; ! ah = 0, hence ax = number of FATs
mov cx, word [bp + bsBPB + bpbSectorsPerFAT]
xor di, di ; di:cx = sectors per FAT
; iff FAT12, FAT16
test cx, cx ; is FAT32 ?
jnz @F ; no -->
mov cx, word [bp + bsBPB + ebpbSectorsPerFATLarge]
mov di, word [bp + bsBPB + ebpbSectorsPerFATLarge + 2] ; for FAT32
@@:
push ax
mul cx
; ax = low word SpF*nF
; dx = high word
xchg bx, ax
xchg cx, dx
; cx:bx = first mul
pop ax
mul di
; ax = high word adjust
; dx = third word
test dx, dx
jnz .error_badchain
xchg dx, ax
; dx = high word adjust
add dx, cx
; dx:bx = result
xchg ax, bx
; dx:ax = result
jc .error_badchain
add ax, [bp + bsBPB + bpbReservedSectors]
adc dx, byte 0
jc .error_badchain
pop cx ; number of root sectors
xor di, di
; first sector of disk data area:
add cx, ax
adc di, dx
mov [bp + lsvDataStart], cx
mov [bp + lsvDataStart + 2], di
retn
.error_badchain:
jmp error_badchain
end_of_handle_lsv_extra_flags:
%if _CHECKSUM
CHECKSUM_SIZE_P equ (payload.actual_end - $$ + 0) / 16
overridedef STANDALONE, 0
%include "inicheck.asm"
resetdef
align 16
checksumheader:
istruc CHECKSUMHEADER
at cshSignature, dw "CS"
at cshLengthBytesStructure, dw CHECKSUMHEADER_size
at cshOffsetStructure, dw paras(checksumheader - $$ + 0)
at cshChecksumStructure, dw \
10000h-(("CS" + CHECKSUMHEADER_size \
+ paras(checksumheader - $$ + 0) \
+ 8106h + CHECKSUM_SIZE_P \
+ 0 + 0) & 0FFFFh)
at cshTypeChecksum
..@checksumtype: dw 8106h
at cshAmountParagraphsData, dw CHECKSUM_SIZE_P
at cshChecksumData
..@checksumfield: dw 0
at cshReserved, dw 0
iend
%endif
%if _MULTIBOOT1 || _MULTIBOOT2
%include "multboot.asm"
%endif
%if _DEBUG3
; INP: word [cs:ip + 1] = two characters to display
; (second one may be NUL to skip)
; OUT: -
; CHG: -
d3_display_two_characters:
lframe near
lenter
push ax
push bx
mov bx, word [bp + ?frame_ip]
mov ax, [cs:bx + 1]
push ax
call d3_disp_al
pop ax
xchg al, ah
test al, al
jz @F
call d3_disp_al
@@:
pop bx
pop ax
lleave
lret
; INP: al = to display
; CHG: ax, bx
d3_disp_al:
push bp
mov ah, 0Eh
mov bx, 7
int 10h
pop bp
retn
%endif
msdos1_com_entry:
mov dx, .msg + 100h
mov ah, 09h
int 21h
int 20h
.msg:
ascic "86-DOS version 1 not supported, aborting.",13,10
align 16, db 38
payload:
incbin _PAYLOAD_FILE
align 16, db 38
.actual_end:
%if _IMAGE_EXE
align 512, db 38 ; until end of page
times 512 db 38 ; a full additional page,
; this is for the bogus exeExtraBytes
; Note that the pages start counting within the EXE header!
; Thus alignment to the file-level page boundary is correct.
%endif
.end:
%if _SECOND_PAYLOAD_EXE
align 16, db 38
second_payload:
incbin _SECOND_PAYLOAD_FILE
align 16, db 38
.actual_end:
align 512, db 38
times 512 db 38
.end:
%endif