
; Public Domain

; test install command: install; devices; devices uninstall
; test run command: ; .; .

%include "lmacros3.mac"
%include "eld.mac"
%include "eldcall.mac"
%include "elddata.mac"


	cpu 8086

	addsection RELOCATEDDATA, nobits vstart=_ELD_RELOC_VSTART
relocateddata:

	addsection HEADER, start=0

	istruc ELD_HEADERX
at eldhxHeader
		; ELD executable header
	istruc ELD_HEADER
at eldhSignature,	db "ELD1"
			db 0,0,0
			db 26
at eldhCodeOffset,	dd CODEOFFSET
at eldhCodeImageLength,	dw code_size
at eldhCodeAllocLength,	dw 0
at eldhDataOffset,	dd DATAOFFSET
at eldhDataImageLength,	dw data_size
at eldhDataAllocLength,	dw total_data_size - data_size
at eldhCodeEntrypoint,	dw linker - code
at eldhReserved
at eldhExtensionSize,	dw header_extension_end - $$
	iend
at eldhxDescriptionOffset,	dd description
PUT_ELDHX_DATETIME_OFFSET
header_extension_end:
	iend

description:		asciz "List device driver headers and DPBs."


	align 16, db 0

	eldstrict on

CODEOFFSET equ $ - $$
	addsection CODE, follows=HEADER vstart=_ELD_CODE_VSTART
%define CODEFIXUP - code + 0
code:
		; ELD instance header
	istruc ELD_INSTANCE
at eldiStartCode
at eldiEndCode
at eldiStartData
at eldiEndData
at eldiIdentifier,	fill 8, 32, db "DEVICES"
at eldiListing,		asciz _ELD_LISTING
	iend


command:
	jmp strict short .entry
.chain:
	extcall cmd3_not_ext, required	; must NOT be extcallcall
	times 10 - ($ - command) nop
.entry:
	push si
	cmp al, '-'
	jne @F
	extcallcall skipcomma
@@:
	dec si

reloc	mov dx, msg.devices
internaldatarelocation
	extcallcall isstring?
	je .ours
	pop si
	dec si
	lodsb
	jmp .chain

.ours:
	pop ax
	extcallcall skipcomma
	dec si
reloc2	mov word [relocateddata], relocateddata
linkdatarelocation lastcmd, -4
linkdatarelocation dmycmd
reloc	mov dx, relocateddata
linkdatarelocation msg.uninstall
	extcallcall isstring?
	je uninstall

	call run
	extcallcall cmd3

uninstall:
	lodsb
	extcallcall chkeol

	call get_es_ext

	push es
	pop ds
	xor bx, bx		; = 0 (no prior, modify ext_command_handler)
reloc	mov di, command		; di -> us
internalcoderelocation
reloc	mov si, word [ss:relocateddata]
linkdatarelocation ext_command_handler
				; si -> first
	test si, si		; none installed ?
	jz .error		; error -->

.loop:
	cmp di, si		; found ?
	je .bx			; yes, use bx -->
	mov bx, si		; bx -> prior handler
	lodsw			; skip entrypoint jmp strict short
	lodsb			; get first byte of chainer
	cmp al, 0E9h		; expecting jmp near ?
	jne .error		; no, error -->
	lodsw			; get rel16 displacement
	add si, ax		; -> next handler
	jmp .loop

.bx:
	test bx, bx		; any prior ?
	jnz .bxnz		; yes -->
	scasw			; skip entrypoint jmp strict short
	cmp byte [di], 0E8h	; is it a call to cmd3_not_ext ?
	jne @F			; no -->
				; yes, reset ext_command_handler to zero
.setbx:
reloc	mov word [ss:relocateddata], bx
linkdatarelocation ext_command_handler
	jmp .done

@@:
	cmp byte [di], 0E9h	; validate
	jne .error		; failure -->
	inc di			; -> rel16 displacement
	mov bx, word [di]	; get displacement
	scasw			; -> after jmp near
	add bx, di		; -> next handler
	jmp .setbx		; set ext_command_handler to next

.bxnz:
	mov si, bx		; -> prior handler with us as downlink
	xchg di, si		; si -> ours, di -> prior
	cmpsw			; skip entrypoint jmp strict short
	movsb			; copy 0E8h/0E9h
	lodsw			; ax = near rel16 displacement
	add ax, si		; add in our base (= absolute offset)
	sub ax, di
	dec ax
	dec ax			; subtract new base (= relative displacement)
	stosw			; store new rel16 displacement
	movsw			; jmp strict short
	movsw			; linkcall target
	movsb			; trailer
.done:
reloc	clropt [code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as free
reloc	mov dx, msg.uninstall_done
internaldatarelocation
@@:
	push ss
	pop ds
	extcallcall putsz
	extcallcall cmd3	; return

.error:
	mov ax, 0E01h
	extcallcall setrc
reloc	mov dx, msg.uninstall_error
internaldatarelocation
	jmp @B


get_es_ext:
reloc	mov es, word [relocateddata]
linkdatarelocation extdssel
	extcallcall ispm
	jz @F
reloc	mov es, word [relocateddata]
linkdatarelocation extseg
@@:
	retn


run:
	push ss
	pop es

	xor ax, ax
reloc	mov word [anyverbose], ax
internaldatarelocation		; also writes verbose_dpb
reloc	mov word [verbose_block], ax
internaldatarelocation		; also writes verbose_char
.verbose_loop:
	extcallcall skipcomma
	extcallcall iseol?
	je .verbose_done
	dec si

reloc	mov bx, keywordtable
internaldatarelocation
@@:
	mov dx, word [bx]
	test dx, dx
	jz .error
	extcallcall isstring?
	je @F
	add bx, 4
	jmp @B

@@:
	mov bx, word [bx + 2]
	mov byte [bx], 0FFh
reloc	mov byte [anyverbose], 0FFh
internaldatarelocation -3
	jmp .verbose_loop

.error:
	extcallcall error

.verbose_done:

display_devices:
	extcallcall InDOS
	jnz error

	xor dx, dx
	mov ah, 52h
	call doscall_dx_for_es	; es:bx -> list of lists
	call setds2dx

	mov cx, 100h
.loopnul:
	cmp bx, -8
	ja .nextnul
	push cx
	mov si, bx
reloc	mov di, msg.nulsign
internaldatarelocation
	mov cx, words(8)
	repe cmpsw
	pop cx
	je .foundnul
.nextnul:
	inc bx
	loop .loopnul
.nonul:
	push ss
	pop ds
reloc	mov dx, msg.nonul
internaldatarelocation
	extcallcall putsz
	retn

.foundnul:
	sub bx, 4 + 2 + 2 + 2		; -> NUL header (link, attr, strat, int)

	push ss
	pop ds

reloc	mov di, relocateddata
linkdatarelocation line_out
reloc	mov si, msg.first
internaldatarelocation
	extcallcall copy_single_counted_string

	mov ax, dx
	extcallcall hexword
	mov ax, "h:"
	stosw
	mov ax, bx
	extcallcall hexword
	mov al, 'h'
	stosb

	push dx
	push bx
	extcallcall putsline_crlf
	pop bx
	pop dx

.loop:
	cmp bx, -1
	jne @F
	retn

@@:
reloc	mov di, relocateddata
linkdatarelocation line_out

	mov ax, 2020h
	stosw

	mov ax, dx
	extcallcall hexword

	mov ax, "h:"
	stosw

	mov ax, bx
	extcallcall hexword

	call setds2dx
	mov ax, [bx + 4]
	push ss
	pop ds

	test ax, 8000h
	jz .blockearly
.charearly:
reloc	mov si, msg.eachdev.1.char
internaldatarelocation
reloc	rol byte [anyverbose], 1
internaldatarelocation
	jnc @F
reloc	rol byte [verbose_char], 1
internaldatarelocation
	jnc .next
	jmp @F

.blockearly:
reloc	mov si, msg.eachdev.1.block
internaldatarelocation
reloc	rol byte [anyverbose], 1
internaldatarelocation
	jnc @F
reloc	rol byte [verbose_dpb], 1
internaldatarelocation
	jc @F
reloc	rol byte [verbose_block], 1
internaldatarelocation
	jnc .next
@@:
	extcallcall copy_single_counted_string

	extcallcall hexword

reloc	mov si, msg.eachdev.2
internaldatarelocation
	extcallcall copy_single_counted_string

	call setds2dx
	push word [bx + 4]
	mov ax, [bx + 6]
	 push word [bx + 8]
	push ss
	pop ds

	extcallcall hexword

reloc	mov si, msg.eachdev.3
internaldatarelocation
	extcallcall copy_single_counted_string

	 pop ax
	extcallcall hexword

	pop ax
reloc	mov si, msg.eachdev.4.block
internaldatarelocation
	test ax, 8000h
	jz @F
reloc	mov si, msg.eachdev.4.char
internaldatarelocation
@@:
	extcallcall copy_single_counted_string

	call setds2dx
	lea si, [bx + 10]
	test ax, 8000h
	jz .block
.char:
	mov cl, 4
	rep movsw
	push ss
	pop ds
	mov cl, 8
@@:
	dec di
	cmp byte [di], 32
	loope @B
	je @F
	inc di
@@:
	mov al, '"'
	stosb
	push dx
	push bx
	extcallcall putsline_crlf
	jmp .common

.block:
	xor ax, ax
	lodsb
	push ss
	pop ds
reloc	mov word [unitsamount], ax
internaldatarelocation
	extcallcall decword

reloc	mov si, msg.eachdev.5.block.singular
internaldatarelocation
	cmp al, 1
	je @F
reloc	mov si, msg.eachdev.5.block.plural
internaldatarelocation
@@:
	extcallcall copy_single_counted_string
	push dx
	push bx
	extcallcall putsline_crlf

reloc	rol byte [anyverbose], 1
internaldatarelocation
	jnc @F
reloc	rol byte [verbose_dpb], 1
internaldatarelocation
	jnc .common
@@:

	mov ax, 3000h
	int 21h
	mov di, 12h
	cmp al, 4
	jb @F
	inc di			; = 13h
@@:
	xor dx, dx
	mov ah, 52h
	call doscall_dx_for_es	; es:bx -> list of lists
	call setds2dx
	call ldsbxbx_dx
reloc	mov word [ss:firstdpb], bx
internaldatarelocation
reloc	mov word [ss:firstdpb + 2], dx
internaldatarelocation
	xor ax, ax
.finddpb:
reloc	mov cx, word [ss:unitsamount]
internaldatarelocation
	jcxz .common_j

.loopdpb:
reloc	mov word [ss:dpbsegment], dx
internaldatarelocation
	pop si
	pop dx
	push dx
	push si
	cmp bx, -1
	je .nodpb
	cmp byte [bx + 1], al		; device unit number
	jne .nextdpb
	cmp word [bx + di], si		; 12h v3, 13h v4+
	jne .nextdpb
	cmp word [bx + di + 2], dx	; device header
	jne .nextdpb

.founddpb:
	push di
	push ax				; low byte = unit
	push word [bx]			; low byte = drive, A: = 0
	push bx

	push ss
	pop es
	push ss
	pop ds

reloc	mov di, relocateddata
linkdatarelocation line_out

reloc	mov si, msg.dpb.1
internaldatarelocation
	extcallcall copy_single_counted_string

	extcallcall hexbyte		; unit

reloc	mov si, msg.dpb.2
internaldatarelocation
	extcallcall copy_single_counted_string

reloc	mov ax, word [dpbsegment]
internaldatarelocation
	extcallcall hexword		; segment

	mov ax, "h:"
	stosw

	pop ax
	extcallcall hexword		; offset

reloc	mov si, msg.dpb.3
internaldatarelocation
	extcallcall copy_single_counted_string

	pop ax				; drive
	add al, 'A'
	mov ah, ':'
	stosw

	extcallcall putsline_crlf
	pop ax
	pop di
	jmp .nextunit

.common_j:
	jmp .common

.nextunit:
	push ss
	pop ds
reloc	dec word [unitsamount]
internaldatarelocation
	inc ax
reloc	mov bx, firstdpb
internaldatarelocation
	call ldsbxbx_dx
	jmp .finddpb

.nextdpb:
	lea bx, [bx + di - 12h + 18h]	; 18h v3, 19h v4+
	call ldsbxbx_dx
	jmp .loopdpb

.nodpb:
	push di
	push ss
	pop es
	push ss
	pop ds

reloc	mov di, relocateddata
linkdatarelocation line_out

reloc	mov si, msg.nodpb.1
internaldatarelocation
	extcallcall copy_single_counted_string

	extcallcall hexbyte

reloc	mov si, msg.nodpb.2
internaldatarelocation
	extcallcall copy_single_counted_string

	push ax
	extcallcall putsline_crlf
	pop ax
	pop di
	jmp .nextunit


.next:
	push dx
	push bx
.common:
	 push ss
	 pop ds
reloc	dec word [devicesamount]
internaldatarelocation
	pop bx
	pop dx
	jz .done
	call setds2dx
	call ldsbxbx_dx
	push ss
	pop ds
	jmp .loop

.no_device_driver:
.done:
	push ss
	pop ds
	retn


doscall_dx_for_es:
	extcallcall ispm
	jnz .rm

subcpu 286
	push dx
	push word 21h
	extcall intcall_ext_return_es, PM required	; must NOT be extcallcall
	pop dx				; discard interrupt number
	pop dx				; get es
subcpureset
	jmp @F
.rm:
	mov es, dx
	int 21h
	mov dx, es
@@:
	push ss
	pop es
	retn


ldsbxbx_dx:
	mov dx, [bx + 2]
	mov bx, [bx]
	call setds2dx
	retn


setds2dx:
	push es
	extcallcall setes2dx
	 push es
	 pop ds
	pop es
	retn


	eldcall_dump_callcall ELDCALL_CALLCALL_LIST

endinstalled equ ($ + CODEFIXUP + 15) & ~15


start:
	mov bx, es
	 push ss
	 pop es
	call skipcomma
	dec si
reloc	mov dx, relocateddata
linkdatarelocation msg.install
	call isstring?
	je install
	call run
	call uninstall_oneshot
	xor ax, ax
	retf


uninstall_oneshot:
reloc	testopt [ss:relocateddata], 1
linkdatarelocation options7, -3
	jnz @F

reloc	mov ax, word [cs:code + eldiEndCode]
internalcoderelocation
reloc	sub ax, word [cs:code + eldiStartCode]
internalcoderelocation
reloc	sub word [relocateddata], ax
linkdatarelocation extseg_used

reloc	mov ax, word [cs:code + eldiEndData]
internalcoderelocation
reloc	sub ax, word [cs:code + eldiStartData]
internalcoderelocation
reloc	sub word [relocateddata], ax
linkdatarelocation extdata_used
@@:
	retn


install:
	lodsb
	extcall chkeol

	houdini
	mov es, bx		; => ext seg (writable)
	mov ax, endresident - endinstalled
reloc	sub word [es:code + eldiEndCode], ax
internalcoderelocation		; adjust size
reloc	sub word [relocateddata], ax
linkdatarelocation extseg_used	; adjust size
reloc	mov bx, word [relocateddata]
linkdatarelocation ext_command_handler
				; -> prior
reloc	mov di, command		; -> our handler
internalcoderelocation
	test bx, bx		; installing as first ?
	jz .only_first		; yes, simple --> (leave as extcall cmd3_not_ext)
	scasw			; skip entrypoint jmp strict short
	mov al, 0E9h		; = jmp near opcode
	stosb			; store
	xchg ax, bx		; ax -> next handler
	sub ax, di
	dec ax
	dec ax			; ax = ax - (di + 2)
	stosw			; store our downlink as rel16 displacement

.only_first:
reloc	setopt [es:code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as resident
reloc2	mov word [relocateddata], command
linkdatarelocation ext_command_handler, -4
internalcoderelocation		; -> our entrypoint

reloc	testopt [relocateddata], 4
linkdatarelocation options7, -3
	jnz @F
reloc	mov dx, msg.installed
internaldatarelocation
	call putsz
@@:
	xor ax, ax
	retf


DATAOFFSET equ CODEOFFSET + code_size
	addsection DATA, follows=CODE vstart=_ELD_DATA_VSTART
%define DATAFIXUP - datastart + 0
datastart:
PUT_ELD_DATETIME

	align 4, db 0
keywordtable:
reloc	dw msg.block
internaldatarelocation
reloc	dw verbose_block
internaldatarelocation

reloc	dw msg.char
internaldatarelocation
reloc	dw verbose_char
internaldatarelocation

reloc	dw msg.dpb
internaldatarelocation
reloc	dw verbose_dpb
internaldatarelocation

	dw 0



msg:
.devices:		asciz "DEVICES"
.uninstall_done:	db "DEVICES uninstalled."
%if _ELD_RECLAIM_HINT
			db " (Don't forget to use reclaim.eld)"
%endif
			asciz 13,10
.uninstall_error:	asciz "DEVICES unable to uninstall!",13,10
.block:			asciz "BLOCK"
.char:			asciz "CHAR"
.dpb:			asciz "DPB"
.nulsign:		fill 8, 32, db "NUL"
.nonul:			asciz "Error: No NUL device header found!",13,10
.first:			counted "NUL device header at "
.eachdev.1.block:	counted "h -> block dev, attr="
.eachdev.1.char:	counted "h -> char dev, attr="
.eachdev.2:		counted "h, strat="
.eachdev.3:		counted "h, int="
.eachdev.4.block:	counted "h, "
.eachdev.4.char:	counted "h, ",'"'
.eachdev.5.block.singular:
			counted " unit"
.eachdev.5.block.plural:
			counted " units"
.nodpb.1:
.dpb.1:			counted "   DPB for unit "
.dpb.2:			counted "h at "
.dpb.3:			counted "h -> drive "
.nodpb.2:		counted "h not found!"

uinit_data: equ $
.installed:		asciz "DEVICES installed.",13,10

	align 16, db 0
transient_data_size equ $ - datastart
data_size equ $ - datastart

	absolute uinit_data

	alignb 2
firstdpb:		resd 1
dpbsegment:		resw 1
unitsamount:		resw 1
devicesamount:		resw 1
anyverbose:		resb 1
verbose_dpb:		resb 1	; must be directly after anyverbose
verbose_block:		resb 1
verbose_char:		resb 1	; must be directly after verbose_block

	alignb 16
uinit_data_end:
resident_data_end:

uinit_data_size equ uinit_data_end - datastart


%if uinit_data_size >= transient_data_size
 total_data_size equ uinit_data_size
%else
 total_data_size equ transient_data_size
%endif
%assign _DATA_SIZE total_data_size

	usesection CODE

%include "eldlink.asm"

	align 16
code_size equ $ - code
