
; Public Domain

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

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

%assign _CATCHINT0C 0
%assign _CATCHINT0D 0
%assign _CATCHINTFAULTCOND 0
%assign _LOADER 0
%assign _DEBUG 0
%assign _DELAY_BEFORE_BP 0
%assign _DHIGHLIGHT 0
%assign _GETLINEHIGHLIGHT 0
%assign _IMMASM 0
%assign _INPUT_FILE_BOOT 0
%assign _INPUT_FILE_HANDLES 0
%assign _MS_0RANGE_COMPAT 0
%assign _PM 0
%assign _RH 0
%assign _SYMBOLIC 0
%assign _VXCHG 0
%include "options.mac"

	cpu 8086

	addsection RELOCATEDDATA, nobits vstart=_ELD_RELOC_VSTART
%xdefine SECTION_OF_relocateddata _CURRENT_SECTION
relocateddata:
%xdefine _EXPR_SECTION _CURRENT_SECTION

	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 transient_data_size
at eldhDataAllocLength,	dw total_data_size - transient_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 "Provide TSC command."


	align 16, db 0

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


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


	usesection CODE

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.tsc
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

	mov es, word [relocateddata]
linkdatarelocation extdssel
	extcallcall ispm
	jz @F
	mov es, word [relocateddata]
linkdatarelocation extseg
@@:
	push es
	pop ds
	xor bx, bx		; = 0 (no prior, modify ext_command_handler)
	mov di, command		; di -> us
internalcoderelocation
	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:
	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:
	clropt [code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as free

	push ss
	pop ds
	mov si, store_table
internaldatarelocation

.write_store_table:
	lodsw
	xchg di, ax		; -> destination
	lodsw
	test ax, ax
	jz @F
	xchg bx, ax
	mov dx, [bx]
	xchg bx, ax
	extcall setes2dx	; => destination
	lodsw
	xchg cx, ax
	mov al, 90h
	rep stosb
	add si, STORETABLEENTRY_size - 6
	jmp .write_store_table

@@:

	mov dx, msg.uninstall_done
internaldatarelocation
	mov di, [ss:nameheader_offset]
internaldatarelocation
	push ss
	pop es
	xor ax, ax
	stosw			; name header
	mov di, word [ss:structure_offset]
internaldatarelocation
	stosw			; ivName
	stosw			; ivFlags
	stosw			; ivAddress
@@:
	push ss
	pop ds
	extcallcall putsz
	extcallcall cmd3	; return

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


run:
	xor bx, bx
	mov dx, msg.quiet
internaldatarelocation
	extcallcall isstring?
	jne @F
	inc bx
@@:
	extcallcall skipcomma
	extcallcall chkeol
subcpu 586
	rdtsc
	mov dword [lasttsc], eax
internaldatarelocation
	mov dword [lasttsc + 4], edx
internaldatarelocation
	test bx, bx
	jnz .ret
	mov di, relocateddata
linkdatarelocation line_out
	xchg eax, edx
	extcallcall hexdword
	mov al, '_'
	stosb
	xchg eax, edx
	extcallcall hexdword
	extcallcall putsline_crlf
.ret:
	retn
subcpureset


		; INP:	ax = array index (0-based)
		;	cx = offset of this handler (ip)
		;	dil = default size of variable (1..4)
		;	dih = length of variable name
		; CHG:	si, ax
		; OUT:	NC if valid,
		;	 bx -> var, di = 0 or di -> mask
		;	 cl = size of variable (1..4)
		;	 ch = length of variable name
varsetup:
	add ax, ax
	add ax, ax		; times 4
	mov bx, lasttsc
internaldatarelocation
	add bx, ax		; index into array
	xor cx, cx		; NC, cx = 0
	xchg cx, di
	extcall var_ext_setup_done, required	; must NOT be extcallcall


error:
	extcall error

	eldcall_dump_callcall ELDCALL_CALLCALL_LIST

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


checktscsupport:
	testopt [relocateddata], has386
linkdatarelocation internalflags, -3
	stc			; CY
	jz .not386
subcpu 386
	push eax
	push ebx
	push ecx
	push edx

; Copied from init.asm, debugging messages
;  and machine variable access removed.
	mov bx, sp		; save current stack pointer to align
	and sp, ~3		; align stack to avoid AC fault
	pushfd			; push original EFLAGS
	pop eax			; get original EFLAGS
	mov ecx, eax		; save original EFLAGS in ECX (including IF)

	xor eax, 40000h		; flip AC bit in EFLAGS
	and ax, ~0200h		; clear IF
	push eax		; put new EFLAGS value on stack
	popfd			; replace EFLAGS value; DI
	pushfd			; get new EFLAGS
	pop eax			; store new EFLAGS value in EAX
	mov ax, cx		; ignore low bits (including IF)
	cmp eax, ecx
	je .cpudone_stack_eax_equals_ecx	; if 80386 -->

		; Intel486 DX CPU, Intel487 SX NDP, and Intel486 SX CPU check.
		; Checking for ability to set/clear ID flag (bit 21) in EFLAGS
		; which indicates the presence of a processor with the ability
		; to use the CPUID instruction.
	mov eax, ecx		; get original EFLAGS
	xor eax, 200000h	; flip ID bit in EFLAGS
	and ax, ~0200h		; clear IF
	push eax		; save new EFLAGS value on stack
	popfd			; replace current EFLAGS value; DI
	pushfd			; get new EFLAGS
	pop eax			; store new EFLAGS in EAX
	mov ax, cx		; ignore low bits (including IF)

.cpudone_stack_eax_equals_ecx:
	push ecx
	popfd			; restore AC,ID bits and IF in EFLAGS (86 Mode)
	mov sp, bx		; restore sp

	cmp eax, ecx		; check if it's changed
	stc
	je .cpudone		; if it's a 486 (can't toggle ID bit) -->

		; Execute CPUID instruction.
subcpu 486		; NASM (at least 2.10rc1) handles cpuid itself as a
			;  586+ instruction, but we know better. So this
			;  part is declared for 486 compatibility, and only
			;  the cpuid instructions are emitted with 586
			;  compatibility to appease NASM.
	xor eax, eax		; set up input for CPUID instruction
	  [cpu 586]
	 cpuid
	  __CPU__
	cmp eax, byte 1
	jb .cpudone		; if 1 is not a valid input value for CPUID (CY)
	xor eax, eax		; otherwise, run CPUID with eax = 1
	inc eax
	  [cpu 586]
	 cpuid
	  __CPU__
	test dl, 1 << 4
	jnz .cpudone		; NC
	stc			; CY
.cpudone:
	pop edx
	pop ecx
	pop ebx
	pop eax
subcpureset
.not386:
	retn


start:
	mov bx, es
	 push ss
	 pop es

	call skipcomma
	dec si
	mov dx, relocateddata
linkdatarelocation msg.install
	call isstring?
	je install
	call checktscsupport
	jc .nottsc
	call run
@@:
	call uninstall_oneshot
	xor ax, ax
	retf


.nottsc_install:
	testopt [relocateddata], 4
linkdatarelocation options7, -3
	jnz @B
.nottsc:
	mov ax, 0E2Eh
	extcall setrc
	mov dx, msg.not386
internaldatarelocation
	extcall putsz
	jmp @B


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

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

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


	usesection DATA

transientdata_displacement_equate equ transientdata_displacement
%assign transientdata_displacement_assign transientdata_displacement_equate
transientdata_vstart equ _ELD_DATA_VSTART + transientdata_displacement_equate
	addsection TRANSIENTDATA, align=1 valign=1 \
		 follows=DATA vstart=transientdata_vstart, DATA
%define TRANSIENTDATAFIXUP - transientdatastart + transientdata_displacement_assign
transientdatastart:

%define IV_NAME_RELOCATION internaldatarelocation
%define IV_ADDRESS_RELOCATION internalcoderelocation
%define IV_SETUP_RELOCATION linkdatarelocation var_ext_setup
%assign IVS_ONEBYTE 0

; align 1-2+.nolist nop
    times (((2) - (($-$$ + transientdata_displacement_assign) % (2))) % (2)) db 0
structure:
isvariablestruc "LASTTSC", 4, ivfArrayOptional, varsetup, 5, 0, relocateddata

; align 1-2+.nolist nop
    times (((16) - (($-$$ + transientdata_displacement_assign) % (16))) % (16)) db 0
transient_data_size equ $ - $$ + transientdata_displacement_assign


	usesection DATA

	align 2, db 0
store_table:
	dw relocateddata
linkdatarelocation patcharea_run
	dw relocateddata
linkdatarelocation patcharea_run.segment
	dw relocateddata
linkdatarelocation patcharea_run.size
	dw store_tsc_run
internalcoderelocation
	dw store_tsc_run.size
	dw msg.store_tsc_run
internaldatarelocation

	dw relocateddata
linkdatarelocation patcharea_intrtn
	dw relocateddata
linkdatarelocation patcharea_intrtn.segment
	dw relocateddata
linkdatarelocation patcharea_intrtn.size
	dw store_tsc_intrtn
internalcoderelocation
	dw store_tsc_intrtn.size
	dw msg.store_tsc_intrtn
internaldatarelocation

.checkpm:
	dw relocateddata
linkdatarelocation patcharea_pm_exc, -2, PM required
	dw relocateddata
linkdatarelocation patcharea_pm_exc.segment, -2, PM required
	dw relocateddata
linkdatarelocation patcharea_pm_exc.size, -2, PM required
	dw store_tsc_intrtn
internalcoderelocation
	dw store_tsc_intrtn.size
	dw msg.store_tsc_pm_exc
internaldatarelocation

	dw 0, 0

	struc STORETABLEENTRY
steAreaOffset:		resw 1
steAreaSegmentVariable:	resw 1
steAreaLength:		resw 1
stePatchOffset:		resw 1
stePatchLength:		resw 1		; must be even
steName:		resw 1
	endstruc


	align 4, db 0			; must be dword aligned for PM exc
lasttsc:	times 3 dq 0
	align 2, db 0
header:
	dw IVS_MOREBYTE_NAMEHEADERS

isvariablestrings ISVARIABLESTRINGS

msg:
.tsc:			asciz "TSC"
.quiet:			asciz "QUIET"
.uninstall_done:	db "TSC command uninstalled."
%if _ELD_RECLAIM_HINT
			db " (Don't forget to use reclaim.eld)"
%endif
			asciz 13,10
.uninstall_error:	asciz "TSC command unable to uninstall!",13,10

uinit_data: equ $

.not386:	asciz "No TSC available, TSC ELD not usable.",13,10
.installed:	asciz "TSC command installed.",13,10
.nofree:	asciz "TSC command cannot install, no ext variable free!",13,10
.unknown:	asciz "TSC command cannot install, unknown ext variable format!",13,10

.store_tsc_run:		asciz "Store TSC at run"
.store_tsc_intrtn:	asciz "Store TSC at 86 Mode intrtn"
.store_tsc_pm_exc:	asciz "Store TSC at Protected Mode exc"

.store_used.1:		asciz "Cannot install, patch area is in use: "
.store_used.2:		asciz 13,10
.store_long.1:		asciz "Cannot install, patch area is too short: "
.store_long.2:		asciz 13,10

transientdata_displacement: equ $ - datastart


	absolute uinit_data

	alignb 2
structure_offset:	resw 1
nameheader_offset:	resw 1

%if $ > msg.installed
 %error Variables overlap message
%endif

	alignb 16
uinit_data_end:
resident_data_end:
resident_data_size equ resident_data_end - datastart

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

subcpu 586
	align 2
store_tsc_run:
.:	push eax
	push edx
	rdtsc
	mov dword [lasttsc + 8 + 0], eax
internaldatarelocation
	mov dword [lasttsc + 8 + 4], edx
internaldatarelocation
	pop edx
	pop eax
	align 2, nop
.end:
.size equ .end - .
%if .size & 1
 %error Expected even patch length
%endif

	align 2
store_tsc_intrtn:
.:	push eax
	push edx
	rdtsc
	mov dword [lasttsc + 16 + 0], eax
internaldatarelocation
	mov dword [lasttsc + 16 + 4], edx
internaldatarelocation
	pop edx
	pop eax
	align 2, nop
.end:
.size equ .end - .
%if .size & 1
 %error Expected even patch length
%endif

subcpureset


install:
	call checktscsupport
	jc start.nottsc_install

	lodsb
	extcall chkeol

	houdini
subcpu 586
	rdtsc			; try it, to fault before install if not available
subcpureset

	xor cx, cx		; = 0
	mov si, store_table.checkpm
internaldatarelocation
	cmp word [si], cx	; check all three PM required data links
	je @F
	cmp word [si + 2], cx
	je @F
	cmp word [si + 4], cx
	jne @FF			; all nonzero -->
@@:
	mov word [si], cx
	mov word [si + 2], cx	; insure two words are zero to terminate table
@@:

	mov si, store_table
internaldatarelocation

.check_store_table:
	lodsw
	xchg di, ax
	lodsw
	test ax, ax
	jz @F
	xchg bx, ax
	mov dx, [bx]
	xchg bx, ax
	extcall setes2dx
	lodsw
	add si, STORETABLEENTRY_size - 6
	cmp ax, [si - STORETABLEENTRY_size + stePatchLength]
	jb .toolongpatch
	push cx
	xchg cx, ax
	mov al, 90h
	repe scasb
	pop cx
	je .check_store_table
	mov dx, msg.store_used.1
internaldatarelocation
	extcall putsz
	mov dx, [si - STORETABLEENTRY_size + steName]
	extcall putsz
	mov dx, msg.store_used.2
internaldatarelocation
	extcall putsz
	inc cx
	jmp .check_store_table

.toolongpatch:
	mov dx, msg.store_long.1
internaldatarelocation
	extcall putsz
	mov dx, [si - STORETABLEENTRY_size + steName]
	extcall putsz
	mov dx, msg.store_long.2
internaldatarelocation
	extcall putsz
	inc ch
	jmp .check_store_table

@@:
	push ss
	pop es
	jcxz @F
	mov ax, 0E66h
	call setrc
	xor ax, ax
	retf

@@:

	mov ax, relocateddata
linkdatarelocation ext_var_format
	cmp ax, 1
	jne .unknown
	mov ax, relocateddata
linkdatarelocation ext_var_size
	cmp ax, ISVARIABLESTRUC_size
	jne .unknown
	mov cx, relocateddata
linkdatarelocation ext_var_amount
	jcxz .unknown
	jmp @F

.unknown:
	mov ax, 0E5Eh
	call setrc
	mov dx, msg.unknown
internaldatarelocation
	extcall putsz
	xor ax, ax
	retf

@@:

	mov di, relocateddata
linkdatarelocation ext_var
	mov si, relocateddata
linkdatarelocation isvariable_morebyte_nameheaders.ext
.loop:
	cmp word [di], 0
	je .found
	add di, ISVARIABLESTRUC_size
	lodsw
	loop .loop

.nofree:
	mov ax, 0E5Fh
	call setrc
	mov dx, msg.nofree
internaldatarelocation
	extcall putsz
	xor ax, ax
	retf

.found:
	mov word [nameheader_offset], si
internaldatarelocation
	mov ax, word [header]
internaldatarelocation
	mov word [si], ax
	mov word [structure_offset], di
internaldatarelocation
	mov si, structure
internaldatarelocation
	mov cx, words(ISVARIABLESTRUC_size)
	rep movsw

	mov es, bx		; => ext seg (writable)

	mov ax, endresident - endinstalled
	sub word [es:code + eldiEndCode], ax
internalcoderelocation		; adjust size
	sub word [relocateddata], ax
linkdatarelocation extseg_used	; adjust size

%if (transient_data_size - resident_data_size) > 0
	mov ax, transient_data_size - resident_data_size
	sub word [es:code + eldiEndData], ax
internalcoderelocation		; adjust size
	sub word [relocateddata], ax
linkdatarelocation extdata_used	; adjust size
%endif

	mov bx, word [relocateddata]
linkdatarelocation ext_command_handler
				; -> prior
	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:
	setopt [es:code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as resident
	mov word [relocateddata], command
linkdatarelocation ext_command_handler, -4
internalcoderelocation		; -> our entrypoint


	mov si, store_table
internaldatarelocation

.write_store_table:
	lodsw
	xchg di, ax		; -> destination
	lodsw
	test ax, ax
	jz @F
	xchg bx, ax
	mov dx, [bx]
	xchg bx, ax
	extcall setes2dx	; => destination
	lodsw			; skip destination length (we know it is enough)
	lodsw
	mov cx, [si]
	xchg si, ax
	 push cs
	 pop ds
	shr cx, 1		; ! must be even
	rep movsw
	 push ss
	 pop ds
	xchg si, ax
	add si, 4		; skip patch length and name
	jmp .write_store_table

@@:
	push ss
	pop es

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


%include "eldlink.asm"

	align 16
code_size equ $ - code
