
%if 0

lDOS DOSENTRY handling
 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

%if 0

(lMS-DOS: Comments not current)

The DOSENTRY section is always located at segment 60h. It includes some data
structures found in MS-DOS (most notably the Int19 interrupt restoration table
at 70h:100h), the device headers for all default devices (except NUL) and all
code entries into the DOS code. If the DOS code is not relocateable, DOSENTRY
and DOSCODE are merged into one section.

Relocated entries which are in DOSENTRY for relocation:
Interrupt 00h		Division error exception
Interrupt 06h		Invalid opcode exception
Interrupt 13h		BIOS disk services
Interrupt 19h		Reboot
Interrupt 1Bh		Ctrl-Break notification
Interrupt 20h		Terminate program
Interrupt 21h		DOS services
Interrupt 25h		Absolute disk read
Interrupt 26h		Absolute disk write
Interrupt 27h		Terminate and stay resident
Interrupt 28h		Idle
Interrupt 29h		Display character
Interrupt 2Fh		Multiplex, DOS services
CALL 5			CP/M compatibility services (subset of interrupt 21h)
Device strategy		All device functions
Case map function	Maps language-specific characters >=80h to uppercase
Redirector mapper	Maps unhandled redirector calls to MS-DOS interface

If DOSCODE is not the same section as DOSENTRY (if DOSCODE is relocateable),
each of the relocated entries calls relocatedentry with a parameter behind the
near call opcode. The near call opcode and the byte sized parameter for each
entry need 4 byte. relocatedentry insures that DOSCODE is available (switches
the A20 line on if DOSCODE is in the HMA) and jumps to the DOSCODE function
relocated_relocatedentry. The latter uses the in-code parameter (behind the
function call within DOSENTRY) as a displacement into doscode_entrypoint_list,
where we store the target near address in DOSCODE.

If the target DOSCODE near address is equal to DEVStrategy, then the entry in
doscode_entrypoint_list contains another word (after the DEVStrategy address)
which points at a device strategy function dispatch table, stored in DOSCODE.
Some of the device strategies also include another byte after the table offset,
which gives the device unit to use (only used for PRN/LPTx and AUX/COMx).

It is insured that an entry of doscode_entrypoint_list with displacement
equal to 0CCh is not used, so that relocated_relocatedentry can reliably
detect breakpoints set after the call jumps. Such a breakpoint hides the true
displacement value and thus must be detected and circumvented.

Entries which are in DOSENTRY but don't execute code in DOSCODE:
Interrupt 23h		Always terminates application
Interrupt 24h		Always fails call
Interrupt 2Eh		Always reports error
Device interrupt	MS-DOS device compatibility (does nothing)
Dummy file sharer	Always returns error 0001h (Invalid function)
Dummy redirector	Always returns error 0001h (Invalid function)
Other hooked interrupts	Do nothing

%endif

usesection DOSENTRY

%assign _FATFS			0
%assign _MAPPER			0
%assign _INT28			0
%assign _CALL5			1


%define ENTRYPOINT_LIST .none,dw "",dw "",db "",db ""

	%imacro _relocate 1-4.nolist "","",""
	call relocatedentry
	retf			; help debugging. handler will reflect back
				;  to the stub here with the modified stack.
add_to_entrypoint_list %%label,dw %1,dw %2,db %3,db %4,ENTRYPOINT_LIST
	extern %1

	db %%label - doscode_entrypoint_list
	%endmacro
%if _RELOCATEDOSCODE
 %idefine relocate _relocate
%endif
%idefine devicerelocate _relocate

	%imacro add_to_entrypoint_list 5-*.nolist
%if %0 % 5
 %fatal Expected a number of arguments that is a multiple of five
%endif
%push
%define %$label	%1
%define %$two	%2
%define %$three	%3
%define %$four	%4
%define %$five	%5
%assign %$found	0
%rotate 5
%rep (%0 - 5) / 5
 %ifn %$found
  %ifidn	%$two,	%2
   %ifidn	%$three,%3
    %ifidn	%$four,	%4
     %ifidn	%$five,	%5
%$label: equ %1
      %assign %$found 1
     %endif
    %endif
   %endif
  %endif
 %endif
 %rotate 5
%endrep
%ifn %$found
 %xdefine ENTRYPOINT_LIST ENTRYPOINT_LIST,%$label,%$two,%$three,%$four,%$five
%endif
%pop
	%endmacro

	%imacro write_entrypoint_list 0-*.nolist
	align 2
doscode_entrypoint_list:
%if %0 % 5
 %fatal Expected a number of arguments that is a multiple of five
%endif
%rep %0 / 5
	align 2
; %if ($ - doscode_entrypoint_list) == 0CCh
;  %warning added word to doscode_entrypoint_list to find bp after call jump
;	dw 9090h
; %endif
%1:		; label (used as displacement from doscode_entrypoint_list)
	%2	; entrypoint (word)
	%3	; device function table, if any (word)
	%4	; device unit number, if any (byte)
	%5	; second device unit number, if any (byte)
%rotate 5
%endrep
doscode_entrypoint_list.end:
	%endmacro


 assume cs:nothing, ds:nothing, es:nothing, ss:nothing
%if _RELOCATEDOSCODE
 %if _DOSCODEHMA
	nop	; ensure ri00p - 1 still reaches correct handler
 %endif
i00:	db __TEST_IMM8		; skip stc, NC
i19:	stc
	relocate relocated_i00_i19
; i06:	relocate relocatedi06
msdisk_i13:	relocate relocatedmsdisk_i13
ms96tpi_i13:	relocate relocatedms96tpi_i13
i1B:	relocate relocatedi1B
i20:	xor ax, ax
i21:	relocate relocatedi21
i25:	db __TEST_IMM8		; skip stc, NC
i26:	stc
	relocate relocated_i25_i26
i27:	relocate relocatedi27
 %if _INT28
i28:	relocate relocatedi28
 %endif
i29:	relocate relocatedi29
i2F:	relocate relocatedhead_i2F
call5:	relocate relocatedcall5
i31:	db __TEST_IMM8		; skip stc, NC
i6C:	stc
	relocate relocated_i31_i6C
 %if _FATFS
fatfs:	jmp short .jump
.next:	jmp FIXDOSENTRY:mapper
.jump:	relocate relocatedfatfs
fatfs2:	jmp short .jump
.next:	jmp FIXDOSENTRY:NotHooked
.jump:	relocate relocatedfatfs2
 %endif
 %if _MAPPER
mapper:	relocate relocatedmapper
 %endif
casemap:relocate relocatedcasemap
fastentry: relocate relocatedfastentry
ifsentry: relocate relocatedifsentry
okcallentry:	db __TEST_IMM8		; skip stc, NC
badcallentry:	stc
		relocate relocated_okcallentry_badcallentry
%endif


i23:
	stc
entry_retf:
	retf

i24:
	mov al, 03h
entry_iret:
	iret

a20off_entry:
 %if _RELOCATEDOSCODE && _DOSCODEHMA
	mov ah, 06h		; Local Disable A20 (04h would be Global)
	 push cs
	call transfer_xmsentry
				; ignore errors
	mov ah, 04h		; Global Disable A20
	 push cs
	call transfer_xmsentry
				; ignore errors
 %endif
	pop bx			; get bx and ax from stack
	pop ax
	iret			; pop ip, cs, fl

 global run_int21_shell
run_int21_shell:
 assume ds:nothing, es:nothing, ss:nothing
	int 21h
 %if _RELOCATEDOSCODE
	relocate relocatedshellreturned
 %else
	jmp shellreturned
 %endif

 %if _RELOCATEDOSCODE && _DOSCODEHMA
transfer_xmsentry:		; far jumps to XMS entrypoint
    	jmp DOSENTRY:entry_retf
dosentry_xmsentry: equ $ - 4	; patched to hold XMS entrypoint address
 %endif

 %if _RELOCATEDOSCODE
global i20, i21, i25, i26, i27, i2F, call5
global fastentry, ifsentry, okcallentry, badcallentry, i00, casemap, i31
global i19, msdisk_i13, ms96tpi_i13
global i29, i1B
global i6C
 %endif
global i23, i24, entry_iret
global a20off_entry
global entry_iret
global conentry, prnentry, auxentry
global com1entry, com2entry, com3entry, com4entry
global lpt1entry, lpt2entry, lpt3entry, clockentry, blockentry

%if 0
	%imacro irtentry 1.nolist
db %1
dd 0
	%endmacro


%assign num 256-($-$$)
%warning num bytes in front of IRT

	_fill 256, 0, dosentry_start
InterruptRestorationTable:
	irtentry 10h
	irtentry 13h
	irtentry 15h
	irtentry 19h
	irtentry 1Bh
		; Above interrupts are in the order MS-DOS 6.x saves them
		;  (FDEMM386?/Jemm expects Int19 within the first five entries)

	irtentry 00h
	irtentry 01h
	irtentry 03h
	irtentry 04h
	irtentry 06h
	irtentry 08h
	irtentry 16h
	irtentry 1Ch
	irtentry 1Eh
	irtentry 21h
	irtentry 23h
	irtentry 24h
	irtentry 29h
	irtentry 2Fh
	irtentry 33h
	db -1
%endif


 %if _RELOCATEDOSCODE
extern dosdata_to_doscode_segment
 %endif

; %if _INT19_IN_DOSENTRY
;  %if _RELOCATEDOSCODE
; i19:
;  %endif
; %include "int19.asm"
; %endif

%if _RELOCATEDOSCODE && _DOSCODEHMA
		; 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
dosentry_check_a20:
 assume cs:DOSENTRYGROUP, ds:nothing, es:nothing, ss:nothing
	cld
	push es
	push di
	push cx
	xor di, di
	mov es, di		; es = 0000h
 assume es:IVT
	dec di
	mov ds, di		; ds = FFFFh
 assume ds:nothing
	inc di			; es:di = 0000h:0000h =  00000h
	mov si, 0010h		; ds:si = FFFFh:0010h = 100000h
				;  (same address if bit 20 off)
	mov cx, si		; 32 byte (16 = 10h word)
	push si
	repe cmpsw		; compare, A20 line switched on if differing
	pop si			; -> FFFFh:0010h = 10_0000h
				;  (in the HMA, part of the VDISK header)
	jne .ret		; differing -->
	xor di, di		; -> 0000h:0000h = 00_0000h
				;  (in the LMA, offset word of int 00h handler)
	cli			; try not to run interrupt handlers during this
	push word [si]		; save value
	dec word [si]		; change value (in HMA, or wrapped around LMA)
	cmpsw			; compare values, NZ if A20 is switched on
	pop word [si - 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


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

dosentry_disp_msg_cs:
 assume ds:nothing, es:nothing, ss:nothing
	cs lodsb
	test al, al
	jnz @B
	retn


dosentry_msg:
.a20_error_common:	asciz "DOSENTRY A20 error: XMS call "
.a20_error_xms:		asciz "failed"
.a20_error_actual:	asciz "reported success but A20 is still off"
.a20_error_after:	asciz ".",13,10,"System halted. "
%endif


		; INP:	byte [cs:ip + 1] = 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
		; OUT:	Jumps to DOSCODE:(relocated entry address)
		;	If the relocated entry address is DEVStrategy,
		;	 ss:sp-> device unit or garbage (word),
		;		 pointer to device function table (word)
		; CHG:	- (not even flags!)
		; STT:	-
%ifn _RELOCATEDOSCODE && _DOSCODEHMA
 times DOSENTRYDEVICEBASE * 16 - ($ - $$) db 26h
%endif

%if ($ - $$) < DOSENTRYDEVICEBASE * 16
 %error Entrypoint must be above or equal device base
%endif

relocatedentry:
 assume cs:nothing, ds:nothing, es:nothing, ss:nothing
	push cs
	jmp DOSENTRY:.fixsegment				; insure cs = 70h
.fixsegment:
 assume cs:DOSENTRYGROUP

	push ax
	push ds

%if _RELOCATEDOSCODE
 %if _DOSCODEHMA
 global ..@dosentry_patch_hma
..@dosentry_patch_hma:
	jmp strict short .not_in_hma	; patch to a 2-byte NOP if in HMA

		; DOSCODE is in HMA: check A20 and try to enable if disabled
		; INP:	-
		; CHG:	ds

	pushf
	push si
		; INP:	-
		; OUT:	A20 enabled
		; CHG:	ax, si, fl, ds
	call dosentry_check_a20
	jne .hma_available	; not equal, A20 line is switched on -->

	mov ah, 05h		; Local Enable A20 (03h would be Global ?)
	push bx
	 push cs
	call transfer_xmsentry	; request local enable A20
	pop bx			; may return an error code in bl
	dec ax			; 0001h if successful
	mov ax, dosentry_msg.a20_error_xms	; access with cs
	jnz .error		; was not 0001h -->

	call dosentry_check_a20	; check again
	jne .hma_available	; not equal, A20 line is now switched on -->

	mov ax, dosentry_msg.a20_error_actual	; access with cs
.error:
	push ax
	mov si, dosentry_msg.a20_error_common	; access with cs
	call dosentry_disp_msg_cs
	pop si
	call dosentry_disp_msg_cs
	mov si, dosentry_msg.a20_error_after	; access with cs
	call dosentry_disp_msg_cs
.loop:
	int3
	xor ax, ax
	int 16h
	jmp .loop


.hma_available:
	pop si
	popf
.not_in_hma:
 %endif

%if 1
	mov ax, 0			; patched to point to DOSCODE
 global ..@dosentry_doscode_segment
..@dosentry_doscode_segment equ $ - 2
%else
%if 0
	mov ds, word [cs:dosentry_to_dosdata_segment]	; => DOSDATA
 assume ds:DOSGROUP
%else
	mov ax, 0					; ! preserve fl
	mov ds, ax
 assume ds:IVT
	mov ds, word [31h * 4 + 2]			; => DOSDATA
 assume ds:DOSGROUP
%endif
	mov ax, word [dosdata_to_doscode_segment]	; => DOSCODE
%endif
%else
	mov ax, cs
%endif

 assume ds:nothing
		; ax => DOSCODE segment

		; have ss:sp ->           (bp), ds, ax, cs, ip, original
		; want ss:sp -> tip, tcs, cip, ccs, ax, cs, ip, original
	lframe 0
	; above this original stack
	lpar word, in_tip		; transfer stub ip
	lpar word, in_tcs		; transfer stub cs
	lpar word, in_ax		; original ax, passed to relocated_
	lpar word, in_ds_out_ccs	; original ds, gets DOSCODE cs
	lenter
	lequ ?frame_bp, in_bp_out_cip	; original bp, gets DOSCODE ip
	 push word [bp + ?in_tcs]
	 push word [bp + ?in_tip]
	lvar dword, out_tcsip		; transfer cs:ip (help debugging)
	xchg ax, word [bp + ?in_ds_out_ccs]
					; => DOSCODE
	mov ds, ax			; restore ds
 assume ds:nothing
	mov ax, relocated_relocatedentry
	xchg ax, word [bp + ?in_bp_out_cip]
					; ax = original bp, ?out_cip -> entrypoint
	lleave ctx

		; INP:	ss:(sp + 6) -> arbitrary stack of entrypoint
		;	word [ss:sp + 4] = entrypoint's ip,
		;	word [ss:sp + 2] = entrypoint's cs,
		;	word [ss:sp + 0] = original ax value
	retf				; control flow goes back to transfer stub
					;  which has a retf to go to DOSCODE
