%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