
%if 0

lDOS initialisation
 by E. C. Masloch, 2018--2025

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"
%include "lstruct.mac"
 extern autobounce_both_options, autobouncehmachs_option, autobouncehmalba_option
 extern config_bounce_hma_chs
 extern config_bounce_hma_lba
 extern DPBHEAD
 extern DSKDEV
 extern DISKSECTOR

	%include "msbds.mac"
	%include "dpb.mac"

defaulting

numdef DEBUG0
numdef DEBUG1
numdef DEBUG2
numdef DEBUG3
numdef DEBUG4
numdef DEBUG5
%idefine d5 _d 5,

%assign _UMA 1
%assign _COMBINED_DOSDATA_DOSCODE 0
;	numdef DOSCODEHMA, 1
;	numdef RELOCATEDOSCODE, 1
	numdef INT19_IN_DOSENTRY, 1
	strdef BIN_FILE_DEFAULT, "LDOS.COM"
	numdef TEST_PAYLOAD, 0
	numdef INIT_ENV_SIZE, 1024

%if _RELOCATEDOSCODE
 %define DOSCODE_SIZE (doscode_end - doscode_start)
 %define DOSCODE_MCB_SIZE 16
%else
 %define DOSCODE_SIZE 0
 %define DOSCODE_MCB_SIZE 0
%endif


%if 0	; lMS-DOS doesn't use these parts yet

	struc FSPARM
fspStart:	resd 1
fspLength:	resd 1
fspUnit:	resb 1
fspPartition:	resb 1
fspBoot:	resb 1
fspType:	resb 1
fspSectorSize:	resw 1
		resw 1
	endstruc


%ifndef _MAP
%elifempty _MAP
%else	; defined non-empty, str or non-str
	[map all _MAP]
%endif


	cpu 8086
	; section INIT0 start=0 vstart=0
init0_start:
	nop
	align 32, nop
init0_kernel_entry:
		; cs:ip = load seg : 32 here
%if ($ - $$) != 32
 %error Wrong kernel mode entrypoint
%endif
	cld
	mov ax, cs
	add ax, (init0_end - init0_start) >> 4
	mov cx, ( 16 \
		+ (dosentry_end - dosentry_start) \
		+ DOSCODE_MCB_SIZE \
		+ DOSCODE_SIZE \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16 \
		+ (init1_end - init1_start) \
		+ 16 \
		+ (init2_end - init2_start) \
		+ 16 \
		+ (init3_end - init3_start) \
		+ 16 \
		) >> 4
	mov dx, word [ bp + ldLoadTop ]
	sub dx, cx
	call init0_movp

	mov ax, dx
	add ax, ( \
		+ 16 \
		+ (dosentry_end - dosentry_start) \
		+ DOSCODE_MCB_SIZE \
		+ DOSCODE_SIZE \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16 \
		) >> 4
	xor bx, bx
	push ax
	push bx
	retf

	align 64, nop
init0_exe_entry:
		; cs:ip = PSP : 256 + 64 here
		;
		; Code must be position independent enough.
%if ($ - $$) != 64
 %error Wrong EXE mode entrypoint
%endif
	mov cx, cs
	add cx, ( 256 \
		+ (init0_end - init0_start) \
		+ 16 \
		+ (dosentry_end - dosentry_start) \
		+ DOSCODE_MCB_SIZE \
		+ DOSCODE_SIZE \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16 \
		+ (init1_end - init1_start) \
		+ 16 \
		+ (init2_end - init2_start) \
		+ 16 \
		+ (init3_end - init3_start) \
		+ 16 \
		+ COMLOADER_position \
		) >> 4
	xor bx, bx
	push cx
	push bx
	retf


		; Move paragraphs
		;
		; INP:	ax:0-> source
		;	dx:0-> destination
		;	cx = number of paragraphs
		; CHG:	-
		; Note:	Doesn't work correctly on HMA; doesn't always wrap to LMA either.
		;	Do not provide a wrapped/HMA source or destination!
init0_movp:
	push cx
	push ds
	push si
	push es
	push di

	cmp ax, dx		; source above destination ?
	ja .up			; yes, move up (forwards) -->
	je .return		; same, no need to move -->
	push ax
	add ax, cx		; (expected not to carry)
	cmp ax, dx		; end of source is above destination ?
	pop ax
	ja .down		; yes, move from top down -->
	; Here, the end of source is below-or-equal the destination,
	;  so they do not overlap. In this case we prefer moving up.

.up:
	push ax
	push dx
.uploop:
	mov ds, ax
	mov es, dx
	xor di, di
	xor si, si		; -> start of segment
	sub cx, 1000h		; 64 KiB left ?
	jbe .uplast		; no -->
	push cx
	mov cx, 10000h /2
	rep movsw		; move 64 KiB
	pop cx
	add ax, 1000h
	add dx, 1000h		; -> next segment
	jmp short .uploop	; proceed for more -->
.uplast:
	add cx, 1000h		; restore counter
	shl cx, 1
	shl cx, 1
	shl cx, 1		; *8, paragraphs to words
	rep movsw		; move last part
	pop dx
	pop ax
	jmp short .return

.down:
	std			; _AMD_ERRATUM_109_WORKAROUND as below
.dnloop:
	sub cx, 1000h		; 64 KiB left ?
	jbe .dnlast		; no -->
	push ax
	push dx
	add ax, cx
	add dx, cx
	mov ds, ax		; -> 64 KiB not yet moved
	mov es, dx
	pop dx
	pop ax
	mov di, -2
	mov si, di		; moved from last word down
	push cx
	mov cx, 10000h /2
	rep movsw		; move 64 KiB
	pop cx
	jmp short .dnloop	; proceed for more -->
.dnlast:
	add cx, 1000h		; restore counter
	shl cx, 1
	shl cx, 1
	shl cx, 1		; *8, paragraphs to words
	mov di, cx
	dec di
	shl di, 1		; words to offset, -> last word
	mov si, di
	mov ds, ax
	mov es, dx		; first segment correct


	numdef AMD_ERRATUM_109_WORKAROUND, 1
%if 0

Jack R. Ellis pointed out this erratum:

Quoting from https://www.amd.com/system/files/TechDocs/25759.pdf page 69:

109   Certain Reverse REP MOVS May Produce Unpredictable Behavior

Description

In certain situations a REP MOVS instruction may lead to
incorrect results. An incorrect address size, data size
or source operand segment may be used or a succeeding
instruction may be skipped. This may occur under the
following conditions:

* EFLAGS.DF=1 (the string is being moved in the reverse direction).

* The number of items being moved (RCX) is between 1 and 20.

* The REP MOVS instruction is preceded by some microcoded instruction
  that has not completely retired by the time the REP MOVS begins
  execution. The set of such instructions includes BOUND, CLI, LDS,
  LES, LFS, LGS, LSS, IDIV, and most microcoded x87 instructions.

Potential Effect on System

Incorrect results may be produced or the system may hang.

Suggested Workaround

Contact your AMD representative for information on a BIOS update.

%endif

%if _AMD_ERRATUM_109_WORKAROUND
	jcxz @FF
	cmp cx, 20
	ja @FF
@@:
	movsw
	loop @B
@@:
%endif
	rep movsw		; move first part
	cld
.return:
	pop di
	pop es
	pop si
	pop ds
	pop cx
	retn

	align 16
init0_end:
	istruc MCB
at mcbSignature,	db "M"
at mcbOwner,		dw 8
at mcbSize,		dw (dosentry_end - dosentry_start) >> 4
at smcbName,		dw "S"
at smcbType,		db S_DOSENTRY
	iend

	; section DOSENTRY align=16 follows=INIT0 vstart=0
dosentry_start:
%include "entry.asm"

%if _RELOCATEDOSCODE
	align 16
dosentry_end:
	istruc MCB
at mcbSignature,	db "M"
at mcbOwner,		dw 8
at mcbSize,		dw (doscode_end - doscode_start) >> 4
at smcbName,		dw "S"
at smcbType,		db S_DOSCODE
	iend

	; section DOSCODE align=16 follows=DOSENTRY vstart=40h
			; vstart:
			; +10h for end of ROM-BIOS image (at 0FFFFh:0)
			; +20h for VDISK header
			; +10h for first HMCB
doscode_start:
%endif

%if !_COMBINED_DOSDATA_DOSCODE

		; The following table and function is used by devicerelocate
		;  even if DOSCODE is merged into DOSENTRY (!_RELOCATEDOSCODE).

	write_entrypoint_list ENTRYPOINT_LIST


%if _RELOCATEDOSCODE && _DOSCODEHMA
	_fill 0C0h + DOSCODE_HMA_start_at, 90h, doscode_start
hma_call5:
	jmp 70h:call5
%endif


		; INP:	byte [cs:ip] = displacement in doscode_entrypoint_list
		;	word [DOSCODE:(displaced)]
		;	 = relocated entry address
		;	word [DOSCODE:(displaced + 2)]
		;	 = optional pointer to device function table
		;	byte [DOSCODE:(displaced + 4)]
		;	 = optional device unit
		;	ss:(sp + 8) -> arbitrary stack of entrypoint
		;	word [ss:sp + 6] = entrypoint's ip,
		;	word [ss:sp + 4] = entrypoint's cs,
		;	word [ss:sp + 2] = original flags value,
		;	word [ss:sp + 0] = original ax value,
relocated_relocatedentry:
	lframe 0
	lpar word, entrypoint_ip
	lpar word, entrypoint_cs
	lpar word, orig_fl
	lpar word, orig_ax
	lenter
	push ds
	push si

	cld
	xor ax, ax
	mov ds, word [bp + ?entrypoint_cs]
	mov si, word [bp + ?entrypoint_ip]
	lodsb
	cmp al, 0CCh	; overwritten by debugger ?
	jne @F		; no -->
	int3		; break to make it remove the breakpoint
	dec si
	lodsb		; reload the byte
	cmp al, 0CCh
	jne @F

.error:
	mov si, doscode_msg.error_common
	call doscode_disp_msg_cs
	mov si, doscode_msg.error_table
	call doscode_disp_msg_cs
	mov si, doscode_msg.error_after
	call doscode_disp_msg_cs
.loop:
	int3
	xor ax, ax
	int 16h
	jmp .loop

@@:
	push cs
	pop ds

	add ax, doscode_entrypoint_list
	cmp ax, doscode_entrypoint_list.end
	jae .error
	xchg ax, si

	lodsw		; get relocated entry address
	cmp ax, DEVStrategy		; set ZF iff DEVStrategy

	xchg word [bp + ?orig_fl], ax	; store address in ?orig_fl, ax = fl
	xchg word [bp + ?orig_ax], ax	; store fl in ?orig_ax, ax = orig ax

	 push ax
	lodsw
	mov word [bp + ?entrypoint_ip], ax

	mov ah, 0
	lodsb
	mov word [bp + ?entrypoint_cs], ax
	 pop ax

	pop si
	pop ds
	lleave
	je .is_dev_strat		; dispatch based on ZF
	popf				; (fl = ?orig_ax = fl)
	retn 4				; (ip = ?orig_fl = address)

.is_dev_strat:
	popf
	retn


doscode_disp_msg_cs:
	push ax
@@:
	cs lodsb
	test al, al
	jz @F
	call doscode_disp_al
	jmp short @B


doscode_disp_al:
	push ax
	push bx
	push bp
	mov ah, 0Eh
	mov bx, 7
	int 10h
	pop bp
	pop bx
@@:
	pop ax
	retn


doscode_disp_dxax_hex:	; dx:ax
		xchg ax, dx
		call doscode_disp_ax_hex
		xchg ax, dx
doscode_disp_ax_hex:	; ax
		xchg al, ah
		call doscode_disp_al_hex
				; display former ah
		xchg al, ah	;  and fall trough for al
doscode_disp_al_hex:	; al
		push cx
		mov cl, 4
		ror al, cl
		call doscode_disp_al_lownibble_hex
				; display former high-nibble
		rol al, cl
		pop cx
				;  and fall trough for low-nibble
doscode_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 doscode_disp_al
		pop ax
		retn


doscode_msg:
.error_common:	asciz "DOSCODE error: "
.error_table:	asciz "Invalid doscode_entrypoint_list reference."
.error_after:	asciz 13,10,"System halted. "


	%include "biocode.asm"

%assign DOSCODE_INSURE_COUNT 0

		; The init2_to_doscode dispatcher in INIT2 (DOSINI)
		;  detects mistaken debugger breakpoints in its
		;  in-code-parameter by checking for a low-byte
		;  value equal to 0CCh (one-byte int3 opcode).
		; Therefore, we insure that all functions called
		;  via that dispatcher do not happen to be located
		;  on an offset that gives 0CCh in the low byte.
	%imacro doscode_insure_low_byte_not_0CCh 0.nolist
 %if (($ - dosentry_start + 0) & 0FFh) == 0CCh
	nop
  %assign DOSCODE_INSURE_COUNT 1 + DOSCODE_INSURE_COUNT
 %endif
	%endmacro

	%include "doscode.asm"
%endif

%if DOSCODE_INSURE_COUNT
 %warning doscode_insure_low_byte_not_0CCh needed DOSCODE_INSURE_COUNT times
%endif

	align 16
%if _RELOCATEDOSCODE
doscode_end:
%else
dosentry_end:
%endif
	istruc MCB
at mcbSignature,	db "M"
at mcbOwner,		dw 8
at mcbSize,		dw (dosdata_end - dosdata_start) >> 4
at smcbName,		dw "S"
at smcbType,		db S_DOSDATA
	iend

%if _RELOCATEDOSCODE
	; section DOSDATA align=16 follows=DOSCODE vstart=0
%else
	; section DOSDATA align=16 follows=DOSENTRY vstart=0
%endif
dosdata_start:
%if _COMBINED_DOSDATA_DOSCODE
	%include "RxDOS.ASM"
%else
	%include "dosdata.asm"
%endif

	align 16
dosdata_end:
	istruc MCB
at mcbSignature,	db "M"
at mcbOwner,		dw 8
at mcbSize,		dw (init1_end - init1_start) >> 4
at smcbName,		dw "S"
at smcbType,		db S_INIT
	iend

	; section INIT1 align=16 follows=DOSDATA vstart=0

%assign INIT1_INSURE_COUNT 0

		; The init2_to_init1 dispatcher in INIT2 (DOSINI)
		;  detects mistaken debugger breakpoints in its
		;  in-code-parameter by checking for a low-byte
		;  value equal to 0CCh (one-byte int3 opcode).
		; Therefore, we insure that all functions called
		;  via that dispatcher do not happen to be located
		;  on an offset that gives 0CCh in the low byte.
	%imacro init1_insure_low_byte_not_0CCh 0.nolist
 %if (($ - init1_start) & 0FFh) == 0CCh
	nop
  %assign INIT1_INSURE_COUNT 1 + INIT1_INSURE_COUNT
 %endif
	%endmacro


init1_start:
		; S0 +28
	pushf	; +26
	jmp sig_valid

msg:
%if _TEST_PAYLOAD
.test:	asciz "Test payload loaded. Press any key to continue booting.",13,10
%endif

	align 4
.foundname:
	times 8+1+3+1 db 0
		; buffer for base name (8) + dot (1) + ext (3) + NUL (1)
	align 2
.foundname_default:
	asciz _BIN_FILE_DEFAULT
	_fill (8 + 1 + 3 + 1), 0, .foundname_default
	align 2
.foundname_none:
	asciz "(None)"
.foundname_none_size: equ $ - .foundname_none
	align 2
.names:
%if _TEST_PAYLOAD
	dw .name_first, 0
	dw .name_second, 0
	dw .name_third, 0
	dw .name_fourth, 0
%else
	dw .foundname_none, 0
%endif
	dw 0
%if _TEST_PAYLOAD
.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
%endif

.bootfail:	asciz "init: Boot failure: "
.bootfail_read:	db "Reading sector failed (error "
.bootfail_read_errorcode:	asciz "__h).",13,10
.bootfail_sig:	asciz "Boot sector signature missing (is not AA55h).",13,10
.bootfail_sig_parttable:	ascii "Partition table signature missing"
				asciz " (is not AA55h).",13,10
.bootfail_secsizediffer:
		asciz "BPB BpS differs from actual sector size.",13,10
.boot_too_many_partitions_error:asciz "Too many partitions (or a loop).",13,10
.boot_partition_cycle_error:	asciz "Partition table cycle detected.",13,10
.boot_partition_not_found:	asciz "Partition not found.",13,10
.boot_access_error:	asciz "Read error.", 13,10
.boot_sector_too_large:	asciz "Sector size too small (< 32 bytes).", 13,10
.boot_sector_too_small:	asciz "Sector size too large (> 8192 bytes).", 13,10
.boot_sector_not_power:	asciz "Sector size not a power of two.", 13,10
.boot_invalid_sectors:	asciz "Invalid geometry sectors.", 13,10
.boot_invalid_heads:	asciz "Invalid geometry heads.", 13,10
.boot_badclusters:	asciz "Bad amount of clusters.",13,10
.boot_badchain:		asciz "Bad cluster chain.",13,10
.boot_internal_error:	asciz "! Internal error !",13,10

.login_partition_error_1:	asciz "init: Error during unit "
.login_partition_error_2:	asciz "h partitions login.",13,10

.excess_partitions_1:		asciz "init: Found "
.excess_partitions_2:		asciz " excess partitions, ignored.",13,10

.list_partition_1:		asciz ": "
.list_partition_4:		asciz " s="
.list_partition_5:		asciz "h ("
.list_partition_6:		asciz ") l="
.list_partition_7:		asciz "h ("
.list_partition_8:		asciz ") "
.list_partition_9:		asciz " t="
.list_partition_10:		asciz "h ("
.list_partition_11:		asciz ").",13,10

.prefixes:			asciz " kMGT"

.bootname_1:	asciz "init: Boot drive and file name: ",'"'
.bootname_2:	asciz '"',13,10


%define pt_messages ..@pt_notype,""

	%imacro pt_type 2.nolist
		dw %2, %%label
%defstr %%str	%1
%xdefine pt_messages pt_messages,%%label,%%str
	%endmacro

	%imacro pt_msg 2-*.nolist
%if %0 & 1
 %error Expected even number of parameters
%endif
%rotate 2
%rep (%0 - 2) / 2
%1:	asciz %2
%rotate 2
%endrep
	%endmacro

	align 4
pt_types:
 pt_type	Empty,		0
 pt_type	FAT12,		1
 pt_type	FAT16 16BIT CHS,4
 pt_type	ExtendedCHS,	5
 pt_type	FAT16 CHS,	6
 pt_type	FAT32 CHS,	0Bh
 pt_type	FAT32,		0Ch
 pt_type	FAT16,		0Eh
 pt_type	Extended,	0Fh
 pt_type	Linux,		83h
 pt_type	ExtendedLinux,	85h
	dw -1, -1

pt_msg pt_messages

ptmsg_unknown:	asciz "Unknown"

%undef pt_messages
%unimacro pt_type 2.nolist
%unimacro pt_msg 2-*.nolist

%endif

init1_msg:
.alloc_error:		asciz "init: Error "
.alloc_error_q:		asciz "h while allocating memory: "
.linebreak:		asciz 13,10
; .internal_error_upb:	asciz "init: Internal error during UPB allocation.",13,10
.reboot_prompt:		asciz "Halted. Press Ctrl+Alt+Del to reboot.",13,10
; .loading_os:		asciz "Starting ",RXDOSLONG_S,"...",13,10
.rploader:		db "RPLOADER"
	endarea .rploader
			db 0
.prefixes:			asciz " kMGT"

	align 2
init1_msg_errors:
.:
	dw .no_error
	dw .invalid_function
	dw .file_not_found
	dw .path_not_found
	dw .no_handles_available
	dw .access_denied
	dw .invalid_handle
	dw .mcb_destroyed
	dw .insufficient_memory
	dw .mcb_invalid
	dw .environment_invalid
	dw .format_invalid
	dw .access_code_invalid
	dw .data_invalid
	dw .fixup_overflow
	dw .invalid_drive
	dw .attempted_to_remove_current_directory
	dw .not_same_device
	dw .no_more_files
.count equ ($ - .) / 2
.unknown_error:		db "Unknown error",0
.no_error:		db "No error",0
.invalid_function:	db "Invalid function",0
.file_not_found:	db "File not found",0
.path_not_found:	db "Path not found",0
.no_handles_available:	db "No handles available",0
.access_denied:		db "Access denied",0
.invalid_handle:	db "Invalid handle",0
.mcb_destroyed:		db "MCB destroyed",0
.insufficient_memory:	db "Insufficient memory",0
.mcb_invalid:		db "MCB invalid",0
.environment_invalid:	db "Environment invalid",0
.format_invalid:	db "Format invalid",0
.access_code_invalid:	db "Access code invalid",0
.data_invalid:		db "Data invalid",0
.fixup_overflow:	db "Fixup overflow",0
.invalid_drive:		db "Invalid drive",0
.attempted_to_remove_current_directory:		db "Attempted to remove current directory",0
.not_same_device:	db "Not same device",0
.no_more_files:		db "No more files",0


%if 0

	align 4
init1_rploader_callback:	dw entry_iret, 70h
%define load_sectors		(bp + bsBPB + bpbCHSSectors)
%define load_heads		(bp + bsBPB + bpbCHSHeads)
%define load_sectorsize		(bp + ldBytesPerSector)
%define load_sectorsizepara	(bp + ldParaPerSector)
%define load_sectorseg		(bp + ldSectorSeg)
load_partition_cycle:		dw 0
%define load_lba		(bp + ldHasLBA)
%define load_unit		(bp + bsBPB + ebpbNew + bpbnBootUnit)
load_current_partition:		db 0

%endif


init1_disp_msg_asciz:
disp_msg_asciz:
 assume ds:nothing, es:nothing, ss:nothing
	push ds
	push si
	push ax
	 push cs
	 pop ds
 assume ds:SYSINITGROUP
	mov si, dx
	call disp_msg
	pop ax
	pop si
	pop ds
 assume ds:nothing
	retn

init1_disp_msg:
disp_msg:
 assume ds:nothing, es:nothing, ss:nothing
@@:
	lodsb
	test al, al
	jz @F
	call disp_al
	jmp short @B

init1_disp_al:
disp_al:
 assume ds:nothing, es:nothing, ss:nothing
	push ax
	push bx
	push bp
	mov ah, 0Eh
	mov bx, 7
	int 10h
	pop bp
	pop bx
	pop ax
@@:
	retn


%if 0

sig_valid:
		; S0 +28
;	pushf	; +26
	sti
	cld
	push ax	; +24
	push bx	; +22
	push cx	; +20
	push dx	; +18
	push si	; +16
	push di	; +14
	push bp	; +12
	mov ax, sp
	add ax, 16
	push ax	; +10 SP
	xor ax, ax
	push ax	; +8 IP
	push cs	; +6
	push ds	; +4
	push es	; +2
	push ss	; +0
%if _TEST_PAYLOAD
	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
%endif


init_mcb_chain:
	push cs
	pop ds
	push cs
	pop es
	mov ax, 60h			; where
	xor cx, cx			; owner
	mov bx, cs
	sub bx, ax			; size
	dec bx				; less 1
	sub bx, ( 16 \
		+ (dosentry_end - dosentry_start) \
		+ DOSCODE_MCB_SIZE \
		+ DOSCODE_SIZE \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16 \
		) >> 4
					; point at DOSENTRY MCB
	mov dx, "M" | (0 << 8)
	call init_an_mcb

	mov bx, mcbtable
	mov ax, cs
	add ax, ( (init1_end - init1_start) \
		+ 16 \
		+ (init2_end - init2_start) \
		+ 16 \
		+ (init3_end - init3_start) \
		+ 16 \
		) >> 4
	mov word [bx], ax
	mov word [bx + 2], -1

	add bx, 4
	mov ax, ss
	mov word [bx], ax
	mov word [bx + 2], S_INITSTACKBPB

	add bx, 4
	mov ax, [bp + lsvFATSeg]
	dec ax
	mov word [bx], ax
	mov word [bx + 2], S_INITFATSEG

	add bx, 4
	mov ax, [bp + ldSectorSeg]
	dec ax
	mov word [bx], ax
	mov word [bx + 2], S_INITSECTORSEG

	add bx, 4
	mov dx, [bp + ldMemoryTop]	; => in front of RPL, or end of memory
	mov word [bx], dx
	mov word [bx + 2], S_EXCLDUMA

	int 12h
	mov cl, 6
	shl ax, cl			; => after RPL, or same as ldMemoryTop
	cmp dx, ax
	je @F
	mov word [bx + 2], 'R' << 8

	add bx, 4
	mov word [bx], ax
	mov word [bx + 2], S_EXCLDUMA
@@:
	

.next_bubble:
	mov bx, mcbtable
	xor cx, cx
.next:
	mov ax, word [bx]
	cmp ax, word [bx + 4]
	jbe @F
	push word [bx]
	push word [bx + 2]
	push word [bx + 4]
	push word [bx + 6]
	pop word [bx + 2]
	pop word [bx]
	pop word [bx + 6]
	pop word [bx + 4]
	inc cx
@@:
	add bx, 4
	cmp word [bx], -1
	jne .next
	test cx, cx
	jnz .next_bubble

init_end_mcb_chain:
	mov si, mcbtable
.next:
	mov ax, word [si]
	dec ax			; = where to place MCB
	mov dx, word [si + 2]	; dl = S MCB type, -1 if none,
				;  0 (S_OTHER) if RPL (dh = 'R' then)
	mov cx, 8		; owner = 8
	cmp dl, -1		; none type ?
	jne @F			; no -->
	xor cx, cx		; yes, owner = 0
@@:
	push dx
	xchg dl, dh		; dh = S MCB type
	mov dl, "M"		; dl = MCB signature
	mov di, word [si + 4]
	mov bx, di
	sub bx, ax
	dec bx
	dec bx			; size = to next
	cmp di, -1		; any next ?
	jne @F			; yes -->
	mov dl, "Z"		; else, mcbSignature = "Z"
	xor bx, bx		; size = zero
@@:
	call init_an_mcb
	pop dx
	cmp dx, 'R' << 8
	jne @F
	push ds
	mov ds, ax
	mov word [mcbName], "RP"
	mov word [mcbName + 2], "L"
	pop ds
@@:
	add si, 4
	cmp word [si], -1
	jne .next

	mov bx, cs
	sub bx, ((dosdata_end - dosdata_start) + 16) >> 4
	mov ds, bx
	mov word [first_umcb], ax


init_dosentry:
	mov ax, cs
	sub ax, ( 16 \
		+ (dosentry_end - dosentry_start) \
		+ DOSCODE_MCB_SIZE \
		+ DOSCODE_SIZE \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16) >> 4
	mov dx, 6Fh
	mov cx, (16 + (dosentry_end - dosentry_start)) >> 4
	call init1_movp			; move DOSENTRY to 70h:0

	add cx, dx			; => behind DOSENTRY
	mov ax, cx
	mov bx, cs
	sub bx, ax			; size
	dec bx				; less 1
	sub bx, ( DOSCODE_MCB_SIZE \
		+ DOSCODE_SIZE \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16 \
		) >> 4			; ax + bx + 1 => DOSCODE MCB
	xor cx, cx			; owner
	mov dx, "M" | (0 << 8)
	call init_an_mcb

	mov ax, 60h			; where
	xor cx, cx			; owner
	mov bx, 6Fh
	sub bx, ax			; size
	dec bx				; less 1
					; ax + bx + 1 => point at DOSENTRY MCB
	mov dx, "M" | (0 << 8)
	call init_an_mcb


listnames:
	push cs
	pop es
	mov bx, msg.names

%assign _NEW_BPB_NAME_SCAN_SIZE 1
%if _NEW_BPB_NAME_SCAN_SIZE
	mov ax, ss
	dec ax
	mov ds, ax
	mov cx, word [mcbSize]	; get size of stack/BPB
	shl cx, 1
	jc @F
	shl cx, 1
	jc @F
	shl cx, 1
	jc @F
	shl cx, 1		; <<= 4, *= 16, to byte offset
	jnc @FF
@@:
	xor cx, cx		; 0 = full 64 KiB
@@:
%endif

	push ss
	pop ds
	lea si, [bp + bsBPB + ebpbNew + BPBN_size]
%if _NEW_BPB_NAME_SCAN_SIZE
	sub cx, si		; byte offset end - byte offset start = length
	dec cx
	dec cx			; don't scan into AA55h sig
%else
	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
@@:
%endif
.nextname:
	call findname
	 lahf
%if _TEST_PAYLOAD
	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
%endif
	 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


set_bootname.filename:
	mov si, word [cs:msg.names + 2]
	test si, si
	jnz @F

	mov si, msg.foundname_default
	jmp @FF

@@:
	call convert_name_to_asciz

	mov si, msg.foundname
@@:
	push cs
	pop ds				; ds:si -> name

	mov cx, (8 + 1 + 3) >> 1
		; note: last byte is already initialised to zero

	mov bx, cs
	sub bx, ((dosdata_end - dosdata_start) + 16) >> 4
	mov es, bx
	mov di, bootname.filename	; es:di -> buffer in DOSDATA
	rep movsw


store_boot_partition:
	push cs
	pop ds
	mov al, [bp + bsBPB + ebpbNew + bpbnBootUnit]
	mov byte [boot_unit], al
	mov ax, [bp + bsBPB + bpbHiddenSectors]
	mov dx, [bp + bsBPB + bpbHiddenSectors + 2]
	mov word [boot_partition], ax
	mov word [boot_partition + 2], dx

scan_for_partitions:
	lea sp, [bp + LOADDATA2]	; allow space for LOADDATA2
	push cs
	pop ds
	and word [bp + bsBPB + bpbHiddenSectors], 0
	and word [bp + bsBPB + bpbHiddenSectors + 2], 0

scan_diskettes:
	mov dl, 80h
	mov ah, 08
	stc
	int 13h			; get number of fixed disks
	mov bx, 0
	jc @F			; fail ? then set bx = 0 and try diskettes
	mov bl, dl
@@:
	mov dl, 0
	mov ah, 08h
	stc
	int 13h			; get number of diskettes
	mov ch, 0
	mov cl, dl		; cx = result
	jc @F			; fail -->
	cmp bx, cx		; differs from number of fixed disks ?
	jne @FF			; yes, use -->
@@:
				; else,
	int 11h			; query int 11 "get equipment list"
	xor cx, cx		; initialise to zero
	test ax, 1		; any specified ?
	jz @F			; no -->

	mov cl, al
	rol cl, 1
	rol cl, 1		; get bits 6 & 7
	and cl, 3		; separate
	inc cx			; +1
@@:
	xor dl, dl
	call scan_given_partitions

		; manufacture units 0 and 1 if not existant
	mov si, word [fsparm_pointer]
	cmp si, fsparm + FSPARM_size*2	; got at least 2 diskettes ?
	jae @FF				; yes -->
	cmp si, fsparm + FSPARM_size	; got 1 diskette ?
	jae @F				; yes -->
	mov di, si
	push cs
	pop es
	xor ax, ax
	mov cx, FSPARM_size >> 1
	rep stosw			; initialise
	mov byte [si + fspUnit], 0
	mov word [si + fspSectorSize], 512
	mov byte [si + fspType], -1	; initialise to unit 0
	inc byte [fsparm_number]
	add si, FSPARM_size		; -> next
@@:
	mov di, si
	push cs
	pop es
	xor ax, ax
	mov cx, FSPARM_size >> 1
	rep stosw
	mov byte [si + fspUnit], 1
	mov word [si + fspSectorSize], 512
	mov byte [si + fspType], -1
	inc byte [fsparm_number]
	add si, FSPARM_size
	mov word [fsparm_pointer], si
@@:

scan_fixed_disk_partitions:
	mov dl, 80h
	mov ah, 08h
	stc
	int 13h				; get number of fixed disks
	mov ch, 0
	mov cl, dl
	jnc @F				; success -->

	mov ax, 40h
	mov es, ax
	mov cl, byte [es:bdaNumberFixedDisks]	; get from BDA instead
@@:
	mov dl, 80h
	call scan_given_partitions

display_excess_partitions:
	push cs
	pop ds
	mov ax, word [fsparm_excess]
	test ax, ax
	jz @F
	mov dx, msg.excess_partitions_1
	call disp_msg_asciz
	call disp_ax_dec
	mov dx, msg.excess_partitions_2
	call disp_msg_asciz
@@:


display_partitions:
	push cs
	pop ds
	mov byte [boot_drive], -1
	xor cx, cx
	mov cl, byte [fsparm_number]
	mov si, fsparm
	mov bx, 'A'
	test cx, cx
	jz .end
.loop:

	mov al, bl
	call disp_al

	mov dx, msg.list_partition_1
	call disp_msg_asciz
	mov al, [si + fspUnit]

	push ax
	cmp al, byte [boot_unit]
	jne @FF

	cmp byte [boot_drive], -1	; found any on this unit yet ?
	jne @F				; yes -->
	mov byte [boot_drive], bh	; default initialise to first part
@@:

	mov ax, [si + fspStart]		; does it match the partition ?
	mov dx, [si + fspStart + 2]
	cmp word [boot_partition], ax
	jne @F
	cmp word [boot_partition + 2], dx
	jne @F				; no -->

	mov byte [boot_drive], bh	; yes, store this drive
@@:
	pop ax

	mov dl, 'h'
	test al, 80h
	jnz @F
	mov dl, 'f'
@@:
	and al, ~80h
	add al, 'a'
	cmp al, 'z'
	jbe @F

	mov al, 'u'
	call disp_al
	mov al, [si + fspUnit]
	call disp_al_hex
	mov al, '_'
	call disp_al

	jmp @FF

@@:
	push ax
	mov al, dl
	call disp_al
	mov al, 'd'
	call disp_al
	pop ax
	call disp_al

@@:
	xor ax, ax
	mov al, [si + fspPartition]
	test ax, ax
	jz .disp_2_blank

	call disp_ax_dec
	cmp ax, 10
	jb .disp_1_blank
	jmp .disp_0_blank

.disp_2_blank:
	mov al, 32
	call disp_al
.disp_1_blank:
	mov al, 32
	call disp_al
.disp_0_blank:

	mov dx, msg.list_partition_4
	call disp_msg_asciz

	mov ax, [si + fspStart]
	mov dx, [si + fspStart + 2]
	call disp_dxax_hex

	push dx
	mov dx, msg.list_partition_5
	call disp_msg_asciz
	pop dx

	 push cx
	 push bx
	mov cx, [si + fspSectorSize]
	mov bx, 4+4
	call disp_dxax_times_cx_width_bx_size

	mov dx, msg.list_partition_6
	call disp_msg_asciz

	mov ax, [si + fspLength]
	mov dx, [si + fspLength + 2]
	call disp_dxax_hex

	push dx
	mov dx, msg.list_partition_7
	call disp_msg_asciz
	pop dx

	call disp_dxax_times_cx_width_bx_size
	 pop bx
	 pop cx

	mov dx, msg.list_partition_8
	call disp_msg_asciz

	mov al, [si + fspBoot]
	test al, 80h
	mov al, 32
	jz @F
	mov al, 'B'
@@:
	call disp_al

	mov dx, msg.list_partition_9
	call disp_msg_asciz

	xor ax, ax
	mov al, byte [si + fspType]
	call disp_al_hex

	mov dx, msg.list_partition_10
	call disp_msg_asciz

	push si
	mov si, pt_types
.types_loop:
	cmp word [si], -1
	je .types_unknown
	cmp word [si], ax
	je .types_known
	add si, 4
	jmp .types_loop

.types_known:
	mov dx, word [si + 2]
	jmp .types_common

.types_unknown:
	mov dx, ptmsg_unknown
.types_common:
	pop si
	call disp_msg_asciz

	mov dx, msg.list_partition_11
	call disp_msg_asciz

	add si, FSPARM_size
	add bx, 0101h
	loop .loop_jmp
	jmp .end
.loop_jmp:
	jmp .loop
.end:

	mov al, byte [boot_drive]
	cmp al, -1
	jne @F
	mov al, [boot_unit]
	cmp al, 2
	jb @F
	mov al, 2
@@:
	mov byte [boot_drive], al

	mov bx, cs
	sub bx, ((dosdata_end - dosdata_start) + 16) >> 4
	mov es, bx

	mov byte [es:_RxDOS_CurrentDrive], al
	inc ax
	mov byte [es:bootdrive], al

	mov di, bootname.drive
	add al, 'A'-1
	stosb


display_bootname:
	mov dx, msg.bootname_1
	call disp_msg_asciz
	push es
	pop ds
	mov si, bootname
	call disp_msg
	mov dx, msg.bootname_2
	call disp_msg_asciz


setup_rploader_callback:
	xor ax, ax
	mov es, ax
	lds si, [es:2Fh * 4]
	push si
	 push cs
	 pop es
	mov di, init1_msg.rploader
	mov cx, init1_msg.rploader_size
	add si, 3
	repe cmpsb
	pop si
	jne .none
	mov word [cs:init1_rploader_callback], si
	mov word [cs:init1_rploader_callback + 2], ds
.none:


save_interrupts:
	mov dx, 70h
	mov ds, dx
	xor ax, ax
	mov es, ax
	mov si, InterruptRestorationTable
.loop:
	xor ax, ax
	lodsb
	cmp al, -1
	je .end
	xchg ax, bx
	add bx, bx
	add bx, bx
	push word [es:bx + 2]
	push word [es:bx]
	pop word [si]
	pop word [si + 2]
	add si, 4
	jmp .loop
.end:


		; ds = 70h
set_dos_segments:
	mov bx, cs
	sub bx, ((dosdata_end - dosdata_start) + 16) >> 4
	mov word [dosentry_to_dosdata_segment], bx
	mov es, bx
%if !_COMBINED_DOSDATA_DOSCODE
 %if _RELOCATEDOSCODE
	mov cx, cs
	sub cx, ( (doscode_end - doscode_start) \
		+ 16 \
		+ (dosdata_end - dosdata_start) \
		+ 16 \
		+ 40h) >> 4
 %else
	mov cx, 70h
 %endif
%else
	mov cx, bx
%endif
	mov word [es:dosdata_to_doscode_segment], cx
	mov ax, word [bp + ldSectorSeg]
	mov word [es:dosdata_to_diskbuffer_segment], ax
	mov ax, word [bp + ldMemoryTop]
	mov word [es:_RxDOS_MaxMemory], ax
	mov word [es:p_buffer_info_record + 2], bx


		; ds = 70h
		; bx = DOSDATA
set_interrupts:
	xor ax, ax
	mov es, ax
	mov word [es:21h * 4], i21
	mov word [es:21h * 4 + 2], ds

%imacro setint 2.nolist
	mov dx, %1
	mov ax, (25h << 8) | %2
	int 21h
%endmacro

	clc
	int3
	jc .skip_ints_00_06
	setint i00, 00h
	setint entry_iret, 01h
	setint entry_iret, 03h
	setint i06, 06h
.skip_ints_00_06:
	setint entry_iret, 02h
	setint entry_iret, 04h
	setint entry_iret, 05h
	setint entry_iret, 07h
	setint entry_iret, 33h
	setint i19, 19h
	setint i20, 20h
		; i21 set prior by direct IVT modification
	setint entry_iret, 22h
	setint i23, 23h
	setint i24, 24h
	setint i25, 25h
	setint i26, 26h
	setint i27, 27h
	setint i28, 28h
	setint i29, 29h
	setint entry_iret, 2Ah
	setint entry_iret, 2Bh
	setint entry_iret, 2Ch
	setint entry_iret, 2Dh
	setint entry_iret, 2Eh
	setint i2F, 2Fh

	mov byte [es:30h * 4], 0EAh
	mov word [es:30h * 4 + 1], call5
	mov word [es:30h * 4 + 3], ds
	mov byte [es:30h * 4 + 5], 0
	mov word [es:30h * 4 + 6], bx	; DR-DOS compatible DOSDATA reference

	mov ax, 5803h
	xor bx, bx
	int 21h				; disable UMB link
		; As we have an UMCB, this sets the LMCB linking to it to "Z".


		; ds = 70h
initialise_psp:
	push ds
	mov ax, cs
	add ax, ( (init1_end - init1_start) \
		+ 16 \
		+ (init2_end - init2_start) \
		+ 16 \
		) >> 4			; => INIT3 (PSP)
	mov ds, ax
	mov word [pspInt22 + 2], ax
	mov word [pspParent], ax
	mov word [pspStack + 2], ax
	mov word [pspPHTAddress + 2], ax

	mov bx, ax
	mov cl, S_ENVIRONMENT
	mov ax, _INIT_ENV_SIZE
	call init1_alloc_s_mcb_top
	mov word [pspEnvironment], es

	pop ds
	mov es, word [dosentry_to_dosdata_segment]
	mov word [es:_RxDOS_CurrentPSP], bx


call_rploader_phase_1:
	mov dx, 1
	call init1_call_rploader


initialise_upb_chain:
	xor cx, cx
	mov cl, byte [cs:fsparm_number]
	mov ax, DISKBLOCK_size
	mul cx
	test dx, dx
	jz @F

	push cs
	pop ds
	mov si, init1_msg.internal_error_upb
	call init1_disp_msg
	jmp init1_reboot_prompt

@@:
	 push cx
	push cx
	mov cl, S_UPB
	call init1_alloc_s_mcb_top
	pop cx

	mov dx, 70h
	mov ds, dx
	mov ds, word [dosentry_to_dosdata_segment]
	mov si, ptrStartBlockedDeviceTable - _dskNextPointer	; -> -> UPB
	mov bx, fsparm
	xor ax, ax

	mov word [si + _dskNextPointer], -1
	mov word [si + _dskNextPointer + 2], -1
	test cx, cx
	jz .end

		; es:di -> next UPB to initialise
		; ds:si -> previous UPB (_dskNextPointer uninitialised)
.next:
	mov al, [cs:bx + fspUnit]
	mov byte [es:di + _dskPhysDriveNumber], al
	mov byte [es:di + _dskDOSLogicalDiskUnit], ah

	push ax

	mov al, byte [cs:bx + fspType]
	mov byte [es:di + _dskFATSystemType], al

	push word [cs:bx + fspStart + 2]
	push word [cs:bx + fspStart]
	pop word [es:di + _dskBPB + bpbHiddenSectors]
	pop word [es:di + _dskBPB + bpbHiddenSectors + 2]
	and word [es:di + _dskBPB + bpbTotalSectors], 0
	push word [cs:bx + fspLength + 2]
	push word [cs:bx + fspLength]
	pop word [es:di + _dskBPB + bpbTotalSectorsLarge]
	pop word [es:di + _dskBPB + bpbTotalSectorsLarge + 2]
	push word [cs:bx + fspSectorSize]
	pop word  [es:di + _dskBPB + bpbBytesPerSector]

	push word [cs:bx + fspStart + 2]
	push word [cs:bx + fspStart]
	pop word [es:di + _dskDefaultBPB + bpbHiddenSectors]
	pop word [es:di + _dskDefaultBPB + bpbHiddenSectors + 2]
	and word [es:di + _dskDefaultBPB + bpbTotalSectors], 0
	push word [cs:bx + fspLength + 2]
	push word [cs:bx + fspLength]
	pop word [es:di + _dskDefaultBPB + bpbTotalSectorsLarge]
	pop word [es:di + _dskDefaultBPB + bpbTotalSectorsLarge + 2]
	push word [cs:bx + fspSectorSize]
	pop word  [es:di + _dskDefaultBPB + bpbBytesPerSector]

	push word [cs:bx + fspStart + 2]
	push word [cs:bx + fspStart]
	pop word [es:di + _dskExtHiddenSectors]
	pop word [es:di + _dskExtHiddenSectors + 2]

	mov word [es:di + _dskPartitionType], 1
	mov byte [es:di + _dskDeviceType], -1

	cmp byte [es:di + _dskPhysDriveNumber], 80h
	jb @F
	or word [es:di + _dskStatusFlag], IsNonRemovable
@@:

	push dx
	push cx
	push bx
	mov ah, 41h
	mov dl, [es:di + _dskPhysDriveNumber]
	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:
	pop bx
	pop cx
	pop dx
	mov byte [es:di + _dskExtReadWrite], al

	pop ax
	mov word [es:di + _dskNextPointer], -1
	mov word [es:di + _dskNextPointer + 2], -1
	mov word [si + _dskNextPointer], di
	mov word [si + _dskNextPointer + 2], es

	add bx, FSPARM_size
	inc ah
	add di, DISKBLOCK_size
	lds si, [si + _dskNextPointer]
	loop .loop_jmp_next
	jmp .end
.loop_jmp_next:
	jmp .next
.end:

	mov ds, dx
	 pop cx
	mov byte [block + devUnits], cl


relocate_int1E_table:
	xor ax, ax
	mov es, ax
	lds si, [es:1Eh * 4]
	mov cx, 10h
	mov di, 522h
	push di
	rep movsb
	mov word [es:1Eh * 4 + 2], es
	pop word [es:1Eh * 4]


finish:
	push cs
	pop ds
%if _TEST_PAYLOAD
	mov si, msg.test
	call disp_msg
	int3
	xor ax, ax
	int 16h
%else
	int3
%endif

	mov al, 20h
	out 20h, al					; reset interrupts

	mov ax, 0500h
	int 10h						; set page zero

	mov si, init1_msg.loading_os
	call disp_msg

	mov bx, cs
	add bx, ( (init1_end - init1_start) \
		+ 16 \
		) >> 4					; => INIT2
	mov ax, init2_start
	push bx
	push ax

	mov es, bx
	mov word [es:init2_to_init1_segment], cs

	xor cx, cx
	mov cl, byte [cs:fsparm_number]


		; INP:	cs = INIT2
		;	word [DOSDATA:dosdata_to_doscode_segment] = DOSCODE segment
		;	word [DOSENTRY:dosentry_to_dosdata_segment] = DOSDATA segment
		;	70h = DOSENTRY segment
		;	ip = 0
		;	cx = number of default block device's units (<= 32)
		;	word [INIT2:init2_to_init1_segment] = INIT1
init1_retf:
	retf


		; INP:	dx = phase code
		; OUT:	-
		; CHG:	-
		;	(EDR-DOS pushes es,ax,bx,cx,dx and notes that
		;	 this function doesn't modify any registers.)
%if 0

The phase codes are:

1 = early before DOS loading, BIO has been relocated to LMA top
2 = before CONFIG.SYS processing
3 = after CONFIG.SYS processing (before shell is loaded)

These are described in RBIL 61, 2F4A06, in the Notes. However, code 1's
 description there is misguided. The relocation of "BIOS init code and data"
 doesn't mean into the HMA; rather, it's to the top of the LMA.
In EDR-DOS/dr70108/DRBIO/biosinit.a86:273, this is noted as
 "BIOSINIT CODE and DATA have now been relocated to high memory",
 but this means the top of the LMA. The HMA is only available later,
 during CONFIG.SYS processing, so it can't be used at this point.

%endif

	init1_insure_low_byte_not_0CCh
init1_call_rploader:
	push es
	push ax
	push bx
	push cx
	push dx

	mov ax, 12FFh
	mov bx, 5
	xor cx, cx
	mov dx, 1

		; The callback variable is initialised to the address of an
		;  iret in the DOSENTRY segment if RPLOADER wasn't detected.
	pushf
	cli
	call far [cs:init1_rploader_callback]

	pop dx
	pop cx
	pop bx
	pop ax
	pop es
	retn


		; INP:	ax = space in bytes to allocate at top of LMA
		;	cl = allocation type
		; OUT:	es:di-> allocated space, initialised to all zeros
		;	(di = 0)
		;	ax = cx = space in bytes allocated,
		;	 ! 0 if size is 65536
		;	does not return after allocation failure
		; CHG:	-
	init1_insure_low_byte_not_0CCh
init1_alloc_s_mcb_top:
	push si
	push bx
	xchg si, ax

	mov ax, 5802h
	int 21h
	mov ah, 0
	push ax
	mov ax, 5800h
	int 21h
	push ax

	mov ax, 5803h
	xor bx, bx
	int 21h			; link out UMB chain
	mov ax, 5801h
	mov bx, 2
	int 21h			; alloc strategy: last fit, LMA only

	xchg ax, si		; reset ax to allocation size, cl = type
	call init1_alloc_s_mcb	; allocate

	pop bx
	mov ax, 5801h
	int 21h			; alloc strategy: restore
	pop bx
	mov ax, 5803h
	int 21h			; restore UMB chain linkage

	mov ax, cx		; ax = allocated size (! 0 if size is 65536)
	pop bx
	pop si
	retn
%endif


		; INP:	ax = space in bytes to allocate
		;	cl = allocation type
		; OUT:	es:di-> allocated space, initialised to all zeros
		;	(di = 0)
		;	ax = cx = space in bytes allocated,
		;	 ! 0 if size is 65536
		;	does not return after allocation failure
		; CHG:	-
;	init1_insure_low_byte_not_0CCh
; init1_alloc_s_mcb:
init2_alloc_s_mcb:
 assume ds:nothing, es:nothing, ss:nothing
	push bx
	add ax, 15
	jnc @F
	mov ax, 1_0000h >> 1
	db __TEST_IMM16
@@:
	shr ax, 1
	shr ax, 1
	shr ax, 1
	shr ax, 1			; = paragraphs

	mov bx, ax
	mov ah, 48h
	stc
	int 21h
	jc .alloc_fail

	dec ax
	mov es, ax
 assume es:MCB
	mov word [es:mcbOwner], 8	; system-owned MCB
	mov word [es:smcbName], "S"	; S MCB
	mov ch, 0
	mov word [es:smcbType], cx	; type = cl, reserved = 0
	mov cl, 0
	mov word [es:smcbLink], cx	; link = 0
	mov word [es:smcbLink + 2], cx	; reserved = 0

	inc ax
	mov es, ax
 assume es:nothing
	xor di, di			; -> allocated space
	mov cx, bx
	shl cx, 1
	shl cx, 1
	shl cx, 1			; = number of words
	xor ax, ax
	push di
	push cx
	rep stosw			; initialise allocated memory to zeros
	pop cx
	pop di
	shl cx, 1
	mov ax, cx
	pop bx
	retn


.alloc_fail:
	push cs
	pop ds
 assume ds:SYSINITGROUP
	 push ax
	mov si, init1_msg.alloc_error	; access with ds
	call init1_disp_msg
	 pop ax
	call init1_disp_ax_hex
	 push ax
	mov si, init1_msg.alloc_error_q	; access with ds
	call init1_disp_msg
	 pop ax

	cmp ax, init1_msg_errors.count
	jb .valid
	mov si, init1_msg_errors.unknown_error	; access with ds
	jmp short .disp_error
.valid:
	mov bx, ax
	add bx, bx
	mov si, [ init1_msg_errors + bx ]
.disp_error:
	call init1_disp_msg
	mov si, init1_msg.linebreak	; access with ds
	call init1_disp_msg

init1_reboot_prompt:
	push cs
	pop ds
 assume ds:SYSINITGROUP
	mov si, init1_msg.reboot_prompt	; access with ds
	call init1_disp_msg
.loop:
	int3
	xor ax, ax
	int 16h
	jmp .loop


%if 0
		; Move paragraphs
		;
		; INP:	ax:0-> source
		;	dx:0-> destination
		;	cx = number of paragraphs
		; CHG:	-
		; Note:	Doesn't work correctly on HMA; doesn't always wrap to LMA either.
		;	Do not provide a wrapped/HMA source or destination!
init1_movp:
	push cx
	push ds
	push si
	push es
	push di

	cmp ax, dx		; source above destination ?
	ja .up			; yes, move up (forwards) -->
	je .return		; same, no need to move -->
	push ax
	add ax, cx		; (expected not to carry)
	cmp ax, dx		; end of source is above destination ?
	pop ax
	ja .down		; yes, move from top down -->
	; Here, the end of source is below-or-equal the destination,
	;  so they do not overlap. In this case we prefer moving up.

.up:
	push ax
	push dx
.uploop:
	mov ds, ax
	mov es, dx
	xor di, di
	xor si, si		; -> start of segment
	sub cx, 1000h		; 64 KiB left ?
	jbe .uplast		; no -->
	push cx
	mov cx, 10000h /2
	rep movsw		; move 64 KiB
	pop cx
	add ax, 1000h
	add dx, 1000h		; -> next segment
	jmp short .uploop	; proceed for more -->
.uplast:
	add cx, 1000h		; restore counter
	shl cx, 1
	shl cx, 1
	shl cx, 1		; *8, paragraphs to words
	rep movsw		; move last part
	pop dx
	pop ax
	jmp short .return

.down:
	std			; _AMD_ERRATUM_109_WORKAROUND as below
.dnloop:
	sub cx, 1000h		; 64 KiB left ?
	jbe .dnlast		; no -->
	push ax
	push dx
	add ax, cx
	add dx, cx
	mov ds, ax		; -> 64 KiB not yet moved
	mov es, dx
	pop dx
	pop ax
	mov di, -2
	mov si, di		; moved from last word down
	push cx
	mov cx, 10000h /2
	rep movsw		; move 64 KiB
	pop cx
	jmp short .dnloop	; proceed for more -->
.dnlast:
	add cx, 1000h		; restore counter
	shl cx, 1
	shl cx, 1
	shl cx, 1		; *8, paragraphs to words
	mov di, cx
	dec di
	shl di, 1		; words to offset, -> last word
	mov si, di
	mov ds, ax
	mov es, dx		; first segment correct


	numdef AMD_ERRATUM_109_WORKAROUND, 1
		; Refer to comment in init0_movp.

%if _AMD_ERRATUM_109_WORKAROUND
	jcxz @FF
	cmp cx, 20
	ja @FF
@@:
	movsw
	loop @B
@@:
%endif
	rep movsw		; move first part
	cld
.return:
	pop di
	pop es
	pop si
	pop ds
	pop cx
	retn

%endif

		; INP:	dx:ax = numerator
		;	cx = multiplier
		;	bx = field width
		; STT:	UP
		; OUT:	displayed
;	init1_insure_low_byte_not_0CCh
disp_dxax_times_cx_width_bx_size:
 global init2_disp_dxax_times_cx_width_bx_size
init2_disp_dxax_times_cx_width_bx_size:
 assume ds:nothing, es:nothing, ss:nothing
	lframe near
	lvar 4 + 4 + 2, buffer
	lvar 6, dividend
	lenter
	 lvar word, width
	 push bx
	push si
	push di
	push ds
	push es
	push cx
	push ax
	push dx

	 push cs
	 pop ds
 assume ds:SYSINITGROUP
	 push ss
	 pop es
 assume es:nothing

	push dx
	mul cx
	xchg ax, di
	xchg dx, si		; si:di = first mul

	pop ax
	mul cx
	add ax, si
	adc dx, 0		; dx:ax = second mul + adj, dx:ax:di = mul

	mov cx, di		; dx:ax:cx = mul

	mov word [bp + ?dividend], cx
	mov word [bp + ?dividend + 2], ax
	mov word [bp + ?dividend + 4], dx

	mov si, init1_msg.prefixes	; access with ds
.loop:
	cmp word [bp + ?dividend + 4], 0
	jnz .divide
	cmp word [bp + ?dividend + 2], 0
	jnz .divide
	cmp word [bp + ?dividend], 2048
	jbe .end
.divide:
	inc si
	mov cx, 1024		; 1000 here if SI units
	xor dx, dx
	mov di, 6
.loop_divide:
	mov ax, [bp + ?dividend - 2 + di]
	div cx
	mov word [bp + ?dividend - 2 + di], ax
	dec di
	dec di
	jnz .loop_divide
				; dx = last remainder
	jmp .loop

.end:
	lea di, [bp + ?buffer + 4 + 4 + 1]
	std			; _AMD_ERRATUM_109_WORKAROUND does not apply
	mov al, "B"
	stosb
	mov al, [si]
	cmp al, 32
	je @F

	and al, ~20h		; uppercase, don't do this if SI units
	push ax
	mov al, "i"
	stosb			; don't store this if SI units
	pop ax
	stosb
@@:
	mov al, 32
	stosb

	mov ax, word [bp + ?dividend]
	mov cx, 10
.loop_write:
	xor dx, dx
	div cx
	xchg ax, dx
				; ax = remainder (next digit)
				; dx = result of div
	add al, '0'
	stosb
	xchg ax, dx		; ax = result of div
	test ax, ax		; any more ?
	jnz .loop_write		; loop -->

	cld

	lea bx, [bp + ?buffer + 4 + 4 + 1]
	sub bx, di

	mov cx, [bp + ?width]
	sub cx, bx
	jbe .none_blank
.loop_blank:
	mov al, 32
	call disp_al
	loop .loop_blank
.none_blank:

	mov cx, bx
.loop_disp:
	inc di
	mov al, [ss:di]
	call disp_al
	loop .loop_disp

	pop dx
	pop ax
	pop cx
	pop es
 assume es:nothing
	pop ds
 assume ds:nothing
	pop di
	pop si
	pop bx
	lleave
	lret


		; INP:	ax = number to display
		; CHG:	-
;	init1_insure_low_byte_not_0CCh
 global init2_disp_ax_dec
init2_disp_ax_dec:
disp_ax_dec:			; ax (no leading zeros)
 assume ds:nothing, es:nothing, ss:nothing
	; In: number in ax
	; Out: displayed
		push bx
		xor bx, bx
.pushax:
		push ax
.pushend:
		or bl, bl
		jz .nobl
		sub bl, 5
		neg bl
.nobl:
		push cx
		mov cx, 10000
		call .divide_out
		mov cx, 1000
		call .divide_out
		mov cx, 100
		call .divide_out
		mov cl, 10
		call .divide_out
							; (Divisor 1 is useless)
		add al, '0'
		call disp_al
		pop cx
		pop ax
		pop bx					; Caller's register
		retn

.divide_out:
	; In: ax = number
	;     cx = divisor
	; Out: ax = remainder of operation
	;      result displayed
		push dx
		xor dx, dx
		div cx				; 0:ax / cx
		push dx				; remainder
		dec bl
		jnz .nobl2
		or bh, 1
.nobl2:
		or bh, al
		jz .leadingzero
		add al, '0'
		call disp_al			; display result
 .leadingzero:
		pop ax				; remainder
		pop dx
		retn


%if 0		; lMS-DOS

		; INP:	es:si -> partition table entry,
		;	 si = partition_table .. (partition_table.end - 16),
		;	 es = cs
		;	ss:bx + di -> above part table metadata,
		;	 dwo [ss:bx + di - 4] = root (outermost extended position)
		;	 dwo [ss:bx + di - 8] = base (current table position)
		;	ss:bp -> BPB area, LOADSTACKVARS, LOADDATA, LOADDATA2
		; CHG:	ax, (cx), dx
login_partition:
	mov al, byte [es:si + piType]
	cmp al, ptFAT12
	je .isfat
	cmp al, ptFAT16_16BIT_CHS
	je .isfat
	cmp al, ptFAT16_CHS
	je .isfat
	cmp al, ptFAT32_CHS
	je .isfat
	cmp al, ptFAT32
	je .isfat
	cmp al, ptFAT16
	je .isfat

	retn

.isfat:
	push ds
	push cx
	push bx
	push si
	push di
	push es
	 push cs
	 pop ds
	cmp byte [fsparm_number], 32
	jne @F

	inc word [fsparm_excess]
.return:
	pop es
	pop di
	pop si
	pop bx
	pop cx
	pop ds
	retn

@@:
	mov dl, al
	inc byte [fsparm_number]
	mov di, [fsparm_pointer]
	add word [fsparm_pointer], FSPARM_size
	 push cs
	 pop es				; -> FSPARM
	xor ax, ax
	mov cx, FSPARM_size >> 1
	push di
	rep stosw			; initialise
	pop di
	mov al, [load_unit]
	mov byte [di + fspUnit], al
	push word [load_sectorsize]
	pop word [di + fspSectorSize]
	mov byte [di + fspType], dl
	mov cx, di
	pop es
	pop di
	pop si
	push si
	push di
	push es

	mov ax, word [ss:bx + di - 8]
	mov dx, word [ss:bx + di - 6]	; root
	add ax, word [es:si + piStart]
	adc dx, word [es:si + piStart + 2]	; add partition offset

	mov di, cx
	mov word [di + fspStart], ax
	mov word [di + fspStart + 2], dx
	push word [es:si + piLength + 2]
	push word [es:si + piLength]
	pop word [di + fspLength]
	pop word [di + fspLength + 2]

	mov al, byte [load_current_partition]
	mov byte [di + fspPartition], al
	mov al, byte [es:si + piBoot]
	mov byte [di + fspBoot], al

	jmp .return


		; INP:	dl = first unit to log in
		;	cx = number of units to log in
scan_given_partitions:
	jcxz .none
.loop:
	push cx
	push dx

	mov word [throw_sp], sp
	mov word [throw_ip], .fail

	mov byte [load_unit], dl
	call query_geometry

	mov cx, login_partition
	call scan_partitions
	push cs
	pop ds
	db __TEST_IMM8		; (NC)
.fail:
	stc
	 push cs
	 pop ds
	pop dx
	pop cx

	jnc @F
	push dx
	xchg ax, dx
	mov dx, msg.login_partition_error_1
	call disp_msg_asciz
	call disp_al_hex
	mov dx, msg.login_partition_error_2
	call disp_msg_asciz
	pop dx
@@:
	inc dx
	loop .loop
.none:
	retn
%endif


	global hexword, hexbyte, hexnyb
hexword:
	xchg al, ah
	call hexbyte
	xchg al, ah

hexbyte:
	push cx
	mov cl, 4
	rol al, cl
	call hexnyb
	rol al, cl
	pop cx

hexnyb:
	push ax
	and al, 0Fh
	add al, 90h
	daa
	adc al, 40h
	daa			; these four instructions change to ASCII hex
	stosb
	pop ax
	retn


%if 0	; lMS-DOS
cmd3:
	push cs
	pop ds
	push cs
	pop es
	mov sp, [throw_sp]
	jmp near [throw_ip]


bootcmd:
.fail_read:
	 push cs
	 pop es
	mov di, msg.bootfail_read_errorcode
	mov al, ah
	call hexbyte
	mov dx, msg.bootfail_read

.fail:
	push dx
	mov dx, msg.bootfail
	call disp_msg_asciz
	pop dx
	call disp_msg_asciz
	jmp cmd3

%define _SCANPTAB_PREFIX
%define _SCANPTAB_DEBUG4_PREFIX init1_
%include "scanptab.asm"


query_geometry:
	mov dl, [load_unit]
;	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
	mov [load_heads], cx
	mov [load_sectors], cx
	stc			; initialise to CY
	call .int13_retry	; query drive geometry
	jc .try_bootsector	; apparently failed -->
	mov dl, dh
	mov dh, 0		; dx = maximum head number
	inc dx			; dx = number of heads (H is 0-based)
	mov ax, cx		; ax & 3Fh = maximum sector number
	and ax, 3Fh		; get sectors (number of sectors, S is 1-based)
	jnz .got_sectors_heads	; valid (S is 1-based), use these -->
				; zero = invalid
.try_bootsector:
	mov es, word [bp + ldSectorSeg]
	xor bx, bx
	mov ax, 0201h			; read sector, 1 sector
	mov cx, 1			; 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

		; note: the smallest supported sector size, 32 bytes,
		;  does contain these entries (offset 18h and 1Ah in sector)
		;  within the first BPB sector.
	mov ax, word [es:bx + bsBPB + bpbCHSSectors]
	mov dx, word [es:bx + bsBPB + bpbCHSHeads]

.got_sectors_heads:
	mov word [load_sectors], ax
	mov word [load_heads], dx

	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 bx, 10h
	mov di, bx
	mov cx, (8192 + 2) >> 1
	mov ax, word [bp + ldSectorSeg]
	dec ax
	mov es, ax			; es:bx -> buffer, es:di = same
	xor ax, ax
	rep stosw			; fill buffer, di -> behind (0:7C00h+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
	scasw				; -> 0:7C00h+8192 (at last word to sca)
	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 -> 0:7C00h+8192 (last word to sca)
	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

	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

	mov byte [load_lba], 0
	mov ah, 41h
	mov dl, [load_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

	inc byte [load_lba]
.no_lba:
	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:
	mov dx, msg.boot_access_error
	jmp .error_common_j
.sector_too_large:
	mov dx, msg.boot_sector_too_large
	jmp .error_common_j
.sector_too_small:
	mov dx, msg.boot_sector_too_small
	jmp .error_common_j
.sector_not_power:
	mov dx, msg.boot_sector_not_power
	jmp .error_common_j
.invalid_sectors:
	mov dx, msg.boot_invalid_sectors
	jmp .error_common_j
.invalid_heads:
	mov dx, msg.boot_invalid_heads
.error_common_j:
	jmp .error_common

.error_common: equ bootcmd.fail


		; 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:	-
		; STT:	ds = ss
read_ae_1536_bytes:
	push cx
	push bx
	mov cx, 1536
.loop:
	call read_sector
	sub cx, word [bp + bsBPB + bpbBytesPerSector]
	ja .loop
	pop es
	pop cx
	retn

		; 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:	-
		; STT:	ds = ss
read_ae_512_bytes:
	push cx
	push bx
	mov cx, 512
.loop:
	call read_sector
	sub cx, word [bp + bsBPB + bpbBytesPerSector]
	ja .loop
	pop es
	pop cx
	retn


		; Read a sector using Int13.02 or Int13.42
		;
		; INP:	dx:ax = sector number (absolute in unit)
		;	bx:0-> buffer
		; 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[load_sectorsizepara])
		;	 es = input bx
		; CHG:	-
		;
		; Note:	If error 09h (data boundary error) is returned,
		;	 the read is done into the load_sectorseg buffer,
		;	 then copied into the user buffer.
read_sector:
.err: equ bootcmd.fail_read
d5	call init1_d5dumpregs
d5	call init1_d5message
d5	asciz 13,10,"In read_sector",13,10

	push ds
	 push ss
	 pop ds
	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]

	xor cx, cx
	push cx
	push cx
	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)

	test byte [bp + ldHasLBA], 1
	jz .no_lba

d5	call init1_d5message
d5	asciz "In read_sector.lba",13,10

	mov dl, byte [load_unit]
	mov ah, 42h
	int 13h		; 13.42 extensions read
	jnc .lba_done

	xor ax, ax
	int 13h
	jc .lba_error

		; 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 + lpCount], 1

	mov ah, 42h
	int 13h
	jnc .lba_done

	cmp ah, 9	; data boundary error?
	jne .lba_error

d4	call init1_d4dumpregs
d4	call init1_d4message
d4	asciz 13,10,"In read_sector.lba_sectorseg",13,10

		; the offset part of the pointer is already zero!
	; push word [si + lpBuffer + 0]
	push word [si + lpBuffer + 2]	; user buffer
	 push word [load_sectorseg]
	 pop word [si + lpBuffer + 2]
	; and word [si + lpBuffer + 0], byte 0

	mov byte [si + lpCount], 1
	mov ah, 42h
	int 13h
	jnc .lba_sectorseg_done

	xor ax, ax
	int 13h
	jc .lba_error

	mov byte [si + lpCount], 1
	mov ah, 42h
	int 13h
	jc .lba_error
.lba_sectorseg_done:

	xor si, si
	mov ds, word [load_sectorseg]
	pop es
	; pop cx
	 push di
	; mov di, cx
	xor di, di
	mov cx, word [load_sectorsize]
	rep movsb
	 pop di

	push ss
	pop ds
.lba_done:
	add sp, 10h
	pop bx
	jmp .chs_done

.lba_error: equ .err

.no_lba:
	add sp, 8
	pop ax
	pop dx
	pop cx
	pop cx

; DX:AX=LBA sector number
; 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
			mov cx,ax
			mov ax,dx
			xor dx,dx
			div word [load_sectors]
			xchg cx,ax
			div word [load_sectors]
			xchg cx,dx

; DX:AX=quotient, CX=remainder=sector (S) - 1
; divide quotient by number of heads
			mov bx, ax
			xchg ax, dx
			xor dx, dx
			div word [load_heads]
			xchg bx, ax
			div word [load_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, [load_unit]
					; dl = drive
			mov ah, 04h	; error number: sector not found
			 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

			mov ax, 0201h
			int 13h		; read one sector
			jnc .done
; reset drive
			xor ax, ax
			int 13h
			jc .err

; try read again
			mov ax, 0201h
			int 13h
	jnc .done
	cmp ah, 9	; data boundary error?
	jne .err

d4	call init1_d4dumpregs
d4	call init1_d4message
d4	asciz 13,10,"In read_sector.chs_sectorseg",13,10

	push es		; user buffer
	 mov es, word [load_sectorseg]

	mov ax, 0201h
	int 13h
	jnc .chs_sectorseg_done

	xor ax, ax
	int 13h
	jc .err

	mov ax, 0201h
	int 13h
	jc .err
.chs_sectorseg_done:

	xor si, si
	mov ds, word [load_sectorseg]
	pop es
	 push di
	xor di, di
	mov cx, word [load_sectorsize]
	rep movsb
	 pop di

	push ss
	pop ds
.done:
; increment segment
	mov bx, es

.chs_done:
	mov es, bx
	add bx, word [load_sectorsizepara]

	pop si
	pop ax
	pop cx
	pop dx
	pop ds
; increment LBA sector number
	inc ax
	jne @F
	inc dx
@@:
	retn
%endif


		; INP:	ax => MCB to initialise
		;	cx = owner to set (8 if S MCB)
		;	bx = size to set
		;	dl = MCB signature, "M" or "Z"
		;	dh = S MCB type
		; OUT:	-
		; CHG:	-
;	init1_insure_low_byte_not_0CCh
init2_init_an_mcb:
 assume ds:nothing, es:nothing, ss:nothing
	push ax
	push ds
	push es
	push di
	mov ds, ax
 assume ds:MCB
	mov es, ax
 assume es:MCB
	xor di, di
	xor ax, ax
	push cx
	mov cx, MCB_size >> 1
	rep stosw
	pop cx
	mov word [di - MCB_size + mcbOwner], cx
	mov word [di - MCB_size + mcbSize], bx
	mov byte [di - MCB_size + mcbSignature], dl
	cmp cx, 8
	jne @F
	mov byte [di - MCB_size + smcbName], "S"
	mov byte [di - MCB_size + smcbType], dh
@@:
	pop di
	pop es
 assume es:nothing
	pop ds
 assume ds:nothing
	pop ax
	retn


%if 0	; lMS-DOS
disp_dxax_hex:			; dx:ax
		xchg ax, dx
		call disp_ax_hex
		xchg ax, dx
init1_disp_ax_hex:
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


%if _DEBUG5 || _DEBUG4
init1_d5dumpregs:
init1_d4dumpregs:
	pushf
	push ax
	push bx
	push cx
	push dx
	push si
	push di
	push bp
	mov ax, sp
	add ax, byte 18
	push ax
	push cs
	push ss
	push ds
	push es

	mov bx, sp
	mov ax, 1<<8|7
	push ax
	mov ax, 14
	push cs
	pop ds
	mov dx, .regnames

	mov bp, sp
%assign _columns	0
%assign _blanks		1

	xor cx, cx

.looprows:
	push ax
	mov al, 13
	call init1_disp_al
	mov al, 10
	call init1_disp_al
%if 0
	push cx
	push dx
	xor cx, cx
	mov cl, byte [ _blanks + bp ]
	jcxz .doneblanks
	mov al, 32
.loopblanks:
	call init1_disp_al
	loop .loopblanks
.doneblanks:
	pop dx
	pop cx
%endif
	pop ax

	mov cl, byte [ _columns + bp ]
.loopcolumns:
	call init1_disp_reg
	add dx, byte 2
	add bx, byte 2
	dec ax
	jz .done
	loop .loopcolumns
	jmp short .looprows

.done:
	pop ax

	pop es
	pop ds
	pop ax
	pop ax
	pop ax
	pop bp
	pop di
	pop si
	pop dx
	pop cx
	pop bx
	pop ax
	popf
	retn

.regnames:
	db "esdssscsspbpdisidxcxbxaxflip"


	; ss:bx-> word value
	; ds:dx-> 2-byte message
init1_disp_reg:
	push ax
	xchg bx, dx
	mov ax, [ bx ]				; get 2 bytes at [ ds:dx ]
	xchg bx, dx
	call init1_disp_al
	xchg al, ah
	call init1_disp_al

	mov al, '='
	call init1_disp_al

	mov ax, [ ss:bx ]
	call disp_ax_hex

	mov al, 32
	call init1_disp_al
	pop ax
	retn

init1_disp_stack_hex:
	push ax
	push bp
	mov bp, sp
	mov ax, [bp+6]
	pop bp
	call init1_disp_ax_hex
	pop ax
	retn 2

init1_d5message:
init1_d4message:
	push ax
	push bx
	push si
	push ds
	push bp
	mov bp, sp
	mov si, [bp+10]
	pushf
	 push cs
	 pop ds
	call init1_disp_msg
	popf
	mov word [bp+10], si
	pop bp
	pop ds
	pop si
	pop bx
	pop ax
	retn

init1_d4uppercase:
	pushf
	cmp al, 'a'
	jb .return
	cmp al, 'z'
	ja .return
	and al, ~ 20h
.return:
	popf
	retn
%endif


%if _TEST_PAYLOAD
	align 4
table:
	dw  +0, "SS"
	dw +12, "BP"
	dw +10, "SP"
	dw  +6, "CS"
	dw  +8, "IP"
	dw +26, "FL"
	db -1, -1, 13,10
	dw  +4, "DS"
	dw +16, "SI"
	dw  +2, "ES"
	dw +14, "DI"
	db -1, -1, 13,10
	dw +24, "AX"
	dw +22, "BX"
	dw +20, "CX"
	dw +18, "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:
%endif


	align 4
mcbtable:
	times 6 dd -1
		; MCB after INIT3
		; S_INITSTACKBPB
		; S_INITFATSEG
		; S_SECTORSEG
		; ldMemoryTop (in front of RPL, or last)
		; int 12h memory top (if RPL exists)
	dd -1

boot_partition:
	dd 0
boot_unit:
	db 0
boot_drive:
	db 0


	align 4
throw_sp:	dw 0
throw_ip:	dw 0

fsparm_pointer:	dw fsparm
fsparm_excess:	dw 0
fsparm_number:	db 0

	align 16
partition_table:
	times 4 * 16 db 0
.end:

	align 16
fsparm:
times FSPARM_size * 32 db 0


	align 16
init1_end:

%if INIT1_INSURE_COUNT
 %warning init1_insure_low_byte_not_0CCh needed INIT1_INSURE_COUNT times
%endif

	istruc MCB
at mcbSignature,	db "M"
at mcbOwner,		dw 8
at mcbSize,		dw (init2_end - init2_start) >> 4
at smcbName,		dw "S"
at smcbType,		db S_INIT
	iend

	; section INIT2 align=16 follows=INIT1 vstart=0
init2_start:
	jmp near RxDOS_initialize

	align 2
init2_to_init1_segment:	dw 0

%endif


%if 1 || (_RELOCATEDOSCODE && _DOSCODEHMA)
	align 4
init2_xmsentry:		dd 0
%endif


	align 16
init2_hma_vdisk_header:
	istruc BS
at bsJump,	db 0,0,0
at bsOEM,	db "VDISK3.3"
at bsBPB
	iend
		istruc EBPB
at bpbBytesPerSector,	db 80h	; (128)
at bpbSectorsPerCluster,db 1
at bpbReservedSectors,	dw 1
at bpbNumFATs,		db 1
at bpbNumRootDirEnts,	dw 40h	; (64)
at bpbTotalSectors,	dw 200h	; (512)	; 512 sectors * 128 B/sector = 64 KiB
at bpbMediaID,		db 0FEh
at bpbSectorsPerFAT,	dw 6
at bpbCHSSectors,	dw 8
at bpbCHSHeads,		dw 1
at bpbHiddenSectors,	dw 0
		%pop ; iend
			dw 0440h	; (1088)
				; VCPI.txt: size word in boot block, "KB addr"
 endarea init2_hma_vdisk_header


%if 0
		; INP:	word [cs:ip] = near function to call in other segment
		;	cs:(ip + 2) -> where to return to
		;	word [DOSDATA:dosdata_to_doscode_segment] = DOSCODE
		;	word [70h:dosentry_to_dosdata_segment] = DOSDATA
		;	DOSCODE:doscode_retf -> retf opcode
		;	INIT1
init2_to_doscode:
	push ax			; word space for ?returnaddress_ip, is ax
	push bx			; word space for ?returnaddress_other_ip, is bx
	push ds
	mov ax, 70h
	mov ds, ax		; => DOSENTRY
	mov ds, word [dosentry_to_dosdata_segment]	; => DOSDATA
	mov ax, word [dosdata_to_doscode_segment]	; => DOSCODE
	pop ds
	mov bx, doscode_retf	; other:bx -> retf opcode
	jmp init2_to_other_segment_common

init2_to_init1:
	push ax			; word space for ?returnaddress_ip
	push bx			; word space for ?returnaddress_other_ip
	mov ax, word [cs:init2_to_init1_segment]	; => INIT1
	mov bx, init1_retf	; other:bx -> retf opcode

init2_to_other_segment_common:
	push ax
	push ax			; dword space for ?jumpaddress
	lframe 0
	lpar word, returnaddress_cs_and_orig_ip
	lpar word, returnaddress_ip
	lpar word, returnaddress_other_ip
	lpar dword, jumpaddress
	lenter

	push si
	pushf
	cld

	mov word [bp + ?jumpaddress + 2], ax		; fill function segment
	mov si, cs
	xchg si, word [bp + ?returnaddress_cs_and_orig_ip]	; fill cs
	cs lodsw
	cmp al, 0CCh		; debugger breakpoint ?
	jne @F			; no -->
	int3			; break to make it remove the breakpoint
	dec si
	dec si
	cs lodsw		; reload the word
	cmp al, 0CCh
	jne @F

.l:
	int3
	jmp .l

@@:
	mov word [bp + ?jumpaddress + 0], ax		; fill function offset
	xchg ax, si					; ip in our cs
	xchg ax, [bp + ?returnaddress_ip]		; fill ip
		; (and restore ax)

	xchg bx, word [bp + ?returnaddress_other_ip]	; fill near ip in other
		; (and restore bx)

	popf
	pop si

	lleave
	retf			; jump to dword [bp + ?jumpaddress]


		; INP:	ax = space in bytes to allocate at top of LMA
		;	cl = allocation type
		; OUT:	es:di-> allocated space, initialised to all zeros
		;	ax = cx = space in bytes allocated,
		;	 ! 0 if size is 65536
		;	does not return after allocation failure
		; CHG:	-
init2_alloc_s_mcb_top:
	call init2_to_init1
	dw init1_alloc_s_mcb_top
	retn


		; INP:	ax = space in bytes to allocate
		;	cl = allocation type
		; OUT:	es:di-> allocated space, initialised to all zeros
		;	ax = cx = space in bytes allocated,
		;	 ! 0 if size is 65536
		;	does not return after allocation failure
		; CHG:	-
init2_alloc_s_mcb:
	call init2_to_init1
	dw init1_alloc_s_mcb
	retn


init2_call_rploader:
	call init2_to_init1
	dw init1_call_rploader
	retn
%endif


		; CHG:	ds, si, ax
		; OUT:	NZ if A20 line is switched on
		;	ZR if A20 line is switched off
		;	may return with IF=0
		;	UP
init2_check_a20:
 assume ds:nothing, es:nothing, ss:nothing
	cld
	push es
	push di
	push cx
	xor si, si
	mov ds, si		; ds = 0000h
 assume ds:IVT
	dec si
	mov es, si		; es = FFFFh
 assume es:nothing
	inc si			; ds:si = 0000h:0000h =  00000h
	mov di, 0010h		; es:di = FFFFh:0010h = 100000h
				;  (same address if bit 20 off)
	mov cx, di		; 32 byte (16 = 10h word)
	repe cmpsw		; compare, A20 line switched on if differing
	jne .ret		; differing -->

	cli			; try not to run interrupt handlers during this
	mov di, 10h		; -> FFFFh:0010h = 10_0000h
				;  (in the HMA, part of the VDISK header)
	lea si, [di - 10h]	; -> 0000h:0000h = 00_0000h
				;  (in the LMA, offset word of int 00h handler)
	push word [es:di]	; save value
	dec word [es:di]	; change value (in HMA, or wrapped around LMA)
	cmpsw			; compare values, NZ if A20 is switched on
	pop word [es:di - 2]	; restore value
		; This can still report a false negative (A20 detected off
		;  when actually it is on), but we don't care about that.
.ret:
	pop cx
	pop di
	pop es
 assume es:nothing
	retn


%if _RELOCATEDOSCODE
extern dosdata_to_doscode_segment
%endif

		; This is to be called after each device driver is installed.
		;  It relocates DOSCODE to the HMA if it is available.
		; May be extended to relocate more things later.
		;
		; INP:	ax = flags
		; CHG:	ds, es, si, di, ax, bx, cx, dx
init2_relocate_device:
 assume ds:nothing, es:nothing, ss:nothing
	lframe 0
	lvar word, umb_first_umcb
	lvar word, umb_last_umcb
	lvar word, last_mcb
	lenter
	lvar word, flags
	 push ax
	lvar word, first_mcb
	mov ah, 52h
	int 21h
 assume es:DOSGROUP
	 push word [es:bx - 2]
	lvar word, first_umcb
	mov ax, 1261h
	int 2Fh
	 push ax

%if !_COMBINED_DOSDATA_DOSCODE && _RELOCATEDOSCODE && _DOSCODEHMA

	rol byte [cs:doscode_relocated_to_final_position], 1
	jc .no_doscode_reloc

%if 0
	mov ax, 70h
	mov ds, ax
	mov ds, word [dosentry_to_dosdata_segment]
%else
	xor ax, ax
	mov ds, ax
 assume ds:IVT
	mov ds, word [31h * 4 + 2]	; => DOSDATA
 assume ds:DOSGROUP
%endif
	mov bx, word [dosdata_to_doscode_segment]
	cmp bx, DOSCODE_HMA_SEGMENT		; already in HMA ?
	jae .not_doscode_to_hma	; yes, return -->

	test word [bp + ?flags], DOSFLAGS_RELOCATE_DOSCODE_HMA
	jz .not_doscode_to_hma

	mov ax, 4300h
	int 2Fh			; XMS present ?
	cmp al, 80h
	jne .not_doscode_to_hma	; no -->

	mov ax, 4310h
	int 2Fh			; get XMS entrypoint
	mov word [cs:init2_xmsentry], bx
	mov word [cs:init2_xmsentry + 2], es		; store

	mov ax, 0100h
	mov dx, -1
	call .call_xms		; allocate HMA
	jc .not_doscode_to_hma	; failure -->

	mov ah, 03h
	call .call_xms		; enable A20
	mov dx, init2_msg.error_enabling_a20_xms
	jc .not_doscode_free	; error -->

	call init2_check_a20	; check A20 is enabled
	mov dx, init2_msg.error_enabling_a20_actual
	jz .not_doscode_free	; error -->

	mov ax, DOSENTRY
	mov ds, ax
 assume ds:DOSENTRYGROUP
	push word [cs:init2_xmsentry + 2]
	push word [cs:init2_xmsentry]
	cli
	pop word [dosentry_xmsentry]
	pop word [dosentry_xmsentry + 2]
 extern ..@dosentry_patch_hma
	mov word [..@dosentry_patch_hma], 0C087h	; xchg ax, modrm ax
	sti

	cmp word [cs:autobounce_both_options], 0
	je .no_bounce_test

	mov ax, DOSENTRY
	mov cl, 4
	shl ax, cl
	add word [cs:int25packet.offset], ax
					; -> into HMA, corresponding to DISKSECTOR

	mov bx, autobouncehmachs_option
	mov di, config_bounce_hma_chs
	xor ax, ax
	call detect_bounce
 assume ds:nothing, es:nothing, ss:nothing

	mov bx, autobouncehmalba_option
	mov di, config_bounce_hma_lba
	mov ax, F_LBA
	call detect_bounce
 assume ds:nothing, es:nothing, ss:nothing
.no_bounce_test:

	mov dx, -1
	mov es, dx
 assume es:nothing
	mov di, 10h
	push cs
	pop ds
 assume ds:SYSINITGROUP
	mov si, init2_hma_vdisk_header
	mov cx, init2_hma_vdisk_header_size_w
	rep movsw
%if init2_hma_vdisk_header_size != 32
 %error Expected HMA VDISK header to be 32 bytes long
%endif
	mov cx, 10h >> 1
	xor ax, ax
	rep stosw
	sub di, 10h

%if 0
	mov cx, (doscode_end - doscode_start)
%else
extern afterdoscodelabel
	mov cx, afterdoscodelabel wrt DOSCODEGROUP
	sub cx, DOSCODE_HMA_OFFSET
%endif

	mov dx, DOSCODE_HMA_SEGMENT
	mov word [es:di + hmcbSignature], "MS"
	mov word [es:di + hmcbOwner], dx
	mov word [es:di + hmcbSize], cx
	mov bx, di
	add bx, HMCB_size
	add bx, cx
	mov word [es:di + hmcbNext], bx
	mov word [es:di + hmcbName], "DO"
	mov word [es:di + hmcbName + 2], "SC"
	mov word [es:di + hmcbName + 4], "OD"
	mov byte [es:di + hmcbName + 6], "E"

	mov di, bx
	xor ax, ax
	 push cx
	mov cx, 16 >> 1
	rep stosw
	 pop cx
	mov di, bx
	mov word [es:di + hmcbSignature], "MS"
	; word [es:di + hmcbOwner] already == 0
	mov si, bx
	add si, 10h
	neg si
	mov word [es:di + hmcbSize], si
	; word [es:di + hmcbNext] already == 0

	mov es, dx
 assume es:nothing
	mov di, DOSCODE_HMA_OFFSET
	mov si, di
%if 0
	mov dx, 70h
	mov ds, dx
	mov ds, word [dosentry_to_dosdata_segment]
%else
	xor dx, dx
	mov ds, dx
 assume ds:IVT
	mov ds, word [31h * 4 + 2]
 assume ds:DOSGROUP
%endif
	mov bx, word [dosdata_to_doscode_segment]
	mov ds, bx
 assume ds:DOSCODEGROUP
	shr cx, 1
	rep movsw

	mov ds, dx
 assume ds:IVT
%if 0
	mov ds, word [dosentry_to_dosdata_segment]
%else
	mov ds, word [31h * 4 + 2]
 assume ds:DOSGROUP
%endif
extern doslocation3306
	or byte [doslocation3306], 10h

	mov word [dosdata_to_doscode_segment], es
	mov ax, DOSENTRY
	mov ds, ax
 assume ds:DOSENTRYGROUP
 extern ..@dosentry_doscode_segment
	mov word [..@dosentry_doscode_segment], es
	not byte [cs:doscode_relocated_to_final_position]
 assume es:DOSCODEGROUP

	mov ax, ss
	cmp ax, bx
	jne @F
	push es
	pop ss
 assume ss:DOSCODEGROUP
@@:
 assume ss:nothing

	add bx, DOSCODE_HMA_OFFSET / 16
	mov es, bx			; => DOSCODE source
 assume es:nothing
	mov ah, 49h
	int 21h				; free it

extern first_hmcb
	mov ds, dx
 assume ds:IVT
%if 0
	mov ds, word [dosentry_to_dosdata_segment]
%else
	mov ds, word [31h * 4 + 2]
 assume ds:DOSGROUP
%endif
	mov word [first_hmcb], 30h

	testopt [cs:verbose_mode], verbose_xms
	jz @F
	mov dx, init2_msg.relocated_to_hma
	call init2_disp_msg_asciz_cs_dx
@@:
	jmp .not_doscode_to_hma


.not_doscode_free:
 assume ds:nothing, es:nothing, ss:nothing
	call init2_disp_msg_asciz_cs_dx

	mov ah, 02h
	call .call_xms		; free HMA

.not_doscode_to_hma:
.no_doscode_reloc:
 assume ds:nothing, es:nothing, ss:nothing
%endif

	push si			; loop detection counter word
	mov si, 40h		; "UMA only" (+ first fit)
	push si
	mov si, sp		; ss:si -> area flags and strategy word

	test word [bp + ?flags], DOSFLAGS_LINK_UMB
	jz .not_link_umb

	mov ax, 4300h
	int 2Fh			; XMS present ?
	cmp al, 80h
	jne .not_link_umb	; no -->

	mov ax, 4310h
	int 2Fh			; get XMS entrypoint
	mov word [cs:init2_xmsentry], bx
	mov word [cs:init2_xmsentry + 2], es		; store

.link_next_umb:
	mov ah, 10h
	mov dx, -1		; size
	call .call_xms		; request UMB
	jnc .not_link_umb_error_succeeded_unexpectedly
	cmp bl, 0B0h		; "only smaller UMB available" ?
	jne .not_link_umb	; no, done -->

	push dx
	mov ah, 10h		; (size initialised by prior call)
	call .call_xms		; request UMB
	pop ax
	jnc .link_umb

	mov dx, init2_msg.umb_unavailable_1
	call init2_disp_msg_asciz_cs_dx

	call init2_disp_ax_hex
	mov dx, init2_msg.umb_unavailable_2
	call init2_disp_msg_asciz_cs_dx

	push bx
	xor dx, dx
	mov cx, 16
	mov bx, 4+4
	call init2_disp_dxax_times_cx_width_bx_size
	pop bx
	mov dx, init2_msg.umb_unavailable_3
	call init2_disp_msg_asciz_cs_dx

	jmp .not_link_umb

.link_umb:
		; dx = allocated UMB size
		; bx = allocated UMB segment
	and word [ss:si + 2], 0	; initialise loop detection counter to zero

	push dx
	mov ax, dx
	testopt [cs:verbose_mode], verbose_xms
	jz @F
	mov dx, init2_msg.umb_allocated_1
	call init2_disp_msg_asciz_cs_dx

	call init2_disp_ax_hex
	mov dx, init2_msg.umb_allocated_2
	call init2_disp_msg_asciz_cs_dx

	push bx
	xor dx, dx
	mov cx, 16
	mov bx, 4+4
	call init2_disp_dxax_times_cx_width_bx_size
	pop bx
	mov dx, init2_msg.umb_allocated_3
	call init2_disp_msg_asciz_cs_dx

	mov ax, bx
	call init2_disp_ax_hex
	mov dx, init2_msg.umb_allocated_4
	call init2_disp_msg_asciz_cs_dx
@@:
	pop dx

		; dx = size, bx = segment
	xor cx, cx		; step = 0

	push cx
	push bx
	push dx
	xor cx, cx		; free
	xchg ax, bx		; ax => MCB
	mov bx, dx		; size = size of UMB
	sub bx, 2		; -1 for initial MCB, -1 for end MCB
	jc @F			; too small!
	mov dx, "M"		; "M"
	mov word [bp + ?umb_first_umcb], ax
		; INP:	ax => MCB to initialise
		;	cx = owner to set (8 if S MCB)
		;	bx = size to set
		;	dl = MCB signature, "M" or "Z"
		;	dh = S MCB type
		; OUT:	-
		; CHG:	-
	call init2_init_an_mcb

	add ax, bx
	inc ax			; => end MCB
	xor bx, bx		; size zero (may be changed to link next UMB)
	mov dx, (S_EXCLDUMA << 8) | "Z"	; type, and signature
	mov cx, 8		; allocate S MCB
	mov word [bp + ?umb_last_umcb], ax
	call init2_init_an_mcb

	db __TEST_IMM8		; (NC) skip stc
@@:
	stc
	pop dx
	pop bx
	pop cx
	jc .not_link_umb_error_mcb_chain

	xor ax, ax		; = 0 = get first UMCB
.search_umb:
	mov cl, 1		; step = 1
	mov word [bp + ?last_mcb], ax
%if 0
	call init2_to_doscode
	dw SNextMCB		; get next (U)MCB
%else
	call init2_SNextMCB
%endif
	jc .not_link_umb_error_mcb_chain	; chain corrupted
	jnz .umb_case_3		; end of chain, word [bp + ?last_mcb] has last
	inc cx			; step = 2
	cmp bx, ax		; area to add is below ?
	jbe .not_link_umb_error_mcb_chain	; if so, error out -->
	mov ds, ax
 assume ds:MCB
	mov di, ax		; => MCB
	add di, word [mcbSize]
	inc di			; => next MCB (if any)
	cmp bx, di
	ja .search_umb		; next -->
	jne .umb_case_2		; UMB is below where next points to

.umb_case_1:
%if 0
case 1: S_EXCLDUMA MCB ends at where our UMB starts
	0123456789
	S2 E
	   U3  L

%endif
	mov cl, 8		; step = 8

	cmp word [mcbOwner], 8
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 9
	cmp word [smcbName], "S"
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = A
	cmp byte [smcbType], S_EXCLDUMA
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = B

	cmp byte [mcbSignature], "Z"
	jne .not_link_umb_error_mcb_chain
	mov byte [mcbSignature], "M"

.check_empty_umcb:
	cmp word [mcbSize], 0	; empty area ?
	jne @F			; no, no question as to whether to free -->

	cmp ax, [bp + ?first_umcb]	; is ds => first UMCB ?
	je .check_enlarge_lma	; yes, either protect UMCB or enlarge LMA -->
				; no, unconditionally delete the hole
.free_umcb_link_next_umb:
	and word [mcbOwner], 0	; empty area found, clear owner
				;  (affects first UMCB only if enlarging LMA)
@@:
	jmp .link_next_umb

.check_enlarge_lma:
 extern enlargelma_option
	rol byte [cs:enlargelma_option], 1
				; enlarge ?
	jnc @B			; no, protect it -->

@@:			; loop to find next UMA hole (or end of UMCB chain)
%if 0
	call init2_to_doscode
	dw SNextMCB		; get next (U)MCB
%else
	call init2_SNextMCB
%endif
	mov cl, 20h		; step = 20
	jc .not_link_umb_error_mcb_chain	; chain corrupted
	mov cl, 21h		; step = 21
	jnz .not_link_umb_error_mcb_chain
	mov ds, ax
 assume ds:MCB
	cmp word [mcbOwner], 8
	jne @B
	cmp word [smcbName], "S"
	jne @B
	cmp byte [smcbType], S_EXCLDUMA
	jne @B
 extern first_umcb
	call sysinit_get_ds_dosbiodata
 assume ds:DOSGROUP
	xchg dx, ax			; new first UMCB to dx

		; natch, we need to link UMBs before changing things !
	mov ax, 5802h
	int 21h
	push ax
	mov ax, 5803h
	mov bx, 1			; link in UMBs before modification
	int 21h

	mov word [first_umcb], dx	; update DOSDATA's reference
	xchg word [bp + ?first_umcb], dx; update our stack frame's reference,
					;  and dx = old first UMCB
	pop bx
	mov ax, 5803h
	int 21h				; restore old UMB link state

	xchg ax, dx			; old first UMCB to ax

 extern configmsg.enlargelma_message
	mov dx, configmsg.enlargelma_message
	call init2_disp_msg_asciz_cs_dx

	mov ds, ax			; => old first UMCB
 assume ds:MCB
	jmp .free_umcb_link_next_umb	; free the old first UMCB


.umb_case_2:
 assume ds:nothing, es:nothing, ss:nothing
%if 0
case 2: S_EXCLDUMA MCB around entire area
	0123456789
	S8       E
	   U3  L
	S2 U3  L1E
S=0
S size=8
di=9
L=7
E=9
L want size=1
L want size=E-L-1
S want size=2
S want size=U-S-1
%endif

	cmp word [mcbOwner], 8
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 3
	cmp word [smcbName], "S"
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 4
	cmp byte [smcbType], S_EXCLDUMA
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 5

	mov es, word [bp + ?umb_last_umcb]
 assume es:nothing
	mov ch, byte [mcbSignature]
	mov byte [es:mcbSignature], ch
	sub di, word [bp + ?umb_last_umcb]
				; (E-L) size of UMB's last UMCB
	dec di			; (E-L-1) minus last UMCB itself
	mov word [es:mcbSize], di
	cmp byte [es:mcbSignature], "Z"	; last ?
	je @F			; yes, do not clear owner (even if empty) -->
	test di, di		; empty area ?
	jnz @F
	and word [es:mcbOwner], 0	; yes, clear owner
@@:
.umb_case_2_3_common:
 assume ds:nothing, es:nothing, ss:nothing
	mov di, bx		; (U)
	sub di, ax		; (U-S)
	dec di			; (U-S-1)
	mov word [mcbSize], di
	mov byte [mcbSignature], "M"	; link to UMB's first UMCB
	jmp .check_empty_umcb


.umb_case_3:
 assume ds:nothing, es:nothing, ss:nothing
%if 0
case 1: S_EXCLDUMA MCB ends somewhere below our UMB
	0123456789
	S1E
	   U3  L
S want size=2
S want size=U-S-1
%endif
	mov cl, 10h		; step = 10

	mov ax, word [bp + ?last_mcb]
	test ax, ax
	jz .not_link_umb_error_mcb_chain
	inc cx			; step = 11
	mov ds, ax
 assume ds:MCB
	cmp word [mcbOwner], 8
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 12
	cmp word [smcbName], "S"
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 13
	cmp byte [smcbType], S_EXCLDUMA
	jne .not_link_umb_error_mcb_chain
	inc cx			; step = 14

	cmp byte [mcbSignature], "Z"
	jne .not_link_umb_error_mcb_chain

	jmp .umb_case_2_3_common


.not_link_umb_error_mcb_chain:
 assume ds:nothing, es:nothing, ss:nothing
	push ax
	mov dx, init2_msg.umb_mcb_chain_corrupted_1
	call init2_disp_msg_asciz_cs_dx

	mov al, cl
	call init2_disp_al_hex
	mov dx, init2_msg.umb_mcb_chain_corrupted_2
	call init2_disp_msg_asciz_cs_dx

	pop ax
	call init2_disp_ax_hex
	mov dx, init2_msg.umb_mcb_chain_corrupted_2
	call init2_disp_msg_asciz_cs_dx

		; Note that we do not free the UMB here! In case the user
		;  wants to do that, the address has already been displayed.
	jmp .link_next_umb


.not_link_umb_error_succeeded_unexpectedly:
	mov dx, init2_msg.umb_succeeded_unexpectedly
	call init2_disp_msg_asciz_cs_dx

.not_link_umb:
 assume ds:nothing, es:nothing, ss:nothing
	pop si			; (discard area flags and strategy word)
	pop si			; (discard loop detection counter)

	rol byte [cs:dosdata_relocate_early], 1
	jnc @F
	mov dx, -1		; != 0 = relocate only if UMA
	call relocate_dosdata
@@:

	lleave code
	retn

%if 1 || (_RELOCATEDOSCODE && _DOSCODEHMA)
.call_xms:
	call far [cs:init2_xmsentry]
	cmp ax, 1
	je @F			; (NC) -->
	stc
@@:
	retn
%endif

		; Get next MCB, insuring the chain doesn't loop
		;
		; INP:	word [ss:si] = area flags and allocation strategy
		;	word [ss:si + 2] = loop detection counter, init to 0
		;	ax = former MCB (0000h to get first)
		; OUT:	CY if MCB chain corrupted or loops,
		;	 ax = error code
		;	NC if MCB chain intact,
		;	 NZ if no next MCB,
		;	  ax = error code (0008h)
		;	 ZR if next MCB available,
		;	  ax = next MCB
		;	di = 0
		; CHG:	-
		; STK:	8 word
;	doscode_insure_low_byte_not_0CCh
init2_SNextMCB:
 assume ds:nothing, es:nothing, ss:nothing
	xor di, di
	dec word [ss:si + 2]
	jnz @F
		; This doesn't branch if ZR, meaning if the 1_0000h iterations
		;  have run out (if counter was 1 prior to the dec).
	stc
	mov ax, errorMCBLoops
	retn

@@:
		; (fall through to NextMCB)


		; Get next MCB
		;
		; INP:	word [ss:si] = area flags and allocation strategy
		;	word [ss:si + 2] = loop detection counter
		;	ax = former MCB (0000h to get first)
		; OUT:	CY if MCB chain corrupted,
		;	 ax = error code
		;	NC if MCB chain intact,
		;	 NZ if no next MCB,
		;	  ax = error code 0008h (Not enough memory)
		;	 ZR if next MCB available,
		;	  ax = next MCB
		;	di = 0
		; CHG:	-
		; STK:	3 word
		;
		; An interesting effect of how this code handles the first UMCB
		; is that it doesn't care whether the previous MCB contains an
		; 'M' (normal MCB chain extended into UMA) or 'Z' (normal MCB
		; chain limited to LMA). Also, the parsed area flags allow to
		; ignore the actual requested UMB link: If it was zero, all
		; area flags are cleared and forced to 10h (LMA only) instead.
%ifn _UMA
init2_NextMCB:
 assume ds:nothing, es:nothing, ss:nothing
	push ds
	test ax, ax				; request for first ?
	jz .first				; yes -->

	call init2_VerifyMCB			; check input MCB first
	jc .return
 assume ds:MCB
	inc ax
	add ax, word [di+mcbSize]		; get address of next (if any)
	cmp byte [di+mcbSignature], 'M'		; current in-chain ?
	je .verify				; yes, verify it -->
		; If this was NZ and didn't jump, then the mcbSignature
		;  contains the letter 'Z', which is above 'M'. Therefore,
		;  it is always true that it is NC at this point.
	mov ax, errorInsufficientMemory		; (NC, NZ)
	pop ds
 assume ds:nothing
	retn
.first:
	mov ax, word [bp + ?first_mcb]
.verify:
	call init2_VerifyMCB			; NC, ZR if valid - CY if invalid
.return:
	pop ds
	retn
%else
		; The UMA build has to evaluate the (parsed) area flags
		; here to show the caller a continuous chain of MCBs.
		; This requires special handling in two cases: If the
		; caller's input was 0000h (to get the first MCB) or if
		; it was the MCB before the first UMCB. Area flags of
		; 80h and 40h want to start their search with the first
		; UMCB instead. Flags 20h want to proceed at the first
		; UMCB, all other flags want to stop there. The first
		; UMCB's handling ignores whether the preceeding MCB
		; contained 'M' or 'Z'.
init2_NextMCB:
 assume ds:nothing, es:nothing, ss:nothing
	push ds
	test ax, ax				; request for first ?
	jz .first				; yes -->

	call init2_VerifyMCB			; check input MCB first
	jc .return
 assume ds:MCB
	inc ax
	add ax, word [di+mcbSize]		; get address of next (whether current is 'M' or 'Z')

	call init2_IsFirstUMCB?			; is the one behind this the first UMCB ?
	jne .notfirstumcb			; nope, check if last -->
	test byte [ss:si], ~2Fh			; "LMA then UMA" ?
		; x = D0h --> x & ~20h != 00h if area other than LMA-then-UMA,
		; x = 20h --> x & ~20h = 00h if area is LMA-then-UMA
	jz .verify				; yes, proceed here -->
		; After test, it is always NC here.
	jmp short .last				; (NC, NZ) else it's the last one (even if 'M') -->
.notfirstumcb:
	cmp byte [di+mcbSignature], 'M'		; current in-chain ?
	je .verify				; no -->
	cmp word [bp + ?first_umcb], byte -2	; any UMA ?
	ja .last				; (NC, NZ) no, really last -->
	test byte [ss:si], ~8Fh			; "UMA then LMA" ?
		; x = 70h --> x & ~80h != 00h if area other than UMA-then-LMA,
		; x = 80h --> x & ~80h = 00h if area is UMA-then-LMA
	jz .firstlma				; yes, start to search LMA instead -->
		; After test, it is always NC here.
.last:
	mov ax, errorInsufficientMemory		; (NC, NZ)
	pop ds
 assume ds:nothing
	retn
.first:
	test byte [ss:si], (80h|40h)		; "UMA then LMA" or "UMA only" ?
	jz .firstlma				; no, start with LMA -->
	mov ax, word [bp + ?first_umcb]		; yes, start with UMA
	cmp ax, -1				; UMA valid ?
	jne .verify				; yes -->
.firstlma:
	mov ax, word [bp + ?first_mcb]		; start with LMA
.verify:
	call init2_VerifyMCB			; NC, ZR if valid - CY if invalid
.return:
	pop ds
 assume ds:nothing
	retn


		; Compare ax to first UMCB
		;
		; INP:	ax = MCB
		; OUT:	NZ if not first UMCB (or if there's no UMCB)
		;	ZR if first UMCB
		; CHG:	-
		; STK:	1 word
init2_IsFirstUMCB?:
 assume ds:nothing, es:nothing, ss:nothing
	cmp word [bp + ?first_umcb], byte -2
	ja .return			; (NZ if it jumps) -->
	cmp ax, word [bp + ?first_umcb]
.return:
	retn
%endif


		; Verify MCB
		;
		; INP:	ax = MCB
		; OUT:	ds = MCB
		;	di = 0
		;	NZ, CY if invalid MCB,
		;	 ax = error code 0007h (MCB chain corrupted)
		;	ZR, NC if valid MCB
		; CHG:	-
		; STK:	1 word
init2_VerifyMCB:
 assume ds:nothing, es:nothing, ss:nothing
	xor di, di
	mov ds, ax				; set up working registers
 assume ds:nothing
	inc ax
	cmp ax, byte 2				; passed value FFFFh or 0000h ?
	jb .invalid				; (NZ, CY) yes -->
 assume ds:MCB
	dec ax
	cmp byte [di+mcbSignature], 'M'		; valid 'M' signature ?
	je .return				; yes --> (ZR, NC)
	cmp byte [di+mcbSignature], 'Z'		; valid 'Z' signature ?
	je .return				; yes --> (ZR, NC)
.invalid:
 assume ds:nothing
	stc
	mov ax, errorMCBDestroyed		; (CY, NZ) else invalid
.return:
	retn

	lleave ctx


%if _RELOCATEDOSCODE && _DOSCODEHMA
detect_bounce:
 assume ds:nothing, es:nothing, ss:nothing
	rol byte [cs:bx], 1
	jnc .retn
	push di
	push ax
	call sysinit_get_ds_dosbiodata
 assume ds:DOSGROUP
	mov byte [di], 0
	les si, [DPBHEAD]
.loop_dpb:
	cmp si, -1
	je .done
 assume es:DPB
	cmp word [es:si + dpb_driver_addr + 2], DOSENTRY + DOSENTRYDEVICEBASE
	jne .next_dpb
	cmp word [es:si + dpb_driver_addr], DSKDEV - DOSENTRYDEVICEBASE * 16
	jne .next_dpb
	mov dx, [es:si + dpb_drive]	; dh = device driver unit
	mov ax, 0803h
	int 2Fh
 assume ds:UPB
.loop_upb:
	cmp di, -1
	je .done_upb
	cmp byte [di + DRIVELET], dh
	jne .next_upb
	testopt [di + FLAGS], FNON_REMOVABLE
	jz .next_upb
	pop ax
	push ax
	xor ax, [di + FLAGS]
	and ax, F_LBA			; differs ?
	jnz .next_upb			; yes -->

	push dx
	push ds
	push es
	push di
	push si

	mov di, DOSENTRY
	mov es, di
 assume es:DOSENTRYGROUP
	mov di, DISKSECTOR	; access with es
	mov ax, 9090h
	mov cx, words(512)
	rep stosw

	mov cx, -1
	mov al, dl
	 push cs
	 pop ds
 assume ds:SYSINITGROUP
	mov bx, int25packet	; access with ds
	int 25h
	jc .next_upb_pop_2
	popf

	mov di, DOSENTRY
	mov es, di
 assume es:DOSENTRYGROUP
	mov di, DISKSECTOR	; access with es
	mov ax, 9090h
	mov cx, words(512)
	repe scasw
	je .next_upb_pop_1
	pop si
	pop di
	pop es
 assume es:DPB
	pop ds
 assume ds:UPB
	pop ax			; al = drive number (0 = A:)

	mov dx, init2_msg.auto_bounce_hma_chs
	pop cx			; cx = flag, NZ if LBA
	jcxz @F
	mov dx, init2_msg.auto_bounce_hma_lba
@@:
	call init2_disp_msg_asciz_cs_dx
	add al, 'A'
	call init2_disp_al
	mov dx, init2_msg.auto_bounce_trail
	call init2_disp_msg_asciz_cs_dx
	pop di			; DOSDATA:di -> the resident choice
	call sysinit_get_ds_dosbiodata
 assume ds:DOSGROUP
	mov byte [di], 0FFh
	retn

.next_upb_pop_2:
	popf
.next_upb_pop_1:
	pop si
	pop di
	pop es
 assume es:DPB
	pop ds
 assume ds:UPB
	pop dx
.next_upb:
	lds di, [di + LINK]
	jmp .loop_upb

.done_upb:
.next_dpb:
	les si, [es:si + dpb_next_dpb]
	jmp .loop_dpb

.done:
	pop ax
	pop di
.retn:
	retn

	align 2, db 0
int25packet:
	dd 0
	dw 1
.offset:
	dw DISKSECTOR + 10h, 0FFFFh
%endif


init2_msg:

%if 0
.internal_error_handles: db "init: Internal error during "
			asciz "handle allocation.",13,10
.con:			asciz "CON"
.aux:			asciz "AUX"
.prn:			asciz "PRN"
.rxconfigsys:		asciz "RxCONFIG.SYS"
.configsys:		asciz "CONFIG.SYS"
.config_error_reading_1:asciz "init: Error "
.config_error_reading_2:asciz "h while reading configuration.",13,10
.config_too_long_line_1:asciz "init: Line "
.config_too_long_line_2:asciz " is too long. Ignoring!",13,10
.config_too_long_file_1: db "init: Configuration file too long, "
			asciz "ignoring remainder starting at line "
.config_too_long_file_2:asciz ".",13,10
.internal_error_config:	asciz "init: Internal error in CONFIG processing.",13,10
%endif

.reboot_prompt:		asciz "Halted. Press Ctrl+Alt+Del to reboot.",13,10

.umb_unavailable_1:
.umb_allocated_1:
	asciz "init: UMB of size "
.umb_unavailable_2:
.umb_allocated_2:
	asciz "h ("
.umb_unavailable_3:
	asciz ") reported free but failed to allocate.",13,10
.umb_allocated_3:
	asciz ") at "
.umb_allocated_4:
	asciz "h allocated.",13,10
.umb_mcb_chain_corrupted_1:
	asciz "init: UMCB chain corrupted, step="
.umb_mcb_chain_corrupted_2:
	asciz "h, ax="
.umb_mcb_chain_corrupted_3:
	asciz "h.",13,10
.umb_succeeded_unexpectedly:
	db "init: Requested UMB allocation of size 0FFFFh"
	asciz " unexpectedly succeeded.",13,10

%if _RELOCATEDOSCODE && _DOSCODEHMA
.error_enabling_a20_xms:
	asciz "init: Error enabling A20, XMS call failed.",13,10
.error_enabling_a20_actual:
	asciz \
"init: Error enabling A20, XMS call reported success but A20 is still off.", \
13,10
.relocated_to_hma:
	asciz "init: Successfully relocated DOSCODE into the HMA.",13,10
.auto_bounce_hma_chs:
	asciz "init: Bounce HMA CHS detected as needed for drive "
.auto_bounce_hma_lba:
	asciz "init: Bounce HMA LBA detected as needed for drive "
.auto_bounce_trail:
	asciz ":",13,10
%endif

%if 1 || (_RELOCATEDOSCODE && _DOSCODEHMA)
 global init2_disp_msg_asciz_cs_dx
init2_disp_msg_asciz_cs_dx:
 assume ds:nothing, es:nothing, ss:nothing
	push ds
	push si
	push ax
	 push cs
	 pop ds
 assume ds:SYSINITGROUP
	mov si, dx
	call init2_disp_msg
	pop ax
	pop si
	pop ds
 assume ds:nothing
	retn

init2_disp_msg:
 assume ds:nothing, es:nothing, ss:nothing
	push ax
@@:
	lodsb
	test al, al
	jz @F
	call init2_disp_al
	jmp short @B

 global init2_disp_al
init2_disp_al:
 assume ds:nothing, es:nothing, ss:nothing
	push ax
	push bx
	push bp
	mov ah, 0Eh
	mov bx, 7
	int 10h
	pop bp
	pop bx
@@:
	pop ax
	retn

 global init2_disp_dxax_hex
init2_disp_dxax_hex:		; dx:ax
 assume ds:nothing, es:nothing, ss:nothing
		xchg ax, dx
		call init2_disp_ax_hex
		xchg ax, dx
 global init2_disp_ax_hex
init1_disp_ax_hex:		; ax
init2_disp_ax_hex:		; ax
 assume ds:nothing, es:nothing, ss:nothing
		xchg al,ah
		call init2_disp_al_hex		; display former ah
		xchg al,ah			;  and fall trough for al
 global init2_disp_al_hex
init2_disp_al_hex:		; al
 assume ds:nothing, es:nothing, ss:nothing
		push cx
		mov cl,4
		ror al,cl
		call init2_disp_al_lownibble_hex; display former high-nibble
		rol al,cl
		pop cx
						;  and fall trough for low-nibble
init2_disp_al_lownibble_hex:
 assume ds:nothing, es:nothing, ss:nothing
		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 init2_disp_al
		pop ax
		retn
%endif


		; INP:	cx = flags
		;	dx = indicator if to use UMA only
		; CHG:	all except bp
relocate_dosdata:
 assume ds:nothing, es:nothing, ss:nothing
	rol byte [cs:dosdata_relocated_to_final_position], 1
	jc .ret

%if 0
	mov ax, (dosdata_end - dosdata_start)
%else
	xor ax, ax
	mov ds, ax
 assume ds:IVT
	mov ax, [31h * 4 + 2]
	dec ax
	mov ds, ax
 assume ds:MCB
	mov ax, [mcbSize]
	mov cl, 4
	shl ax, cl
%endif
	mov si, alloc_dosdata

	push ax
	test dx, dx
	jz @F
	xor dx, dx
	call allocate_relocate_block.large_upper_only
	jnc .allocated
	pop cx
.ret:
	retn

@@:
	call allocate_relocate_block
.allocated:			; es:di -> DOSDATA destination
 assume es:nothing
	pop cx

%if 0
	mov ax, 70h
	mov ds, ax			; => DOSENTRY
	mov bx, word [dosentry_to_dosdata_segment]
					; get DOSDATA segment
%else
	xor ax, ax
	mov ds, ax
 assume ds:IVT
	mov bx, [31h * 4 + 2]
%endif
	mov ds, bx
 assume ds:DOSGROUP
	xor si, si			; ds:si -> DOSDATA source
	shr cx, 1			; get number of words
	rep movsw			; move

%if 0		; done by DOSREINIT
	mov ds, ax
	mov word [dosentry_to_dosdata_segment], es
					; DOSDATA reference in DOSENTRY
%if _COMBINED_DOSDATA_DOSCODE
	push es
	pop ds
	mov word [dosdata_to_doscode_segment], es
%endif
	xor ax, ax
	mov ds, ax
	mov word [30h * 4 + 6], es	; DR-DOS compatible DOSDATA reference
%endif

	mov ax, ss
	cmp ax, bx
	jne @F
	push es
	pop ss
 assume ss:nothing
@@:

	mov dx, bx			; => old DOSDATA
	mov ax, es			; => new DOSDATA
	invoke DOSREINIT
 assume ds:nothing, es:DOSGROUP

	not byte [cs:dosdata_relocated_to_final_position]

	mov es, dx			; => DOSDATA source
 assume es:nothing
	mov ah, 49h
	int 21h				; free it
	retn


		; This is to be called after all device drivers are installed.
		;  It relocates DOSCODE and DOSDATA.
		; May be extended to relocate more things later.
		;
		; INP:	ax = flags
		; CHG:	ds, es, si, di, ax, bx, cx, dx
init2_relocate_end:
 assume ds:nothing, es:nothing, ss:nothing
	lframe 0
	lvar word, count
	lvar dword, prior
	lenter
	lvar word, flags
	 push ax
	xchg cx, ax			; cx = flags

	mov ax, 5800h
	int 21h
	push ax

	xor dx, dx			; 0 = relocate in any case, LMA if needed
	call relocate_dosdata

%if !_COMBINED_DOSDATA_DOSCODE && _RELOCATEDOSCODE

	rol byte [cs:doscode_relocated_to_final_position], 1
	jc .no_doscode_reloc

	mov ax, 5801h
	mov bx, 0110h		; ignore UMB link
	int 21h			; alloc strategy: first fit, LMA only

	test word [bp + ?flags], DOSFLAGS_RELOCATE_DOSCODE_UMA
	jz .got_doscode_strat

	mov ax, 5801h
	mov bx, 0180h		; ignore UMB link
	int 21h			; alloc strategy: first fit, try UMA then LMA
.got_doscode_strat:

%if 0
	mov ax, 70h
	mov ds, ax
	mov ds, word [dosentry_to_dosdata_segment]
%else
	xor ax, ax
	mov ds, ax
 assume ds:IVT
	mov ds, word [31h * 4 + 2]	; => DOSDATA
 assume ds:DOSGROUP
%endif
	cmp word [dosdata_to_doscode_segment], DOSCODE_HMA_SEGMENT
	jae .skip_doscode

%if 0
	mov ax, (doscode_end - doscode_start)
%else
extern afterdoscodelabel
	mov ax, afterdoscodelabel wrt DOSCODEGROUP
	sub ax, DOSCODE_HMA_OFFSET
%endif
	mov cl, S_DOSCODE
	call init2_alloc_s_mcb
 assume es:nothing

%if 0
	mov ax, 70h
	mov ds, ax			; => DOSENTRY
	mov ds, word [dosentry_to_dosdata_segment]	; => DOSDATA
%else
	xor ax, ax
	mov ds, ax
 assume ds:IVT
	mov ds, word [31h * 4 + 2]	; => DOSDATA
 assume ds:DOSGROUP
%endif
	mov bx, es
	sub bx, DOSCODE_HMA_OFFSET / 16	; start offset
	mov es, bx
 assume es:nothing
	mov di, DOSCODE_HMA_OFFSET	; es:di -> DOSCODE destination
	mov bx, word [dosdata_to_doscode_segment]
	mov ds, bx
 assume ds:DOSCODEGROUP
	mov si, di			; ds:si -> DOSCODE source
	shr cx, 1			; = number of words
	rep movsw			; move

%if 0
	mov ds, ax
	mov ds, word [dosentry_to_dosdata_segment]
%else
	mov ds, ax
 assume ds:IVT
	mov ds, word [31h * 4 + 2]	; => DOSDATA
 assume ds:DOSGROUP
%endif
	mov word [dosdata_to_doscode_segment], es
	mov ax, DOSENTRY
	mov ds, ax
 assume ds:DOSENTRYGROUP
 extern ..@dosentry_doscode_segment
	mov word [..@dosentry_doscode_segment], es
	not byte [cs:doscode_relocated_to_final_position]
 assume es:DOSCODEGROUP

	mov ax, ss
	cmp ax, bx
	jne @F
	push es
	pop ss
 assume ss:DOSCODEGROUP
@@:
 assume ss:nothing

	add bx, DOSCODE_HMA_OFFSET / 16	; start offset
	mov es, bx			; => DOSCODE source
 assume es:nothing
	mov ah, 49h
	int 21h				; free it

.skip_doscode:
.no_doscode_reloc:
%endif
 assume ds:nothing, es:nothing, ss:nothing

	pop bx
	mov ax, 5801h
	int 21h			; alloc strategy: restore

	lleave
	retn


init2_internal_error_reboot_prompt:
	call init2_disp_msg

init2_reboot_prompt:
	push cs
	pop ds
 assume ds:SYSINITGROUP
	mov si, init2_msg.reboot_prompt	; access with ds
	call init2_disp_msg
.loop:
	int3
	xor ax, ax
	int 16h
	jmp .loop


%if 0
	align 4
%include "RxDOSINI.ASM"

	align 16
init2_end:
	istruc MCB
at mcbSignature,	db "M"
at mcbOwner,		dw 8
at mcbSize,		dw (init3_end - init3_start) >> 4
at smcbName,		dw "S"
at smcbType,		db S_INITPSP
	iend

	; section INIT3 align=16 follows=INIT2 vstart=0
init3_start:
	istruc PSP
at pspCall0,		int 20h
at pspCall5,		call 0:(30h * 4)
at pspInt22,		dw init3_terminated, 0		; +2 segment here
at pspInt23,		dw i23, 70h
at pspInt24,		dw i24, 70h
at pspParent,		dw 0				; +0 segment here
at pspPHT,		times pspPHT_size db -1
at pspEnvironment,	dw 0				; +0 env segment here
at pspStack,		dw init3_stack.end - 32, 0	; +2 segment here
at pspPHTEntries,	dw pspPHT_size
at pspPHTAddress,	dw pspPHT, 0			; +2 segment here
at pspShareNext,	dd -1
at pspVersion,		dw RXDOS_MS_VERSION
at pspInt21Far,		int 21h
			retf
at pspCommandLine
	; iend
	%pop		; (pop off the istruc context)

init3_msg:
.internal_error_terminated:	db "init: Internal error, init PSP "
				asciz "has been terminated.",13,10
.reboot_prompt:		asciz "Halted. Press Ctrl+Alt+Del to reboot.",13,10

init3_terminated:
	cli
	cld
	mov ax, cs
	mov ss, ax
	mov sp, init3_stack.end
	sti

	push cs
	pop ds
	mov si, init3_msg.internal_error_terminated
init3_internal_error_reboot_prompt:
	call init3_disp_msg

init3_reboot_prompt:
	push cs
	pop ds
	mov si, init3_msg.reboot_prompt
	call init3_disp_msg
.loop:
	int3
	xor ax, ax
	int 16h
	jmp .loop

init3_disp_msg_asciz:
	push ds
	push si
	push ax
	 push cs
	 pop ds
	mov si, dx
	call init3_disp_msg
	pop ax
	pop si
	pop ds
	retn

init3_disp_msg:
	push ax
@@:
	lodsb
	test al, al
	jz @F
	call init3_disp_al
	jmp short @B

init3_disp_al:
	push ax
	push bx
	push bp
	mov ah, 0Eh
	mov bx, 7
	int 10h
	pop bp
	pop bx
@@:
	pop ax
	retn


	align 16
init3_stack:
	times 512 db 0
.end:

	align 16
init3_end:
	istruc MCB
at mcbSignature,	db "Z"
at mcbOwner,		dw 0
at mcbSize,		dw 0
	iend

%include "comload.asm"
%endif

	struc ALLOCDATA
adSMCBType:	resb 1
adSDSign:	resb 1
adHMCBName:	resb 8
	endstruc

%imacro allocdata 4.nolist
%1:
	istruc ALLOCDATA
at adSMCBType,	db %2
at adSDSign,	db %3
at adHMCBName,	db %4
	iend
%endmacro

	align 2, db 0
allocdata alloc_sft, S_SFT, DEVMARK_FILES, "SFT"
allocdata alloc_fcb, S_FCBSFT, DEVMARK_FCBS, "FCBSFT"
allocdata alloc_initfcb, S_INITFCBSFT, DEVMARK_FCBS, -1
allocdata alloc_buffers, S_CCB, DEVMARK_BUF, "Buffers"
allocdata alloc_cds, S_CDS, DEVMARK_CDS, "CDS"
allocdata alloc_stack, S_IRQSCODE, DEVMARK_STK, -1	; not for HMA
allocdata alloc_init, S_INIT, 0, -1
allocdata alloc_initcds, S_INITCDS, 0, -1
allocdata alloc_initconfig, S_INITCONFIG, 0, -1
allocdata alloc_initpsp, S_INITPSP, 0, -1
allocdata alloc_upb_temporary, S_UPB, 0, -1
allocdata alloc_upb, S_UPB, 0, "UPB"
alloc_dpb_temporary:
allocdata alloc_dpb_no_hma, S_DPB, 0, -1
allocdata alloc_dpb, S_DPB, 0, "DPB"
allocdata alloc_initstack, S_INITSTACK, 0, "IniStack"
allocdata alloc_dosdata, S_DOSDATA, 0, -1

global alloc_init, alloc_initcds, alloc_initconfig, alloc_dpb_no_hma
global alloc_upb_temporary, alloc_upb
global alloc_dpb_temporary, alloc_dpb
global allocate_relocate_block
global allocate_temporary_block
global allocate_temporary_block.large_allow_error

	align 2, db 0
	db 0			; pad so start is odd
smcb_template:
.:	db 0, 0, 0
	db "S", 0
	times 6 db 0
.end:
.size equ .end - .

		; INP:	cs:si -> data
		;	(dx:)ax = bytes needed
		; OUT:	es:di -> memory allocated, zeroed
		;	di = 0 if not in HMA
allocate_relocate_block:
 assume ds:nothing, es:nothing, ss:nothing
	xor dx, dx
.large:
 assume ds:nothing, es:nothing, ss:nothing
	db __TEST_IMM8		; skip stc, NC
.large_upper_only:
	stc
 assume ds:nothing, es:nothing, ss:nothing
	lframe
	lvar word, parasize
	lenter
	lvar word, bit0_upper_only
	 pushf
	lvar word, data
	 push si
	lvar dword, size
	 push dx
	 push ax

	cmp byte [cs:si + adHMCBName], -1
	je .nothma		; if cannot relocate to HMA -->
	test dx, dx		; >= 64 KiB ?
	jnz .nothma		; cannot fit in the HMA -->
	xchg bx, ax		; bx = how many bytes needed
	mov di, -1		; harden
	mov cx, 26h		; owner
	mov dl, 0		; allocate low in the largest free block
	mov ax, 4A03h
	int 2Fh			; allocate HMA memory block
 assume es:nothing
	cmp di, -1
	je .nothma
	add di, - HMCB_size + hmcbName
				; -> HMCB name field
	push cs
	pop ds
 assume ds:SYSINITGROUP
	add si, adHMCBName	; -> our data's name field
	mov cx, words(8)	; length of name
	rep movsw		; es:di -> at data again
	push di
	xor ax, ax
	mov cx, bx
	shr cx, 1		; amount words actually allocated
	rep stosw
	test di, di
	jz @F
	cmp di, 0FFF0h		; near end ?
	jae @F			; yes -->
	mov di, 0FFFEh		; no, address with segment 0FFFEh instead
	mov es, di		; es => HMA
 assume es:nothing
	pop di
	add di, 10h		; -> allocation in segment 0FFFEh
	db __TEST_IMM8		; (skip pop)
@@:
	pop di			; es:di -> allocated memory block
.ret_NC:
	clc
.ret:
	lleave code
	retn

.nothma:
	mov ax, 5800h
	int 21h			; get strategy
	push ax
	mov ax, 5801h
	mov bx, 0141h		; UMA only, best fit, ignore UMB link status
	int 21h			; set strategy

	mov dx, word [bp + ?size + 2]
	mov ax, word [bp + ?size]
	add ax, 15		; round up
	adc dx, 0
.err_CY:
	jc MEM_ERR

	mov cx, 4
@@:
	shr dx, 1
	rcr ax, 1
	loop @B
	test dx, dx		; >= 1 MiB ?
	stc
	jnz .err_CY		; error -->

	mov word [bp + ?parasize], ax
	xchg bx, ax
	mov ah, 48h
	int 21h
	jnc .success

	testopt [bp + ?bit0_upper_only], 1
	jz .trylma
	pop bx
	mov ax, 5801h
	int 21h			; restore strategy

	push cs
	pop ds
 assume ds:SYSINITGROUP
	stc
	jmp .ret

.trylma:
 assume ds:nothing
	mov ax, 5801h
	mov bx, 0180h		; UMA then LMA, first fit, ignore UMB link status
	int 21h			; set strategy

	mov bx, word [bp + ?parasize]
	mov ah, 48h
	int 21h
	jc .fail

.success:
	push ax			; => UMA memory block
	dec ax
	mov es, ax		; => UMCB
 assume es:MCB
	mov di, mcbReserved	; -> UMCB reserved field
	push cs
	pop ds
 assume ds:SYSINITGROUP
	mov al, byte [si + adSMCBType]
				; al = S MCB type
	mov si, smcb_template	; -> our template
	mov cx, smcb_template.size
	mov word [es:mcbOwner], 8
				; MCB owner = 8 (system)
	rep movsb
	mov byte [es:smcbType], al
				; set the type

	pop es			; => UMA memory block
 assume es:nothing
	xor di, di		; -> start of allocation
	mov cx, bx		; size in paras

	pop bx
	mov ax, 5801h
	int 21h			; restore strategy

	mov bx, cx		; = amount paragraphs

.ret_init_bx:
	push es
	push di

@@:
	mov cx, 8000h		; words in 64 KiB
	sub bx, 1000h		; another 64 KiB ?
	jae @F			; yes -->
	add bh, 10h		; restore bx
	xor cx, cx		; cx = 0
	xchg bx, cx		; bx = 0, cx = amount paragraphs
	shl cx, 1
	shl cx, 1
	shl cx, 1		; cx = amount words
@@:
	xor ax, ax
	rep stosw		; zero the memory
	mov ax, es
	add ax, 1000h
	mov es, ax		; => next segment (skip 64 KiB)
 assume es:nothing
	test bx, bx		; any remaining ?
	jnz @BB			; yes -->

	pop di
	pop es			; -> back at start
 assume es:nothing
	jmp .ret_NC

.fail:
	pop bx
	mov ax, 5801h
	int 21h			; restore strategy

	push cs
	pop ds
 assume ds:SYSINITGROUP
%if 0
	INVOKE	ROUND
	mov al, [si + adSDSign]
	; cmp al, 1
	; jb .err_CY
	call SetDevMark
	mov bx, [bp + ?parasize]
	les di, [ss:MEMLO]	; es:di -> allocated data block
	add word [ss:MEMHI], bx	; size in paragraphs
	or byte [ss:SetDevMarkFlag], FOR_DEVMARK
	INVOKE	ROUND		; allocate, will error out if not enough
	jmp .ret_init_bx
%else
 assume ds:nothing, es:nothing, ss:nothing
	jmp MEM_ERR
%endif

	lleave ctx


		; INP:	cs:si -> data
		;	(dx:)ax = bytes needed
		; OUT:	es:di -> memory allocated, zeroed
		;	di = 0
		;	ds = cs
allocate_temporary_block:
 assume ds:nothing, es:nothing, ss:nothing
	xor dx, dx
.large:
 assume ds:nothing, es:nothing, ss:nothing
	db __TEST_IMM8		; skip stc, NC
.large_allow_error:
	stc
 assume ds:nothing, es:nothing, ss:nothing
	lframe
	lvar word, parasize
	lenter
	lvar word, bit0_allow_error
	 pushf
	lvar word, data
	 push si
	lvar dword, size
	 push dx
	 push ax

	mov ax, 5800h
	int 21h			; get strategy
	push ax
	mov ax, 5801h
	mov bx, 0522h		; LMA then UMA as two areas,
				;  last fit, ignore UMB link status
	int 21h			; set strategy

	mov dx, word [bp + ?size + 2]
	mov ax, word [bp + ?size]
	add ax, 15		; round up
	adc dx, 0
.err_CY:
	jc MEM_ERR

	mov cx, 4
@@:
	shr dx, 1
	rcr ax, 1
	loop @B
	test dx, dx		; >= 1 MiB ?
	stc
	jnz .err_CY		; error -->

	mov word [bp + ?parasize], ax
	xchg bx, ax
	mov ah, 48h
	int 21h
	jnc .success
	test byte [bp + ?bit0_allow_error], 1
	stc
	jz .err_CY

	pop bx
	mov ax, 5801h
	int 21h			; restore strategy

	push cs
	pop ds
 assume ds:SYSINITGROUP
	stc
	jmp .ret

.success:
 assume ds:nothing, es:nothing, ss:nothing
	push ax			; => LMA memory block
	dec ax
	mov es, ax		; => LMCB
 assume es:MCB
	mov di, mcbReserved	; -> LMCB reserved field
	push cs
	pop ds			; ! ds = cs
 assume ds:SYSINITGROUP
	mov al, byte [si + adSMCBType]
				; al = S MCB type
	mov si, smcb_template	; -> our template
	mov cx, smcb_template.size
	mov word [es:mcbOwner], 8
				; MCB owner = 8 (system)
	rep movsb
	mov byte [es:smcbType], al
				; set the type

	pop es			; => LMA memory block
 assume es:nothing
	xor di, di		; -> start of allocation
	mov cx, bx		; size in paras

	pop bx
	mov ax, 5801h
	int 21h			; restore strategy

	mov bx, cx		; = amount paragraphs

	push es
	push di

@@:
	mov cx, 8000h		; words in 64 KiB
	sub bx, 1000h		; another 64 KiB ?
	jae @F			; yes -->
	add bh, 10h		; restore bx
	xor cx, cx		; cx = 0
	xchg bx, cx		; bx = 0, cx = amount paragraphs
	shl cx, 1
	shl cx, 1
	shl cx, 1		; cx = amount words
@@:
	xor ax, ax
	rep stosw		; zero the memory
	mov ax, es
	add ax, 1000h
	mov es, ax		; => next segment (skip 64 KiB)
 assume es:nothing
	test bx, bx		; any remaining ? (NC)
	jnz @BB			; yes -->

	pop di
	pop es			; -> back at start
 assume es:nothing
.ret:
	lleave
	retn
