diff --git a/test/ldosboot/boot.asm b/test/ldosboot/boot.asm new file mode 100644 index 0000000..cd20c63 --- /dev/null +++ b/test/ldosboot/boot.asm @@ -0,0 +1,1854 @@ + +%if 0 + +File system boot sector loader code for FAT12 or FAT16 + +Adapted from 2002-11-26 fatboot.zip/fat12.asm, + released as public domain by Chris Giese + +Public domain by C. Masloch, 2012 + +%endif + + +%include "lmacros2.mac" + +%ifndef _MAP +%elifempty _MAP +%else ; defined non-empty, str or non-str + [map all _MAP] +%endif + + defaulting + + numdef FAT16, 0 ; 0 = FAT12, 1 = FAT16 + strdef OEM_NAME, " lDOS" + strdef OEM_NAME_FILL, '_' + strdef DEFAULT_LABEL, "lDOS" + numdef VOLUMEID, 0 + + strdef LOAD_NAME, "LDOS" + strdef LOAD_EXT, "COM" ; name of file to load + numdef LOAD_ADR, 02000h ; where to load + numdef LOAD_MIN_PARA, paras(1536) + numdef LOAD_NON_FAT, 0, 2048 ; use FAT-less loading (value is amount bytes) + numdef EXEC_SEG_ADJ, 0 ; how far cs will be from _LOAD_ADR + numdef EXEC_OFS, 400h ; what value ip will be + numdef CHECKOFFSET, 1020 + numdef CHECKVALUE, "lD" + numdef LOAD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere) + numdef ADD_SEARCH, 0 ; whether to search second file + strdef ADD_NAME, "" + strdef ADD_EXT, "" ; name of second file to search + numdef ADD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere) + + gendef _ADR_DIRBUF, end -start+7C00h ; 07E00h + gendef _ADR_FATBUF, end -start+7C00h ; 07E00h + + numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access) + numdef USE_PART_INFO, 1 ; use ds:si-> partition info from MBR, if any + numdef USE_AUTO_UNIT, 1 ; use unit passed from ROM-BIOS in dl + numdef RPL, 1 ; support RPL and do not overwrite it + numdef RPL_GRACE_AREA, 130 * 1024 + ; alternative RPL support, + ; assume RPL fits in this area + 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 RELOCATE, 0 ; relocate the loader to top of memory + numdef SET_BL_UNIT, 0 ; if to pass unit in bl as well + numdef SET_DL_UNIT, 0 ; if to pass unit in dl + numdef SET_AXBX_DATA, 0 ; if to pass first data sector in ax:bx + numdef SET_DSSI_DPT, 0 ; if to pass DPT address in ds:si + numdef PUSH_DPT, 0 ; if to push DPT address + numdef MEMORY_CONTINUE, 1 ; if to just execute when memory full + numdef SET_DI_CLUSTER, 0 ; if to pass first load file cluster in di + numdef DIRBUF_500, 0 ; if to load root dir sector(s) to 0:500h + numdef DIR_ENTRY_500, 0 ; if to copy directory entry to 0:500h + numdef DIR_ENTRY_520, 0 ; if to copy next directory entry to 0:520h + numdef TURN_OFF_FLOPPY, 0 ; if to turn off floppy motor after loading + numdef DATASTART_HIDDEN,0 ; if to add hidden sectors to data_start + numdef LBA_SET_TYPE, 0 ; if to set third byte to LBA partition type + numdef SET_LOAD_SEG, 1 ; if to set load_seg (word [ss:bp - 6]) + numdef SET_FAT_SEG, 1 ; if to set fat_seg (word [ss:bp - 8]) + numdef SET_CLUSTER, 1 ; if to set first_cluster (word [ss:bp - 16]) + numdef ZERO_ES, 0 ; if to set es = 0 before jump + numdef ZERO_DS, 0 ; if to set ds = 0 before jump + + numdef FIX_SECTOR_SIZE, 0 ; fix sector size (0 = disable, else = sector size) + numdef FIX_SECTOR_SIZE_SKIP_CHECK, 0 ; don't check sector size + numdef FIX_CLUSTER_SIZE, 0 ; fix cluster size + numdef FIX_CLUSTER_SIZE_SKIP_CHECK, 0 ; don't check cluster size + numdef NO_LIMIT, 0 ; allow using more memory than a boot sector + ; also will not write 0AA55h signature! + numdef LBA_SKIP_CHECK, 1 ; don't use proper LBA extensions check + numdef LBA_RETRY, 0 ; retry LBA reads one time + numdef CHS_RETRY, 1 ; retry CHS reads one time + numdef CHS_RETRY_REPEAT,16 ; retry CHS reads multiple times + ; (value of the def is used as count) + + numdef MEDIAID, 0F0h ; media ID + numdef UNIT, 0 ; load unit in BPB + numdef CHS_SECTORS, 18 ; CHS geometry field for sectors + numdef CHS_HEADS, 2 ; CHS geometry field for heads + numdef HIDDEN, 0 ; number of hidden sectors + numdef SPI, 2880 ; sectors per image + numdef BPS, 512 ; bytes per sector + numdef SPC, 1 ; sectors per cluster + numdef SPF, 9 ; sectors per FAT + numdef NUMFATS, 2 ; number of FATs + numdef NUMROOT, 224 ; number of root directory entries + numdef NUMRESERVED, 1 ; number of reserved sectors + +%if _FAT16 + ; Unlike the 1440 KiB diskette image defaults for the FAT12 + ; loader we just fill the FAT16 BPB with zeros by default. + numdef MEDIAID, 0 ; media ID + numdef UNIT, 0 ; load unit in BPB + numdef CHS_SECTORS, 0 ; CHS geometry field for sectors + numdef CHS_HEADS, 0 ; CHS geometry field for heads + numdef HIDDEN, 0 ; number of hidden sectors + numdef SPI, 0 ; sectors per image + numdef BPS, 0 ; bytes per sector + numdef SPC, 0 ; sectors per cluster + numdef SPF, 0 ; sectors per FAT + numdef NUMFATS, 0 ; number of FATs + numdef NUMROOT, 0 ; number of root directory entries + numdef NUMRESERVED, 0 ; number of reserved sectors +%endif + +%if _DIRBUF_500 + gendef _ADR_DIRBUF, 500h +%endif + + + numdef COMPAT_FREEDOS, 0 ; partial FreeDOS load compatibility + numdef COMPAT_IBM, 0 ; partial IBMDOS load compatibility + numdef COMPAT_MS7, 0 ; partial MS-DOS 7 load compatibility + numdef COMPAT_MS6, 0 ; partial MS-DOS 6 load compatibility + numdef COMPAT_LDOS, 0 ; lDOS load compatibility + numdef COMPAT_KERNEL7E, 0 ; kernel at 0:7E00h load compatibility + +%if (!!_COMPAT_FREEDOS + !!_COMPAT_IBM + \ + !!_COMPAT_MS7 + !!_COMPAT_MS6 + \ + !!_COMPAT_LDOS || _COMPAT_KERNEL7E) > 1 + %error At most one set must be selected. +%endif + +%if _COMPAT_FREEDOS + strdef LOAD_NAME, "KERNEL" + strdef LOAD_EXT, "SYS" + numdef LOAD_ADR, 00600h + numdef LOAD_MIN_PARA, paras(512) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 0 + + numdef CHECKVALUE, 0 + numdef SET_LOAD_SEG, 0 + numdef SET_FAT_SEG, 0 + numdef SET_CLUSTER, 0 + + numdef SET_BL_UNIT, 1 + numdef MEMORY_CONTINUE, 0 + numdef RELOCATE, 1 + ; The FreeDOS load protocol mandates that the entire file be loaded. +%endif + +%if _COMPAT_IBM + strdef LOAD_NAME, "IBMBIO" + strdef LOAD_EXT, "COM" + numdef LOAD_ADR, 00700h + numdef LOAD_MIN_PARA, paras(512) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 0 + numdef LOAD_DIR_SEG, 50h + numdef ADD_SEARCH, 1 + strdef ADD_NAME, "IBMDOS" + strdef ADD_EXT, "COM" + numdef ADD_DIR_SEG, 52h + ; Note: The IBMBIO.COM directory entry must be stored at + ; 0:500h, and the IBMDOS.COM directory entry at 0:520h. + + numdef CHECKVALUE, 0 + numdef SET_LOAD_SEG, 0 + numdef SET_FAT_SEG, 0 + numdef SET_CLUSTER, 0 + + numdef SET_DL_UNIT, 1 + numdef MEMORY_CONTINUE, 1 + ; 3 sectors * 512 BpS should suffice. We load into 700h--7A00h, + ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB). + numdef SET_AXBX_DATA, 1 + numdef DATASTART_HIDDEN,1 + numdef SET_DSSI_DPT, 1 + numdef PUSH_DPT, 1 +%endif + +%if _COMPAT_MS7 + strdef LOAD_NAME, "IO" + strdef LOAD_EXT, "SYS" + numdef LOAD_ADR, 00700h + numdef LOAD_MIN_PARA, paras(1024) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 200h + + numdef CHECKVALUE, 0 + numdef SET_LOAD_SEG, 0 + numdef SET_FAT_SEG, 0 + numdef SET_CLUSTER, 0 + + numdef SET_DL_UNIT, 1 + numdef SET_DSSI_DPT, 0 + numdef PUSH_DPT, 1 + numdef MEMORY_CONTINUE, 1 + ; 4 sectors * 512 BpS should suffice. We load into 700h--7A00h, + ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB). + numdef SET_DI_CLUSTER, 1 + numdef DATASTART_HIDDEN,1 + numdef LBA_SET_TYPE, 1 +%endif + +%if _COMPAT_MS6 + strdef LOAD_NAME, "IO" + strdef LOAD_EXT, "SYS" + numdef LOAD_ADR, 00700h + numdef LOAD_MIN_PARA, paras(512) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 0 + numdef LOAD_DIR_SEG, 50h + numdef ADD_SEARCH, 1 + strdef ADD_NAME, "MSDOS" + strdef ADD_EXT, "SYS" + numdef ADD_DIR_SEG, 52h + ; Note: The IO.SYS directory entry must be stored at + ; 0:500h, and the MSDOS.SYS directory entry at 0:520h. + + numdef CHECKVALUE, 0 + numdef SET_LOAD_SEG, 0 + numdef SET_FAT_SEG, 0 + numdef SET_CLUSTER, 0 + + numdef SET_DL_UNIT, 1 + numdef MEMORY_CONTINUE, 1 + ; 3 sectors * 512 BpS should suffice. We load into 700h--7A00h, + ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB). + numdef SET_AXBX_DATA, 1 + numdef DATASTART_HIDDEN,1 + numdef SET_DSSI_DPT, 1 + numdef PUSH_DPT, 1 +%endif + +%if _COMPAT_LDOS + strdef LOAD_NAME, "LDOS" + strdef LOAD_EXT, "COM" + numdef LOAD_ADR, 02000h + numdef LOAD_MIN_PARA, paras(1536) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 400h + numdef CHECKOFFSET, 1020 + numdef CHECKVALUE, "lD" + + numdef SET_DL_UNIT, 0 + numdef SET_CLUSTER, 1 + numdef SET_FAT_SEG, 1 + numdef SET_LOAD_SEG, 1 + numdef MEMORY_CONTINUE, 1 + numdef DATASTART_HIDDEN,0 +%endif + +%if _COMPAT_KERNEL7E + strdef OEM_NAME, "KERNEL7E" + strdef LOAD_NAME, "KERNEL7E" + strdef LOAD_EXT, "BIN" + numdef LOAD_ADR, 07E00h + numdef LOAD_MIN_PARA, paras(512) + numdef EXEC_SEG_ADJ, -7E0h + numdef EXEC_OFS, 7E00h + numdef CHECKVALUE, 0 + + numdef SET_DL_UNIT, 1 + numdef SET_BL_UNIT, 0 + numdef SET_CLUSTER, 0 + numdef SET_FAT_SEG, 0 + numdef SET_LOAD_SEG, 0 + numdef MEMORY_CONTINUE, 0 + numdef DATASTART_HIDDEN,0 + + gendef _ADR_FATBUF, 4000h + gendef _ADR_DIRBUF, 4000h + numdef RPL, 0 + numdef ZERO_ES, 1 +%endif + + +%if 0 + +Notes about partial load compatibilities + +* FreeDOS: + * Relocates to an address other than 27A00h (1FE0h:7C00h) + * A lot of options between _USE_PART_INFO, _QUERY_GEOMETRY, _CHS, _LBA, + and/or _RPL need to be disabled to make the loader fit +* IBMDOS: +* MS-DOS 6: + * Does not actually relocate DPT, just provide its address + * A lot of options between _USE_PART_INFO, _QUERY_GEOMETRY, _CHS, + and/or _LBA need to be disabled to make the loader fit +* MS-DOS 7: + * Does not actually relocate DPT, just provide its address + * Does not contain message table used by loader + +%endif + +%if _SET_BL_UNIT && _SET_AXBX_DATA + %error Cannot select both of these options! +%endif + +%if _DIR_ENTRY_520 + %assign _DIR_ENTRY_500 1 +%endif + +%if _DIRBUF_500 && _ADD_SEARCH + %error Cannot select both of these options! +%endif + +%if _ADD_SEARCH + %if _LOAD_DIR_SEG == 0 || _ADD_DIR_SEG == 0 + %error Assuming dir segs should be set if add search set + %endif +%endif + +%if _RPL + %assign _RPL_GRACE_AREA 0 +%endif + + +%assign LOADLIMIT 0A0000h +%assign POSITION 07C00h + +%if _FIX_SECTOR_SIZE + %assign i 5 + %rep 13-5 + %if (1 << i) != (_FIX_SECTOR_SIZE) + %assign i i+1 + %endif + %endrep + %if (1 << i) != (_FIX_SECTOR_SIZE) + %error Invalid sector size _FIX_SECTOR_SIZE + %endif +%endif + +%if _FIX_CLUSTER_SIZE + %if _FIX_CLUSTER_SIZE > 256 + %error Invalid cluster size _FIX_CLUSTER_SIZE + %endif + %assign i 0 + %rep 8-0 + %if (1 << i) != (_FIX_CLUSTER_SIZE) + %assign i i+1 + %endif + %endrep + %if (1 << i) != (_FIX_CLUSTER_SIZE) + %warning Non-power-of-two cluster size _FIX_CLUSTER_SIZE + %endif +%endif + + +; 512-byte stack (minus the variables). +ADR_STACK_LOW equ 7C00h - 200h ; 07A00h + +%if _DIRBUF_500 + gendef _ADR_DIRBUF, 500h +%elif _RELOCATE + gendef _ADR_DIRBUF, _LOAD_ADR +%endif + +; one-sector directory buffer. Assumes sectors are no larger than 8 KiB +ADR_DIRBUF equ __ADR_DIRBUF + +%if ! _RELOCATE +; this used to be a two-sector FAT buffer -- two sectors because FAT12 +; entries are 12 bits and may straddle a sector boundary. +; however, with the FAT12 loaded completely, the buffer only needs to hold +; one 8 KiB sector, two 4 KiB sectors, three 2 KiB sectors, six 1 KiB sectors, +; or twelve 512 byte sectors. +; this shares its area with the directory buffer as they +; are not simultaneously used. (if not _DIRBUF_500.) +ADR_FATBUF equ __ADR_FATBUF +%endif + +; start of unused memory after loader: +ADR_END equ end -start+7C00h +%if ! _RELOCATE + %if (ADR_FATBUF + 8192) > ADR_END + ADR_FREE_FROM equ (ADR_FATBUF + 8192) ; 09E00h + %else + ADR_FREE_FROM equ ADR_END ; 07E00h + %endif + +; end of unused memory before loader: + %if ADR_FATBUF < ADR_STACK_LOW +ADR_FREE_UNTIL equ ADR_FATBUF + %else +ADR_FREE_UNTIL equ ADR_STACK_LOW + %endif + + %if ((ADR_FATBUF + 8192 - 1) & ~0FFFFh) != (ADR_FATBUF & ~0FFFFh) + %warning Possibly crossing 64 KiB boundary while reading FAT + %endif +%endif + +%if ((ADR_DIRBUF + 8192 - 1) & ~0FFFFh) != (ADR_DIRBUF & ~0FFFFh) + %warning Possibly crossing 64 KiB boundary while reading directory +%endif + +%if _RELOCATE + ADR_FREE_FROM equ 0 ; make next conditional true + ADR_FREE_UNTIL equ 0 +%endif +%if _LOAD_ADR >= ADR_FREE_FROM + ; If reading on a sector size boundary, no crossing can occur. + ; Check for all possible sector sizes (32 B to 8 KiB). If one + ; of them fails display a warning, including the minimum size. + %assign SECSIZECHECK 32 + %assign EXITREP 0 + %rep 256 + %ifn EXITREP + %if _LOAD_ADR & (SECSIZECHECK - 1) + %warning Possibly crossing 64 KiB boundary while reading file (sector size >= SECSIZECHECK) + %assign EXITREP 1 + %exitrep + %endif + %if SECSIZECHECK == 8192 + %assign EXITREP 1 + %exitrep + %endif + %assign SECSIZECHECK SECSIZECHECK * 2 + %endif + %endrep +%else + ; If loading below the boot sector, address 1_0000h is never reached. +%endif + + +%if (_LOAD_ADR & 0Fh) + %error Load address must be on a paragraph boundary +%endif + +%if _LOAD_ADR > LOADLIMIT + %error Load address must be in LMA +%elif _LOAD_ADR < 00500h + %error Load address must not overlap IVT or BDA +%endif + +%if ! _RELOCATE + %if _LOAD_ADR > (POSITION-512) && _LOAD_ADR < (POSITION+512) + %error Load address must not overlap loader + %endif + + %if ADR_FATBUF > LOADLIMIT + %error FAT buffer address must be in LMA + %elif ADR_FATBUF < 00500h + %error FAT buffer address must not overlap IVT or BDA + %elif (ADR_FATBUF + 8192) > (POSITION-512) && ADR_FATBUF < (POSITION+512) + %error FAT buffer address must not overlap loader + %endif +%endif + +%if ADR_DIRBUF > LOADLIMIT + %error Dir buffer address must be in LMA +%elif ADR_DIRBUF < 00500h + %error Dir buffer address must not overlap IVT or BDA +%elif (ADR_DIRBUF + 8192) > (POSITION-512) && ADR_DIRBUF < (POSITION+512) + %error Dir buffer address must not overlap loader at initial position +%endif + +%if ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS) < 0 + %error Execution address must be in loaded file +%elif ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS+_LOAD_ADR) > LOADLIMIT + %error Execution address must be in LMA +%endif + +%if (_EXEC_OFS & ~0FFFFh) + %error Execution offset must fit into 16 bits +%endif + +%if (_EXEC_SEG_ADJ > 0FFFFh || _EXEC_SEG_ADJ < -0FFFFh) + %error Execution segment adjustment must fit into 16 bits +%endif + + +%if !_CHS && _QUERY_GEOMETRY + %warning No CHS support but querying geometry anyway +%endif + +%if !_CHS && !_LBA + %error Either CHS or LBA or both must be enabled +%endif + + +%if 0 + +There is some logic inside MS-DOS's hard disk partition initialisation +code that sets up several requirements for us to fulfil. Otherwise, +it will not accept the information given in the BPB (using default +information based on the length as specified by MBR/EPBR instead) or +make the whole file system inaccessible except for formatting. Both of +those are undesirable of course. Some/all(?) checks are documented on +pages 601,602 in "DOS Internals", Geoff Chappell 1994, as follows: + +* First three bytes contain either "jmp sho xx\ nop" or "jmp ne xx". +* Media descriptor field >= 0F0h. +* Bytes per sector field == 512. +* Sectors per cluster field a power of 2 (1,2,4,8,16,32,64,128). +* OEM name "version" (last three to four characters) + * must be "20.?", "10.?" (? = wildcard), but no other with "0.?", + * otherwise, must be "2.0", or + * 2nd-to-last,3rd-to-last character codes together > "3.", or + * those == "3.", last character code > "0" + +To stay compatible to those, display a warning here if the name +itself would disqualify our boot sector already. + +%endif + +%push +%strlen %$len _OEM_NAME_FILL +%if %$len != 1 + %error Specified OEM name fill must be 1 character +%endif +%strlen %$len _OEM_NAME +%define %$nam _OEM_NAME +%if %$len > 8 + %error Specified OEM name is too long +%else + %assign %$warn 0 + %rep 8 - %$len + %strcat %$nam %$nam,_OEM_NAME_FILL + %endrep + %substr %$prefix %$nam 5 ; "wvxyZa.b", get "Z" + %substr %$major %$nam 6,7 ; "wvxyzA.b", get "A." + %substr %$minor %$nam 8 ; "wvxyza.B", get "B" + %if %$major == "0." + %ifn %$prefix == "1" || %$prefix == "2" + %assign %$warn 1 + %endif + %elifn %$major == "2." && %$minor == "0" + %if %$major < "3." + %assign %$warn 1 + %elif %$major == "3." && %$minor < "1" + %assign %$warn 1 + %endif + %endif + %if %$warn + %warning Specified OEM name fails MS-DOS's validation + %endif +%endif +%pop + + + struc DIRENTRY +deName: resb 8 +deExt: resb 3 +deAttrib: resb 1 + resb 8 +deClusterHigh: resw 1 +deTime: resw 1 +deDate: resw 1 +deClusterLow: resw 1 +deSize: resd 1 + endstruc + +ATTR_READONLY equ 1 +ATTR_HIDDEN equ 2 +ATTR_SYSTEM equ 4 +ATTR_VOLLABEL equ 8 +ATTR_DIRECTORY equ 10h +ATTR_ARCHIVE equ 20h + + +; use byte-offset addressing from BP for smaller code +%define VAR(x) ((x) - start) + bp + + + cpu 8086 +; bootsector loaded at address 07C00h, addressable using 0000h:7C00h + org POSITION +start: + + +%define _LASTVARIABLE start + %macro nextvariable 2-3.nolist +%1 equ (_LASTVARIABLE - %2) +%define _LASTVARIABLE %1 +%ifidn %3, relocatestart + %define _RELOCATESTART %1 +%elifempty %3 +%else + %error Invalid third parameter +%endif + %endmacro + +; Variables + +; (dword) sector where the first cluster's data starts + nextvariable data_start, 4 + +; (word) current load segment (points behind last loaded data) + nextvariable load_seg, 2 + +; (word) segment of FAT buffer +; for FAT12 this holds the entire FAT +; for FAT16 this holds the sector given by wo[fat_sector] +; for FAT32 this holds the sector given by dwo[fat_sector] + nextvariable fat_seg, 2 + +; (word for FAT16) currently loaded sector-in-FAT, -1 if none + nextvariable fat_sector, 4 + +; (word for FAT12/FAT16) first cluster of load file + nextvariable first_cluster, 4, relocatestart + +ADR_STACK_START equ _LASTVARIABLE -start+POSITION + +%ifn _FIX_CLUSTER_SIZE +; (word) actual sectors per cluster + nextvariable adj_sectors_per_cluster, 2, relocatestart +%endif + +%ifn _FIX_SECTOR_SIZE +; (word) number of 16-byte paragraphs per sector + nextvariable para_per_sector, 2, relocatestart +%endif + +%ifn ! _RELOCATE && _LOAD_ADR < ADR_FREE_UNTIL && _FIX_SECTOR_SIZE +; (word) segment of last available memory for sector + nextvariable last_available_sector, 2 +%endif + +lowest_variable equ _LASTVARIABLE + + + jmp strict short skip_bpb +%if !_CHS && _LBA_SET_TYPE + db 0Eh ; LBA-enabled FAT16 FS partition type +%else + nop ; default: no LBA +%endif + + +; BIOS Parameter Block (BPB) +; +; Installation will use the BPB already present in your file system. +; These values must be initialised when installing the loader. +; +; The values shown here work only with 1440 KiB disks (CHS=80:2:18) + +oem_id: ; offset 03h (03) - not used by this code + fill 8,_OEM_NAME_FILL,db _OEM_NAME +bytes_per_sector: ; offset 0Bh (11) - refer to _FIX_SECTOR_SIZE ! + dw _BPS +sectors_per_cluster: ; offset 0Dh (13) - refer to _FIX_CLUSTER_SIZE ! + db _SPC & 255 +fat_start: +num_reserved_sectors: ; offset 0Eh (14) + dw _NUMRESERVED +num_fats: ; offset 10h (16) + db _NUMFATS +num_root_dir_ents: ; offset 11h (17) + dw _NUMROOT +total_sectors: ; offset 13h (19) - not used by this code +%if _SPI < 1_0000h + dw _SPI +%else + dw 0 +%endif +media_id: ; offset 15h (21) - not used by this code + db _MEDIAID +sectors_per_fat: ; offset 16h (22) + dw _SPF +sectors_per_track: ; offset 18h (24) + dw _CHS_SECTORS +heads: ; offset 1Ah (26) + dw _CHS_HEADS +hidden_sectors: ; offset 1Ch (28) + dd _HIDDEN +total_sectors_large: ; offset 20h (32) - not used by this code +%if _SPI >= 1_0000h + dd _SPI +%else + dd 0 +%endif + +; Extended BPB + +boot_unit: db _UNIT + db 0 +ext_bpb_signature: db 29h +serial_number: dd _VOLUMEID +volume_label: fill 11,32,db _DEFAULT_LABEL +filesystem_identifier: fill 8,32,db "FAT1",'2'+4*!!_FAT16 + + +; Initialised data + +load_name: + fill 8,32,db _LOAD_NAME + fill 3,32,db _LOAD_EXT +%if _ADD_SEARCH +add_name: + fill 8,32,db _ADD_NAME + fill 3,32,db _ADD_EXT + + ; align 2 + ; This happens to be aligned anyway. But even if + ; it didn't, we'd rather save that byte than use + ; it to align these fields. So comment this out. +filename: + dw add_name +dirseg: + dw _ADD_DIR_SEG +%endif + + + numdef TMPINC, 0 + +%if _TMPINC + [list -] +%endif + %imacro errorhandler 0 +%if _TMPINC + %include "error.tmp" + [list -] +%else +; === error.tmp === +read_sector.err: + mov al, 'R' ; Disk 'R'ead error +%if ! _MEMORY_CONTINUE || _RELOCATE || _LOAD_ADR >= ADR_FREE_FROM + db __TEST_IMM16 ; (skip mov) +error_filetoobig: +error_memory: + mov al,'M' ; Not enough 'M'emory +%endif + +error: +%if _RELOCATE + mov bx, 7 + mov ds, bx + mov bh, [462h - 70h] +%else + mov bh, [462h] + mov bl, 7 +%endif + mov ah, 0Eh + int 10h ; display character + mov al, 07h + int 10h ; beep! + + xor ax, ax ; await key pressed + int 16h + + int 19h ; re-start the boot process +; === eof === +%endif +%if _TMPINC + [list +] +%endif + %endmacro + + + %imacro readhandler 0 +%if _TMPINC + %include "read.tmp" + [list -] +%else +; === read.tmp === + ; INP: dx:ax = sector + ; OUT: only if successful + ; dx:ax = incremented + ; bx => behind read sector + ; es = ADR_FATBUF>>4 = ADR_DIRBUF>>4 + ; CHG: - +%if ! _RELOCATE + %if ADR_DIRBUF == ADR_FATBUF +read_sector_dirbuf: + %endif +read_sector_fatbuf: + mov bx, ADR_FATBUF>>4 +%endif + + ; Read a sector using Int13.02 or Int13.42 + ; + ; INP: dx:ax = sector number within partition + ; bx => 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 => next buffer (bx = es+word[para_per_sector]) + ; es = input bx + ; CHG: - +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,[VAR(hidden_sectors + 0)] + adc dx,[VAR(hidden_sectors + 2)] + %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33 + jc .err + %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 + push cx ; highest word = 0 + %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 + push bx + push cx ; bx => buffer + inc cx + push cx ; word number of sectors to read + mov cl, 10h + push cx ; word size of disk address packet + mov si, sp ; ds:si -> disk address packet (on stack) + + %if _LBA_SKIP_CHECK ; -14 bytes + mov dl, [VAR(boot_unit)] + %else + mov ah, 41h + mov dl, [VAR(boot_unit)] + mov bx, 55AAh + stc + int 13h ; 13.41.bx=55AA extensions installation check + jc .no_lba + cmp bx, 0AA55h + jne .no_lba + test cl, 1 ; support bitmap bit 0 + jz .no_lba + %endif + +%if _LBA_RETRY + mov ah, 42h + int 13h ; 13.42 extensions read + jnc .lba_done + + xor ax, ax + int 13h + + ; have to reset the LBAPACKET's lpCount, as the handler may + ; set it to "the number of blocks successfully transferred". + ; (in any case, the high byte is still zero.) + mov byte [si + 2], 1 +%endif + + mov ah, 42h + int 13h + %if _LBA_SKIP_CHECK && _CHS + jc .lba_check_error_1 + %else +.cy_err: + jc .lba_error + %endif + +.lba_done: +%if _CHS && _LBA_SET_TYPE + mov byte [bp + 2], 0Eh ; LBA-enabled FAT16 FS partition type +%endif + add sp, 10h + pop bx + mov es, bx +%if _CHS + jmp short .chs_done +%endif + +.lba_error: equ .err + + %if !_CHS +.no_lba: equ .err + %else + %if _LBA_SKIP_CHECK +.lba_check_error_1: + cmp ah, 1 ; invalid function? + jne .lba_error ; no, other error --> + ; try CHS instead +.cy_err: equ .err + %endif +.no_lba: + add sp, 8 + pop cx ; cx = low word of sector + pop ax + pop dx ; dx:ax = middle two words of sector + ; Here dx <= 1 if _LBA_33_BIT, else zero. + ; If dx is nonzero then the CHS calculation + ; should fail. If CHS sectors is equal to 1 + ; (very unusual) then the div may fail. Else, + ; we will detect a cylinder > 1023 eventually. + pop si ; discard highest word of qword + %endif +%endif + +%if !_LBA +.cy_err: equ .err +%endif + +%if _CHS ; +70 bytes +; 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 + ; from the .no_lba popping we already have: + ; cx = low word of sector + ; dx:ax = high word of sector + %endif + div word [VAR(sectors_per_track)] + xchg cx,ax + div word [VAR(sectors_per_track)] + 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 [VAR(heads)] + ; ax = high / heads, dx = high % heads + xchg bx, ax ; bx = high / heads, ax = low quotient + div word [VAR(heads)] + +; 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,[VAR(boot_unit)] ; dl = drive +.nz_err: + jnz .err ; error if cylinder >= 1024 --> + ; ! bx = 0 (for 13.02 call) + +; we call INT 13h AH=02h once for each sector. Multi-sector reads +; may fail if we cross a track or 64K boundary + pop es +%if _CHS_RETRY_REPEAT + mov si, _CHS_RETRY_REPEAT + 1 +.loop_chs_retry_repeat: + mov ax, 0201h + int 13h ; read one sector + jnc .done + xor ax, ax + int 13h ; reset disk + dec si ; another attempt ? + jnz .loop_chs_retry_repeat ; yes --> + jmp .err +%else + %if _CHS_RETRY + mov ax, 0201h + int 13h ; read one sector + jnc .done +; reset drive + xor ax, ax + int 13h + %endif +; try read again + mov ax, 0201h + int 13h + %if _LBA_SKIP_CHECK + inc bx + jc .nz_err + %else + jc .cy_err + %endif +%endif + +.done: +; increment segment + mov bx, es +%endif + +.chs_done: +%if _FIX_SECTOR_SIZE + add bx, _FIX_SECTOR_SIZE >> 4 +%else + add bx, [VAR(para_per_sector)] +%endif + + pop si + pop ax + pop cx + pop dx +; increment LBA sector number + inc ax + jne @F + inc dx +@@: + + retn +; === eof === +%endif +%if _TMPINC + [list +] +%endif + %endmacro +%if _TMPINC + [list +] +%endif + + +; Code + +skip_bpb: + cli + cld + xor cx, cx + mov bp, start ; magic bytes - checked by instsect + mov ss, cx + mov sp, ADR_STACK_START +%if _USE_AUTO_UNIT + mov [VAR(boot_unit)], dl; magic bytes - checked by instsect +%else + mov dl, [VAR(boot_unit)]; magic bytes - checked by instsect +%endif + + ; Note: es is left uninitialised here until the first call to + ; read_sector if the below conditional is false. +%if _USE_PART_INFO ; +19 bytes + mov es, cx +; Note: Award Medallion BIOS v6.0 (ASUS MED 2001 ACPI BIOS Revision 1009) +; loads from a floppy disk drive with ds:si = 0F000h:0A92Dh -> +; FF FF FF FF 08 00 08 01 FF FF FF FF FF FF FF FF, which was detected +; as a valid partition table entry by this handling. Therefore, we +; only accept partition information when booting from a hard disk now. + test dl, dl ; floppy ? + jns @F ; don't attempt detection --> +; Check whether an MBR left us partition information. +; byte[ds:si] bit 7 means active and must be set if valid. + cmp byte [si], cl ; flags for xx-00h (result is xx), SF = bit 7 + jns @F ; xx < 80h, ie info invalid --> +; byte[ds:si+4] is the file system type. Check for valid one. + cmp byte [si+4], cl ; is it zero? + je @F ; yes, info invalid --> +; Info valid, trust their hidden sectors over hardcoded. +; Assume the movsw instructions won't run with si = FFFFh. + mov di, hidden_sectors ; -> BPB field + add si, 8 ; -> partition start sector in info + movsw + movsw ; overwrite BPB field with value from info +@@: +%endif + mov ds, cx + sti + + +%if _QUERY_GEOMETRY ; +27 bytes +; 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 + ; Already from prologue cx = 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 [VAR(sectors_per_track)], cx + mov cl, dh ; cx = maximum head number + inc cx ; cx = number of heads (H is 0-based) + mov [VAR(heads)], cx +@@: +%endif + +%if _FIX_SECTOR_SIZE + %if !_FIX_SECTOR_SIZE_SKIP_CHECK + cmp word [VAR(bytes_per_sector)], _FIX_SECTOR_SIZE + mov al, 'S' + jne error + %endif + mov bx, _FIX_SECTOR_SIZE >> 5 + %if _FIX_CLUSTER_SIZE + %if !_FIX_CLUSTER_SIZE_SKIP_CHECK + cmp byte [VAR(sectors_per_cluster)], _FIX_CLUSTER_SIZE & 0FFh + mov al, 'C' + jne error + %endif + %else +; calculate some values that we need: +; adjusted sectors per cluster (store in a word, +; and decode EDR-DOS's special value 0 meaning 256) + xor ax, ax + mov al, [VAR(sectors_per_cluster)] + dec al + inc ax + push ax ; push into word [VAR(adj_sectors_per_cluster)] + %endif + mov ch, 0 ; ! ch = 0 +%else +; 16-byte paragraphs per sector + mov bx,[VAR(bytes_per_sector)] + mov cx,4 ; ! ch = 0 + shr bx,cl + %if _FIX_CLUSTER_SIZE + %if !_FIX_CLUSTER_SIZE_SKIP_CHECK + cmp byte [VAR(sectors_per_cluster)], _FIX_CLUSTER_SIZE & 0FFh + mov al, 'C' + jne error + %endif + %else +; calculate some values that we need: +; adjusted sectors per cluster (store in a word, +; and decode EDR-DOS's special value 0 meaning 256) + ; ! ch = 0 + mov cl, [VAR(sectors_per_cluster)] + ; therefore cx = sectors_per_cluster + dec cl + inc cx + push cx ; push into word [VAR(adj_sectors_per_cluster)] + dec cx ; ! ch = 0 + %endif + push bx ; push into word [VAR(para_per_sector)] + +; 32-byte FAT directory entries per sector + shr bx, 1 ; /2 = 32-byte entries per sector +%endif + +; number of sectors used for root directory (store in CX) + mov si, [VAR(num_root_dir_ents)] + mov ax, bx + ; The ax value here is the last value of bx, which is set + ; by the shr instruction. Therefore, it cannot be higher + ; than 7FFFh, so this cwd instruction always zeros dx. + cwd + dec ax ; rounding up + add ax, si ; from BPB + adc dx, dx ; account for overflow (dx was zero) + div bx ; get number of root sectors + xchg ax, cx ; cx = number of root secs, ! ah = 0 + +; first sector of root directory + mov al,[VAR(num_fats)] ; ! ah = 0, hence ax = number of FATs + mul word [VAR(sectors_per_fat)] + add ax,[VAR(num_reserved_sectors)] + adc dl, dh ; account for overflow (dh was and is 0) + + xor di, di + +; first sector of disk data area: + add cx, ax + adc di, dx + mov [VAR(data_start)], cx + mov [VAR(data_start+2)], di + +next_dir_search: +%if _ADD_SEARCH + push dx + push ax + push si +%endif + +; Scan root directory for file. We don't bother to check for deleted +; entries (E5h) or entries that mark the end of the directory (00h). + ; number of root entries in si here +next_sect: + mov cx, bx ; entries per sector as loop counter +%if ! _RELOCATE + %if ADR_DIRBUF == ADR_FATBUF + call read_sector_dirbuf + %else + mov bx, ADR_DIRBUF>>4 + call read_sector + %endif +%else + mov bx, ADR_DIRBUF>>4 + call read_sector +%endif + mov bx, cx ; restore bx for next iteration later + + xor di, di ; es:di-> first entry in this sector +next_ent: + push si + push di + push cx +%if _ADD_SEARCH + mov si, [VAR(filename)] +%else + mov si, load_name ; ds:si-> name to match +%endif + mov cx, 11 ; length of padded 8.3 FAT filename + repe cmpsb ; check entry + pop cx + pop di + pop si + lea di, [di + DIRENTRY_size] + je found_it ; found entry --> + + dec si ; count down entire root's entries + loopnz next_ent ; count down sector's entries (jumps iff si >0 && cx >0) + jnz next_sect ; (jumps iff si >0 && cx ==0) + ; ends up here iff si ==0 + ; ie all root entries checked unsuccessfully +%if 0 + +qemu prior to 2020-08 has a bug which affects the above +conditionals. The bug is that if NZ is set (like when the +branch to the label found_it is not taken) and then another +instruction sets ZR (like the dec si at the end of the root +directory) and then loopnz is used which sets cx to zero +then after the loopnz FL will be NZ leading to the jnz branch +to be taken. Eventually the entire load unit is traversed and +qemu returns error 01h when trying to read past the end of +the unit (at least for 1440 KiB diskettes). + +The bug can be worked around in two ways as done by lDebug: + +https://hg.pushbx.org/ecm/ldebug/rev/c95e2955bbca + +https://hg.pushbx.org/ecm/ldebug/rev/c84047f15d9c + +However, both cost a few bytes each. Therefore the proper +fix is considered to be updating qemu. Error behaviour occurs +when a file is not found. In an unlikely case, another sector +in the data area may hold a match for the searched entry. +Otherwise, the read eventually fails and the loader aborts +with an R error (instead of the expected F error). + +Reference: https://bugs.launchpad.net/qemu/+bug/1888165 + +%endif + + mov al,'F' ; File not 'F'ound + jmp error + +found_it: +%if _ADD_SEARCH || _LOAD_DIR_SEG + mov cx, 32 + mov ax, _LOAD_DIR_SEG + sub di, cx ; es:di -> dir entry + %if _ADD_SEARCH + xchg ax, word [VAR(dirseg)] + %endif + push ds + mov si, di + push di + push es + pop ds ; ds:si -> dir entry + mov es, ax + xor di, di ; es:di -> destination + rep movsb ; store dir entry + push ds + pop es + pop di ; restore es:di -> dir entry + pop ds + %if _ADD_SEARCH +%if ((load_name - start) & 0FF00h) == ((add_name - start) & 0FF00h) + mov byte [VAR(filename)], (load_name - start + 7C00h) & 255 +%else + mov word [VAR(filename)], load_name +%endif + ; update name to second iteration's +%if (_LOAD_DIR_SEG & 255) != (_ADD_DIR_SEG & 255) + cmp al, _ADD_DIR_SEG & 255 +%elif (_LOAD_DIR_SEG) != (_ADD_DIR_SEG) + cmp ax, _ADD_DIR_SEG ; was first iteration ? +%else + %error Must not store directory entries to same segment +%endif + pop si + pop ax + pop dx ; restore root start and count + ; (bx still holds entries per sector) + je next_dir_search ; jump to search load file next --> + %endif + %if _RELOCATE + push word [es:di + deClusterLow] + ; (word on stack) = first cluster number + %endif +%else + %if _DIR_ENTRY_500 ; +24 bytes, probably + mov cx, 32 + push ds + push es + xchg si, di + sub si, cx + push es + pop ds ; ds:si -> directory entry + xor ax, ax + mov es, ax + mov di, 500h ; es:di -> 0:500h + %if _DIR_ENTRY_520 + rep movsw ; move to here (two directory entries) + %else + rep movsb ; move to here + %endif + pop es + pop ds + xchg si, di ; es:di -> behind (second) directory entry + %endif + %if _RELOCATE + push word [es:di + deClusterLow - DIRENTRY_size \ + - (DIRENTRY_size * !!_DIR_ENTRY_520)] + ; (word on stack) = first cluster number + %endif +%endif + +%if _RELOCATE || _LOAD_ADR >= ADR_FREE_FROM +; Get conventional memory size and store it + int 12h + mov cl, 6 + shl ax, cl + %if _RPL ; +31 bytes + xchg dx, ax + 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: + push ss + pop ds + xchg ax, dx + %endif + %if _RELOCATE + %if _LOAD_NON_FAT + sub ax, (_RPL_GRACE_AREA + 20 * 1024 \ + + 512 + 7C00h) >> 4 + %else + sub ax, (_RPL_GRACE_AREA + 20 * 1024 \ + + 8192 + (8192-16) + 512 + 7C00h) >> 4 + %endif + ; RPL grace area preserved for RPL + ; 20 KiB: reserved for iniload + ; 8 KiB: FAT buffer + ; 8 KiB - 16 B: to allow rounding down FAT buffer position + ; 512: sector + ; 7C00h: stack and to allow addressing with 7C00h in bp + ; + ; Note also that by addressing the stack and sector + ; with bp at 7C00h, and insuring this subtraction doesn't + ; underflow, makes sure that we do not overwrite the IVT or + ; BDA. (However, we assume that ax is at least 60h or so.) + ; + ; The FAT buffer segment is masked so that the actual buffer + ; is stored on an 8 KiB boundary. This is to ensure that + ; the buffer doesn't possibly cross a 64 KiB DMA boundary. + jc .error_memory_j_CY + cmp ax, (end -start+7C00h - ADR_STACK_LOW + 15) >> 4 + ; This check is to ensure that the start of the destination + ; for the relocation (stack, sector) is + ; above-or-equal the end of the source for the relocation. + ; That is, to avoid overwriting any of the source with the + ; string move instruction (which for simplicity is always UP). +.error_memory_j_CY: + jb error_memory + %if ! _LOAD_NON_FAT + mov bx, ((8192 - 16) + 512 + 7C00h)>>4 + add bx, ax +; this is like calculating the following for the bx value: +; ((LMA_top - 20 KiB - 8 KiB - (8 KiB - 16 B) - 512 - 7C00h) + \ +; ((8 KiB - 16 B) + 512 + 7C00h))>>4 +; == (LMA_top - 20 KiB - 8 KiB)>>4 + and bx, ~ ((8192>>4) - 1) + mov word [VAR(fat_seg)], bx + %endif + + mov di, relocated + push ax + push di ; -> relocation target label relocated + ; (We cannot use a near call here to push the target IP + ; because we do not control whether the ROM-BIOS loader + ; entered us at 0:7C00h or 7C0h:0. So we need to create + ; the correct offset manually here.) + + mov es, ax ; => destination + mov si, sp ; ds:si = ss:_RELOCATESTART - 2 - 4 + mov di, si ; es:di -> destination for stack low + mov cx, (end - (_RELOCATESTART - 2 - 4)) >> 1 + ; end is the top of used memory + ; _RELOCATESTART is the lowest filled stack frame slot + ; 2 is for the first cluster word on the stack + ; 4 is for the additional slots taken by the return address + rep movsw ; relocate stack, sector + retf ; jump to relocated code + + + readhandler + + errorhandler + + +relocated: + mov ss, ax + mov ds, ax ; relocate these + add ax, (ADR_STACK_LOW) >> 4 ; (rounding down) => behind available + + pop si + %else + sub ax, (_RPL_GRACE_AREA + 20 * 1024) >> 4 + ; RPL grace area, plus + ; 20 KiB reserved for iniload + jb error_memory + %endif +%elif _LOAD_ADR < ADR_FREE_UNTIL + %if !_FIX_SECTOR_SIZE + mov ax, ADR_FREE_UNTIL >> 4 ; rounding *down* + %endif +%else + %error Load address within used memory +%endif +%if ! _RELOCATE && _LOAD_ADR < ADR_FREE_UNTIL && _FIX_SECTOR_SIZE + ; user of last_available_sector will hardcode the value! +%else + %if _FIX_SECTOR_SIZE + sub ax, _FIX_SECTOR_SIZE >> 4 + %else + sub ax, [VAR(para_per_sector)] + %endif + push ax ; push into word [VAR(last_available_sector)] +%endif + +; get starting cluster of file +%if ! _RELOCATE + %if _ADD_SEARCH || _LOAD_DIR_SEG + mov si,[es:di + deClusterLow] + %else + mov si,[es:di + deClusterLow - DIRENTRY_size \ + - (DIRENTRY_size * !!_DIR_ENTRY_520)] + %endif +%endif +%if _SET_CLUSTER + mov word [VAR(first_cluster)], si +%endif +%if _SET_DI_CLUSTER + push si ; remember cluster for later +%endif + +%if !_FAT16 && !_LOAD_NON_FAT +; 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, [VAR(sectors_per_fat)] + ; maximum size of this FS's FAT +%if ! _RELOCATE && _LOAD_ADR < ADR_FREE_UNTIL && !_FIX_SECTOR_SIZE + ; Under these conditions, ax here is + ; below ADR_FREE_UNTIL >> 4 so the + ; following cwd instruction zeros dx. + cwd +%else + xor dx, dx +%endif + mov ax, [VAR(fat_start)]; = first FAT sector + %if _RELOCATE + ; bx already = FAT buffer segment here + %else + mov bx, ADR_FATBUF>>4 + %if _SET_FAT_SEG + mov word [VAR(fat_seg)], bx + %endif + %endif +@@: + call read_sector ; read next FAT sector +%if _FIX_SECTOR_SIZE + sub di, _FIX_SECTOR_SIZE +%else + sub di, [VAR(bytes_per_sector)] +%endif + ; 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 +%endif + + mov bx, _LOAD_ADR>>4 ; => load address +%if _FAT16 && !_LOAD_NON_FAT + mov di, -1 ; = no FAT sector read yet + %if _SET_FAT_SEG + %if ! _RELOCATE + mov word [VAR(fat_seg)], ADR_FATBUF>>4 + %endif + mov word [VAR(fat_sector)], di + %endif +%endif +%if _LOAD_NON_FAT + %if _SET_FAT_SEG + %if _FAT16 + or word [VAR(fat_sector)], -1 + %else + and word [VAR(fat_seg)], 0 + %endif + %endif +%endif + +next_cluster: +; convert 16-bit cluster value (in SI) to 32-bit LBA sector value (in DX:AX) +; and get next cluster in SI + + + ; Converts cluster number to sector number + ; and finds next cluster in chain + ; + ; INP: si = valid cluster number + ; (!_FAT16) [ADR_FATBUF] = entire FAT as read from FS + ; (_FAT16) di = currently buffered FAT sector, -1 if none + ; OUT: If unable to read a FAT sector, + ; ! jumps to error instead of returning + ; If everything is okay, + ; si = next cluster number (or EOC value) + ; dx:ax = sector number + ; (_FAT16) di = currently buffered FAT sector + ; CHG: cx + +%if ! _LOAD_NON_FAT +; prepare to load entry from FAT + +%if _FAT16 + push si ; preserve cluster number for later + +; Multiply cluster number by 2. + xchg ax, si + ; xor dx, dx ; dx:ax = entry to load (0..FFF6h) + ; add ax, ax + ; adc dx, dx ; dx:ax = byte offset into FAT (0..131052) + cwd ; dx = FFFFh if ax >= 8000h, else = 0 + add ax, ax ; ax = (2 * ax) & FFFFh + neg dx ; dx = 1 if 2 * ax overflowed, else = 0 + +; Divide by sector size to split dx:ax into the remainder as the byte offset +; into the FAT sector, and quotient as the sector offset into the FAT. + div word [VAR(bytes_per_sector)] + mov si, dx ; = byte offset in sector + ; dx = byte offset into sector (0..8190) + ; ax = sector offset into FAT (0..4095) + +; quotient in AX is FAT sector. +; check the FAT buffer to see if this sector is already loaded +; (simple disk cache; speeds things up a little -- +; actually, it speeds things up a lot) + cmp ax, di + je @F + mov di, ax + %if _SET_FAT_SEG + mov word [VAR(fat_sector)], ax + %endif + +; read the target FAT sector. + push bx + ; As noted above, the sector offset in ax here is + ; 0..4095 (ie, 4 Ki S * 32 B/S = 128 KiB of FAT data), + ; therefore this cwd instruction always zeros dx. + cwd + add ax, [VAR(fat_start)] + adc dx, dx ; (account for overflow) + %if _RELOCATE + mov bx, word [VAR(fat_seg)] + call read_sector + %else + call read_sector_fatbuf + %endif + pop bx +@@: + +; get 16 bits from FAT + es lodsw + xchg ax, si + pop ax ; restore cluster number +%else +; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2, +; so multiply cluster number by 3 first, then divide by 2. + mov ax, si ; = cluster number (up to 12 bits set) + ; (remember cluster number in ax) + shl si, 1 ; = 2n (up to 13 bits set) + add si, ax ; = 2n+n = 3n (up to 14 bits set) + shr si, 1 ; si = byte offset into FAT (0..6129) + ; CF = whether to use high 12 bits + %if _RELOCATE + mov es, word [VAR(fat_seg)] + ; es lodsw + ; xchg ax, si + mov si, word [es:si] + %else +; Use the calculated byte offset as an offset into the FAT +; buffer, which holds all of the FAT's relevant data. + ; lea si, [ADR_FATBUF + si] + ; -> 16-bit word in FAT to load +; get 16 bits from FAT + ; lodsw + ; xchg ax, si + mov si, [ADR_FATBUF + si] + ; = 16-bit word in FAT to load + %endif + + mov cl, 4 + jc @F ; iff CY after shift --> + shl si, cl ; shift up iff even entry +@@: + shr si, cl ; shift down (clears top 4 bits) + + ; (ax holds cluster number) +%endif + +%else ; _LOAD_NON_FAT + mov di, _LOAD_NON_FAT + mov ax, word [VAR(adj_sectors_per_cluster)] + mov cx, ax + mul word [VAR(bytes_per_sector)] + test dx, dx + jnz @F + cmp ax, di + mov al, 'N' + jb error +@@: + xchg ax, si +%endif +; adjust cluster number to make it 0-based + dec ax + dec ax + +%if ! _LOAD_NON_FAT +%if _FIX_CLUSTER_SIZE + mov cx, _FIX_CLUSTER_SIZE +%else + mov cx, [VAR(adj_sectors_per_cluster)] +%endif +%endif + +; convert from clusters to sectors + mul cx + add ax, [VAR(data_start)] + adc dx, [VAR(data_start)+2] + ; dx:ax = sector number + +; xxx - this will always load an entire cluster (e.g. 64 sectors), +; even if the file is shorter than this +@@: + %if _LOAD_ADR < ADR_FREE_UNTIL && _FIX_SECTOR_SIZE + cmp bx, (ADR_FREE_UNTIL >> 4) - (_FIX_SECTOR_SIZE >> 4) + %else + cmp bx, [VAR(last_available_sector)] + %endif +%if _MEMORY_CONTINUE + ja @F +%else + ja error_filetoobig +%endif +%if _FAT16 && ! _LOAD_NON_FAT + push es ; (must preserve ADR_FATBUF reference) +%endif + call read_sector +%if _FAT16 && ! _LOAD_NON_FAT + pop es +%endif +%if _SET_LOAD_SEG + mov [VAR(load_seg)], bx ; => after last read data +%endif +%if _LOAD_NON_FAT + sub di, word [VAR(bytes_per_sector)] + ja @B +%else + loop @B + +%if _FAT16 +; FFF7h: bad cluster +; FFF8h-FFFFh: end of cluster chain + cmp si, 0FFF7h +%else +; 0FF7h: bad cluster +; 0FF8h-0FFFh: end of cluster chain + cmp si, 0FF7h +%endif + jb next_cluster +%endif ; _LOAD_NON_FAT +@@: + +%if _LOAD_MIN_PARA + cmp bx, (_LOAD_ADR >> 4) + _LOAD_MIN_PARA + mov al, 'E' + jb error +%endif + +%if _TURN_OFF_FLOPPY +; turn off floppy motor + mov dx,3F2h + mov al,0 + out dx,al +%endif + +; Set-up registers for and jump to loaded file +; Already: ss:bp-> boot sector containing BPB +%if _CHECKVALUE +CHECKLINEAR equ _LOAD_ADR + _CHECKOFFSET + %if ! _RELOCATE && CHECKLINEAR <= (64 * 1024 - 2) + cmp word [CHECKLINEAR], _CHECKVALUE + %else + mov ax, CHECKLINEAR >> 4 + mov es, ax + cmp word [es:CHECKLINEAR & 15], _CHECKVALUE + %endif + mov al, 'V' ; check 'V'alue mismatch + jne error +%endif +%if _SET_DL_UNIT + mov dl, [VAR(boot_unit)]; set dl to unit +%endif +%if _SET_DI_CLUSTER && (_PUSH_DPT || _SET_DSSI_DPT) + pop cx +%endif +%if _DATASTART_HIDDEN + mov bx, [VAR(hidden_sectors + 0)] + mov ax, [VAR(hidden_sectors + 2)] + add word [VAR(data_start + 0)], bx + adc word [VAR(data_start + 2)], ax +%endif +%if _SET_BL_UNIT + %if _SET_DL_UNIT + mov bl, dl ; set bl to unit, too + %else + mov bl, [VAR(boot_unit)]; set bl to unit + %endif +%endif +%if _PUSH_DPT || _SET_DSSI_DPT + %ifn _SET_DSSI_DPT ; (implying that only _PUSH_DPT is set) + %if _RELOCATE + xor ax, ax + mov es, ax + mov di, 1Eh * 4 + les si, [es:di] + push es + push si + push ax + push di + %else + mov di, 1Eh*4 + les si, [di] ; -> original (also current) DPT + push es + push si ; original (also current) DPT address + push ss + push di ; 0000h:0078h (address of 1Eh IVT entry) + %endif + %else + %if _RELOCATE + xor ax, ax + mov ds, ax + %endif + mov di, 1Eh*4 + lds si, [di] ; -> original (also current) DPT + %if _PUSH_DPT + push ds + push si ; original (also current) DPT address + %if _RELOCATE + push ax + push di + %else + push ss + push di ; 0000h:0078h (address of 1Eh IVT entry) + %endif + %endif + %endif +%else + %if _RELOCATE && (_ZERO_ES || _ZERO_DS) + xor ax, ax + %endif +%endif +%if _RELOCATE + %if _ZERO_ES + mov es, ax + %endif + %if _ZERO_DS + mov ds, ax + %endif +%endif + +%if _SET_AXBX_DATA + mov bx, [VAR(data_start)] + mov ax, [VAR(data_start+2)] +%endif +%if _SET_DI_CLUSTER + %if _PUSH_DPT || _SET_DSSI_DPT + mov di, cx + %else + pop di + %endif +%endif +%if ! _RELOCATE + %if _ZERO_ES + push ss + pop es + %endif +%endif + ; ss:bp-> boot sector with BPB + jmp (_LOAD_ADR>>4)+_EXEC_SEG_ADJ:_EXEC_OFS + + +%if ! _RELOCATE + errorhandler + + readhandler +%endif + + +%if !_NO_LIMIT +available: + _fill 508,38,start + +signatures: + dw 0 +; 2-byte magic bootsector signature + dw 0AA55h + +%assign num signatures-available +%assign fatbits 12 +%if _FAT16 + %assign fatbits 16 +%endif +%warning FAT%[fatbits]: num bytes still available. +%endif + +end: diff --git a/test/ldosboot/boot32.asm b/test/ldosboot/boot32.asm new file mode 100644 index 0000000..540596e --- /dev/null +++ b/test/ldosboot/boot32.asm @@ -0,0 +1,1730 @@ + +%if 0 + +File system boot sector loader code for FAT32 + +Adapted from 2002-11-26 fatboot.zip/fat12.asm, + released as public domain by Chris Giese + +Public domain by C. Masloch, 2012 + +%endif + + +%include "lmacros2.mac" + +%ifndef _MAP +%elifempty _MAP +%else ; defined non-empty, str or non-str + [map all _MAP] +%endif + + defaulting + + strdef OEM_NAME, " lDOS" + strdef OEM_NAME_FILL, '_' + strdef DEFAULT_LABEL, "lDOS" + numdef VOLUMEID, 0 + strdef FSIBOOTNAME, "FSIBOOT4" + ; used to set experimental name + ; strdef FSIBOOTNAME, "FSIBEX02" + + strdef LOAD_NAME, "LDOS" + strdef LOAD_EXT, "COM" ; name of file to load + numdef LOAD_ADR, 02000h ; where to load + numdef LOAD_MIN_PARA, paras(1536) + numdef EXEC_SEG_ADJ, 0 ; how far cs will be from _LOAD_ADR + numdef EXEC_OFS, 400h ; what value ip will be + numdef CHECKOFFSET, 1020 + numdef CHECKVALUE, "lD" + numdef LOAD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere) + numdef ADD_SEARCH, 0 ; whether to search second file + strdef ADD_NAME, "" + strdef ADD_EXT, "" ; name of second file to search + numdef ADD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere) + + numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access) + numdef USE_PART_INFO, 1 ; use ds:si-> partition info from MBR, if any + numdef USE_AUTO_UNIT, 1 ; use unit passed from ROM-BIOS in dl + 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 RELOCATE, 0 ; relocate the loader to top of memory + numdef SET_DL_UNIT, 0 ; if to pass unit in dl + numdef SET_BL_UNIT, 0 ; if to pass unit in bl as well + numdef SET_AXBX_DATA, 0 ; if to pass first data sector in ax:bx + numdef SET_DSSI_DPT, 0 ; if to pass DPT address in ds:si + numdef PUSH_DPT, 0 ; if to push DPT address + numdef MEMORY_CONTINUE, 1 ; if to just execute when memory full + numdef SET_SIDI_CLUSTER,0 ; if to pass first load file cluster in si:di + numdef TURN_OFF_FLOPPY, 0 ; if to turn off floppy motor after loading + numdef DATASTART_HIDDEN,0 ; if to add hidden sectors to data_start + numdef LBA_SET_TYPE, 0 ; if to set third byte to LBA partition type + numdef SET_LOAD_SEG, 1 ; if to set load_seg (word [ss:bp - 6]) + numdef SET_FAT_SEG, 1 ; if to set fat_seg (word [ss:bp - 8]) + numdef SET_CLUSTER, 1 ; if to set first_cluster (dword [ss:bp - 16]) + numdef ZERO_ES, 0 ; if to set es = 0 before jump + numdef ZERO_DS, 0 ; if to set ds = 0 before jump + + numdef FIX_SECTOR_SIZE, 0 ; fix sector size (0 = disable, else = sector size) + numdef FIX_SECTOR_SIZE_SKIP_CHECK, 0 ; don't check sector size + numdef FIX_CLUSTER_SIZE, 0 ; fix cluster size + numdef FIX_CLUSTER_SIZE_SKIP_CHECK, 0 ; don't check cluster size + numdef NO_LIMIT, 0 ; allow using more memory than a boot sector + ; also will not write 0AA55h signature! + numdef LBA_SKIP_CHECK, 1 ; don't use proper LBA extensions check + numdef LBA_RETRY, 0 ; retry LBA reads one time + numdef CHS_RETRY, 1 ; retry CHS reads one time + numdef CHS_RETRY_REPEAT,16 ; retry CHS reads multiple times + ; (value of the def is used as count) + + ; Unlike the 1440 KiB diskette image defaults for the FAT12 + ; loader we just fill the BPB with zeros by default. + numdef MEDIAID, 0 ; media ID + numdef UNIT, 0 ; load unit in BPB + numdef CHS_SECTORS, 0 ; CHS geometry field for sectors + numdef CHS_HEADS, 0 ; CHS geometry field for heads + numdef HIDDEN, 0 ; number of hidden sectors + numdef SPI, 0 ; sectors per image + numdef BPS, 0 ; bytes per sector + numdef SPC, 0 ; sectors per cluster + numdef SPF, 0 ; sectors per FAT + numdef SECTOR_FSINFO, 0 ; FSINFO sector + numdef SECTOR_BACKUP, 0 ; backup boot sector + numdef CLUSTER_ROOT, 0 ; root directory first cluster + numdef NUMFATS, 0 ; number of FATs + numdef NUMRESERVED, 0 ; number of reserved sectors + + numdef COMPAT_FREEDOS, 0 ; partial FreeDOS load compatibility + numdef COMPAT_IBM, 0 ; partial IBMDOS load compatibility + numdef COMPAT_MS7, 0 ; partial MS-DOS 7 load compatibility + numdef COMPAT_LDOS, 0 ; lDOS load compatibility + +%if (!!_COMPAT_FREEDOS + !!_COMPAT_IBM + !!_COMPAT_MS7 + !!_COMPAT_LDOS) > 1 + %error At most one set must be selected. +%endif + +%if _COMPAT_FREEDOS + strdef LOAD_NAME, "KERNEL" + strdef LOAD_EXT, "SYS" + numdef LOAD_ADR, 00600h + numdef LOAD_MIN_PARA, paras(512) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 0 + numdef CHECKVALUE, 0 + + numdef SET_BL_UNIT, 1 + numdef MEMORY_CONTINUE, 0 + numdef RELOCATE, 1 + ; The FreeDOS load protocol mandates that the entire file be loaded. +%endif + +%if _COMPAT_IBM + strdef LOAD_NAME, "IBMBIO" + strdef LOAD_EXT, "COM" + numdef LOAD_ADR, 00700h + numdef LOAD_MIN_PARA, paras(512) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 0 + numdef CHECKVALUE, 0 + numdef LOAD_DIR_SEG, 50h + numdef ADD_SEARCH, 1 + strdef ADD_NAME, "IBMDOS" + strdef ADD_EXT, "COM" + numdef ADD_DIR_SEG, 52h + ; Note: The IBMBIO.COM directory entry must be stored at + ; 0:500h, and the IBMDOS.COM directory entry at 0:520h. + + numdef SET_DL_UNIT, 1 + numdef MEMORY_CONTINUE, 1 + ; 3 sectors * 512 BpS should suffice. We load into 700h--7A00h, + ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB). + numdef SET_AXBX_DATA, 1 + numdef DATASTART_HIDDEN,1 + numdef SET_DSSI_DPT, 1 + numdef PUSH_DPT, 1 +%endif + +%if _COMPAT_MS7 + strdef LOAD_NAME, "IO" + strdef LOAD_EXT, "SYS" + numdef LOAD_ADR, 00700h + numdef LOAD_MIN_PARA, paras(1024) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 200h + numdef CHECKVALUE, 0 + + numdef SET_DL_UNIT, 1 + numdef SET_DSSI_DPT, 0 + numdef PUSH_DPT, 1 + numdef MEMORY_CONTINUE, 1 + ; 4 sectors * 512 BpS should suffice. We load into 700h--7A00h, + ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB). + numdef SET_SIDI_CLUSTER,1 + numdef DATASTART_HIDDEN,1 + numdef LBA_SET_TYPE, 1 +%endif + +%if _COMPAT_LDOS + strdef LOAD_NAME, "LDOS" + strdef LOAD_EXT, "COM" + numdef LOAD_ADR, 02000h + numdef LOAD_MIN_PARA, paras(1536) + numdef EXEC_SEG_ADJ, 0 + numdef EXEC_OFS, 400h + numdef CHECKOFFSET, 1020 + numdef CHECKVALUE, "lD" + + numdef SET_DL_UNIT, 0 + numdef SET_CLUSTER, 1 + numdef SET_FAT_SEG, 1 + numdef SET_LOAD_SEG, 1 + numdef MEMORY_CONTINUE, 1 + numdef DATASTART_HIDDEN,0 +%endif + +%if 0 + +Notes about partial load compatibilities + +* FreeDOS: + * Relocates to an address other than 27A00h (1FE0h:7C00h) +* IBMDOS: + * Does not actually relocate DPT, just provide its address +* MS-DOS 7: + * Does not actually relocate DPT, just provide its address + * Does not contain message table used by loader + +%endif + +%if _SET_BL_UNIT && _SET_AXBX_DATA + %error Cannot select both of these options! +%endif + + +%assign LOADLIMIT 0A0000h +%assign POSITION 07C00h + +%if _FIX_SECTOR_SIZE + %assign i 5 + %rep 13-5 + %if (1 << i) != (_FIX_SECTOR_SIZE) + %assign i i+1 + %endif + %endrep + %if (1 << i) != (_FIX_SECTOR_SIZE) + %error Invalid sector size _FIX_SECTOR_SIZE + %endif +%endif + +%if _FIX_CLUSTER_SIZE + %if _FIX_CLUSTER_SIZE > 256 + %error Invalid cluster size _FIX_CLUSTER_SIZE + %endif + %assign i 0 + %rep 8-0 + %if (1 << i) != (_FIX_CLUSTER_SIZE) + %assign i i+1 + %endif + %endrep + %if (1 << i) != (_FIX_CLUSTER_SIZE) + %warning Non-power-of-two cluster size _FIX_CLUSTER_SIZE + %endif +%endif + + +%if (_LOAD_ADR & 0Fh) + %error Load address must be on a paragraph boundary +%endif + +%if _LOAD_ADR > LOADLIMIT + %error Load address must be in LMA +%elif _LOAD_ADR < 00500h + %error Load address must not overlap IVT or BDA +%endif +%if ! _RELOCATE + %if _LOAD_ADR > (POSITION-512) && _LOAD_ADR < (POSITION+1024) + %error Load address must not overlap loader + %endif +%endif + +%if ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS) < 0 + %error Execution address must be in loaded file +%elif ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS+_LOAD_ADR) > LOADLIMIT + %error Execution address must be in LMA +%endif + +%if (_EXEC_OFS & ~0FFFFh) + %error Execution offset must fit into 16 bits +%endif + +%if (_EXEC_SEG_ADJ > 0FFFFh || _EXEC_SEG_ADJ < -0FFFFh) + %error Execution segment adjustment must fit into 16 bits +%endif + + +%if !_CHS && _QUERY_GEOMETRY + %warning No CHS support but querying geometry anyway +%endif + +%if !_CHS && !_LBA + %error Either CHS or LBA or both must be enabled +%endif + + +%if 0 + +There is some logic inside MS-DOS's hard disk partition initialisation +code that sets up several requirements for us to fulfil. Otherwise, +it will not accept the information given in the BPB (using default +information based on the length as specified by MBR/EPBR instead) or +make the whole file system inaccessible except for formatting. Both of +those are undesirable of course. Some/all(?) checks are documented on +pages 601,602 in "DOS Internals", Geoff Chappell 1994, as follows: + +* First three bytes contain either "jmp sho xx\ nop" or "jmp ne xx". +* Media descriptor field >= 0F0h. +* Bytes per sector field == 512. +* Sectors per cluster field a power of 2 (1,2,4,8,16,32,64,128). +* OEM name "version" (last three to four characters) + * must be "20.?", "10.?" (? = wildcard), but no other with "0.?", + * otherwise, must be "2.0", or + * 2nd-to-last,3rd-to-last character codes together > "3.", or + * those == "3.", last character code > "0" + +To stay compatible to those, display a warning here if the name +itself would disqualify our boot sector already. + +%endif + +%push +%strlen %$len _OEM_NAME_FILL +%if %$len != 1 + %error Specified OEM name fill must be 1 character +%endif +%strlen %$len _OEM_NAME +%define %$nam _OEM_NAME +%if %$len > 8 + %error Specified OEM name is too long +%else + %assign %$warn 0 + %rep 8 - %$len + %strcat %$nam %$nam,_OEM_NAME_FILL + %endrep + %substr %$prefix %$nam 5 ; "wvxyZa.b", get "Z" + %substr %$major %$nam 6,7 ; "wvxyzA.b", get "A." + %substr %$minor %$nam 8 ; "wvxyza.B", get "B" + %if %$major == "0." + %ifn %$prefix == "1" || %$prefix == "2" + %assign %$warn 1 + %endif + %elifn %$major == "2." && %$minor == "0" + %if %$major < "3." + %assign %$warn 1 + %elif %$major == "3." && %$minor < "1" + %assign %$warn 1 + %endif + %endif + %if %$warn + %warning Specified OEM name fails MS-DOS's validation + %endif +%endif +%pop + +; 512-byte stack (minus the variables). +ADR_STACK_LOW equ 7C00h - 200h ; 07A00h + +ADR_FSIBOOT equ end -start+7C00h ; 07E00h + +%define _AFTER (ADR_FSIBOOT + 512) +%if _RELOCATE + ; dynamic allocation of FAT buffer +%else + %if _LOAD_ADR < 7C00h + ADR_FATBUF equ _AFTER + %define _AFTER (ADR_FATBUF + 8192) + %else + ADR_FATBUF equ 2000h + %endif +%endif + +%if _ADD_SEARCH + %if _RELOCATE + ; dynamic allocation of dir buffer + %elif _LOAD_ADR < 7C00h + ADR_DIRBUF equ _AFTER + %define _AFTER (ADR_DIRBUF + 8192) + %else + ADR_DIRBUF equ 4000h + %endif +%else + %if _RELOCATE + ADR_DIRBUF equ _LOAD_ADR ; 00600h + %elif _LOAD_ADR < 7C00h + ; one-sector directory buffer. Assumes sectors are no larger than 8 KiB + ADR_DIRBUF equ _AFTER ; 0A000h + %define _AFTER (ADR_DIRBUF + 8192) + %else + ADR_DIRBUF equ 4000h + %endif +%endif +ADR_FREE equ _AFTER ; 08000h + +%if ! _RELOCATE + %if ((ADR_FATBUF + 8192 - 1) & ~0FFFFh) != (ADR_FATBUF & ~0FFFFh) + %warning Possibly crossing 64 KiB boundary while reading FAT + %endif +%endif + +%ifn _RELOCATE && _ADD_SEARCH + %if ((ADR_DIRBUF + 8192 - 1) & ~0FFFFh) != (ADR_DIRBUF & ~0FFFFh) + %warning Possibly crossing 64 KiB boundary while reading directory + %endif +%endif + +%if _LOAD_ADR >= ADR_FREE || _RELOCATE + ; If reading on a sector size boundary, no crossing can occur. + ; Check for all possible sector sizes (32 B to 8 KiB). If one + ; of them fails display a warning, including the minimum size. + %assign SECSIZECHECK 32 + %assign EXITREP 0 + %rep 256 + %ifn EXITREP + %if _LOAD_ADR & (SECSIZECHECK - 1) + %warning Possibly crossing 64 KiB boundary while reading file (sector size >= SECSIZECHECK) + %assign EXITREP 1 + %exitrep + %endif + %if SECSIZECHECK == 8192 + %assign EXITREP 1 + %exitrep + %endif + %assign SECSIZECHECK SECSIZECHECK * 2 + %endif + %endrep +%else + ; If loading below the boot sector, address 1_0000h is never reached. +%endif + + + struc FSINFO ; FAT32 FSINFO sector layout +.signature1: resd 1 ; 41615252h ("RRaA") for valid FSINFO +.reserved1: ; former unused, initialized to zero by FORMAT +.fsiboot: resb 480 ; now used for FSIBOOT +.signature2: resd 1 ; 61417272h ("rrAa") for valid FSINFO +.numberfree: resd 1 ; FSINFO: number of free clusters or -1 +.nextfree: resd 1 ; FSINFO: first free cluster or -1 +.reserved2: resd 3 ; unused, initialized to zero by FORMAT +.signature3: resd 1 ; AA550000h for valid FSINFO or FSIBOOT + endstruc + + struc FSIBOOTG ; FSIBOOT general layout +.signature: resq 1 ; 8 bytes that identify the FSIBOOT type +.fsicode: resb 470 ; 470 bytes FSIBOOT type specific data or code +.dirsearch: resw 1 ; 1 word -> directory search entrypoint + endstruc + + struc DIRENTRY +deName: resb 8 +deExt: resb 3 +deAttrib: resb 1 +dePlusSize: resb 1 + resb 7 +deClusterHigh: resw 1 +deTime: resw 1 +deDate: resw 1 +deClusterLow: resw 1 +deSize: resd 1 + endstruc + +ATTR_READONLY equ 1 +ATTR_HIDDEN equ 2 +ATTR_SYSTEM equ 4 +ATTR_VOLLABEL equ 8 +ATTR_DIRECTORY equ 10h +ATTR_ARCHIVE equ 20h + + +; use byte-offset addressing from BP for smaller code +%define VAR(x) ((x) - start) + bp + + + cpu 8086 +; bootsector loaded at address 07C00h, addressable using 0000h:7C00h + org POSITION +start: + + +%define _LASTVARIABLE start + %macro nextvariable 2.nolist +%1 equ (_LASTVARIABLE - %2) +%define _LASTVARIABLE %1 + %endmacro + +; Variables + +; (dword) sector where the first cluster's data starts + nextvariable data_start, 4 + +; (word) current load segment (points behind last loaded data) + nextvariable load_seg, 2 + +; (word) segment of FAT buffer +; for FAT12 this holds the entire FAT +; for FAT16 this holds the sector given by wo[fat_sector] +; for FAT32 this holds the sector given by dwo[fat_sector] + nextvariable fat_seg, 2 + +; (dword for FAT32) currently loaded sector-in-FAT, -1 if none + nextvariable fat_sector, 4 + +; (dword for FAT32) first cluster of load file + nextvariable first_cluster, 4 + +ADR_STACK_START equ _LASTVARIABLE -start+POSITION + +; (word) number of 16-byte paragraphs per sector + nextvariable para_per_sector, 2 + +; (word) number of 32-byte directory entries per sector + nextvariable entries_per_sector, 2 + +; (word) segment of last available memory for sector + nextvariable last_available_sector, 2 + +; (word) actual sectors per cluster + nextvariable adj_sectors_per_cluster, 2 + +; (word) paragraphs left to read + nextvariable paras_left, 2 + +lowest_variable equ _LASTVARIABLE + + + jmp strict short skip_bpb +%if !_CHS && _LBA_SET_TYPE + db 0Ch ; LBA-enabled FAT32 FS partition type +%else + nop ; default: no LBA +%endif + + +; BIOS Parameter Block (BPB) +; +; Installation will use the BPB already present in your file system. +; These values must be initialised when installing the loader. + +oem_id: ; offset 03h (03) - not used by this code + fill 8,_OEM_NAME_FILL,db _OEM_NAME +bytes_per_sector: ; offset 0Bh (11) - refer to _FIX_SECTOR_SIZE ! + dw _BPS +sectors_per_cluster: ; offset 0Dh (13) - refer to _FIX_CLUSTER_SIZE ! + db _SPC & 255 +fat_start: +num_reserved_sectors: ; offset 0Eh (14) + dw _NUMRESERVED +num_fats: ; offset 10h (16) + db _NUMFATS +num_root_dir_ents: ; offset 11h (17) + dw 0 +total_sectors: ; offset 13h (19) - not used by this code +%if _SPI < 1_0000h + dw _SPI +%else + dw 0 +%endif +media_id: ; offset 15h (21) - not used by this code + db _MEDIAID +sectors_per_fat: ; offset 16h (22) + dw 0 +sectors_per_track: ; offset 18h (24) + dw _CHS_SECTORS +heads: ; offset 1Ah (26) + dw _CHS_HEADS +hidden_sectors: ; offset 1Ch (28) + dd _HIDDEN +total_sectors_large: ; offset 20h (32) - not used by this code +%if _SPI >= 1_0000h + dd _SPI +%else + dd 0 +%endif + +sectors_per_fat_large: ; offset 24h (36) + dd _SPF +fsflags: ; offset 28h (40) + dw 0 +fsversion: ; offset 2Ah (42) + dw 0 +root_cluster: ; offset 2Ch (44) + dd _CLUSTER_ROOT +fsinfo_sector: ; offset 30h (48) + dw _SECTOR_FSINFO +backup_sector: ; offset 32h (50) + dw _SECTOR_BACKUP + + times 12 db 0 ; offset 34h (52) reserved + +; Extended BPB ; offset 40h (64) + +boot_unit: db _UNIT + db 0 +ext_bpb_signature: db 29h +serial_number: dd _VOLUMEID +volume_label: fill 11,32,db _DEFAULT_LABEL +filesystem_identifier: fill 8,32,db "FAT32" + + +; Initialised data + + align 2 +fsiboot_table: ; this table is used by the FSIBOOT stage +.error: dw error + ; INP: al = error condition letter + ; ('B' = bad chain / FS error, 'F' = file not found, + ; 'R' = disk read error, 'M' = not enough memory, + ; 'E' = not enough data in file) +.success: dw load_finish + ; INP: dword [ss:sp] = first cluster + ; Note: The first cluster dword is always filled in + ; by FSIBOOT; the option _SET_SIDI_CLUSTER only + ; affects usage in the primary loader. +%if _MEMORY_CONTINUE +.memory_full: dw load_finish_mc + ; INP: al = error condition letter ('M'), + ; dword [ss:sp] = current cluster number, + ; dword [ss:sp + 4] = first cluster, refer to .success +%else +.memory_full: dw error + ; refer to previous .memory_full comment +%endif +.read_sector: dw read_sector + ; INP: dx:ax = sector number within partition, + ; bx => buffer, (_LBA) ds = ss + ; OUT: dx:ax incremented, bx => incremented, + ; es = input bx, does not return if error occurred + ; CHG: none +%if _RELOCATE && _ADD_SEARCH +.dirbuf: dw 8192 >> 4 + ; initialised to segment displacement from FAT buffer +%else +.dirbuf: dw ADR_DIRBUF>>4 + ; => directory sector buffer (one sector) +%endif +.writedirentry: dw writedirentry.loaddir +.filename: dw .load_name + ; -> name to search +.minpara: dw _LOAD_MIN_PARA +.loadseg: dw _LOAD_ADR>>4 + ; => where to load + +.load_name: ; = blank-padded 11-byte filename to search for + fill 8,32,db _LOAD_NAME + fill 3,32,db _LOAD_EXT + +fsiboot_name: + fill 8, 32, db _FSIBOOTNAME + + +; Code + + ; Note that this may be entered with cs:ip = 07C0h:0 ! +skip_bpb: + cli + cld + xor cx, cx + mov bp, start ; magic bytes - checked by instsect + mov ss, cx + mov sp, ADR_STACK_START +%if _USE_AUTO_UNIT + mov [VAR(boot_unit)], dl; magic bytes - checked by instsect +%else + mov dl, [VAR(boot_unit)]; magic bytes - checked by instsect +%endif + + ; Note: es is left uninitialised here until the first call to + ; read_sector if the below conditional is false. +%if _USE_PART_INFO ; +19 bytes + mov es, cx +; Note: Award Medallion BIOS v6.0 (ASUS MED 2001 ACPI BIOS Revision 1009) +; loads from a floppy disk drive with ds:si = 0F000h:0A92Dh -> +; FF FF FF FF 08 00 08 01 FF FF FF FF FF FF FF FF, which was detected +; as a valid partition table entry by this handling. Therefore, we +; only accept partition information when booting from a hard disk now. + test dl, dl ; floppy ? + jns @F ; don't attempt detection --> +; Check whether an MBR left us partition information. +; byte[ds:si] bit 7 means active and must be set if valid. + cmp byte [si], cl ; flags for xx-00h (result is xx), SF = bit 7 + jns @F ; xx < 80h, ie info invalid --> +; byte[ds:si+4] is the file system type. Check for valid one. + cmp byte [si+4], cl ; is it zero? + je @F ; yes, info invalid --> +; Info valid, trust their hidden sectors over hardcoded. +; Assume the movsw instructions won't run with si = FFFFh. + mov di, hidden_sectors ; -> BPB field + add si, 8 ; -> partition start sector in info + movsw + movsw ; overwrite BPB field with value from info +@@: +%endif + mov ds, cx + sti + + +%if _QUERY_GEOMETRY ; +27 bytes +; 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 + ; Already from prologue cx = 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 [VAR(sectors_per_track)], cx + mov cl, dh ; cx = maximum head number + inc cx ; cx = number of heads (H is 0-based) + mov [VAR(heads)], cx +@@: +%endif + + +; 16-byte paragraphs per sector + mov ax, [VAR(bytes_per_sector)] + mov cl, 4 + shr ax, cl + push ax ; push into word [VAR(para_per_sector)] + +; 32-byte FAT directory entries per sector + shr ax, 1 ; /2 = 32-byte entries per sector + push ax ; push into word [VAR(entries_per_sector)] + +load_fsiboot: + cmp ax, 1024 >> 5 ; sector size is at least 1 KiB (large) ? + jae .loaded ; already loaded as part of the boot sector --> + + ; If this code runs, then ax was below 1024 >> 5, + ; therefore this cwd instruction always zeros dx. + cwd + + mov ax, [VAR(fsinfo_sector)] +; inc ax +; jz error_fsiboot ; was FFFFh, invalid --> +; dec ax + ; FFFFh always fails the < num_reserved_sectors check + test ax, ax + jz @F ; is 0 ? invalid --> (ZR, NC) + cmp ax, [VAR(num_reserved_sectors)] +@@: ; (ZR, NC if ax == 0) + jae error_fsiboot ; (jump if NC) + ; dx:ax = FSINFO sector (dx = 0 from cwd) + mov cx, 512 + mov bx, ADR_FSIBOOT >> 4 +@@: + call read_sector + sub cx, [VAR(bytes_per_sector)] + ja @B ; read 512 bytes --> + +.loaded: + push ds + pop es + mov di, fsiboot.signature + mov si, fsiboot_name + mov cx, 4 ; size of fsiboot_name + repe cmpsw + jne error_fsiboot + ; Note that now es:di -> fsiboot.start + +%if _LOAD_ADR >= ADR_FREE || _RELOCATE +; Get conventional memory size and store it + int 12h + mov cl, 6 + shl ax, cl + %if _RPL ; +33 bytes + xchg dx, ax + 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: + push ss + pop ds + xchg ax, dx + %endif + %if _RELOCATE + %if _ADD_SEARCH + sub ax, (20 * 1024 + 8192 + 8192 + (8192-16) + 1024 + 7C00h) >> 4 + %else + sub ax, (20 * 1024 + 8192 + (8192-16) + 1024 + 7C00h) >> 4 + %endif + ; 20 KiB: reserved for iniload + ; 8 KiB: dir buffer (only if _ADD_SEARCH) + ; 8 KiB: FAT buffer + ; 8 KiB - 16 B: to allow rounding down position of buffers + ; 1 KiB: sector + FSIBOOT + ; 7C00h: stack and to allow addressing with 7C00h in bp + ; + ; Note also that by addressing the stack and sector and FSIBOOT + ; with bp at 7C00h, and insuring this subtraction doesn't + ; underflow, makes sure that we do not overwrite the IVT or + ; BDA. (However, we assume that ax is at least 60h or so.) + ; + ; The FAT buffer segment is masked so that the actual buffer + ; is stored on an 8 KiB boundary. This is to ensure that + ; the buffer doesn't possibly cross a 64 KiB DMA boundary. + jc error_fsiboot + cmp ax, (end_after_fsiboot -start+7C00h - ADR_STACK_LOW + 15) >> 4 + ; This check is to ensure that the start of the destination + ; for the relocation (stack, sector, FSIBOOT) is + ; above-or-equal the end of the source for the relocation. + ; That is, to avoid overwriting any of the source with the + ; string move instruction (which for simplicity is always UP). + jb error_fsiboot + + ; With _RELOCATE enabled, the FAT buffer segment + ; is fixed up by the primary loader here before + ; FSIBOOT is executed. + mov bx, ((8192 - 16) + 1024 + 7C00h)>>4 +; bx is initialised to the value +; ((8192 - 16) + 1024 + 7C00h)>>4 +; so this is like calculating the following for its value: +; ((LMA_top - 20 KiB - 8 KiB - 8 KiB{AS} - (8 KiB - 16 B) - 1 KiB - 7C00h) + \ +; ((8 KiB - 16 B) + 1 KiB + 7C00h))>>4 +; == (LMA_top - 20 KiB - 8 KiB - 8 KiB{AS})>>4 +; {AS} = only if _ADD_SEARCH + add bx, ax + and bx, ~ ((8192>>4) - 1) ; => FAT sector buffer (one sector) + %if _ADD_SEARCH +; .dirbuf is initialised to 8192 >> 4 + add word [VAR(fsiboot_table.dirbuf)], bx + ; => dir sector buffer (one sector) + %endif + + push ax + push di ; -> reloc destination of fsiboot.start + + mov es, ax ; => destination + mov si, sp ; ds:si = ss:entries_per_sector - 4 + mov di, si ; es:di -> destination for stack low + mov cx, (end_after_fsiboot - (entries_per_sector - 4)) >> 1 + ; end_after_fsiboot is the top of used memory + ; entries_per_sector is the lowest filled stack frame slot + ; 4 is for the additional slots taken by the return address + rep movsw ; relocate stack, sector, and FSIBOOT + mov ss, ax + mov ds, ax ; relocate these + add ax, (ADR_STACK_LOW) >> 4 ; (rounding down) => behind available + + retf ; jump to relocated FSIBOOT + %else + sub ax, (20 * 1024) >> 4 ; 20 KiB reserved for iniload + jc error_fsiboot + mov bx, ADR_FATBUF >> 4 ; => FAT sector buffer (one sector) + push es + push di ; -> fsiboot.start + retf + ; Do a far return here to ensure that cs is zero. + %endif +%elif _LOAD_ADR < ADR_STACK_LOW + mov ax, (ADR_STACK_LOW >> 4) + mov bx, ADR_FATBUF >> 4 ; => FAT sector buffer (one sector) + push es + push di ; -> fsiboot.start + retf + ; Do a far return here to ensure that cs is zero. +%else + %error Load address within used memory +%endif + + + ; INP: es:bx -> found dir entry in dir sector buffer + ; si:di = loaded sector-in-FAT + ; CHG: ax, cx, dx + ; STT: ss:bp -> boot sector + ; ds = ss + ; UP + ; OUT: directory entry copied if so desired + ; (is a no-op if not to copy dir entry) +writedirentry: +%if _LOAD_DIR_SEG +.loaddir: + mov ax, _LOAD_DIR_SEG +%else +.loaddir: equ read_sector.retn +%endif +%if _LOAD_DIR_SEG || (_ADD_DIR_SEG && _ADD_SEARCH) + ; INP: ax => where to store directory entry +.ax: + push ds + push si + push di + push es + pop ds + mov si, bx ; ds:si -> directory entry + mov cx, DIRENTRY_size >> 1 + mov es, ax + xor di, di ; es:di -> where to store directory entry + rep movsw ; move to here (one directory entry) + push ds + pop es ; es:bx -> dir entry in dir sector buffer + pop di + pop si + pop ds + retn +%endif + + +%if _MEMORY_CONTINUE +load_finish_mc: + pop bx + pop cx +%endif +load_finish: + +%if _ADD_SEARCH + mov word [VAR(fsiboot_table.filename)], add_name + call near word [dirsearch_entrypoint] + %if _ADD_DIR_SEG + mov ax, _ADD_DIR_SEG + call writedirentry.ax + %endif +%endif + +%if _TURN_OFF_FLOPPY +; turn off floppy motor + mov dx,3F2h + mov al,0 + out dx,al +%endif + +; Set-up registers for and jump to loaded file +; Already: ss:bp-> boot sector containing BPB +%if _CHECKVALUE +CHECKLINEAR equ _LOAD_ADR + _CHECKOFFSET + %if CHECKLINEAR <= (64 * 1024 - 2) && ! _RELOCATE + cmp word [CHECKLINEAR], _CHECKVALUE + %else + mov ax, CHECKLINEAR >> 4 + mov es, ax + cmp word [es:CHECKLINEAR & 15], _CHECKVALUE + %endif + mov al, 'V' ; check 'V'alue mismatch + jne error +%endif +%if _SET_SIDI_CLUSTER && (_PUSH_DPT || _SET_DSSI_DPT) + pop cx + pop dx +%endif +%if _PUSH_DPT || _SET_DSSI_DPT + %ifn _SET_DSSI_DPT ; (implying that only _PUSH_DPT is set) + %if _RELOCATE + xor ax, ax + mov es, ax ; => IVT + mov di, 1Eh*4 + les si, [es:di] ; -> original (also current) DPT + push es + push si ; original (also current) DPT address + push ax + push di ; 0000h:0078h (address of 1Eh IVT entry) + %else + ; If not _RELOCATE, ds = 0000h (=> IVT) + mov di, 1Eh*4 + les si, [di] ; -> original (also current) DPT + push es + push si ; original (also current) DPT address + ; If not _RELOCATE, ss = 0000h (=> IVT) + push ss + push di ; 0000h:0078h (address of 1Eh IVT entry) + %endif + %else + %if _RELOCATE + xor ax, ax + mov ds, ax ; => IVT + %endif + ; If not _RELOCATE, ds = 0000h (=> IVT) + mov di, 1Eh*4 + lds si, [di] ; -> original (also current) DPT + %if _PUSH_DPT + push ds + push si ; original (also current) DPT address + %if _RELOCATE + push ax + push di ; 0000h:0078h (address of 1Eh IVT entry) + %else + ; If not _RELOCATE, ss = 0000h (=> IVT) + push ss + push di ; 0000h:0078h (address of 1Eh IVT entry) + %endif + %endif + %endif +%endif +%if _ZERO_ES || _ZERO_DS + %if _RELOCATE + %ifn _PUSH_DPT || _SET_DSSI_DPT + xor ax, ax + %endif + %if _ZERO_ES + mov es, ax + %endif + %if _ZERO_DS + mov ds, ax + %endif + %else + %if _ZERO_ES + push ss + pop es + %endif + %if _ZERO_DS && _SET_DSSI_DPT + push ss + pop ds + %endif + %endif +%endif +%if _DATASTART_HIDDEN + mov bx, [VAR(hidden_sectors + 0)] + mov ax, [VAR(hidden_sectors + 2)] + add word [VAR(data_start + 0)], bx + adc word [VAR(data_start + 2)], ax +%endif +%if _SET_AXBX_DATA + mov bx, [VAR(data_start)] + mov ax, [VAR(data_start+2)] +%endif +%if _SET_SIDI_CLUSTER + %if _SET_DSSI_DPT + %error Cannot select both of these. + %endif + %if _PUSH_DPT || _SET_DSSI_DPT + mov di, cx + mov si, dx + %else + pop di + pop si + %endif +%endif +%if _SET_DL_UNIT + mov dl, [VAR(boot_unit)]; set dl to unit + %if _SET_BL_UNIT + mov bl, dl ; set bl to unit, too + %endif +%elif _SET_BL_UNIT + mov bl, [VAR(boot_unit)]; set bl to unit +%endif + ; ss:bp-> boot sector with BPB + jmp (_LOAD_ADR>>4)+_EXEC_SEG_ADJ:_EXEC_OFS + + +error_fsiboot: + mov al,'I' + + db __TEST_IMM16 ; (skip mov) +read_sector.err: + mov al, 'R' ; Disk 'R'ead error + +error: +%if _RELOCATE + mov bx, 7 + mov ds, bx + mov bh, [462h - 70h] +%else + mov bh, [462h] + mov bl, 7 +%endif + mov ah, 0Eh + int 10h ; display character + mov al, 07h + int 10h ; beep! + + xor ax, ax ; await key pressed + int 16h + + int 19h ; re-start the boot process + + + ; Read a sector using Int13.02 or Int13.42 + ; + ; INP: dx:ax = sector number within partition + ; bx => 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 => next buffer (bx = es+word[para_per_sector]) + ; es = input bx + ; CHG: - +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,[VAR(hidden_sectors + 0)] + adc dx,[VAR(hidden_sectors + 2)] + %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33 + jc .err + %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 + push cx ; highest word = 0 + %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 + push bx + push cx ; bx => buffer + inc cx + push cx ; word number of sectors to read + mov cl, 10h + push cx ; word size of disk address packet + mov si, sp ; ds:si -> disk address packet (on stack) + + %if _LBA_SKIP_CHECK ; -14 bytes + mov dl, [VAR(boot_unit)] + %else + mov ah, 41h + mov dl, [VAR(boot_unit)] + mov bx, 55AAh + stc + int 13h ; 13.41.bx=55AA extensions installation check + jc .no_lba + cmp bx, 0AA55h + jne .no_lba + test cl, 1 ; support bitmap bit 0 + jz .no_lba + %endif + +%if _LBA_RETRY + mov ah, 42h + int 13h ; 13.42 extensions read + jnc .lba_done + + xor ax, ax + int 13h + + ; have to reset the LBAPACKET's lpCount, as the handler may + ; set it to "the number of blocks successfully transferred". + ; (in any case, the high byte is still zero.) + mov byte [si + 2], 1 +%endif + + mov ah, 42h + int 13h + %if _LBA_SKIP_CHECK && _CHS + jnc .lba_done + cmp ah, 1 ; invalid function? + je .no_lba ; try CHS instead --> +.err_CY: +.err_2: + jmp .lba_error + %else +.err_CY: + jc .lba_error +.err_2: equ .err + %endif + +.lba_done: +%if _CHS && _LBA_SET_TYPE + mov byte [bp + 2], 0Ch ; LBA-enabled FAT32 FS partition type +%endif + add sp, 10h + pop bx + mov es, bx +%if _CHS + jmp short .chs_done +%endif + +.lba_error: equ .err + + %if !_CHS +.no_lba: equ .err + %else +.no_lba: + add sp, 8 + pop cx ; cx = low word of sector + pop ax + pop dx ; dx:ax = middle two words of sector + ; Here dx <= 1 if _LBA_33_BIT, else zero. + ; If dx is nonzero then the CHS calculation + ; should fail. If CHS sectors is equal to 1 + ; (very unusual) then the div may fail. Else, + ; we will detect a cylinder > 1023 eventually. + pop si ; discard highest word of qword + %endif +%else +.err_2: equ .err +%endif + +%if _CHS ; +70 bytes +; 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 + ; from the .no_lba popping we already have: + ; cx = low word of sector + ; dx:ax = high word of sector + %endif + div word [VAR(sectors_per_track)] + xchg cx,ax + div word [VAR(sectors_per_track)] + 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 [VAR(heads)] + ; ax = high / heads, dx = high % heads + xchg bx, ax ; bx = high / heads, ax = low quotient + div word [VAR(heads)] + +; 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,[VAR(boot_unit)] ; dl = drive + jnz .err_2 ; 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 +%if _CHS_RETRY_REPEAT + mov si, _CHS_RETRY_REPEAT + 1 +.loop_chs_retry_repeat: + mov ax, 0201h + int 13h ; read one sector + jnc .done + xor ax, ax + int 13h ; reset disk + dec si ; another attempt ? + jnz .loop_chs_retry_repeat ; yes --> + jmp .err_2 +%else + %if _CHS_RETRY + mov ax, 0201h + int 13h ; read one sector + jnc .done +; reset drive + xor ax, ax + int 13h + %endif +; try read again + mov ax, 0201h + int 13h + jc .err_CY +%endif +%if ! _LBA +.err_CY: equ .err +%endif + +.done: +; increment segment + mov bx, es +%endif + +.chs_done: +%if _FIX_SECTOR_SIZE + add bx, _FIX_SECTOR_SIZE >> 4 +%else + add bx, [VAR(para_per_sector)] +%endif + + pop si + pop ax + pop cx + pop dx +; increment LBA sector number + inc ax + jne @F + inc dx +@@: + +.retn: + retn + + +%if _ADD_SEARCH +add_name: ; = blank-padded 11-byte filename to search for + fill 8,32,db _ADD_NAME + fill 3,32,db _ADD_EXT +%endif + + +%if !_NO_LIMIT +available: + _fill 508,38,start + +signatures: + dw 0 +; 2-byte magic bootsector signature + dw 0AA55h + +%assign num signatures-available +%warning FAT32: num bytes still available. +%else ; for testing + align 4 +signatures: + dw 0 +; 2-byte magic bootsector signature + dw 0AA55h +%endif + + align 16, nop +end: + +fsiboot: + dd "RRaA" +.signature: + fill 8, 32, db _FSIBOOTNAME + +%if ($ - .signature) != 8 + %error Unexpected name size +%endif +.start: + ; INP: ax => after last segment to be used for loading + ; bx => FAT buffer (8 KiB) + ; ss:bp -> boot sector, with EBPB + ; dwo [ss:bp - 4] = data_start (uninit) + ; wo [ss:bp - 6] = load_seg (uninit) + ; wo [ss:bp - 8] = fat_seg (uninit) + ; dwo [ss:bp - 12] = fat_sector (uninit) + ; dwo [ss:bp - 16] = first_cluster (uninit) + ; wo [ss:bp - 18] = para_per_sector + ; wo [ss:bp - 20] = entries_per_sector + ; ss:sp = ss:bp - 20 + ; (Note: The following stack frame entries are currently + ; only used by FSIBOOT itself, so they may be + ; considered implementation detail instead of + ; part of the FSIBOOT protocol.) + ; wo [ss:bp - 22] = last_available_sector (uninit) + ; wo [ss:bp - 24] = adj_sectors_per_cluster (uninit) + ; wo [ss:bp - 26] = paras_left (uninit) + ; Stack layout has to match! + ; ss = ds + ; ss:bp + ((11 + ebpbNew + BPBN_size + 1) & ~1) + ; = ss:fsiboot_table, refer to its comments + ; cs set to address jump table offsets in fsiboot_table + ; ds set to address fsiboot_table.load_name + ; OUT: Jumps to fsiboot_table.error if error occurs: + ; al = error condition letter + ; Else: + ; data_start (within partition) initialised + ; load_seg => behind last loaded data + ; fat_seg initialised (from bx) + ; fat_sector initialised + ; (-1 initially, loaded sector-in-FAT later) + ; first_cluster initialised + ; last_available_sector initialised + ; (from input ax minus word [para_per_sector]) + ; adj_sectors_per_cluster initialised (from BPB) + ; paras_left initialised + ; Jumps to fsiboot_table.success if loaded entirely: + ; dword [ss:sp] = first cluster + ; Jumps to fsiboot_table.memory_full if loaded partially: + ; al = error condition letter ('M') + ; dword [ss:sp] = current cluster number + ; dword [ss:sp + 4] = first cluster + + mov word [VAR(fat_seg)], bx ; initialise => FAT buffer + + sub ax, word [VAR(para_per_sector)] + jc .CY_fsiboot_error_badchain + push ax ; push into word [VAR(last_available_sector)] + + mov bx, word [VAR(fsiboot_table.loadseg)] + mov word [VAR(load_seg)], bx ; initialise => load address + cmp ax, bx + jb .CY_fsiboot_error_badchain + +; adjusted sectors per cluster (store in a word, +; and decode EDR-DOS's special value 0 meaning 256) + xor ax, ax + mov al, [VAR(sectors_per_cluster)] + dec al + inc ax + push ax ; push into word [VAR(adj_sectors_per_cluster)] + dec ax ; ! ah = 0 + mov al,[VAR(num_fats)] ; ! ah = 0, hence ax = number of FATs + push ax + mul word [VAR(sectors_per_fat_large)] + ; ax = low word SpF*nF + ; dx = high word + xchg bx, ax + xchg cx, dx + ; cx:bx = first mul + pop ax + mul word [VAR(sectors_per_fat_large + 2)] + ; ax = high word adjust + ; dx = third word + test dx, dx + stc + jnz .CY_fsiboot_error_badchain + ; dx = zero + xchg dx, ax + ; dx = high word adjust + ; ax = zero + add dx, cx + jc .CY_fsiboot_error_badchain + ; dx:bx = result + xchg ax, bx + ; dx:ax = result + ; bx = zero + + add ax,[VAR(num_reserved_sectors)] + adc dx, bx ; bx is zero here +.CY_fsiboot_error_badchain: + jc ..@CY_2_fsiboot_error_badchain + +; first sector of disk data area: + mov [VAR(data_start)], ax + mov [VAR(data_start+2)], dx + + mov di, -1 + mov si, di + mov word [VAR(fat_sector + 2)], si + mov word [VAR(fat_sector + 0)], di + call dirsearch + + +found_load_file: + call near word [VAR(fsiboot_table.writedirentry)] + +; get starting cluster of file + push word [es:bx + deClusterHigh] + push word [es:bx + deClusterLow] + + ; check FAT+ size bits + test byte [es:bx + dePlusSize], 0E7h + ; test whether bits 7-5 and 2-0 NZ +; https://web.archive.org/web/20150219123449/http://www.fdos.org/kernel/fatplus.txt + jnz .large_file ; yes, clamp to maximum paras --> + mov ax, [es:bx + deSize + 2] + mov bx, [es:bx + deSize] + ; ax:bx = file size (non-FAT+) + + mov cx, [VAR(bytes_per_sector)] + dec cx ; BpS - 1 + add bx, cx + adc ax, 0 ; large ? + jc .large_file ; yes, clamp to maximum paras --> + + not cx ; ~ (BpS - 1) + and bx, cx ; mask to limit to rounded-up sector + ; (this also rounds up paragraphs) + mov cx, 4 +@@: + shr ax, 1 + rcr bx, 1 + loop @B + ; ax:bx = size in paragraphs + ; bx = size in paragraphs if < 1_0000h + test ax, ax ; > 0FFFFh paras ? + jz @F ; no, take actual size --> +.large_file: + mov bx, 0FFFFh ; cx = clamp size to 0FFFFh paras +@@: + call check_enough.in_bx ; (CHG ax) + + pop ax + pop dx ; dx:ax = first cluster + + push bx ; push into word [VAR(paras_left)] + + mov word [VAR(first_cluster + 0)], ax + mov word [VAR(first_cluster + 2)], dx + push dx + push ax ; remember cluster for later + + call check_clust +..@CY_2_fsiboot_error_badchain: + jc ..@CY_3_fsiboot_error_badchain + +next_load_cluster: + call clust_to_first_sector + ; dx:ax = first sector of cluster + ; cx:bx = cluster value + push cx + push bx ; preserve cluster number for later + + mov cx, [VAR(adj_sectors_per_cluster)] + +; xxx - this will always load an entire cluster (e.g. 64 sectors), +; even if the file is shorter than this +@@: + mov bx, [VAR(load_seg)] ; => where to read next sector + cmp bx, [VAR(last_available_sector)] + jbe @F + call check_enough + mov al, 'M' ; (! _MEMORY_CONTINUE: error code) + jmp near word [VAR(fsiboot_table.memory_full)] + +@@: + call near word [VAR(fsiboot_table.read_sector)] + mov [VAR(load_seg)], bx ; => after last read data + + mov bx, [VAR(para_per_sector)] + sub word [VAR(paras_left)], bx + jbe @F ; read enough --> + + loop @BB + pop bx + pop cx + + call clust_next + jnc next_load_cluster + inc ax + inc ax + test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh, + ; clear in 0, 1, and 0FFF_FFF7h + jz fsiboot_error_badchain + db __TEST_IMM16 +@@: + pop bx + pop cx + call check_enough + jmp near word [VAR(fsiboot_table.success)] + + +dirsearch: + mov ax, [VAR(root_cluster)] + mov dx, [VAR(root_cluster + 2)] + call check_clust +..@CY_3_fsiboot_error_badchain: + jc fsiboot_error_badchain + +next_root_clust: + call clust_to_first_sector + push cx + push bx + mov cx, [VAR(adj_sectors_per_cluster)] +next_root_sect: + push cx + mov cx, [VAR(entries_per_sector)] + +; Scan root directory for file. We don't bother to check for deleted +; entries (E5h) or entries that mark the end of the directory (00h). + mov bx, [VAR(fsiboot_table.dirbuf)] + call near word [VAR(fsiboot_table.read_sector)] + + push di + xor di, di ; es:di-> first entry in this sector +next_ent: + test byte [es:di + deAttrib], ATTR_DIRECTORY | ATTR_VOLLABEL + jnz @F ; directory, label, or LFN entry --> (NZ) + push si + push di + push cx + mov si, [VAR(fsiboot_table.filename)] + ; ds:si-> name to match + mov cx, 11 ; length of padded 8.3 FAT filename + repe cmpsb ; check entry + pop cx + pop di + pop si +@@: ; ZR = match, NZ = mismatch + je found_it ; found entry --> + lea di, [di + DIRENTRY_size] + + loop next_ent ; count down sector's entries (jumps iff cx >0) + pop di + pop cx + loop next_root_sect + pop bx + pop cx + call clust_next + jnc next_root_clust +file_not_found: + mov al, 'F' + db __TEST_IMM16 +fsiboot_error_badchain: + mov al, 'B' + +fsiboot_error: + jmp near word [VAR(fsiboot_table.error)] + + + ; 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 [VAR(adj_sectors_per_cluster)] + xchg bx, ax + xchg cx, dx + pop ax + mul word [VAR(adj_sectors_per_cluster)] + test dx, dx + jnz fsiboot_error_badchain + xchg dx, ax + add dx, cx + jc ..@CY_fsiboot_error_badchain + xchg ax, bx + + add ax, [VAR(data_start)] + adc dx, [VAR(data_start + 2)] +..@CY_fsiboot_error_badchain: + jc fsiboot_error_badchain + ; dx:ax = first sector in cluster + pop bx + pop cx ; cx:bx = cluster + retn + + +check_enough: + mov bx, [VAR(load_seg)] + ; => behind last read sector + sub bx, word [VAR(fsiboot_table.loadseg)] +.in_bx: + cmp bx, word [VAR(fsiboot_table.minpara)] + mov al, 'E' + jb fsiboot_error + retn + + +found_it: + ; es:di -> dir entry in dir sector buffer + mov bx, di + pop di ; restore si:di = loaded FAT sector + pop cx ; (discard sectors per cluster counter) + pop cx + pop cx ; (discard current cluster number) + ; es:bx -> dir entry + 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, es +clust_next: + xchg ax, bx ; ax = low word cluster, clobbers bx + mov dx, cx + add ax, 2 + adc dx, 0 + + add ax, ax + adc dx, dx + add ax, ax + adc dx, dx ; * 4 = byte offset into FAT (0--4000_0000h) + push ax + xchg ax, dx + xor dx, dx ; dx:ax = high word + div word [VAR(bytes_per_sector)] + xchg bx, ax ; bx = result high word, clobbers ax + pop ax ; dx = remainder, ax = low word + div word [VAR(bytes_per_sector)] + 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 [VAR(fat_sector + 2)], dx + mov word [VAR(fat_sector + 0)], ax + + push bx + add ax, [VAR(fat_start)] + adc dx, 0 + mov bx, [VAR(fat_seg)] + call near word [VAR(fsiboot_table.read_sector)] + pop bx +@@: + mov es, [VAR(fat_seg)] + mov ax, [es:bx] + mov dx, [es:bx + 2] + + ; 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 dx, 0FFFh + jb @F ; CY here means valid ...- + cmp ax, 0FFF7h - 2 +@@: ; -... or if NC first, CY here also + cmc ; NC if valid + retn + + +fsinfo_available: + _fill 480 + 4 - 2,38,fsiboot + +%assign num $-fsinfo_available +%warning FSINFO: num bytes still available. + + + ; INP: si:di = loaded sector-in-FAT + ; word [fsiboot_table.filename] -> 8.3 filename + ; CHG: ax, cx, dx + ; OUT: si:di updated, if so + ; es:bx -> found directory entry + ; jumps to error handler if an error occurs + ; STT: stack variables as set up by main FSIBOOT entry +dirsearch_entrypoint: + dw dirsearch + + dd "rrAa" + dd -1 ; number of free clusters + dd -1 ; first free cluster + times 3 dd 0 ; FSINFO.reserved2 + dd 0_AA55_0000h ; FSINFO.signature3 + +end_after_fsiboot: +%if $ - fsiboot != 512 + %error Wrong FSIBOOT layout +%endif diff --git a/test/ldosboot/doc/ldosboot.src b/test/ldosboot/doc/ldosboot.src new file mode 100644 index 0000000..72cde2a --- /dev/null +++ b/test/ldosboot/doc/ldosboot.src @@ -0,0 +1,325 @@ +\cfg{chapter}{Section} + +\cfg{text-filename}{ldosboot.txt} +\cfg{text-chapter-numeric}{true} +\cfg{text-indent-preamble}{false} +\cfg{text-quotes}{"}{"} +\cfg{text-indent}{4} +\cfg{text-width}{72} + +\cfg{html-chapter-numeric}{true} +\cfg{html-suppress-address}{true} +\cfg{html-single-filename}{ldosboot.htm} +\cfg{html-leaf-level}{0} +\cfg{html-template-fragment}{%k}{%b} +\cfg{html-head-end}{} + +\cfg{pdf-filename}{ldosboot.pdf} + +\cfg{ps-filename}{ldosboot.ps} + +\cfg{info-filename}{ldosboot.info} + +\cfg{chm-filename}{ldosboot.chm} + +\cfg{winhelp-filename}{ldosboot.hlp} + +\cfg{man-filename}{ldosboot.7} +\cfg{man-identity}{ldosboot}{7}{2020}{}{C. Masloch} + +\title lDOS boot documentation + +\copyright 2020 by C. Masloch. +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. + +This document has been compiled on \date{%Y-%m-%d}. + + +\C{protocols} lDOS boot protocols + + +\H{protocol-sector-iniload} Sector to iniload protocol + +The iniload kernel is loaded to an arbitrary segment. +The segment must be at least 60h. +Common choices are 60h, 70h, and 200h. +At least 1536 bytes of the file must be loaded. +Current loaders will load at least 8192 bytes +if the file is as large or larger than that. +The entrypoint is found by applying no segment adjustment (0) +and choosing the offset 400h (1024). + + +\S{protocol-sector-iniload-signatures} Signatures + +At offset 1020 (3FCh) there is the signature \cq{lD}. +Behind that there are two bytes with printable non-blank ASCII codepoints. +Currently the following signatures are defined: + +\dt \cq{lDOS} + +\dd lDOS kernel (not yet in use) + +\dt \cq{lDRx} + +\dd RxDOS kernel + +\dt \cq{lDFD} + +\dd FreeDOS kernel wrapped in iniload (fdkernpl.asm) + +\dt \cq{lDeb} + +\dd lDebug + +\dt \cq{lDDb} + +\dd lDDebug (debuggable lDebug) + +\dt \cq{lDTP} + +\dd lDOS test payload kernel (testpl.asm) + +\dt \cq{lDTW} + +\dd lDOS test result writer kernel (testwrit.asm) + + +\S{protocol-sector-iniload-lsv} Load Stack Variables (LSV) + +Under this protocol, the pointer \cq{ss:bp} is passed. +It points to a boot sector with (E)BPB. +The stack pointer must be at most \cq{bp - 10h}. +Below the pointed to location there live the Load Stack Variables. +These follow this structure: + +\c struc LOADSTACKVARS, -10h +\c lsvFirstCluster: resd 1 +\c lsvFATSector: resd 1 +\c lsvFATSeg: resw 1 +\c lsvLoadSeg: resw 1 +\c lsvDataStart: resd 1 +\c endstruc + +\dt lsvFirstCluster + +\dd (FAT12, FAT16) Low word gives starting cluster of file. +High word uninitialised. + +\dd (FAT32) Dword gives starting cluster of file. + +\dd (else) Should be zero. + +\dt lsvFATSector + +\dd (FAT16) Low word gives loaded sector-in-FAT. +-1 if none loaded yet. +High word uninitialised. + +\dd (FAT32) Dword gives loaded sector-in-FAT. +-1 if none loaded yet. + +\dd (FAT12, else) Unused. + +\dt lsvFATSeg + +\dd (FAT16, FAT32) Word gives segment of FAT buffer +if word/dword [lsvFATSector] != -1. + +\dd (FAT12) Word gives segment of FAT buffer. +Zero if none. +Otherwise, buffer holds entire FAT data, up to 6 KiB. + +\dt lsvLoadSeg + +\dd Word points to segment beyond last loaded paragraph. +Allows iniload to determine how much of it is already loaded. + +\dt lsvDataStart + +\dd Dword gives sector-in-partition of first cluster's data. + + +An LSV extension allows to pass a command line to the kernel. +The stack pointer must be at most \cq{bp - 114h} then. +This follows the structure like this: + +\c lsvclSignature equ "CL" +\c lsvclBufferLength equ 256 +\c +\c struc LSVCMDLINE, LOADSTACKVARS - lsvclBufferLength - 4 +\c lsvCommandLine: +\c .start: resb lsvclBufferLength +\c .signature: resw 1 +\c lsvExtra: resw 1 +\c endstruc + +\dt lsvCommandLine.start + +\dd Command line buffer. Contains zero-terminated command line string. + +\dt lsvCommandLine.signature + +\dd Contains the signature value \cq{CL} if command line is given. + +\dt lsvExtra + +\dd Used internally by iniload. +Space for this must be reserved when passing a command line. + +If no command line is passed then either the stack pointer +must be \cq{bp - 10h}, or \cq{bp - 12h}, or +the word in the lsvCommandLine.signature variable +(\cw{word [ss:bp - 14h]}) +must not equal the string \cq{CL}. + +\b dosemu2's RxDOS.3 support sets \cq{sp = bp - 10h} + +\b ldosboot boot.asm (FAT12/FAT16) loader +uses the variable for a \q{paragraphs per sector} value +which is always a power of two and always below-or-equal 200h. + +\b ldosboot boot32.asm (FAT32) loader +uses the variable for an \q{entries per sector} value +which is always a power of two and always below-or-equal 100h. + +\b lDebug with protocol options \cw{cmdline=0 push_dpt=0} +sets \cq{sp = bp - 10h} + + +\H{protocol-iniload-payload} Iniload to payload protocol + +The payload is loaded to an arbitrary segment. +The segment must be at least 60h. +The entire payload must be loaded. +The size of the payload is determined at iniload build time. +The entrypoint is found by applying a segment adjustment +and choosing an offset. +The segment adjustment is specified at iniload build time +by the numeric define \cw{_EXEC_SEGMENT} (default 0), +and the offset by the define \cw{_EXEC_OFFSET} (default 0). + + +\S{protocol-iniload-payload-ebpb} Extended BIO Parameter Block (EBPB) + +Above the LSV, \cw{ss:bp} points to an EBPB and surrrounding boot sector. +Note that this is always a FAT32-style EBPB. +If the filesystem that is loaded from is not FAT32, +and is therefore FAT16 or FAT12, +then the FAT16/FAT12 BPBN structure is moved up. +It is placed where the FAT32 BPBN is usually expected. +In this case, the entire boot sector contents behind the BPBN +are also moved up by the size of the FAT32-specific fields. +The FAT32-specific fields are filled with zeros, +except for the FAT32 \q{sectors per FAT} field. +It is filled with the contents of the FAT16/FAT12 +\q{sectors per FAT} field. + + +\S{protocol-iniload-payload-lsv} Load Stack Variables (LSV) + +Refer to \k{protocol-sector-iniload-lsv}. + + +\S{protocol-iniload-payload-ld} Load Data 1 (LD) + +Below the LSV, iniload passes the LOADDATA (1) structure. + +\c struc LOADDATA, LOADSTACKVARS - 10h +\c ldMemoryTop: resw 1 +\c ldLoadTop: resw 1 +\c ldSectorSeg: resw 1 +\c ldFATType: resb 1 +\c ldHasLBA: resb 1 +\c ldClusterSize: resw 1 +\c ldParaPerSector:resw 1 +\c ldLoadingSeg: resw 1 +\c ldLoadUntilSeg: resw 1 +\c endstruc + +\dt ldMemoryTop + +\dd Word. Segment pointer to behind usable memory. +Points at the first of the EBDA, RPL-reserved memory, or +video memory or otherwise UMA. +Indicates how much memory may be used by a typical kernel. +(lDebug detects the EBDA to move that below where it installs.) + +\dt ldLoadTop + +\dd Word. Segment pointer to lowest lDOS boot memory in use. +All memory between linear 600h and the segment indicated here +is usable by the payload. +Only the payload itself is stored in this area. +The other buffers, stack, and structures passed by iniload +must live above this segment. + +\dt ldSectorSeg + +\dd Word. Segment pointer to an 8 KiB transfer buffer. +It is insured that this buffer does not cross a 64 KiB boundary. +This may be needed by some disk units. +The buffer is not initialised to anything generally. + +\dt ldFATType + +\dd Byte. Indicates length of FAT entry in bits. +12 indicates FAT12, 16 FAT16, 32 FAT32. +It is planned to allow zero for non-FAT filesystems. + +\dt ldHasLBA + +\dd Byte. Only least significant bit used. +Bit on indicates LBA extensions available for the load disk unit. +Bit off indicates LBA extensions not available. + +\dt ldClusterSize + +\dd Word. Contains amount of sectors per cluster. +Unlike the byte field for the same purpose in the BPB, +this field can encode 256 (EDR-DOS compatible) without any masking. +May be given as zero for non-FAT filesystems. + +\dt ldParaPerSector + +\dd Word. Contains amount of paragraphs per sector. +Must be a power of two between 2 (32 B/s) and 200h (8192 B/s). +May be given as zero for non-FAT filesystems. + +\dt ldLoadingSeg + +\dd Word. Internally used by iniload. +Available for re-use by payload. + +\dt ldLoadUntilSeg + +\dd Word. Internally used by iniload. +Available for re-use by payload. + + +\S{protocol-iniload-payload-lcl} Load Command Line (LCL) + +Below the LOADDATA structure, iniload passes the LOADCMDLINE structure. + +\c lsvclBufferLength equ 256 +\c +\c struc LOADCMDLINE, LOADDATA - lsvclBufferLength +\c ldCommandLine: +\c .start: resb lsvclBufferLength +\c endstruc + +This buffer is always initialised to an ASCIZ string. +At most 255 bytes may be initialised to string data. +At most the 256th byte is a zero. + +If the first word of the buffer is equal to 0FF00h, +that is there is an empty command line +the terminator of which is followed by a byte with the value 0FFh, +then no command line was passed to iniload. +Currently lDebug can pass a command line to iniload when +loading with its lDOS, RxDOS.2, or RxDOS.3 protocols. +When iniload is loaded as a Multiboot1 or Multiboot2 specification kernel, +it is also assumed that a command line can be passed. diff --git a/test/ldosboot/doc/mak.sh b/test/ldosboot/doc/mak.sh new file mode 100755 index 0000000..f65e02d --- /dev/null +++ b/test/ldosboot/doc/mak.sh @@ -0,0 +1,10 @@ +#! /bin/bash + +# 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. + +echo -ne "\\U Source Control Revision ID\n\nhg $(hg id -i), from commit on at $(hg log -r . --template="{date|isodatesec}\n")\n\nIf this is in ecm's repository, you can find it at \\W{https://hg.pushbx.org/ecm/ldosboot/rev/$(hg log -r . --template "{node|short}")}{https://hg.pushbx.org/ecm/ldosboot/rev/$(hg log -r . --template "{node|short}")}\n" > screvid.src +halibut --precise ldosboot.src screvid.src --html --text --pdf diff --git a/test/ldosboot/fdkernpl.asm b/test/ldosboot/fdkernpl.asm new file mode 100644 index 0000000..f75cf68 --- /dev/null +++ b/test/ldosboot/fdkernpl.asm @@ -0,0 +1,246 @@ + +%if 0 + +Loader adjustment to load FreeDOS kernel + 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 + + +%include "lmacros3.mac" + + struc BS +bsJump: resb 3 +bsOEM: resb 8 +bsBPB: + endstruc + + struc EBPB ; BPB sec +bpbBytesPerSector: resw 1 ; offset 00h 0Bh +bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh +bpbReservedSectors: resw 1 ; offset 03h 0Eh +bpbNumFATs: resb 1 ; offset 05h 10h +bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32 +bpbTotalSectors: resw 1 ; offset 08h 13h +bpbMediaID: resb 1 ; offset 0Ah 15h +bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32 +bpbCHSSectors: resw 1 ; offset 0Dh 18h +bpbCHSHeads: resw 1 ; offset 0Fh 1Ah +bpbHiddenSectors: resd 1 ; offset 11h 1Ch +bpbTotalSectorsLarge: resd 1 ; offset 15h 20h +bpbNew: ; offset 19h 24h + +ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h +ebpbFSFlags: resw 1 ; offset 1Dh 28h +ebpbFSVersion: resw 1 ; offset 1Fh 2Ah +ebpbRootCluster: resd 1 ; offset 21h 2Ch +ebpbFSINFOSector: resw 1 ; offset 25h 30h +ebpbBackupSector: resw 1 ; offset 27h 32h +ebpbReserved: resb 12 ; offset 29h 34h +ebpbNew: ; offset 35h 40h + endstruc + + struc BPBN ; ofs B16 S16 B32 S32 +bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h + resb 1 ; 01h 1Ah 25h 36h 41h +bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN +bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h +bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h +bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h + endstruc ; 1Ah 33h 3Eh 4Fh 5Ah + + +%ifndef _MAP +%elifempty _MAP +%else ; defined non-empty, str or non-str + [map all _MAP] +%endif + + strdef PAYLOAD_FILE, "KERNEL.SYS" + + + 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 + + + cpu 8086 + org 0 + addsection ENTRY, start=0 vstart=0 +entry: + + mov ax, cs + add ax, entry_size_p + payload_size_p + xor bx, bx + push ax + push bx + retf + + align 16 + endarea entry + + + addsection PAYLOAD, follows=ENTRY +payload: +realpayload: + incbin _PAYLOAD_FILE + align 16, db 38 + endarea realpayload + + + addsection STACKRELOCATE, follows=PAYLOAD vstart=0 +stackrelocate: + + mov ax, 60h + payload_size_p + mov es, ax + xor di, di + xor si, si + lea cx, [ bp + 512 ] + rep movsb + mov ds, ax + mov si, bp + xor di, di + cmp word [ds:bp + ldCommandLine], 0FF00h + je @F + lea si, [bp + ldCommandLine + lsvclBufferLength - 2] + lea di, [bp + lsvCommandLine.start + lsvclBufferLength - 2] + mov cx, words(lsvclBufferLength) +%if words(lsvclBufferLength) <= 20 + %error AMD erratum 109 workaround needed +%endif + std + rep movsw + cld + lea si, [bp + lsvCommandLine.start] + mov di, lsvclSignature +@@: + cli + mov ss, ax + mov sp, si + mov word [bp + lsvCommandLine.signature], di + ; Note that this access uses the new ss. + ; Also note: If no command line is passed, + ; si will equal bp. That means the word + ; written here is technically below sp, + ; that is it belongs to the unused stack. + ; This does not cause any problems however. + ; It hardens the next load stage against + ; accidentally expecting a command line if + ; it does not check the offsets properly. + sti + + jmp 60h:0 + + align 16 + endarea stackrelocate + +payload_size equ realpayload_size + stackrelocate_size + endarea payload, 1 + + + addsection RELOCATE, follows=STACKRELOCATE vstart=0 +relocate: + mov bx, 1000h + mov ax, 60h + mov es, ax + mov cx, payload_size_p + mov ax, cs + sub ax, cx + mov ds, ax + xor si, si + xor di, di + + mov ax, cx + cmp ax, bx + jbe @F + mov cx, bx +@@: + sub ax, cx + shl cx, 1 + shl cx, 1 + shl cx, 1 + rep movsw + +@@: + 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 + mov cx, ax ; 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 --> + ; ax = 0 + + mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit] + mov bl, dl + + push ss + pop ds + cmp word [bp + bsBPB + bpbSectorsPerFAT], ax + je @F + push ss + pop es + lea si, [bp + bsBPB + ebpbNew] + lea di, [bp + bsBPB + bpbNew] + mov cx, (512 - bsBPB - bpbNew + 1) >> 1 + rep movsw +@@: + + jmp 60h + realpayload_size_p:0 diff --git a/test/ldosboot/iniload.asm b/test/ldosboot/iniload.asm new file mode 100644 index 0000000..7fabc39 --- /dev/null +++ b/test/ldosboot/iniload.asm @@ -0,0 +1,2477 @@ + +%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 + diff --git a/test/ldosboot/kernshim.asm b/test/ldosboot/kernshim.asm new file mode 100644 index 0000000..cd995b0 --- /dev/null +++ b/test/ldosboot/kernshim.asm @@ -0,0 +1,58 @@ + +%if 0 + +FreeDOS kernel executable MZ header shim + by C. Masloch, 2022 + +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 + +%include "lmacros2.mac" + + defaulting + + strdef FILE, "" +%ifidn _FILE,"" + %fatal Has to specify a file! +%endif + + + org 0 +header: + db "MZ" ; exeSignature + dw (payload.end - $$) % 512 ; exeExtraBytes + dw (payload.end - $$ + 511) / 512 ; exePages + dw 0 ; exeRelocItems + dw (payload -$$+0) >> 4 ; exeHeaderSize + dw 0 ; exeMinAlloc + dw -1 ; exeMaxAlloc + dw 0 ; exeInitSS + dw -2 ; exeInitSP + dw 0 ; exeChecksum + dw 0, 0 ; exeInitCSIP + dw 0 ; exeRelocTable + endarea header + + + align 16, db 38 +payload: + jmp strict short entry + db "CONFIG" + dw 1 + db -1 + + times 32 - ($ - payload) db 0 +entry: equ $ + jmp entry_common + + times 0xC0 - ($ - payload) db 0 +entry_common: equ $ + + incbin _FILE +.actual_end: +.end: diff --git a/test/ldosboot/multboot.asm b/test/ldosboot/multboot.asm new file mode 100644 index 0000000..e0e4134 --- /dev/null +++ b/test/ldosboot/multboot.asm @@ -0,0 +1,566 @@ + +%if 0 + +Multiboot header and loader + 2008--2019 by C. Masloch + +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 + +%macro mbootchecksummed 1-2.nolist 1BADB002h + dd %2 ; signature + dd %1 ; flags + dd -((%2+%1) & 0FFFF_FFFFh) + ; checksum: dword sum of these three = 0 +%endmacro + +%macro mboot2checksummed 0-1.nolist 0E852_50D6h + dd %1 ; signature + dd 0 ; platform (i386) + dd mboot2_header_end - mboot2_header + ; size of header, including header tags + dd -((%1 + (mboot2_header_end - mboot2_header))) & 0FFFF_FFFFh + ; checksum +%endmacro + + +MULTIBOOT_CMDLINE_LENGTH equ lsvclBufferLength + +MULTIBOOT_BASE equ (1024+64)*1024 ; one paragraph above the HMA +MULTIBOOT_END equ MULTIBOOT_BASE + (payload.actual_end - $$ + 0) +MULTIBOOT_BSS_END equ MULTIBOOT_END \ + + fromdwords(dwords(MULTIBOOT_CMDLINE_LENGTH)) +MULTIBOOT_TARGET_SEGMENT equ 200h +MULTIBOOT_TARGET equ MULTIBOOT_TARGET_SEGMENT << 4 +MULTIBOOT_BPB equ MULTIBOOT_TARGET - 512 - 16 +MULTIBOOT_CMDLINE_START equ MULTIBOOT_BPB + lsvCommandLine.start +MULTIBOOT_STACK_TOP equ MULTIBOOT_CMDLINE_START + +%if _MULTIBOOT1 + ; Multiboot header + ; must be dword aligned and in first 8 KiB! + align 4, db 0 +mbootheader: + mbootchecksummed 00010000h + ; flags: provides info where to load image + dd MULTIBOOT_BASE + mbootheader ; load address of header + dd MULTIBOOT_BASE ; load address + dd MULTIBOOT_END ; load area end (0 = load whole file) + dd MULTIBOOT_BSS_END ; uninitialised area end (0 = none) + dd MULTIBOOT_BASE + mbootentry ; entry point +%endif + +%if _MULTIBOOT2 + ; Multiboot2 header + ; qword (64-bit) aligned and in first 32 KiB + align 8, db 0 +mboot2_header: + mboot2checksummed +mboot2_tag_information_request: +.: +.type: dw 1 +.flags: dw 0 +.size: dd .end - . + dd 1 ; command line + dd 5 ; ROM-BIOS boot device +.end: + align 8, db 0 +mboot2_tag_addresses: +.: +.type: dw 2 +.flags: dw 0 +.size: dd .end - . + dd MULTIBOOT_BASE + mboot2_header + ; load address of header + dd MULTIBOOT_BASE ; load address + dd MULTIBOOT_END ; load area end (0 = load whole file) + dd MULTIBOOT_BSS_END ; uninitialised area end (0 = none) +.end: + align 8, db 0 +mboot2_tag_entrypoint: +.: +.type: dw 3 +.flags: dw 0 +.size: dd .end - . + dd MULTIBOOT_BASE + mboot2entry ; entry point +.end: + align 8, db 0 +mboot2_tag_end: +.: +.type: dw 0 +.flags: dw 0 +.size: dd .end - . +.end: +mboot2_header_end: +%endif + + +%unmacro mbootchecksummed 1-2.nolist 1BADB002h +%unmacro mboot2checksummed 0-1.nolist 0E85250D6h + +%if ($-$$) > 8192 + %fatal Multiboot header must be in the first 8 KiB +%endif + + struc MBI +mbiFlags: resd 1 + resb 8 +mbiBootDevice: resd 1 +mbiCmdLine: resd 1 +mbiModuleCount: resd 1 +mbiModuleTable: resd 1 + ; (More data follows but none which seems useful.) + endstruc + +[cpu 386] +[bits 32] + + numdef MULTIBOOT_CHECKS, 1 + numdef MULTIBOOT_DEBUG, 0 + numdef MULTIBOOT_HALT, 0 + numdef MULTIBOOT_PROGRESS, 0 + + + %if _MULTIBOOT_CHECKS || _MULTIBOOT_HALT +mbootentry.halt: +mboot2entry.halt: + ; Unfortunately, as the environment is unknown, we can only + ; literally halt here, as we don't know how to do I/O. + cli +@@: + hlt + jmp short @B + %endif + +%if _MULTIBOOT1 + ; INP: eax = 2BADB002h + ; ebx-> multiboot info structure + ; STT: loaded and executed according to Multiboot header, ie + ; loaded at MULTIBOOT_BASE + ; eip = MULTIBOOT_BASE+mbootentry + ; in PM32 with paging disabled + ; CPL 0 + ; cs,ds,es,fs,gs,ss = flat 32-bit code/data selectors + ; DI + ; ! stack not set up + ; ! GDT might not be set up + ; ! IDT might not be set up + ; A20 on + ; + ; Note: Although A20 is on, HMA access without an XMM + ; to manage it might be undesirable. Multiboot + ; also doesn't tell us whether and if so then + ; how we can switch A20. +mbootentry: + %if _MULTIBOOT_HALT + jmp .halt + %endif + %if _MULTIBOOT_CHECKS +[bits 16] + test ax, 0 ; (test eax if in 32-bit segment) + jmp short .halt ; (skipped if in 32-bit segment) +[bits 32] + cmp eax, 2BADB002h ; signature ? + jne short .halt ; no --> + smsw eax + rol eax, 1 ; 2 = protection, 1 = paging + and al, 3 ; mask off others + cmp al, 2 ; PE but not PG ? + jne short .halt ; no --> + mov eax, cs + and al, 3 ; CPL 0 ? + jnz short .halt ; no --> + %endif + + %if _MULTIBOOT_PROGRESS + mov ebp, 0B8000h + 2 * 80 * 20 + mov word [ebp], 5000h | '1' + inc ebp + inc ebp + %endif + + ; prepare this and that + or edx, -1 + ; locate boot drive in Multiboot info structure + test byte [ ebx + mbiFlags ], 2 ; boot device info valid ? + jz @F + mov eax, [ ebx + mbiBootDevice ]; get the info + rol eax, 16 + xchg al, ah + mov edx, eax ; dl = boot load unit (or 0FFh), + ; dh = partition (or 0FFh) + ; (edxh = subpartition numbers) +@@: + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | '2' + inc ebp + inc ebp + %endif + + xor eax, eax + mov edi, MULTIBOOT_END + mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 + test byte [ ebx + mbiFlags ], 4 ; command line valid ? + jz @F + mov esi, [ ebx + mbiCmdLine ] + rep movsd + mov byte [ edi - 1 ], 0 ; insure it is terminated + ; (this may truncate the line) +@@: + rep stosd + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | '3' + inc ebp + inc ebp + %endif + + %if _MULTIBOOT2 + jmp multiboot_1_2_common + %endif +%endif + +%if _MULTIBOOT2 + ; INP: eax = 36D7_6289h + ; ebx-> multiboot2 info structure + ; STT: loaded and executed according to Multiboot2 header, ie + ; loaded at MULTIBOOT_BASE + ; eip = MULTIBOOT_BASE+mboot2entry + ; in PM32 with paging disabled + ; CPL 0 + ; cs,ds,es,fs,gs,ss = flat 32-bit code/data selectors + ; DI + ; ! stack not set up + ; ! GDT might not be set up + ; ! IDT might not be set up + ; A20 on + ; + ; Note: Although A20 is on, HMA access without an XMM + ; to manage it might be undesirable. Multiboot + ; also doesn't tell us whether and if so then + ; how we can switch A20. +mboot2entry: + %if _MULTIBOOT_HALT + jmp .halt + %endif + %if _MULTIBOOT_CHECKS +[bits 16] + test ax, 0 ; (test eax if in 32-bit segment) + jmp short .halt ; (skipped if in 32-bit segment) +[bits 32] + cmp eax, 36D7_6289h ; signature ? + jne .halt ; no --> + smsw eax + rol eax, 1 ; 2 = protection, 1 = paging + and al, 3 ; mask off others + cmp al, 2 ; PE but not PG ? + jne .halt ; no --> + mov eax, cs + and al, 3 ; CPL 0 ? + jnz .halt ; no --> + %endif + + %if _MULTIBOOT_PROGRESS + mov ebp, 0B8000h + 2 * 80 * 20 + mov word [ebp], 5000h | 'A' + inc ebp + inc ebp + %endif + + or edx, -1 ; initialise boot partition info + + xor eax, eax + mov edi, MULTIBOOT_END + mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 + rep stosd ; initialise command line buffer + + add ebx, 8 ; skip fixed header (size, reserved) +.tags_loop: + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'B' + inc ebp + inc ebp + %endif + + mov eax, dword [ebx] + test eax, eax ; end of tags ? + jz .tags_end ; yes --> + + cmp eax, 1 + je .cmdline + + cmp eax, 5 + je .partition + +.tags_next: + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'C' + inc ebp + inc ebp + %endif + + add ebx, dword [ebx + 4] + ; -> after end of tag + ; (at padding or next tag) + add ebx, 7 + and ebx, ~7 ; skip any padding + jmp .tags_loop + +.cmdline: + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'D' + inc ebp + inc ebp + %endif + + lea esi, [ebx + 8] + mov edi, MULTIBOOT_END + mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 + rep movsd ; copy command line + mov byte [ edi - 1 ], 0 + ; insure it is terminated + ; (this may truncate the line) + jmp .tags_next + +.partition: + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'E' + inc ebp + inc ebp + %endif + + mov eax, dword [ebx + 8] + cmp eax, 100h + jae @F + mov dl, al ; get ROM-BIOS unit +@@: + + mov eax, dword [ebx + 12] + cmp eax, 100h + jae @F + mov dh, al ; get partition number (0 = first primary) +@@: + jmp .tags_next + +.tags_end: + xor eax, eax + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'F' + inc ebp + inc ebp + %endif + +%endif + +multiboot_1_2_common: + mov esp, MULTIBOOT_STACK_TOP ; set a valid stack, at 8 KiB + ; (also required by 86M entry later) + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 's' + inc ebp + inc ebp + %endif + + ; A20 is on. set up some things + xor ecx, ecx + mov edi, 1024*1024 + mov cl, 10h ; (ecx = 10h) + rep stosd ; clear this area so A20 tests succeed +; The above writes to 10_0000h..10_003Fh, leaving edi = 10_0040h + mov al, 0C0h ; (eax = C0h) + mov byte [ edi-40h+eax ], 0EAh + mov [ edi-40h+eax+1 ], eax ; write jump for CP/M entry hack here +; The above writes to 10_00C0h..10_00C4h + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 't' + inc ebp + inc ebp + %endif + + ; relocate image to a good location, 2000h + mov esi, MULTIBOOT_BASE ; -> iniload image + mov edi, MULTIBOOT_TARGET + mov ecx, (payload.actual_end - $$ + 0 + 3) / 4 + rep movsd + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'u' + inc ebp + inc ebp + %endif + +MULTIBOOT_ESI_AFTER equ MULTIBOOT_BASE + (payload.actual_end - $$ + 0 + 3) / 4 * 4 +%if MULTIBOOT_ESI_AFTER != MULTIBOOT_END + mov esi, MULTIBOOT_END +%endif + mov edi, MULTIBOOT_CMDLINE_START + mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 + rep movsd + + %if _MULTIBOOT_PROGRESS + mov word [ebp], 5000h | 'v' + inc ebp + inc ebp + %endif + + ; now back to real mode + o32 lgdt [ MULTIBOOT_BASE+mbootgdtdesc ] + ; set our GDT + mov ax, 10h + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax ; set 64 KiB segment limits + o32 push byte 0 + o32 popf ; reset all flags + jmp 08h:.pm16 ; use 16-bit selector + +[bits 16] + ; now really switch to real mode + ; (already executing relocated code) +.pm16: + mov eax, cr0 + dec ax + mov cr0, eax ; clear PE bit + + jmp dword MULTIBOOT_TARGET_SEGMENT:.rm ; reload cs + + ; Set up registers and the environment for + ; the kernel entry point. It doesn't need initialised + ; segment registers but we'll initialise them in case + ; they still contain selector content. +.rm: + ; some sources indicate we should set the IDT + o32 lidt [cs:mbootidtdesc] ; set IDT to RM IVT + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + ; (cs should be = 200h now, need no far jump) + ; cs = 200h, ip = mboot_86m_entry + ; ds = es = fs = gs = ss = 0 + ; sp = 2000h - 512 - 16 + lsvCommandLine.start + ; dl = boot unit (or -1) + ; dh = boot partition (0..3 = primary, 4+ = logical, or -1) + + mov bp, MULTIBOOT_BPB ; -> pseudo BPB + mov di, MULTIBOOT_BPB + lsvCommandLine.signature + mov ax, lsvclSignature + stosw ; store signature + mov cx, (- (lsvCommandLine.signature + 2) + 512 + 3) / 4 + xor eax, eax + rep stosd ; clear + ; lsvFirstCluster = 0 (same as for FreeDOS entrypoint) + ; bsBPB + bpbHiddenSectors = 0 (invalid or unpartitioned) + ; bsBPB + bpbCHSSectors = 0 + ; bsBPB + bpbCHSHeads = 0 + + inc ax + mov dword [bp + lsvDataStart], eax + ; lsvDataStart = 1 (placeholder) +__CPU__ + mov word [bp + bsBPB + bpbNumRootDirEnts], ax + mov word [bp + bsBPB + bpbSectorsPerFAT], ax + ; do not detect as FAT32 + mov byte [bp + bsBPB + bpbSectorsPerCluster], al + mov word [bp + bsBPB + bpbBytesPerSector], 512 + mov word [bp + bsBPB + bpbTotalSectors], 8192 + ; 1 C/s * 8 Ki sectors = 8 Ki clusters, detect as FAT16 + ; that is, do not load FAT at all (would load a FAT12) + + mov byte [bp + bsBPB + bpbNew + bpbnBootUnit], dl + ; set boot load unit + +%if _LSVEXTRA + %if _MULTIBOOT_DEBUG + mov ax, dx + call mb_disp_ax_hex + %endif + xchg dl, dh ; dl = Multiboot spec partition number + ; (0..3 primary, 4+ logical, -1 invalid) + inc dx ; 0 invalid, 1..4 primary, 5+ logical + mov dh, lsvefPartitionNumber + mov word [bp + lsvExtra], dx +%endif + jmp freedos_entry.multiboot_entry + + +%if _MULTIBOOT_DEBUG +mb_disp_ax_hex: ; ax + xchg al,ah + call mb_disp_al_hex ; display former ah + xchg al,ah ; and fall trough for al +mb_disp_al_hex: ; al + push cx + mov cl,4 + ror al,cl + call mb_disp_al_lownibble_hex ; display former high-nibble + rol al,cl + pop cx + ; and fall trough for low-nibble +mb_disp_al_lownibble_hex: + push ax ; save ax for call return + and al,00001111b ; high nibble must be zero + add al,'0' ; if number is 0-9, now it's the correct character + cmp al,'9' + jna .decimalnum ; if we get decimal number with this, ok --> + add al,7 ; otherwise, add 7 and we are inside our alphabet + .decimalnum: + call mb_disp_al + pop ax + retn + +mb_disp_al: + push ax + push bx + push bp + mov ah, 0Eh + mov bx, 7 + int 10h + pop bp + pop bx + pop ax + retn +%endif + + + align 16, db 0 +mbootgdt: + dw 0,0 + db 0,0,0,0 + + ; selector 8: 16-bit real mode CS + ; base = 00002000h, limit 0FFFFh (1 B Granularity), present + ; type = 16-bit code execute/read only/conforming, DPL = 0 + dw 0FFFFh,MULTIBOOT_TARGET + db 0,9Eh,0,0 + + ; selector 10h: 16-bit real mode DS + ; base = 00000000h, limit 0FFFFh (1 B Granularity), present + ; type = 16-bit data read/write, DPL = 0 + dw 0FFFFh,0 + db 0,92h,0,0 + endarea mbootgdt + +mbootgdtdesc: + dw mbootgdt_size-1 ; limit + dd MULTIBOOT_BASE+mbootgdt ; address + +mbootidtdesc: + dw 400h-1 ; limit + dd 0 ; address (86M IVT) diff --git a/test/ldosboot/patch.sld b/test/ldosboot/patch.sld new file mode 100644 index 0000000..14b118f --- /dev/null +++ b/test/ldosboot/patch.sld @@ -0,0 +1,89 @@ +@goto :help + +Patch iniload script + by C. Masloch, 2021 + +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. + + +@:help +; Available patches: +; +; :patch_iniload_no_query_geometry +; :patch_iniload_no_lba +; +; Inputs: vef = nonzero to debug +; cs:0 -> iniload +; Output: vee = nonzero if error +; iniload patched +; Change: ve0 to vef, src, sro, aao, stack +; +; Requires lDebug release 3 or later +@goto :eof + +:patch_iniload_no_query_geometry +@if not (vef) then r ysf |= C000 +r ve0 word (sp - 100) +a ss:ve0 + mov ah, 08 + xor cx, cx + stc + int 13 + jc (ve0) + . +r ve1 := aao - 1 +r vec := ve1 - ve0 +s cs:0 l #8192 range ss:ve0 l vec +if not (src) then goto :error +r byte [srs:sro + vec - 1] := EB +goto :success + +:patch_iniload_no_lba +@if not (vef) then r ysf |= C000 +r ve0 word (sp - 100) +a ss:ve0 + mov ah, 41 + mov dl, byte [bp + 40] + mov bx, 55AA + stc + int 13 + mov al, 0 + jc (ve0) + . +r ve1 := aao - 1 +r vec := ve1 - ve0 +s cs:0 l #8192 range ss:ve0 l vec +if (src) then goto :patch_iniload_no_lba.success +a ss:ve0 + mov ah, 41 + pop dx + mov bx, 55AA + stc + int 13 + mov al, 0 + jc (ve0) + . +r ve1 := aao - 1 +r vec := ve1 - ve0 +s cs:0 l #8192 range ss:ve0 l vec +if not (src) then goto :error + +:patch_iniload_no_lba.success +r byte [srs:sro + vec - 1] := EB +goto :success + +:success +r vee := 0 +r ysf &= ~C000 +; Patched successfully +@goto :eof + +:error +r vee := 1 +r ysf &= ~C000 +; Patch failed +@goto :eof diff --git a/test/ldosboot/test/cfg.sh b/test/ldosboot/test/cfg.sh new file mode 100644 index 0000000..9c5cab0 --- /dev/null +++ b/test/ldosboot/test/cfg.sh @@ -0,0 +1,38 @@ +#! /bin/bash + +# 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. + +# If you want to override configuration without the +# hassle of having to exclude differences in this file +# (cfg.sh) from the SCM, you may provide ovr.sh. +# The meaning of this line allows you to copy cfg.sh +# to serve as a template for ovr.sh without changes. +[ -f ovr.sh ] && [[ "${BASH_SOURCE[0]##*/}" != ovr.sh ]] && . ovr.sh + +# As the below only are set if no value is provided +# yet, any value can be overridden from the shell. +[ -z "$LMACROS_DIR" ] && LMACROS_DIR=../../lmacros/ +[ -z "$LDOSBOOT_DIR" ] && LDOSBOOT_DIR=../ +[ -z "$SCANPTAB_DIR" ] && SCANPTAB_DIR=../../scanptab/ +[ -z "$INICHECK_DIR" ] && INICHECK_DIR=../../crc16-t/ +[ -z "$BOOTIMG_DIR" ] && BOOTIMG_DIR=../../bootimg/ +[ -z "$LDEBUG_DIR" ] && LDEBUG_DIR=../../ldebug/bin/ +[ -z "$LDOSMBR_DIR" ] && LDOSMBR_DIR=../../ldosmbr/ +[ -z "$INSTSECT_DIR" ] && INSTSECT_DIR=../../instsect/ + +[ -z "$DOSEMU" ] && DOSEMU=dosemu +[ -z "$QEMU" ] && QEMU=qemu-system-i386 +[ -z "$DEFAULT_MACHINE" ] && DEFAULT_MACHINE=dosemu +[ -z "$SENDKEYS" ] && SENDKEYS=sendkeys +[ -z "$BOOT_KERNEL" ] && BOOT_KERNEL=~/.dosemu/drive_c/kernel.sys +[ -z "$BOOT_COMMAND" ] && BOOT_COMMAND=~/.dosemu/drive_c/command.com +[ -z "$BOOT_PROTOCOL" ] && BOOT_PROTOCOL=FREEDOS +[ -z "$BOOT_OPTIONS" ] && BOOT_OPTIONS=" " +[ -z "$NASM" ] && NASM=nasm +[ -z "$CHECKSUM" ] && CHECKSUM="${INICHECK_DIR%/}"/iniload/checksum + +[ -z "$use_build_inicheck" ] && use_build_inicheck=0 diff --git a/test/ldosboot/test/quit.asm b/test/ldosboot/test/quit.asm new file mode 100644 index 0000000..b12b156 --- /dev/null +++ b/test/ldosboot/test/quit.asm @@ -0,0 +1,60 @@ + +%if 0 + +Shut down machine + by C. Masloch, 2020 + +Usage of the works is permitted provided that this +instrument is retained with the works, so that any entity +that uses the works is notified of this instrument. + +DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. + +%endif + + + cpu 8086 + org 256 +quit: + int3 + + mov ax, 0F000h + mov es, ax + mov di, 0FFF5h + mov si, msg.dosemudate + mov cx, 4 + repe cmpsw ; running in DosEmu? + jne .quit_not_dosemu + + xor bx, bx + mov ax, -1 + int 0E6h ; dosemu quit + +.quit_not_dosemu: + +; from https://stackoverflow.com/a/5240330/738287 + mov ax, 5301h + xor bx, bx + int 15h ; connect to APM API + + mov ax, 530Eh + xor bx, bx + mov cx, 0102h + int 15h ; set APM version to 1.02 + + mov ax, 5307h + mov bx, 1 + mov cx, 3 + int 15h ; shut down system + + mov dx, msg.failed + mov ah, 09h + int 21h + mov ax, 4C00h + int 21h + + + align 4 +msg: +.dosemudate: db "02/25/93" +.failed: db "Quit failed.",13,10,36 diff --git a/test/ldosboot/test/run.sh b/test/ldosboot/test/run.sh new file mode 100755 index 0000000..d958e63 --- /dev/null +++ b/test/ldosboot/test/run.sh @@ -0,0 +1,20 @@ +#! /bin/bash + +# 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. + +testrunname="fat12 default: " \ + ./test.sh diskette +testrunname="fat12 direct: " \ + ./test.sh diskette direct +testrunname="fat12 hdimage: " \ + ./test.sh hdimage +testrunname="fat12 16spc 20MiB: " \ + ./test.sh hdimage aligndata bpe 12 spc 16 mib 20 nr 512 +testrunname="fat16 4spc 32MiB: " \ + ./test.sh hdimage aligndata bpe 16 spc 4 mib 32 nr 512 +testrunname="fat32 1spc 34MiB: " \ + ./test.sh hdimage aligndata bpe 32 spc 1 mib 34 diff --git a/test/ldosboot/test/test.sh b/test/ldosboot/test/test.sh new file mode 100755 index 0000000..5775ae8 --- /dev/null +++ b/test/ldosboot/test/test.sh @@ -0,0 +1,491 @@ +#! /bin/bash + +# 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. + +if [[ "$1" != selfcall ]] +then + setsid -w "$0" selfcall "$@" + exit $? +fi +shift + +. cfg.sh + +if [ -n "$LMACROS_DIR" ]; then { + options_i_lmacros=-I"${LMACROS_DIR%/}"/ +} fi + +if [ -n "$LDOSBOOT_DIR" ]; then { + options_i_ldosboot=-I"${LDOSBOOT_DIR%/}"/ +} fi + +if [ -n "$SCANPTAB_DIR" ]; then { + options_i_scanptab=-I"${SCANPTAB_DIR%/}"/ +} fi + +if [ -n "$BOOTIMG_DIR" ]; then { + options_i_bootimg=-I"${BOOTIMG_DIR%/}"/ +} fi + +if [ -n "$LDEBUG_DIR" ]; then { + options_i_ldebug=-I"${LDEBUG_DIR%/}"/ +} fi + +if [ -n "$LDOSMBR_DIR" ]; then { + options_i_ldosmbr=-I"${LDOSMBR_DIR%/}"/ +} fi + +if [ -n "$INSTSECT_DIR" ]; then { + options_i_instsect=-I"${INSTSECT_DIR%/}"/ +} fi + +direct=0 +options_dosemu_direct="-A" +bpe=12 +spc=1 +spi=2880 +nr=224 +pitype="" +chsheads=8 +chssectors=8 + +((debug)) && echo DEFAULT_MACHINE="$DEFAULT_MACHINE" parameters="$@" +machine="$DEFAULT_MACHINE" +if [[ "$1" == dosemu ]] +then + machine="$1" + shift +elif [[ "$1" == qemu ]] +then + machine="$1" + shift +fi + +driveletters=( {C..Z} ) +unitletters=( {a..z} ) +dosemu=0 +qemu=0 +if [[ "$machine" == dosemu ]] +then + dosemu=1 + if [ -z "$dosemu_drives" ] || [ "$dosemu_drives" == test ] + then + # Depending on the dosemu2 configuration, dosemu may + # set up 2, 3, or 4 system drives. To support all + # these cases, the variable dosemu_drives may now + # be set to indicate the amount of drives. + # If it is not set or set to "test" we do a bit of + # magic detection: Run dosemu -E lredir and count + # the amount of redirections displayed. Also check + # that all of the redirections are on the expected + # drives, starting with C:. + # We assume that each of the system drives corresponds + # to its own ROM-BIOS unit. + # Discussed here: https://github.com/dosemu2/dosemu2/discussions/1456 + driveslist="$("$DOSEMU" -dumb -quiet -E lredir -te 2> /dev/null < /dev/null | + sed -re '/^about to execute|^current drive redirections/Id' )" + dosemu_drives="$(echo "$driveslist" | wc -l)" + for checkdrivenumber in $(seq 0 "$((dosemu_drives - 1))") + do + driveletter="${driveletters[checkdrivenumber]}" + if ! echo "$driveslist" | grep -Eiq "^$driveletter:" + then + echo "Error: Drive $driveletter: not found in lredir list" >&2 + exit 1 + fi + done + fi + ((debug)) && echo "Drive ${driveletters[dosemu_drives]}:" +elif [[ "$machine" == qemu ]] +then + qemu=1 + dosemu_drives=0 +else + echo "Error: invalid machine \"$machine\" selected" >&2 + exit 1 +fi + +if [[ "$1" == hdimage ]] +then + shift + options_hdimage="-D_MBR" + if ((dosemu)) + then + options_hdimage="$options_hdimage -D_MBR_DOSEMU_IMAGE_HEADER" + fi + dosemu_category="disk" + dosemu_device="hdimage" + dosemu_drive="${driveletters[dosemu_drives]}:" + qemu_switch="-hda" + name="hdimage" + bootfile="" + unit="$(printf "%03Xh" "$((0x80 + $dosemu_drives))")" + options_ldebug_unit="hd${unitletters[dosemu_drives]}" + diskette=0 + if [[ "$1" == direct ]] + then + shift + # output of the following command is either: + # 1. "CPU set to 486", followed by the sign ons for dosemu, FreeDOS, + # and config.sys and autoexec.bat output, then executing "exitemu" + # (if -C4 switch is not supported, parsed as -C -4). + # 2. Only the dosemu sign on with the error message as follows: + # "ERROR: Drive G not defined, can't boot!" (execution aborted). + if ((dosemu)) && "$DOSEMU" -dumb -E exitemu -C4 < /dev/null 2>&1 | grep "CPU set to 486" > /dev/null + then + echo "Error: dosemu lacks support for -C4 switch" >&2 + exit 1 + fi + options_dosemu_direct="-C$dosemu_drives" + unit=80h + direct=1 + fi + if ((qemu)) + then + unit=80h + options_ldebug_unit="hda" + fi + ((debug)) && echo "unit=\"$unit\"" + while true + do + if [[ "$1" == bpe && -n "$2" ]] + then + ((bpe="$2")) + if (($?)) || [[ "$bpe" != 32 && "$bpe" != 16 && "$bpe" != 12 ]] + then + echo "Error: Invalid bpe \"$2\" given, expected 12, 16, 32" >&2 + exit 1 + fi + shift + shift + elif [[ "$1" == spc && -n "$2" ]] + then + ((spc="($2)")) + (($?)) && exit $? + shift + shift + elif [[ "$1" == mib && -n "$2" ]] + then + ((spi="(($2) * 1024 * 2)")) + (($?)) && exit $? + shift + shift + elif [[ "$1" == spi && -n "$2" ]] + then + ((spi="($2)")) + (($?)) && exit $? + shift + shift + elif [[ "$1" == nr && -n "$2" ]] + then + ((nr="($2)")) + (($?)) && exit $? + shift + shift + elif [[ "$1" == pitype && -n "$2" ]] + then + pitype="$2" + shift + shift + elif [[ "$1" == aligndata ]] + then + options_hdimage="$options_hdimage -D_ALIGNDATA" + shift + elif [[ "$1" == chsheads && -n "$2" ]] + then + ((chsheads="($2)")) + (($?)) && exit $? + shift + shift + elif [[ "$1" == chssectors && -n "$2" ]] + then + ((chssectors="($2)")) + (($?)) && exit $? + shift + shift + else + break + fi + done + if ((qemu && chsheads > 16)) + then + echo "Warning: qemu autodetection requires CHS heads <= 16" >&2 + fi + if ((dosemu)) + then + options_mcopy_offset="@@$((16 + chsheads * chssectors))s" + else + options_mcopy_offset="@@$((chsheads * chssectors))s" + fi + options_hdimage="$options_hdimage -D_CHS_HEADS=$chsheads -D_CHS_SECTORS=$chssectors" +elif [[ "$1" == diskette ]] +then + shift + options_hdimage="" + dosemu_category="floppy" + dosemu_device="device" + dosemu_drive="A:" + qemu_switch="-fdb" + name="diskette" + bootfile="boot${bpe}tw.bin" + unit=01h + options_ldebug_unit="fdb" + options_mcopy_offset="" + diskette=1 + if [[ "$1" == direct ]] + then + shift + qemu_switch="-fda" + unit=00h + direct=1 + fi +else + echo "Error: Invalid disk type \"$1\" specified , must be hdimage or diskette" >&2 + exit 1 +fi + +if [[ "$bpe" == 32 ]] +then + if [[ -z "$pitype" ]] + then + pitype=ptFAT32 + fi + bootname=boot32 +else + if [[ -z "$pitype" ]] + then + if [[ "$bpe" == 16 ]] + then + pitype=ptFAT16 + else + pitype=ptFAT12 + fi + fi + bootname=boot +fi + +echo -ne 'failure\r\n' > result.txt + + "$NASM" "${LDOSBOOT_DIR%/}"/$bootname.asm -w-user \ + -D_LOAD_NAME="'TESTWRIT'" -D_LOAD_EXT="'SYS'" -D_FAT$bpe \ + -D_UNIT=$unit \ + "$@" \ + "$options_i_lmacros" \ + -D_MAP=boot${bpe}tw.map -l boot${bpe}tw.lst -o boot${bpe}tw.bin && + "$NASM" "${LDOSBOOT_DIR%/}"/testwrit.asm \ + "$options_i_lmacros" \ + -o testwrit.bin -l testwrit.lst && + "$NASM" "${LDOSBOOT_DIR%/}"/iniload.asm -w-user \ + "$options_i_ldosboot" \ + "$options_i_lmacros" \ + "$options_i_scanptab" \ + -D_PAYLOAD_FILE="'testwrit.bin'" -o testwrit.sys -l testwrin.lst \ + -D_INILOAD_SIGNATURE='"TW"' && + "$NASM" "${BOOTIMG_DIR%/}"/bootimg.asm \ + -D_WARN_DEFAULT_OFF=1 \ + -D_WARN_TOOMANYFAT=0 -D_WARN_ALIGNDATA=0 \ + $options_hdimage -D_MBR_PART_TYPE="$pitype" \ + -D_BPE="$bpe" -D_SPC="$spc" -D_SPI="$spi" \ + -D_SPF="$(( (spi / spc * bpe / 8 + 511) / 512 ))" \ + -D_NUMROOT="$nr" \ + -o $name.img -l $name.lst \ + -D_PAYLOADFILE="testwrit.sys,result.txt" \ + -D_BOOTFILE="'$bootfile'" \ + -D_UNIT=$unit \ + "$@" \ + -I ./ \ + "$options_i_lmacros" \ + "$options_i_bootimg" +(($?)) && exit $? + +pgid="$(ps -o pgid= $$)" +function handle_timeout_process() { + stty sane + ((debug)) && ps -e -o pgid=,comm=,pid= | grep -E "^\s*$pgid " + pidlist="$(ps -e -o pgid=,comm=,pid= | + grep -E "^\s*$pgid " | + grep -Ev " (bash|test.sh|ps|grep) ")" + pidlist="$(echo "$pidlist" | + sed -re 's/^\s+//g' | + tr -s " " | cut -d" " -f 3)" + if [[ -n "$pidlist" ]] + then + ((debug)) && ps $pidlist + kill $pidlist + fi +} + +if ((! diskette)) +then + if ((dosemu)) + then + seekmbr=8192 + else + seekmbr=0 + fi + "$NASM" "${LDOSMBR_DIR%/}"/oldmbr.asm -w-user \ + "$options_i_ldosmbr" \ + "$options_i_lmacros" \ + -o oldmbr.bin && + dd if=oldmbr.bin of=hdimage.img seek="$seekmbr" bs=1 count=440 conv=notrunc status=none + (($?)) && exit $? + + "$NASM" "${INSTSECT_DIR%/}"/instsect.asm \ + -w-macro-params-legacy \ + -I ./ \ + "$options_i_instsect" \ + "$options_i_lmacros" \ + -D_FAT12=0 -D_FAT16=0 -D_FAT32=0 -D_FAT$bpe=1 \ + -D_PAYLOAD_FAT$bpe="'boot${bpe}tw.bin'" \ + -o inst${bpe}tw.com -l inst${bpe}tw.lst -D_MAP=inst${bpe}tw.map + (($?)) && exit $? + if ((dosemu)) + then + timeout --foreground 10 "$DOSEMU" \ + -I "$dosemu_category { $dosemu_device $name.img }" \ + -dumb -quiet > result.log 2>&1 -K "$PWD" \ + -E "inst${bpe}tw.com $dosemu_drive" < /dev/null + rc=$? + handle_timeout_process + if ((rc)) + then + if ((rc == 124)) + then + echo "Error: instsect run timed out" >&2 + fi + cat result.log + exit 1 + fi + # && "$DOSEMU" -I "..." -dumb -K "$PWD" -E dirg.bat; + elif ((qemu)) + then + cp -aL "$BOOT_KERNEL" "${BOOT_KERNEL##*/}" + cp -aL "$BOOT_COMMAND" "${BOOT_COMMAND##*/}" + echo -ne "@echo off\r\ninst${bpe}tw.com C:\r\nquit.com\r\n" > autoexec.bat + "$NASM" quit.asm \ + "$options_i_lmacros" \ + -o quit.com && + "$NASM" "${LDOSBOOT_DIR%/}"/boot.asm -w-user \ + "$options_i_lmacros" \ + -D_COMPAT_"$BOOT_PROTOCOL"=1 \ + -D_LBA=0 -D_USE_PART_INFO=0 -D_QUERY_GEOMETRY=0 \ + $BOOT_OPTIONS \ + -D_MAP=bootinst.map -l bootinst.lst -o bootinst.bin && + "$NASM" "${BOOTIMG_DIR%/}"/bootimg.asm \ + -I ./ \ + "$options_i_ldebug" \ + "$options_i_bootimg" \ + "$options_i_lmacros" \ + -o diskinst.img -l diskinst.lst \ + -D_PAYLOADFILE="${BOOT_KERNEL##*/},${BOOT_COMMAND##*/},autoexec.bat,inst${bpe}tw.com,quit.com" \ + -D_BOOTFILE="'bootinst.bin'" + (($?)) && exit $? + timeout --foreground 10 "$QEMU" -fda diskinst.img "$qemu_switch" "$name".img -boot order=a -display none 2> /dev/null + rc=$? + handle_timeout_process + if ((rc == 124)) + then + echo "Error: instsect run timed out" >&2 + exit 1 + fi + fi +fi + +if ((! direct)) +then + "$NASM" "${LDOSBOOT_DIR%/}"/boot.asm -w-user \ + "$options_i_lmacros" \ + -D_LOAD_NAME="'LDEBUG'" -D_LOAD_EXT="'COM'" \ + -D_MAP=boot12db.map -l boot12db.lst -o boot12db.bin && + "$NASM" "${BOOTIMG_DIR%/}"/bootimg.asm \ + -I ./ \ + "$options_i_ldebug" \ + "$options_i_bootimg" \ + "$options_i_lmacros" \ + -o diskldbg.img -l diskldbg.lst \ + -D_PAYLOADFILE="ldebug.com" -D_BOOTFILE="'boot12db.bin'" + (($?)) && exit $? + + if ((dosemu)) + then + timeout --foreground 10 "$DOSEMU" -input \ + "$(echo -ne "boot $options_ldebug_unit"'\rif (rc) then boot quit\rq\r')" \ + -I "floppy { device diskldbg.img }" \ + -I "$dosemu_category { $dosemu_device $name.img }" \ + -A -dumb 2> result.err > result.log < /dev/null + elif ((qemu)) + then + ( (sleep 2; "$SENDKEYS" "boot $options_ldebug_unitif (rc) then boot quitq" | socat - UNIX-CONNECT:qemu-monitor > /dev/null ) & > /dev/null) + timeout --foreground 10 "$QEMU" -fda diskldbg.img "$qemu_switch" "$name".img -boot order=a -monitor unix:qemu-monitor,server,nowait -display none 2> /dev/null + fi +else + if ((dosemu)) + then + timeout --foreground 10 "$DOSEMU" \ + -I "$dosemu_category { $dosemu_device $name.img }" \ + "$options_dosemu_direct" -dumb 2> result.err > result.log < /dev/null + elif ((qemu)) + then + timeout --foreground 10 "$QEMU" "$qemu_switch" "$name".img -display none 2> /dev/null + fi +fi + +rc=$? +handle_timeout_process +if ((rc == 124)) +then + echo "${testrunname}timeout" +fi +if [[ "$(mtype -t -i $name.img$options_mcopy_offset ::RESULT.TXT 2> /dev/null)" == success ]] +then + echo "${testrunname}success" +elif ((dosemu)) +then + echo "${testrunname}failure, log contains:" + if [ ! -s result.log ] + then + echo "Result log file empty, dosemu error?" + fi + cat result.log | perl -e ' + my %errorlookup = ( + V => "Check value mismatch", + F => "File not found", + E => "Not enough file data", + R => "Disk read error", + B => "Bad chain / bad FS", + M => "Out of memory", + I => "FSIBOOT error", + S => "Fix sector size mismatch", + C => "Fix cluster size mismatch", + N => "Non-FAT load needs larger cluster size", + ); + my $empty = 1; + while (<>) { + next if $empty and /^\s*$/; + next if /^(dosemu2 2|Configured: |Please test against)/; + next if /^(Get the latest code|Submit Bugs via|Ask for help in)/; + next if /^(This program comes with|This is free software,)/; + $empty = 0; + if (/^([A-Z])\x7/ and defined $errorlookup{$1}) { + s/^(.).//; + print "lDOS boot error condition letter \"$1\" = $errorlookup{$1}\n"; + s/[\x7\n]//g; + s/^\s*$//; + next if /^(dosemu2 2|Configured: |Please test against)/; + next if /^(Get the latest code|Submit Bugs via|Ask for help in)/; + next if /^(This program comes with|This is free software,)/; + print; + print "\n" unless /^$/; + } else { + s/[\x7\n]//g; + print; + print "\n"; + }; + };' +else + echo "${testrunname}failure" +fi diff --git a/test/ldosboot/testpl.asm b/test/ldosboot/testpl.asm new file mode 100644 index 0000000..1071edf --- /dev/null +++ b/test/ldosboot/testpl.asm @@ -0,0 +1,867 @@ + +%if 0 + +Loader test payload + 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 + + +%include "lmacros2.mac" + + numdef LARGE, 1 + numdef PADDING, 0 + + 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 + + +%ifndef _MAP +%elifempty _MAP +%else ; defined non-empty, str or non-str + [map all _MAP] +%endif + + cpu 8086 + org 0 +payload: + ; The device header is of a fixed format. + ; For our purposes, the 4-byte code for + ; each the strategy entry and the + ; interrupt entry is part of this format. + ; (DOS may read the attributes or entrypoint + ; offsets before calling either, so the + ; inicomp stage needs to recreate in its + ; entrypoints part exactly what we have here.) +device_header: +.next: + fill 2, -1, jmp strict short j_zero_entrypoint + dw -1 +.attributes: + dw 8000h ; character device +.strategy: + dw .strategy_entry ; -> strategy entry +.interrupt: + dw .interrupt_entry ; -> interrupt entry +.name: + fill 8, 32, db "TESTPL$$" ; character device name +.strategy_entry: + fill 4, 90h, jmp device_entrypoint +.interrupt_entry: + fill 4, 90h, retf + + +j_zero_entrypoint: + jmp zero_entrypoint + + + nop + align 32, nop +kernel_entrypoint: + ; cs:ip = load seg : 32 here +%if ($ - $$) != 32 + %error Wrong kernel mode entrypoint +%endif + + ; S0 +28 + pushf ; +26 + push ax ; +24 + push cs ; +22 + call .push_ip +.push_ip: + pop ax + sub ax, .push_ip - kernel_entrypoint + push ax ; +20 IP + +common_entrypoint: + push es ; +18 ES + push bx ; +16 BX + cmp word [cs:signature], 2638h + je sig1_valid + jmp sig_invalid + + align 64, nop +dos_exe_entrypoint: + ; cs:ip = PSP : 256 + 64 here + ; + ; Code must be position independent enough. +%if ($ - $$) != 64 + %error Wrong EXE mode entrypoint +%endif + + ; S0 +28 + pushf ; +26 + push ax ; +24 + push cs ; +22 + call .push_ip +.push_ip: + pop ax + sub ax, .push_ip - dos_exe_entrypoint + push ax ; +20 IP + +dos_com_entrypoint: + mov byte [cs:100h + loadmode], 1 + mov ax, cs + add ax, 10h ; simulate kernel loading + push ax +jump_common_entrypoint: + mov ax, common_entrypoint + push ax + retf ; jump to cs + 10h : common_entrypoint + + +zero_entrypoint: + ; S0 +28 + pushf ; +26 + push ax ; +24 + push cs ; +22 + call .push_ip +.push_ip: + pop ax + sub ax, .push_ip + push ax ; +20 IP + + cmp word [cs:0], 20CDh + jne @F + cmp ax, 100h + je dos_com_entrypoint +@@: + + push cx + mov cl, 4 + shr ax, cl + mov cx, cs + add ax, cx + pop cx + push ax + jmp jump_common_entrypoint + + +device_entrypoint: + pushf + push ax + push cs + call .push_ip +.push_ip: + pop ax + sub ax, .push_ip - device_header.strategy_entry + push ax + mov byte [cs:loadmode], 2 + mov word [cs:dev_sp], sp + mov word [cs:dev_exit], dev_exit.1 + jmp common_entrypoint + + +sig_invalid: + call error + db "Signature invalid.", 0 + +sig1_valid: + push ds + push bx + mov bx, cs + add bx, (signature2 -$$+0) >> 4 + mov ds, bx + cmp word [(signature2 -$$+0) & 0Fh], 2638h + pop bx + pop ds + jne sig_invalid + jmp sig_valid + +msg: +.error: db "Test payload error: ", 0 +.test: db "Test payload loaded.", 13, 10, 0 + +.psp_and_size_before: asciz "PSP at " +.psp_and_size_between: asciz "h, size of memory block is " +.psp_and_size_after: asciz "h paragraphs.",13,10 +.cmdline.kern.none: asciz "No kernel command line given!",13,10 +.cmdline_before.kern: asciz "Kernel command line = ",'"' +.cmdline_before.app: asciz "Application command line = ",'"' +.cmdline_before.device: asciz "Device command line = ",'"' +.cmdline_after: asciz '"',13,10 + + align 4 +.foundname: + times 8+1+3+1 db 0 + ; buffer for base name (8) + dot (1) + ext (3) + NUL (1) + align 2 +.foundname_none: + asciz "(None)" +.foundname_none_size: equ $ - .foundname_none + align 2 +.names: + dw .name_first, 0 + dw .name_second, 0 + dw .name_third, 0 + dw .name_fourth, 0 + dw 0 +.name_first: asciz "1st name" +.name_second: asciz "2nd name" +.name_third: asciz "3rd name" +.name_fourth: asciz "4th name" +.name_before: asciz ": " +.name_quote: asciz '"' +.name_after: asciz 13,10 + + align 4 +dev_request_header: + dd 0 +loadmode: dw 0 ; 0 = loaded as boot payload, + ; 1 = loaded as DOS application, + ; 2 = loaded as DOS device driver +dev_sp: dw 0 +dev_exit: dw 0 +.1: + mov sp, word [cs:dev_sp] + jmp .common_1 + +.2: mov sp, word [cs:dev_sp] + jmp .common_2 + +.common_2: + pop ax ; ss + pop ds + pop ax ; sp + pop bp + pop di + pop si + pop dx + pop cx + +.common_1: + pop bx ; bx + pop es ; es + pop ax ; (IP) + pop ax ; (CS) + pop ax ; ax + mov word [es:bx + 3], 8103h + ; error, done, error code: unknown command + popf ; flags + retf ; far return to DOS + + +error: + push cs + pop ds + mov si, msg.error + call disp_msg + pop si + call disp_msg + test byte [cs:loadmode], 2 + jz .dos_or_bios + jmp near [cs:dev_exit] + +.dos_or_bios: + test byte [cs:loadmode], 1 + jz .bios + + mov ax, 4C01h + int 21h + +.bios: + xor ax, ax + int 16h + int 19h + +disp_msg_asciz: + push ds + push si + push ax + push cs + pop ds + mov si, dx + call disp_msg + pop ax + pop si + pop ds + retn + +disp_msg: +@@: + lodsb + test al, al + jz @F + call disp_al + jmp short @B + +disp_al: + push ax + push bx + push dx + push bp + test byte [cs:loadmode], 1 | 2 + jz .bios + + mov dl, al + mov ah, 02h + int 21h + jmp .common + +.bios: + mov ah, 0Eh + mov bx, 7 + int 10h + +.common: + pop bp + pop dx + pop bx + pop ax +@@: + retn + +disp_msg_length: + push cx + jcxz .ret +.loop: + lodsb + call disp_al + loop .loop +.ret: + pop cx + retn + + +sig_valid: + ; S0 +28 +; pushf ; +26 +; push ax ; +24 +; push cs ; +22 +; call .push_ip +;.push_ip: +; pop ax +; sub ax, .push_ip +; push ax ; +20 IP +; push es ; +18 +; push bx ; +16 + sti + cld + push cx ; +14 + push dx ; +12 + push si ; +10 + push di ; +8 + push bp ; +6 + mov ax, sp + add ax, 22 + push ax ; +4 SP + push ds ; +2 + push ss ; +0 + + test byte [cs:loadmode], 2 + jz @F + + mov word [cs:dev_sp], sp + mov word [cs:dev_exit], dev_exit.2 + mov word [cs:dev_request_header], bx + mov word [cs:dev_request_header + 2], es + + cmp byte [es:bx + 2], 0 ; command code 0 (init) ? + jne dev_exit.2 ; else immediately return --> + + mov byte [es:bx + 13], 0 ; number of units = 0 + mov word [es:bx + 14 + 2], cs + and word [es:bx + 14], 0 ; -> after end of memory to allocate + or word [cs:device_header.next], -1 + ; fill in offset of device header link +@@: + + mov si, sp + push ss + pop ds + mov di, table + push cs + pop es +loop_table: + mov bx, [es:di + 0] + mov al, 32 + call disp_al + mov ax, [es:di + 2] + call disp_al + xchg al, ah + call disp_al + cmp bx, -1 + je @F + mov al, '=' + call disp_al + mov ax, [si + bx] + call disp_ax_hex +@@: + add di, 4 + cmp di, table.end + jb loop_table + +listnames: + test byte [cs:loadmode], 1 | 2 + jnz .skip + + mov bx, msg.names + push ss + pop ds + lea si, [bp + bsBPB + ebpbNew + BPBN_size] + mov cx, (512 - (bsBPB + ebpbNew + BPBN_size)) - 2 + ; -2 = AA55h sig + cmp word [bp + bsBPB + bpbSectorsPerFAT], 0 + je @F + mov cx, (512 + (ebpbNew - bpbNew) - (bsBPB + ebpbNew + BPBN_size)) - 2 +@@: +.nextname: + call findname + lahf + mov dx, [cs:bx] + call disp_msg_asciz + mov dx, msg.name_before + call disp_msg_asciz + sahf + jc @F ; skip quote if no name --> + mov dx, msg.name_quote + call disp_msg_asciz +@@: + mov dx, msg.foundname + call disp_msg_asciz + sahf + jc @F ; skip quote if no name --> + mov dx, msg.name_quote + call disp_msg_asciz +@@: + mov dx, msg.name_after + call disp_msg_asciz + sahf + mov ax, 0 + jc @F ; set to zero if no name --> + lea ax, [si - 11] ; -> name in buffer +@@: + mov word [cs:bx + 2], ax ; -> name in buffer, or 0 + add bx, 4 + cmp word [cs:bx], 0 + jne .nextname + +.skip: + + push cs + pop ds + mov si, msg.test + call disp_msg + + test byte [cs:loadmode], 1 + jz .skip_psp_dos + + mov si, msg.psp_and_size_before + call disp_msg + mov ah, 51h + int 21h + mov ax, bx + call disp_ax_hex + mov si, msg.psp_and_size_between + call disp_msg + dec bx + mov es, bx + mov ax, word [es:3] + call disp_ax_hex + mov si, msg.psp_and_size_after + call disp_msg + + + mov si, msg.cmdline_before.app + call disp_msg + + mov ah, 51h + int 21h + mov ds, bx + mov si, 81h + xor cx, cx + mov cl, byte [si - 1] + call disp_msg_length + + push cs + pop ds + mov si, msg.cmdline_after + call disp_msg + + jmp .after_cmdline + +.skip_psp_dos: + test byte [cs:loadmode], 2 + jz .skip_device + + mov si, msg.cmdline_before.device + call disp_msg + + les bx, [cs:dev_request_header] + les di, [es:bx + 18] + push es + pop ds + mov si, di + + ; Writing MS-DOS Device Drivers, second edition, page 349 + ; specifies the following as to the command line termination: + ; "Note that the DEVICE= command string is terminated by an + ; Ah when there are no arguments. When there are arguments, + ; the string is terminated with the following sequence: + ; 0h, Dh, Ah." + db __TEST_IMM8 +@@: + inc di + cmp byte [di], 0 + je @F + cmp byte [di], 13 + je @F + cmp byte [di], 10 + jne @B +@@: ; di -> at terminator + sub di, si + mov cx, di + call disp_msg_length + + push cs + pop ds + mov si, msg.cmdline_after + call disp_msg + + jmp .after_cmdline + +.skip_device: + mov si, msg.cmdline.kern.none + cmp word [bp + ldCommandLine], 0FF00h + je .no_kernel_cmdline + + mov si, msg.cmdline_before.kern + call disp_msg + + push ss + pop ds + lea si, [bp + ldCommandLine] + call disp_msg + + push cs + pop ds + mov si, msg.cmdline_after +.no_kernel_cmdline: + call disp_msg + +.after_cmdline: + int3 + + test byte [cs:loadmode], 2 + jz .dos_or_bios + + jmp near [cs:dev_exit] + +.dos_or_bios: + test byte [cs:loadmode], 1 + jz .bios + + mov ax, 4C00h + int 21h + +.bios: + xor ax, ax + int 16h + int 19h + + +disp_ax_hex: ; ax + xchg al,ah + call disp_al_hex ; display former ah + xchg al,ah ; and fall trough for al +disp_al_hex: ; al + push cx + mov cl,4 + ror al,cl + call disp_al_lownibble_hex ; display former high-nibble + rol al,cl + pop cx + ; and fall trough for low-nibble +disp_al_lownibble_hex: + push ax ; save ax for call return + and al,00001111b ; high nibble must be zero + add al,'0' ; if number is 0-9, now it's the correct character + cmp al,'9' + jna .decimalnum ; if we get decimal number with this, ok --> + add al,7 ; otherwise, add 7 and we are inside our alphabet + .decimalnum: + call disp_al + pop ax + retn + + + ; INP: ds:si -> first byte to check for name + ; cx = number of bytes left + ; OUT: (8+1+3+1)bytes[es:msg.foundname] = found name, + ; converted to 8.3 ASCIZ format, + ; "(None)" if none + ; CY if no filename found, + ; si = INP:si + INP:cx + ; cx = 0 + ; NC if filename found, + ; ds:si -> byte behind the name, thus ds:(si-11)-> name + ; cx = number of bytes left + ; CHG: di, ax +findname: +.: + cmp cx, 11 ; enough for another name ? + jb .none ; no --> + ; (cx == 0 jumps here too) +.check: + push cx + push si + mov cx, 11 + lodsb + mov ah, al ; check for same char in all 11 places + cmp al, 32 ; first character must not be blank + je .check_fail ; if it is --> +; cmp al, 5 ; first character may be 05h to indicate 0E5h +; je .check_pass + db __TEST_IMM8 ; (skip lodsb) +.check_loop_same: + lodsb + cmp ah, al + jne .check_loop_differs + call .check_character + jc .check_fail + loop .check_loop_same + ; if we arrive here, all characters (while valid) are the + ; same character repeated 11 times. we disallow this in case + ; that the padding character is an allowed one (eg '&' 26h). +.check_fail: + pop si + pop cx + dec cx ; lessen the counter + inc si ; -> next position to check + jmp . + +.check_character: + cmp al, 32 + jb .check_character_fail + cmp al, 127 +; je .check_character_fail + jae .check_character_fail + ; note: with all characters >= 128 allowed, + ; we get false positives in our sectors. + cmp al, '.' + je .check_character_fail + cmp al, '/' + je .check_character_fail + cmp al, '\' + je .check_character_fail + cmp al, 'a' + jb .check_character_pass + cmp al, 'z' + ja .check_character_pass +.check_character_fail: + stc + retn + +.check_character_pass: + clc + retn + +.check_loop: + lodsb +.check_loop_differs: + call .check_character + jc .check_fail +.check_pass: + loop .check_loop + + pop ax ; (discard si) + sub si, 11 ; -> at name + + call convert_name_to_asciz + ; si -> behind name + pop cx + sub cx, 11 ; lessen the counter + clc + retn + +.none: + add si, cx + mov di, msg.foundname + push si + push ds + push cs + pop ds + mov si, msg.foundname_none + mov cx, (msg.foundname_none_size + 1) >> 1 + rep movsw + pop ds + pop si + xor cx, cx + stc + retn + + + ; INP: ds:si -> 11-byte blank-padded name + ; es:msg.foundname -> (8+1+3+1)-byte buffer + ; OUT: ds:si -> behind 11-byte blank-padded name + ; es:msg.foundname filled + ; CHG: cx, di, ax +convert_name_to_asciz: + mov di, msg.foundname + mov cx, 8 + rep movsb ; copy over base name, si -> extension + cmp byte [es:di - 8], 05h ; is it 05h ? + jne @F ; no --> + mov byte [es:di - 8], 0E5h ; yes, convert to 0E5h +@@: + + db __TEST_IMM8 ; (skip dec) +@@: + dec di ; decrement -> at previous trailing blank + cmp byte [es:di - 1], 32 ; trailing blank ? + je @B ; yes --> + + mov al, '.' + stosb ; store dot (if needed) + mov cl, 3 + rep movsb ; copy over extension, si -> behind name + + db __TEST_IMM8 ; (skip dec) +@@: + dec di ; decrement -> at previous trailing blank + cmp byte [es:di - 1], 32 ; trailing blank ? + je @B ; yes --> + + cmp byte [es:di - 1], '.' ; trailing dot ? (only occurs if all-blank ext) + jne @F ; no --> + dec di ; -> at the dot +@@: + mov al, 0 + stosb ; store filename terminator + retn + + + align 4 +table: + dw +0, "SS" + dw +6, "BP" + dw +4, "SP" + dw +22, "CS" + dw +20, "IP" + dw +26, "FL" + db -1, -1, 13,10 + dw +2, "DS" + dw +10, "SI" + dw +18, "ES" + dw +8, "DI" + db -1, -1, 13,10 + dw +24, "AX" + dw +16, "BX" + dw +14, "CX" + dw +12, "DX" + db -1, -1, 13,10 + dw +28, "S0" + dw +30, "S1" + dw +32, "S2" + dw +34, "S3" + dw +36, "S4" + dw +38, "S5" + dw +40, "S6" + dw +42, "S7" + db -1, -1, 13,10 + dw +44, "S8" + dw +46, "S9" + dw +48, "SA" + dw +50, "SB" + dw +52, "SC" + dw +54, "SD" + dw +56, "SE" + dw +58, "SF" + db -1, -1, 13,10 +.end: + + +signature: + dw 2638h + align 16, db 0 + +%if _LARGE + times 64 * 1024 db 0 +%endif + +signature2: + dw 2638h + +%if _PADDING + times _PADDING - ($ - $$) db 0 +%endif diff --git a/test/ldosboot/testwrit.asm b/test/ldosboot/testwrit.asm new file mode 100644 index 0000000..d07c96c --- /dev/null +++ b/test/ldosboot/testwrit.asm @@ -0,0 +1,1023 @@ + +%if 0 + +Write success indicator after file system booting + by C. Masloch, 2020 + +Usage of the works is permitted provided that this +instrument is retained with the works, so that any entity +that uses the works is notified of this instrument. + +DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. + +%endif + + +%assign __lMACROS1_MAC__DEBUG_DEFAULTS 1 +%include "lmacros3.mac" + + struc BS +bsJump: resb 3 +bsOEM: resb 8 +bsBPB: + endstruc + + struc EBPB ; BPB sec +bpbBytesPerSector: resw 1 ; offset 00h 0Bh +bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh +bpbReservedSectors: resw 1 ; offset 03h 0Eh +bpbNumFATs: resb 1 ; offset 05h 10h +bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32 +bpbTotalSectors: resw 1 ; offset 08h 13h +bpbMediaID: resb 1 ; offset 0Ah 15h +bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32 +bpbCHSSectors: resw 1 ; offset 0Dh 18h +bpbCHSHeads: resw 1 ; offset 0Fh 1Ah +bpbHiddenSectors: resd 1 ; offset 11h 1Ch +bpbTotalSectorsLarge: resd 1 ; offset 15h 20h +bpbNew: ; offset 19h 24h + +ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h +ebpbFSFlags: resw 1 ; offset 1Dh 28h +ebpbFSVersion: resw 1 ; offset 1Fh 2Ah +ebpbRootCluster: resd 1 ; offset 21h 2Ch +ebpbFSINFOSector: resw 1 ; offset 25h 30h +ebpbBackupSector: resw 1 ; offset 27h 32h +ebpbReserved: resb 12 ; offset 29h 34h +ebpbNew: ; offset 35h 40h + endstruc + + struc BPBN ; ofs B16 S16 B32 S32 +bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h + resb 1 ; 01h 1Ah 25h 36h 41h +bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN +bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h +bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h +bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h + endstruc ; 1Ah 33h 3Eh 4Fh 5Ah + + struc LOADSTACKVARS, -10h +lsvFirstCluster: resd 1 +lsvFATSector: resd 1 +lsvFATSeg: resw 1 +lsvLoadSeg: resw 1 +lsvDataStart: resd 1 + endstruc + +lsvclSignature equ "CL" +lsvclBufferLength equ 256 + + struc LOADDATA, LOADSTACKVARS - 10h +ldMemoryTop: resw 1 +ldLoadTop: resw 1 +ldSectorSeg: resw 1 +ldFATType: resb 1 +ldHasLBA: resb 1 +ldClusterSize: resw 1 +ldParaPerSector:resw 1 +ldLoadingSeg: resw 1 +ldLoadUntilSeg: resw 1 + endstruc + + struc LOADCMDLINE, LOADDATA - lsvclBufferLength +ldCommandLine: +.start: resb lsvclBufferLength + endstruc + + struc LBAPACKET +lpSize: resw 1 +lpCount: resw 1 +lpBuffer: resd 1 +lpSector: resq 1 + endstruc + + struc DIRENTRY +deName: resb 8 +deExt: resb 3 +deAttrib: resb 1 + resb 8 +deClusterHigh: resw 1 +deTime: resw 1 +deDate: resw 1 +deClusterLow: resw 1 +deSize: resd 1 + endstruc + +ATTR_READONLY equ 1 +ATTR_HIDDEN equ 2 +ATTR_SYSTEM equ 4 +ATTR_VOLLABEL equ 8 +ATTR_DIRECTORY equ 10h +ATTR_ARCHIVE equ 20h + + +%ifndef _MAP +%elifempty _MAP +%else ; defined non-empty, str or non-str + [map all _MAP] +%endif + + defaulting + + numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access) + numdef CHS, 1 ; support CHS (if it fits) + numdef LBA, 1 ; support LBA (if available) + numdef LBA_33_BIT, 1 ; support 33-bit LBA + numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry + numdef LBA_SKIP_CHECK, 0 ; don't use proper LBA extensions check + numdef LBA_RETRY, 1 ; retry LBA reads + numdef CHS_RETRY, 1 ; retry CHS reads + + strdef FILE_NAME, "RESULT" + strdef FILE_EXT, "TXT" ; name of file to write + strdef FILE_SUCCESS_MSG,"success" + + + cpu 8086 + org 0 +start: + push ss + pop ds + mov word [cs:dirsp], sp + + mov ax, cs + cmp ax, 200h + 200h ; loaded high ? + mov dx, 200h ; yes, use low buffer + jae @F + add ax, paras(end - $$) ; => behind used load image + mov dx, ax + neg ax ; - behind used + add ax, word [bp + ldLoadTop] ; top - used = length of free + cmp ax, 200h ; enough ? + jb error_outofmemory ; no --> + ; (For simplicity we do not attempt to relocate our load image.) +@@: + mov word [cs:dirbuf], dx + +%if _QUERY_GEOMETRY || !_LBA_SKIP_CHECK + call query_geometry + ; The ebpbNew BPBN needs to be initialised + ; to use this function. It must be called + ; before using read_sector. +%endif + + cmp byte [bp + ldFATType], 16 + ja search32 + cmp byte [bp + ldFATType], 0 + ja search1216 + call error + asciz "Unknown file system (ldFATType = 0)." + +search1216: + xor cx, cx + mov bx, word [bp + ldParaPerSector] + shr bx, 1 ; = entries per sector + +; number of sectors used for root directory (store in CX) + mov si, [bp + bsBPB + bpbNumRootDirEnts] + mov ax, bx + ; The ax value here is the last value of bx, which is set + ; by the shr instruction. Therefore, it cannot be higher + ; than 7FFFh, so this cwd instruction always zeros dx. + cwd + dec ax ; rounding up + add ax, si ; from BPB + adc dx, dx ; account for overflow (dx was zero) + div bx ; get number of root sectors + xchg ax, cx ; cx = number of root secs, ! ah = 0 + +; first sector of root directory + mov al, [bp + bsBPB + bpbNumFATs]; ! ah = 0, hence ax = number of FATs + mul word [bp + bsBPB + bpbSectorsPerFAT] + add ax, [bp + bsBPB + bpbReservedSectors] + adc dl, dh ; account for overflow (dh was and is 0) + +; Scan root directory for file. We don't bother to check for deleted +; entries (E5h) or entries that mark the end of the directory (00h). + ; number of root entries in si here +.next_sect: + mov cx, bx ; entries per sector as loop counter + call read_sector_to_dirbuf + mov bx, cx ; restore bx for next iteration later + + xor di, di ; es:di-> first entry in this sector +.next_ent: + call check_entry + dec si ; count down entire root's entries + loopnz .next_ent ; count down sector's entries (jumps iff si >0 && cx >0) + jnz .next_sect ; (jumps iff si >0 && cx ==0) + ; ends up here iff si ==0 + ; ie all root entries checked unsuccessfully + jmp error_filenotfound + + +search32: + mov ax, [bp + bsBPB + ebpbRootCluster] + mov dx, [bp + bsBPB + ebpbRootCluster + 2] + mov si, word [bp + lsvFATSector + 2] + mov di, word [bp + lsvFATSector] + call check_clust + jc error_filenotfound + +.next_root_clust: + call clust_to_first_sector + push cx + push bx + mov cx, [bp + ldClusterSize] +.next_root_sect: + push cx + mov cx, [bp + ldParaPerSector] + shr cx, 1 + +; Scan root directory for file. We don't bother to check for deleted +; entries (E5h) or entries that mark the end of the directory (00h). + call read_sector_to_dirbuf + + push di + xor di, di ; es:di-> first entry in this sector +.next_ent: + call check_entry + loop .next_ent ; count down sector's entries (jumps iff cx >0) + pop di + pop cx + loop .next_root_sect + pop bx + pop cx + call clust_next + jnc .next_root_clust + jmp error_filenotfound + + +read_sector_to_dirbuf: + mov bx, word [cs:dirbuf] + mov word [cs:dirsec], ax + mov word [cs:dirsec + 2], dx + jmp read_sector + +check_entry: + test byte [es:di + deAttrib], ATTR_DIRECTORY | ATTR_VOLLABEL + jnz @F ; directory, label, or LFN entry --> (NZ) + push si + push di + push cx + push ds + push cs + pop ds + mov si, msg.filename ; ds:si-> name to match + mov cx, 11 ; length of padded 8.3 FAT filename + repe cmpsb ; check entry + pop ds + pop cx + pop di + pop si +@@: ; ZR = match, NZ = mismatch + je found_it ; found entry --> + lea di, [di + DIRENTRY_size] + retn + +found_it: + mov sp, word [cs:dirsp] + mov word [cs:dirofs], di + + cmp word [es:di + deSize], 0 + jne @F + cmp word [es:di + deSize + 2], 0 + jne @F + +error_emptyfile: equ $ + call error + asciz "File is empty." + +@@: +; get starting cluster of file + mov ax, [es:di + deClusterLow] + mov dx, [es:di + deClusterHigh] ; dx:ax = first cluster + cmp byte [bp + ldFATType], 16 + ja @F + xor dx, dx +@@: + + mov di, [bp + lsvFATSector] + mov si, [bp + lsvFATSector + 2] + call check_clust + jc error_emptyfile + + call clust_to_first_sector + mov bx, cs + add bx, paras(filbuf - $$) + call write_sector + + mov es, word [cs:dirbuf] + mov bx, word [cs:dirofs] + mov word [es:bx + deSize], filbuf.size + and word [es:bx + deSize + 2], 0 + mov bx, es + mov ax, word [cs:dirsec] + mov dx, word [cs:dirsec + 2] + call write_sector + + push cs + pop ds + mov si, msg.success + call disp_error + +quit: + int3 + + mov ax, 0F000h + mov es, ax + push cs + pop ds ; avoid "repe cs cmpsw" (8086 bug) + mov di, 0FFF5h + mov si, msg.dosemudate + mov cx, 4 + repe cmpsw ; running in DosEmu? + jne .quit_not_dosemu + + xor bx, bx + mov ax, -1 + int 0E6h ; dosemu quit + +.quit_not_dosemu: + +; from https://stackoverflow.com/a/5240330/738287 + mov ax, 5301h + xor bx, bx + int 15h ; connect to APM API + + mov ax, 530Eh + xor bx, bx + mov cx, 0102h + int 15h ; set APM version to 1.02 + + mov ax, 5307h + mov bx, 1 + mov cx, 3 + int 15h ; shut down system + + ; setopt [cs:quitrecurse], 1 + call error + asciz "Quit failed." + + +error: + push cs + pop ds + mov si, msg.error + call disp_error + pop si + call disp_error + mov si, msg.error.trailer + call disp_error +%if 0 + testopt [cs:quitrecurse], 1 + jz quit +%endif + int3 + xor ax, ax + int 16h + int 19h + +disp_error: +.: + lodsb + test al, al + jz .ret + mov ah, 0Eh + mov bx, 7 + push bp + ; (call may change bp) + int 10h + pop bp + jmp short . + + + align 4 +dirsec: dd 0 +dirofs: dw 0 +dirbuf: dw 0 +dirsp: dw 0 +; quitrecurse: db 0 + +msg: +.error: asciz "Load error: " +.success: db "Test writer loaded successfully. Quitting the machine." +.error.trailer: asciz 13,10 +.filename: fill 8, 32, db _FILE_NAME + fill 3, 32, db _FILE_EXT + align 4 +.dosemudate: db "02/25/93" + + align 16, db 38 +filbuf: +.: + db _FILE_SUCCESS_MSG,13,10 +.size: equ $ - . +%if .size > 32 + %error File success message is too long +%endif + _fill 8192, 38, . + + +query_geometry: +%if _QUERY_GEOMETRY ; +30 bytes + mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit] + %if !_LBA_SKIP_CHECK + push dx + %endif +; test dl, dl ; floppy? +; jns @F ; don't attempt query, might fail --> + ; Note that while the original PC BIOS doesn't support this function + ; (for its diskettes), it does properly return the error code 01h. + ; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1) + mov ah, 08h + xor cx, cx ; initialise cl to 0 + stc ; initialise to CY + int 13h ; query drive geometry + jc @F ; apparently failed --> + and cx, 3Fh ; get sectors + jz @F ; invalid (S is 1-based), don't use --> + mov [bp + bsBPB + bpbCHSSectors], cx + mov cl, dh ; cx = maximum head number + inc cx ; cx = number of heads (H is 0-based) + mov [bp + bsBPB + bpbCHSHeads], cx +@@: +%endif + +%if !_LBA_SKIP_CHECK + mov ah, 41h + %if _QUERY_GEOMETRY + pop dx + %else + mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit] + %endif + mov bx, 55AAh + stc + int 13h ; 13.41.bx=55AA extensions installation check + mov al, 0 ; zero in case of no LBA support + jc .no_lba + cmp bx, 0AA55h + jne .no_lba + test cl, 1 ; support bitmap bit 0 + jz .no_lba + inc ax ; al = 1 to indicate LBA support +.no_lba: + mov byte [bp + ldHasLBA], al +%endif + +%if 1 || _QUERY_GEOMETRY || !_LBA_SKIP_CHECK +disp_error.ret: + retn +%endif + + + ; Read a sector using Int13.02 or Int13.42 + ; + ; INP: dx:ax = sector number within partition + ; bx:0-> buffer + ; (_LBA) ds = ss + ; OUT: If unable to read, + ; ! jumps to error instead of returning + ; If sector has been read, + ; dx:ax = next sector number (has been incremented) + ; bx:0-> next buffer (bx = es+word[para_per_sector]) + ; es = input bx + ; CHG: - + ; STT: ds = ss + ; + ; Note: If error 09h (data boundary error) is returned, + ; the read is done into the ldSectorSeg buffer, + ; then copied into the user buffer. +read_sector: + clropt [bp + ldHasLBA], 2 + jmp @F + +write_sector: + setopt [bp + ldHasLBA], 2 +@@: + + push dx + push cx + push ax + push si + + push bx + +; DX:AX==LBA sector number +; add partition start (= number of hidden sectors) + add ax,[bp + bsBPB + bpbHiddenSectors + 0] + adc dx,[bp + bsBPB + bpbHiddenSectors + 2] + + %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33 + jc .err_CY + %endif +%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS) + %if _LBA_33_BIT + sbb si, si ; -1 if was CY, 0 else + neg si ; 1 if was CY, 0 else + %endif + xor cx, cx ; cx = 0 (needed if jumping to .no_lba_checked) + %if !_LBA_SKIP_CHECK + test byte [bp + ldHasLBA], 1 + jz .no_lba_checked + %endif + push cx + %if _LBA_33_BIT + push si ; bit 32 = 1 if operating in 33-bit space + %else + push cx ; second highest word = 0 + %endif + push dx + push ax ; qword sector number (lpSector) + push bx + push cx ; bx:0 -> buffer (lpBuffer) + inc cx + push cx ; word number of sectors to read (lpCount) + mov cl, 10h + push cx ; word size of disk address packet (lpSize) + mov si, sp ; ds:si -> disk address packet (on stack) + + mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit] + call .get_ah_3_write_2_read + or ah, 40h ; 42h extensions read or 43h extensions write +%if _LBA_RETRY + call .int13_retry +%else + call .int13_preserve_lpcount +%endif + jnc .lba_done + +%if _LBA_SKIP_CHECK + cmp ah, 1 ; invalid function? + je .no_lba_skip ; try CHS instead --> +%endif + cmp ah, 9 ; data boundary error? + jne .lba_error + + testopt [bp + ldHasLBA], 2 + jz @F + + mov es, word [si + 4 + 2] ; user buffer + call .sectorseg_helper_write + mov word [si + 4 + 2], es ; => sector buffer + mov ah, 43h +%if _LBA_RETRY + call .int13_retry +%else + int 13h + ; (don't need .int13_preserve_lpcount as no further call) +%endif + jc .lba_error + jmp .lba_done + +@@: + ; push word [si + 4 + 0] + push word [si + 4 + 2] ; user buffer + push word [bp + ldSectorSeg] + pop word [si + 4 + 2] + ; and word [si + 4 + 0], byte 0 + + mov ah, 42h +%if _LBA_RETRY + call .int13_retry +%else + int 13h + ; (don't need .int13_preserve_lpcount as no further call) +%endif + jc .lba_error + + pop es + ; pop cx + call .sectorseg_helper_read + +.lba_done: + add sp, 10h + pop bx + jmp short .chs_done + +.lba_error: equ .err + + %if !_CHS +.no_lba_skip: equ .err +.no_lba_checked: equ .err + %elif _LBA_SKIP_CHECK +.no_lba_skip: + ; si == sp + add sp, 8 + pop ax + pop dx + %if _LBA_33_BIT + pop si + pop cx ; cx = 0 (needed as input for next cwd instruction) + test si, si + mov si, sp ; si == sp + %else + pop cx + pop cx + ; si == sp - 16 + %endif + %else +.no_lba_checked: + %if _LBA_33_BIT + test si, si + %endif + mov si, sp ; si == sp + %endif +%endif + +%if _CHS ; +70 bytes + %if _LBA && _LBA_33_BIT + jnz .err + %endif +; dx:ax = LBA sector number, (if _LBA) cx = 0 +; divide by number of sectors per track to get sector number +; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability +; Use two-step 32:16 divide to avoid overflow + %if !_LBA + xchg cx, ax ; cx = low word of sector, clobbers ax + xchg ax, dx ; ax = high word of sector, clobbers dx + xor dx, dx ; dx:ax = high word of sector + %else + xchg cx, ax ; cx = low word of sector, ax = 0 + push dx ; stack = high word of sector + cwd ; dx = 0 (because ax was 0) + pop ax ; ax = high word of sector + ; dx:ax = high word of sector + %endif + div word [bp + bsBPB + bpbCHSSectors] + xchg cx,ax + div word [bp + bsBPB + bpbCHSSectors] + xchg cx,dx + +; DX:AX=quotient, CX=remainder=sector (S) - 1 +; divide quotient by number of heads + xchg bx, ax ; bx = low word of quotient, clobbers ax + xchg ax, dx ; ax = high word of quotient, clobbers dx + xor dx, dx ; dx = 0 + div word [bp + bsBPB + bpbCHSHeads] + ; ax = high / heads, dx = high % heads + xchg bx, ax ; bx = high / heads, ax = low quotient + div word [bp + bsBPB + bpbCHSHeads] + +; bx:ax=quotient=cylinder (C), dx=remainder=head (H) +; move variables into registers for INT 13h AH=02h + mov dh, dl ; dh = head + inc cx ; cl5:0 = sector + xchg ch, al ; ch = cylinder 7:0, al = 0 + shr ax, 1 + shr ax, 1 ; al7:6 = cylinder 9:8 + ; bx has bits set iff it's > 0, indicating a cylinder >= 65536. + or bl, bh ; collect set bits from bh + or cl, al ; cl7:6 = cylinder 9:8 + ; ah has bits set iff it was >= 4, indicating a cylinder >= 1024. + or bl, ah ; collect set bits from ah + mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit] + ; dl = drive + jnz .err ; error if cylinder >= 1024 --> + ; ! bx = 0 (for 13.02 call) + +; we call INT 13h AH=02h once for each sector. Multi-sector reads +; may fail if we cross a track or 64K boundary + pop es + + call .get_ah_3_write_2_read + mov al, 01h ; access one sector + +%if _CHS_RETRY + call .int13_retry +%else + int 13h +%endif + jnc .done + + cmp ah, 9 ; data boundary error? + jne .err + + testopt [bp + ldHasLBA], 2 + jz @F + + push es ; user buffer + call .sectorseg_helper_write + mov ax, 0301h +%if _LBA_RETRY + call .int13_retry +%else + int 13h +%endif + jc .err + pop es + jmp .done + +@@: + push es ; user buffer + mov es, word [bp + ldSectorSeg] + + mov ax, 0201h +%if _CHS_RETRY + call .int13_retry +%else + int 13h +%endif + jc .err + + pop es + call .sectorseg_helper_read + +.done: +; increment segment + mov bx, es +%endif + +.chs_done: + mov es, bx + add bx, word [bp + ldParaPerSector] + + pop si + pop ax + pop cx + pop dx +; increment LBA sector number + inc ax + jne @F + inc dx +@@: + retn + + +%if (_LBA && _LBA_RETRY) || (_CHS && _CHS_RETRY) +.int13_retry: + push ax +%if _LBA + call .int13_preserve_lpcount +%else + int 13h ; first try +%endif + jnc @F ; NC, success on first attempt --> + +; reset drive + xor ax, ax + int 13h + jc @F ; CY, reset failed, error in ah --> + +; try read again + pop ax ; restore function number +%if _LBA + call .int13_preserve_lpcount +%else + int 13h ; retry, CF error status, ah error number +%endif + retn + +@@: ; NC or CY, stack has function number + inc sp + inc sp ; discard word on stack, preserve CF + retn +%endif + +%if _LBA + ; have to reset the LBAPACKET's lpCount, as the handler may + ; set it to "the number of blocks successfully transferred". + + ; hack: si points into unclaimed stack space + ; when this is called from the CHS handler. + ; this should not cause any issues however. + ; actually, if !_LBA_SKIP_CHECK, then si is set + ; to point to claimed stack space. also legal. +.int13_preserve_lpcount: + push word [si + lpCount] + int 13h + pop word [si + lpCount] + retn +%endif + + ; INP: word [bp + ldSectorSeg] => source buffer with read data + ; es => destination buffer + ; CHG: si, cx + ; OUT: ss = ds + ; data copied to destination buffer + ; STT: UP +.sectorseg_helper_read: + xor si, si + mov ds, word [bp + ldSectorSeg] + push di + ; mov di, cx + xor di, di + mov cx, word [bp + bsBPB + bpbBytesPerSector] + rep movsb + pop di + + push ss + pop ds + retn + + ; INP: es => source buffer with data to read + ; word [bp + ldSectorSeg] => destination buffer + ; CHG: - + ; OUT: ss = ds + ; es => destination buffer + ; data copied to destination buffer + ; STT: UP +.sectorseg_helper_write: + push es + pop ds + push si + push di + push cx + xor di, di + mov es, word [bp + ldSectorSeg] + xor si, si + mov cx, word [bp + bsBPB + bpbBytesPerSector] + rep movsb + pop cx + pop di + pop si + + push ss + pop ds + retn + +.get_ah_3_write_2_read: + mov ah, 02h ; read + testopt [bp + ldHasLBA], 2 + jz @F + mov ah, 03h ; write +@@: + retn + +.err: +error_diskaccess: + testopt [bp + ldHasLBA], 2 + jz @F + call error + db "Disk write error.", 0 + +@@: + call error + db "Disk read error.", 0 + +error_badchain: + call error + db "Bad cluster chain.", 0 + +error_badclusters: + call error + db "Bad amount of clusters.", 0 + +error_outofmemory: + call error + db "Out of memory.", 0 + +error_filenotfound: + call error + db "File not found.", 0 + + + ; INP: dx:ax = cluster - 2 (0-based cluster) + ; OUT: cx:bx = input dx:ax + ; dx:ax = first sector of that cluster + ; CHG: - +clust_to_first_sector: + push dx + push ax + push dx + mul word [bp + ldClusterSize] + xchg bx, ax + xchg cx, dx + pop ax + mul word [bp + ldClusterSize] + test dx, dx + jnz short error_badchain + xchg dx, ax + add dx, cx +.cy_error_badchain: + jc short error_badchain + xchg ax, bx + + add ax, [bp + lsvDataStart] + adc dx, [bp + lsvDataStart + 2] + jc short .cy_error_badchain + ; dx:ax = first sector in cluster + pop bx + pop cx ; cx:bx = cluster + retn + + + ; INP: cx:bx = cluster (0-based) + ; si:di = loaded FAT sector, -1 if none + ; OUT: CY if no next cluster + ; NC if next cluster found, + ; dx:ax = next cluster value (0-based) + ; si:di = loaded FAT sector + ; CHG: cx, bx +clust_next: + mov ax, bx + mov dx, cx + add ax, 2 + adc dx, 0 + + push es + cmp byte [bp + ldFATType], 16 + je .fat16 + ja .fat32 + +.fat12: +; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2, +; so multiply cluster number by 3 first, then divide by 2. + ; ax = cluster number (up to 12 bits set) + mov dx, ax + shl ax, 1 ; = 2n (up to 13 bits set) + add ax, dx ; = 2n+n = 3n (up to 14 bits set) + shr ax, 1 ; ax = byte offset into FAT (0..6129) + ; CF = whether to use high 12 bits + sbb cx, cx ; = -1 iff CY, else 0 + +; Use the calculated byte offset as an offset into the FAT +; buffer, which holds all of the FAT's relevant data. + mov es, [bp + lsvFATSeg] + xchg bx, ax ; bx -> 16-bit word in FAT to load + +; get 16 bits from FAT + mov ax, [es:bx] + + and cl, 4 ; = 4 iff CY after shift, else 0 + shr ax, cl ; shift down iff odd entry, else unchanged + and ax, 0FFFh ; insure it's only 12 bits + jmp short .gotvalue_zero_dx + +.fat32: + ; * 4 = byte offset into FAT (0--4000_0000h) + add ax, ax + adc dx, dx +.fat16: + ; * 2 = byte offset into FAT (0--2_0000h) + add ax, ax + adc dx, dx + + push ax + xchg ax, dx + xor dx, dx ; dx:ax = high word + div word [bp + bsBPB + bpbBytesPerSector] + xchg bx, ax ; bx = high word / divisor + pop ax ; dx = remainder, ax = low word + div word [bp + bsBPB + bpbBytesPerSector] + xchg dx, bx ; dx:ax = result, bx = remainder + ; dx:ax = sector offset into FAT (0--200_0000h) + ; bx = byte offset into FAT sector (0--8190) + cmp dx, si + jne @F ; read sector + cmp ax, di + je @FF ; sector is already buffered +@@: + mov si, dx + mov di, ax + mov word [bp + lsvFATSector + 2], dx + mov word [bp + lsvFATSector + 0], ax + + push bx + add ax, [bp + bsBPB + bpbReservedSectors] + adc dx, 0 + mov bx, [bp + lsvFATSeg] + call read_sector + pop bx +@@: + mov es, [bp + lsvFATSeg] + mov dx, [es:bx + 2] + mov ax, [es:bx] ; dx:ax = FAT32 entry + + cmp byte [bp + ldFATType], 16 ; is it FAT32 ? + jne @F ; yes --> +.gotvalue_zero_dx: + xor dx, dx ; no, clear high word +@@: + pop es + + ; INP: dx:ax = cluster value, 2-based + ; OUT: dx:ax -= 2 (makes it 0-based) + ; CY iff invalid cluster +check_clust: + and dh, 0Fh + sub ax, 2 + sbb dx, 0 + + cmp byte [bp + ldFATType], 16 + ja .fat32 + je .fat16 + +.fat12: + cmp ax, 0FF7h - 2 + jmp short .common + +.fat32: + cmp dx, 0FFFh + jb @F ; CY here means valid ...- + +.fat16: + cmp ax, 0FFF7h - 2 +@@: ; -... or if NC first, CY here also +.common: + cmc ; NC if valid + retn + + + align 16, db 38 +end: