
%if 0

Example of an application running in DPMI-allocated memory
Public Domain

%endif

%include "lmacros3.mac"


	cpu 386
	org 100h
	addsection R86M_MEM_SECTION, start=100h
	addsection DPMI_MEM_SECTION, vstart=0 follows=R86M_MEM_SECTION align=16

	usesection R86M_MEM_SECTION
	bits 16
r86m_start:
	pop ax			; get word saved on stack for COM files
	mov bx, sp
	shr bx, 4
	jnz .smallstack
	mov bx, 1000h		; it was a full 64 KiB stack
.smallstack:
	mov ah, 4Ah		; free unused memory
	int 21h
	xor ax, ax
	xchg ax, word [2Ch]
	mov es, ax
	mov ah, 49h
	int 21h			; free environment if any

	mov bx, 80h >> 4
	mov ah, 48h
	int 21h			; allocate memory for child process
	jc nomemory

		; ax:0-> DOS memory allocated for child process
	mov es, ax
	xor di, di		; es:di-> 
	xor si, si		; cs:si = ds:si -> our PSP
	mov cx, 128 >> 1	; PSP size
	rep movsw		; copy process segment prefix into newly allocated block
	dec ax
	mov ds, ax		; ds = MCB of allocated block
	inc ax			; ax = allocated block!
	mov word [ 8 ], "DP"
	mov word [ 8+2 ], "MI"
	mov word [ 8+4 ], "PS"
	mov word [ 8+6 ], "PC"	; Force MCB string to "DPMIPSPC"
	; mov word [ 1 ], ax	; Set owner to itself
	; (leave owner as us)

	mov es, ax		; es = new PSP
	mov dx, cs
	mov ds, dx		; ds = old PSP
	mov di, 18h
	mov cx, 20
	mov word [ es:32h ], cx
	mov word [ es:34h ], di
	mov word [ es:34h+2 ], ax	; fix the new PSP's PHT pointer
	mov word [ es:0Ah ], childterminated
	mov word [ es:0Ah+2 ], dx	; set termination address
	mov word [ es:16h ], dx	; set parent PSP to current one

				; The stack address set on the child's termination
				; is that of the last Int21 call to a usual function
				; (such as Int21.48 or .45) from the parent process.
				; All registers will be lost though, because we don't
				; build a fake stack frame.

	push di
	mov al, -1
	rep stosb		; initialize new PHT with empty entries
	pop di
	mov cx, word [ 32h ]	; = number of current process handles
	jcxz .phtdone		; (weird, but handle this correctly)
	cmp cx, 20
	jbe .normalpht
	mov cx, 20
.normalpht:
	lds si, [ 34h ]		; -> current process handle table
	xor bx, bx
.phtloop:
	mov dl, -1
	cmp byte [si+bx], dl	; source PHT handle used?
	je .phtnext		; no -->
	mov ah, 45h
	int 21h			; DUP
	jc nohandle
	xchg ax, bx
	xchg dl, byte [si+bx]	; get DUPed handle and kill PHT entry
	xchg ax, bx
	mov byte [es:di+bx], dl	; write into PHT of new process
.phtnext:
	inc bx			; next handle
	loop .phtloop
.phtdone:
	push cs
	pop ds

	mov bx, es
	mov ah, 50h
	int 21h			; set current PSP to new
	; mov ah, 1Ah
	; mov ds, 
	; mov dx, 80h
	; int 21h		; set DTA

		; Now executing in the child process's environment.
		; Terminating will return to label childterminated.

	mov ax, cs
	mov word [rmcallstruc0.cs], ax
	mov word [rmcallstruc0.ds], ax
	mov word [rmcallstruc0.es], ax
	mov word [rmcallstruc0.ss], ax
	pushf
	pop word [rmcallstruc0.flags]	; setup with RM values

	mov ax, 1687h
	int 2Fh
	test ax, ax		; DPMI host installed?
	jnz nohost
	push es			; save DPMI entry address
	push di
	test si, si		; host requires client-specific DOS memory?
	jz .nomemneeded		; no -->
	mov bx, si
	mov ah, 48h		; alloc DOS memory
	int 21h
	jc nomemory
	mov es, ax
.nomemneeded:
	mov si, msg.debuginfo
	call printstring
	int3
	mov bp, sp
	mov ax, 0001h		; start a 32-bit client
	call far [bp]		; initial switch to protected-mode
	jnc initsuccessful
initfailed:
	mov si, msg.initfailed
	jmp short rmerror
nohandle:
	cmp cx, 20
	je .no_handles_duplicated_yet
			; In this case, there may be some already duplicated
			;  file handles. Switch to the child process and then
			;  terminate it, in order to close the handles.
	mov word [ es:0Ah ], .half_initialised_child_terminated
				; set Parent Return Address to ours
	mov bx, es
	mov ah, 50h
	int 21h
	mov ax, 4C00h
	int 21h

.half_initialised_child_terminated:
	cld
.no_handles_duplicated_yet:
	push cs
	pop ds
	mov si, msg.nohandle
	jmp short rmerror
nohost:
	mov si, msg.nohost
	jmp short rmerror
nomemory:
	mov si, msg.nomemory
rmerror:
	call printstring
	mov ax, 4CFFh
	int 21h

initsuccessful:
initsuccessful_ofs equ (initsuccessful-$$+100h)
			; now in protected mode
	mov bx, cs
	mov cx, cs
	lar cx, cx
	shr cx, 8
	or ch, 40h		; make a 32-bit cs
	mov ax, 9
	int 31h
			; now in 32-bit PM
bits 32
now32bit:
now32bit_ofs equ (now32bit-$$+100h)
	mov word [ data.pspsel ], es
	push ds
	pop es

	mov esi, msg.welcome
	call printstring

	mov [r86m_end + dpmi_to_r86m_code_sel - dpmi_start], cs
	mov [r86m_end + dpmi_to_r86m_data_sel - dpmi_start], ds

	xor eax, eax
	mov cx, 2
	int 31h			; allocate 2 selectors
	jc @F
	xchg eax, ebx

	mov eax, 3
	int 31h
	jc @F

	mov dword [r86m_end + dpmi_dpmi_code_sel - dpmi_start], ebx
	push ebx
	add ebx, eax
	mov dword [r86m_end + dpmi_dpmi_data_sel - dpmi_start], ebx
	mov ax, 8
	or ecx, -1
	mov edx, ecx
	int 31h			; set limit to -1
	pop ebx
	jc @F

	mov ax, 8
	int 31h			; set limit to -1
	jc @F

	mov ax, 9
	mov cx, 1100_1111_1_11_1_1011b
	int 31h			; set to 32-bit readable code segment
	jc @F

	mov ax, 0501h
	mov bx, (dpmi_end - dpmi_start) >> 16
	mov cx, (dpmi_end - dpmi_start) & 0FFFFh
	int 31h
	jc @F

	push bx
	push cx
	pop dword [data.dpmi_block_address]
	push si
	push di
	pop dword [data.dpmi_block_handle]

	mov ebx, dword [r86m_end + dpmi_dpmi_data_sel - dpmi_start]
	mov edx, dword [data.dpmi_block_address]
	mov ecx, dword [data.dpmi_block_address + 2]
	mov ax, 7
	int 31h
	jc @F

	push ecx
	mov es, ebx
	xor edi, edi
	mov esi, r86m_end
	mov ecx, dwords(dpmi_end - dpmi_start)
	rep movsd
	pop ecx

	mov ebx, dword [r86m_end + dpmi_dpmi_code_sel - dpmi_start]
	mov ax, 7
	int 31h
	jc @F

	mov esi, msg.info2.1
	call printstring
	mov eax, dword [data.dpmi_block_address]
	call disp_eax_hex
	mov esi, msg.info2.2
	call printstring
	mov eax, ebx
	call disp_ax_hex
	mov esi, msg.info2.3
	call printstring
	mov eax, ebx
	call disp_ax_hex
	mov esi, msg.info2.4
	call printstring
	mov eax, ebx
	call disp_ax_hex
	mov esi, msg.info2.5
	call printstring

	int3
	push ebx
	push dpmi_start
	retf

@@:
	push ss
	pop ds
	mov esi, msg.error
	call printstring
	or eax, -1

pmend:
	push ss
	pop ds
	push eax
	mov ebx, dword [r86m_end + dpmi_dpmi_code_sel - dpmi_start]
	mov ax, 1
	int 31h
	mov ebx, dword [r86m_end + dpmi_dpmi_data_sel - dpmi_start]
	mov ax, 1
	int 31h
	mov edi, dword [data.dpmi_block_handle]
	mov esi, dword [data.dpmi_block_handle + 2]
	cmp edi, -1
	je @F
	mov ax, 0502h
	int 31h
@@:
	pop eax
	mov ah, 4Ch		; normal client exit
	int 21h

disp_eax_hex:
	rol eax, 16
	call disp_ax_hex
	rol eax, 16
disp_ax_hex:
	xchg al, ah
	call disp_al_hex
	xchg al, ah
disp_al_hex:
	rol al, 4
	call disp_al_nybble_hex
	rol al, 4
disp_al_nybble_hex:
	push eax
	and al, 0Fh
	add al, '0'
	cmp al, '9'
	jbe @F
	add al, -'9' -1 +'A'
@@:
	call disp_al
	pop eax
	retn

disp_al:
	push edx
	push eax
	mov dl, al
	mov ah, 2
	int 21h
	pop eax
	pop edx
	retn


bits 16

callrm:
	; int3			; int 03h or int3 traps (in real or V86 mode) don't cause any problems.
	int strict byte 03h
	xor ax, ax
	push ax
	push ax
	call -1:-1
.callback equ $-4
	pop ax
	pop ax
	; int3
	int strict byte 03h
	iret


		; Print a string with simple instructions. Don't use
		; pointers or instructions depending on the default operation
		; size, this is called in both 16- and 32-bit modes.
printstring.next:
	mov dl, al
	mov ah, 2
	int 21h
printstring:
	lodsb
	test al, al
	jnz .next
	retn


		; The (DPMIed) child process has terminated.
childterminated:
childterminated_ofs equ (childterminated-$$+100h)
	cld
	push cs
	pop ds
	mov ah, 4Dh
	int 21h
	mov si, msg.backsuccess
	test al, al
	jz .success
	mov si, msg.backerror
.success:
	call printstring
	mov ax, 4C00h
	int 21h			; terminate DOS process


	align 4
data:
.dpmi_block_address:	dd 0
.dpmi_block_handle:	dd -1
.pspsel:		dw 0

	align 4
		; This one is used to call down to our RM part
		; from the 32-bit PM code. All values are filled
		; in either here or in our RM initialization.
rmcallstruc0:
.edi:		dd 0
.esi:		dd 0
.ebp:		dd 0
		dd 0
.ebx:		dd 0
.edx:		dd 0
.ecx:		dd 0
.eax:		dd 0
.flags:		dw 0
.es:		dw 0
.ds:		dw 0
.fs:		dw 0
.gs:		dw 0
.ip:		dw callrm
.cs:		dw 0
.sp:		dw rmcallsp
.ss:		dw 0

	align 4
		; This one is utilized by the DPMI host and
		; our RM callback when called from RM. All
		; values are filled in by the DPMI host.
rmcallstruc1:
.edi:		dd 0
.esi:		dd 0
.ebp:		dd 0
		dd 0
.ebx:		dd 0
.edx:		dd 0
.ecx:		dd 0
.eax:		dd 0
.flags:		dw 0
.es:		dw 0
.ds:		dw 0
.fs:		dw 0
.gs:		dw 0
.ip:		dw 0
.cs:		dw 0
.sp:		dw 0
.ss:		dw 0

msg:
.backsuccess:	asciz "Child process terminated okay, back in real mode.",13,10
.backerror:	asciz "Child process terminated with error, back in real mode.",13,10
.error:		asciz "Error during allocation setup.",13,10
.info2.1:	asciz "DPMI allocation at "
.info2.2:	asciz "h.",13,10,"DPMI allocation entrypoint at "
.info2.3:	db "h:00000000h.",13,10
		asciz "Real mode procedure called at "
.info2.4:	db "h:",_8digitshex(dpmi_start.callingrm - dpmi_start),"h.",13,10
		asciz "DPMI allocation exit at "
.info2.5:	asciz "h:",_8digitshex(dpmi_start.return - dpmi_start),"h.",13,10

.nohost:	asciz "No DPMI host installed.",13,10
.nohandle:	asciz "No free DOS file handle for child process creation.",13,10
.nomemory:	asciz "Not enough DOS memory for child process creation or client initialization.",13,10
.initfailed:	asciz "DPMI initialization failed.",13,10
.debuginfo:	db "Protected mode breakpoint at ",_4digitshex(initsuccessful_ofs),"h.",13,10
		db "32-bit code segment breakpoint at ",_4digitshex(now32bit_ofs),"h.",13,10
		db "Return from child process at ",_4digitshex(childterminated_ofs),"h.",13,10
		asciz 13,10
.welcome:	asciz "Welcome in 32-bit protected mode.",13,10

	align 16
	times 256 db 26h
	align 2
rmcallsp:

	align 16
r86m_end:
	usesection DPMI_MEM_SECTION
	bits 32
dpmi_start:
	nop
	mov ds, [cs:dpmi_dpmi_data_sel]
	mov esi, dpmi_msg.hello
	call dpmi_printstring

	mov ax, 0303h
	 push cs
	 pop ds
	mov esi, dpmi_callback	; ds:esi-> called procedure
	 push ss
	 pop es
	mov edi, rmcallstruc1	; es:edi-> (inreentrant) real mode call structure
	int 31h			; allocate RM callback
	 push ss
	 pop ds
	jc .nocallback
	mov dword [callrm.callback], edx
	mov word [callrm.callback+2], cx

	mov ax, 0302h
	xor ebx, ebx
	xor ecx, ecx
	mov edi, rmcallstruc0
.callingrm:
	int 31h			; call RM procedure with interrupt stack
	nop
	jc .callrmfailed

	mov ecx, dword [callrm.callback+2]
	mov edx, dword [callrm.callback]
	mov ax, 0304h
	int 31h			; free RM callback
	mov ds, [cs:dpmi_dpmi_data_sel]
	mov esi, dpmi_msg.bye
	call dpmi_printstring
.return:
	nop
	xor eax, eax
.ret_al:
	jmp far [cs:dpmi_return_address]


.callrmfailed:
	mov esi, dpmi_msg.callrmfailed
	jmp short @F
.nocallback:
	mov esi, dpmi_msg.nocallback
@@:
	push cs
	pop ds
	call dpmi_printstring
	push ss
	pop ds
	movzx ecx, word [callrm.callback+2]
	movzx edx, word [callrm.callback]
	mov eax, edx
	and eax, ecx
	inc ax
	jnz @F
	mov ax, 0304h
	int 31h			; free RM callback
@@:
	or eax, -1
	jmp .ret_al


		; This is a RM callback. Called from RM, executed in PM.
		;
		; INP:	es:edi-> RM call structure
		;	ds:esi-> RM stack
		;	ss:esp-> DPMI host internal stack
		; CHG:	all (?) except es:edi
		;	read/write RM call structure to communicate with RM code
dpmi_callback:
	push dword [esi]
	pop dword [es:edi+2Ah]	; set RM cs:ip
	add word [es:edi+2Eh], byte 4	; pop 4 byte from the stack
	int strict byte 03h
	nop
	int3
	nop
	iret


		; Print a string with simple instructions.
dpmi_printstring.next:
	mov dl, al
	mov ah, 2
	int 21h
dpmi_printstring:
	lodsb
	test al, al
	jnz .next
	retn


dpmi_msg:
.hello:		asciz "Hello from DPMI memory section!",13,10
.nocallback:	asciz "Could not allocate real mode callback.",13,10
.callrmfailed:	asciz "Calling real mode procedure failed.",13,10
.bye:		asciz "Calling real mode procedure which called callback successful.",13,10


	align 4
dpmi_return_address:	dd pmend
dpmi_to_r86m_code_sel:	dd 0
dpmi_to_r86m_data_sel:	dd 0
dpmi_dpmi_code_sel:	dd 0
dpmi_dpmi_data_sel:	dd 0

	align 16
dpmi_end:
