Source file: /~heha/hs/vcall0.zip/VCALL0.ASM

;VCALL0: Universelles VxD zum Aufruf beliebiger 16-bit-Programmteile
;als privilegierter Code. Sowohl für DOS-Real-Mode, DPMI und Windows-Programme.
;Leider ist (nebenbei) noch Windows im Enhanced Mode erforderlich, ob 3.0,
;3.10, 3.11 oder Windows95 sollte dabei keine Rolle spielen.
;
;Besonders geeignet für (Pascal-) Programme, die auf geschützte Ports
;oder solche >3FF in dichter Folge zugreifen müssen. Ebenso wird CLI
;und STI sowie der Zugriff auf ungeschützte Ports beschleunigt.
;Man kann aber auch allerlei anderen Unsinn treiben.
;
;Man bedenke jedoch beim Debuggen, daß diese privilegierten Code-Stücke
;undebugbar sind - es sei denn, man hat SoftICE für Windows.
	.386p
	.XLIST
	INCLUDE TVMM.Inc
	INCLUDE Debug.Inc
	include	vtd.inc
	.LIST
	LOCALS

VCall0_Major_Ver	equ	1
VCall0_Minor_Ver	equ	10
VCall0_Ver = VCall0_Major_Ver*256+VCall0_Minor_Ver

;"You have been assigned a device ID of 3A7Ah for your VCALL0.386 virtual
; device.  This number is uniquely assigned to this device."
VCall0_Device_ID	equ	3A7Ah

DIOCParams STRUC
Internal1	DD	?
VMHandle	DD	?
Internal2	DD	?
dwIoControlCode	DD	?
lpvInBuffer	DD	?
cbInBuffer	DD	?
lpvOutBuffer	DD	?
cbOutBuffer	DD	?
lpcbBytesReturned DD	?
lpoOverlapped	DD	?
hDevice		DD	?
tagProcess	DD	?
DIOCParams ENDS

Declare_Virtual_Device	VCall0, VCall0_Major_Ver, VCall0_Minor_Ver,\
	VCall0_Control_Proc, VCall0_Device_ID,\
	Undefined_Init_Order, VCall0_API, VCall0_API

TIMERFREQ	equ	1193180	;Hertz, Primfaktoren: 2*2*5*59659
TIMERINT	equ	50h	;statt 08h unter DOS
;**********************************
;*** Residente Daten (gesperrt) ***
;**********************************
VxD_LOCKED_DATA_SEG

SR_Ring0 label fword
SR_Ring0_IP	dd	0	;E-Teil ist Null
SR_Ring0_CS	dd	0	;E-Teil ist Null (zumindest bis zum Pentium)
SR_Ring0_DS	dd	0	;(... sind Selektoren noch 16 bit)

SR_RETF_From_16	dd	0	;Althergebrachtes CS:IP ist hier gemeint

TimerVal	dd	0	;üblicher Startwert
TimerAdd	dd	27	;44.1 kHz CD-Qualität
TimerLimit	dd	65536	;wahrscheinlich (oder auch nicht)

OldInt08	dd	?	;naja, ist ja eigentlich INT50

OldSetService	dd	?	;für gehookte Timer-Services
OldReleaseService dd	?

OwningVM	dd	0
data32seg	dw	30h	;wahrscheinlich!

VxD_LOCKED_DATA_ENDS


;**********************************
;*** Residenter Code (gesperrt) ***
;**********************************
;Programmverteiler zuerst, damit man's beim Debuggen leichter findet.
VxD_LOCKED_CODE_SEG

BeginProc VCall0_Control_Proc
	Control_Dispatch Device_Init, VCall0_Init
	Control_Dispatch VM_Not_Executeable, VMDies
	Control_Dispatch 1bh, VCall0_Dynamic_Init
	Control_Dispatch 1ch, VCall0_Dynamic_Exit
;	Control_Dispatch 23h, VCall0_IOCTL	;23h=W32_DeviceIOControl
Retu:	clc
	ret
EndProc VCall0_Control_Proc

;-------------------------------------------------------------------------
; Unterprogramm rufen, diverse globale Variablen enthalten Ansprungdaten
;-------------------------------------------------------------------------
BeginProc VCall0_CallSR, high_freq, no_log

	Trace_Out "Setting up stack frames and preparing to call TSR."

	push	ebp ebx			;Wichtige VxD-Register retten
	mov	ebp,esp

	push	cs			;RETF (16:32) vorbereiten
	push	OFFSET32 VCall0_Back_To_Flat

	push	ds			;VxD-DS (30h) retten
	push	[SR_RETF_From_16]	;RETF (16:16) vorbereiten

	mov	ds,[SR_Ring0_DS]
	jmp	cs:[SR_Ring0]		;Serviceroutine anspringen

;Dieses Stückchen Code bekommt einen Extra-Selektor spendiert:
VCall0_Switch_To_Flat:
	pop	ds			;VxD-DS wiederherstellen
	retf				;zum VxD-CS gehen
VCall0_STF_Limit = $-VCall0_Switch_To_Flat-1

VCall0_Back_To_Flat:
	pop	ebx ebp

	Trace_Out "Back in flat model. Return from TSR = #AX"

	ret
EndProc VCall0_CallSR

BeginProc NewInt08, high_freq, no_log	;um Himmels willen keine Debugstrings!
;dies ist die 16-bit-Version
	pushad
	push	ds
	 mov	ds,cs:[data32seg]
	 push	es

	  push	cs
	  push	OFFSET32 @@cont

	  push	ds
	  push	[SR_RETF_From_16]

	  mov	es,[data32seg]
	  mov	ds,[SR_Ring0_DS]
	  mov	ebp,esp
	  jmp	cs:[SR_Ring0]
@@cont:
	 pop	es
	 mov	eax,[TimerVal]
	 add	eax,[TimerAdd]
	 cmp	eax,[TimerLimit]
	 jnc	short @@overlimit	;möglichst selten springen!!
	 mov	[TimerVal],eax
	 ;(Alles muß man selber machen!!)
	pop	ds
	mov	al,60h
	out	20h,al		;Spezifischer EOI IRQ0
	popad
	iretd			;nichts weiter tun
@@overlimit:
	 sub	eax,[TimerLimit]
	 mov	[TimerVal],eax
	pop	ds
	mov	al,60h		;TEST
	out	20h,al		;Spezifischer EOI IRQ0
	popad
	jmp	cs:[OldInt08]	;zum OldIntHandler springen (->VPICD->VTD)
EndProc NewInt08

;Millisekunden-->Timerzählerwert (EAX-->EAX) ohne Rundung
BeginProc ms2ticks
	push	ecx
	push	edx
	 mov	ecx,TIMERFREQ
	 mul	ecx
	 mov	ecx,1000
	 div	ecx
	pop	edx
	pop	ecx
	ret
EndProc ms2ticks

;Hertz-->Timerzählerwert (EAX-->EAX) mit Rundung
BeginProc hz2ticks
	cmp	eax,100		;Samplerate <100Hz?? ->Fehler
	jc	short @@e
	push	edx
	 push	ecx
	  mov	ecx,eax
	  mov	eax,TIMERFREQ	;Frequenz = Divident für Zeitkonstante
	  xor	edx,edx
	  div	ecx
	 pop	ecx
	 shl	edx,1		;Rest verdoppeln
	 cmp	eax,edx		;Rest>Quotiont/2?
	 adc	eax,0		;Wenn ja, dann eins drauf (Runden)
	pop	edx
@@e:	ret
EndProc hz2ticks

;Timer-Auflösung setzen wird abgefangen,
; um Timer ggf. nach UNSEREN Wünschen nachzuprogrammieren
BeginProc NewSetService
	Trace_Out "SetService"
	call	[OldSetService]
	jmp	CommonTimer
EndProc NewSetService

;Timer-Auflösung rücksetzen wird abgefangen,
; um Timer ggf. nach UNSEREN Wünschen nachzuprogrammieren
BeginProc NewReleaseService
	Trace_Out "ReleaseService"
	call	[OldReleaseService]
CommonTimer:
	jc	short @@e2	;bei Fehler
	push	eax		;unverändert lassen
	 VxDcall VTD_Get_Interrupt_Period
	 call	ms2ticks	;EAX-->EAX
	 pushfd
	  cli			;Kein Timer-Interrupt hier!
	  mov	[TimerLimit],eax;Aktualisieren (Nachführen)
	  xor	eax,eax
	  mov	[TimerVal],eax	;Neu starten (wie die Hardware es tut?)
	Debug_Halt
	  mov	eax,[TimerAdd]
	  call	ProgT0		;Timer wieder SCHNELL machen!!
	 popfd
	pop	eax
	clc
@@e2:	ret
EndProc NewReleaseService

VxD_LOCKED_CODE_ENDS

;**********************************
;*** Residenter Code (pageable) ***
;**********************************
VxD_CODE_SEG

BeginProc SetupDescriptors
;PE: ebx=hVM
;    ebp=Client_Reg ...
;	ES:BX = Code-Zeiger
;	DS = Datensegment
;PA: (allozierte Deskriptoren werden mit <start> und <limit> versehen)
;    CY=1: Fehler

;Testen, welcher Betriebsmodus (PM16, PM32, V86) vorliegt und verzweigen
	TESTFLAG [ebx.CB_VM_Status],VMStat_PM_Use32
	jnz	@@error			;Nicht aus USE32 aufrufbar!
	TESTFLAG [ebx.CB_VM_Status],VMStat_PM_Exec
	jnz	@@protmode

;Aliasdeskriptoren für V86-Mode aufbauen und setzen
	mov	eax,dword ptr [ebp.Client_ES]
	shl	eax,4			;Lineare Anfangsadresse CS
	add	eax,[ebx.CB_High_Linear]
	VMMCall	_BuildDescriptorDWords,<eax, 0FFFFh,\
		Code_Type+D_DPL0, 0, BDDExplicitDPL>
	VMMCall	_SetDescriptor,<[SR_Ring0_CS],ebx,edx,eax,0>
	or	eax,eax
	jz	@@error

	mov	eax,dword ptr [ebp.Client_DS]
	shl	eax,4			;Lineare Anfangsadresse DS
	add	eax,[ebx.CB_High_Linear]
	VMMCall	_BuildDescriptorDWords,<eax, 0FFFFh,\
		RW_Data_Type+D_DPL0, 0, BDDExplicitDPL>
	jmp	@@comm		;gemeinsam weiter

;Aliasdeskriptoren für Protected Mode kopieren und setzen
@@protmode:
	VMMCall	_GetDescriptor,<<dword ptr [ebp.Client_ES]>, ebx, 0>
	push	eax
	 or	eax,edx
	pop	eax
	jz	@@error
	MASKFLAG edx,not 6000h		;DPL=0 setzen
	VMMCall	_SetDescriptor,<[SR_Ring0_CS],ebx,edx,eax,0>
	or	eax,eax
	jz	@@error
	VMMCall	_GetDescriptor,<<dword ptr [ebp.Client_DS]>, ebx, 0>
	push	eax
	 or	eax,edx
	pop	eax
	jz	@@error
	MASKFLAG edx,not 6000h		;DPL=0 setzen
@@comm:
	VMMCall	_SetDescriptor,<[SR_Ring0_DS],ebx,edx,eax,0>
	or	eax,eax
	jz	@@error

;Deskriptoren sind nun gesetzt, die Routine darf angesprungen werden
	movzx	eax,[ebp.Client_BX]	;Offset nicht vergessen!
	mov	[SR_Ring0_IP],eax
	clc
	ret
@@error:
	stc
	ret
EndProc SetupDescriptors

;Timer Kanal 0 auf Zeitkonstante AX programmieren
;VR: F, EAX (oberste 8bits werden nullgesetzt)
BeginProc ProgT0
	shl	eax,8
	mov	al,34h
	out	43h,al		;Erst-Low-Dann-High-Kommando
	shr	eax,8
	out	40h,al
	xchg	al,ah
	out	40h,al
	xchg	ah,al
	ret
EndProc ProgT0

;Patcht die IDT, um den Timerinterrupt einzuklinken
;PE: EAX=neue Adresse
;PA: EAX=alte Adresse
;VR: EAX
;N:  Interrupts müssen disabled sein!!
BeginProc SwapInt50
	Assert_Ints_Disabled
	;IDT-Eintrags-Adresse bestimmen
	push	esi
	 sub	esp,6
	 sidt	[esp]		;IDT-Register speichern
	 pop	si		;2 Bytes LIMIT (verwerfen)
	 pop	esi		;4 Bytes BASIS
	 ;IDT direkt anzapfen, um möglichst kurze Antwortzeiten zu erreichen!
	 rol	eax,16
	 xchg	ax,[esi+8*TIMERINT+6]	;Vektor tauschen
	 rol	eax,16
	 xchg	ax,[esi+8*TIMERINT]
	pop	esi
	ret
EndProc SwapInt50

;Patcht die IDT, um den Timerinterrupt einzuklinken
;VR: EAX
;N:  Interrupts müssen disabled sein, enthält Eigensicherung
;    gegen "Mehrfach-Anzapfung", indem OldInt08 NULL enthält
BeginProc HookInt50
	cmp	[OldInt08],0
	jnz	@@e		;schon angezapft, nicht noch einmal!
	lea	eax,NewInt08
	call	SwapInt50
	mov	[OldInt08],eax
@@e:	ret
EndProc HookInt50

BeginProc UnhookInt50
	xor	eax,eax
	xchg	eax,[OldInt08]
	or	eax,eax		;schon ausgeklinkt!
	jz	@@e
	call	SwapInt50
@@e:	ret
EndProc UnhookInt50

BeginProc HookTimerServices
	mov	eax,VTD_Begin_Min_Int_Period
	lea	esi,NewSetService
	VMMcall	Hook_Device_Service
	mov	[OldSetService],esi

	mov	eax,VTD_End_Min_Int_Period
	lea	esi,NewReleaseService
	VMMcall	Hook_Device_Service
	mov	[OldReleaseService],esi

	VxDcall	VTD_Get_Interrupt_Period
	call	ms2ticks
	mov	[TimerLimit],eax
	ret
EndProc HookTimerServices

BeginProc UnhookTimerServices
	mov	eax,VTD_Begin_Min_Int_Period
	mov	esi,[OldSetService]
	VMMcall	Hook_Device_Service

	mov	eax,VTD_End_Min_Int_Period
	mov	esi,[OldReleaseService]
	VMMcall	Hook_Device_Service
	ret
EndProc UnhookTimerServices

BeginProc Kill_High_Freq
	LD	[OwningVM],0
	mov	eax,[TimerLimit]
	cli
	call	ProgT0
	call	UnhookInt50
	call	UnhookTimerServices
	sti
	ret
EndProc Kill_High_Freq

; Nach außen sichtbare Schnittstelle:
; Codestück im Ring0 rufen (auch vom V86-Mode aufrufbar!)
; das in den Registern ES:BX angegebene Codestück wird in jedem
; Fall im Ring0-Protected-Mode ausgeführt (keine ungültigen Selektoren bitte!)
;
; PE: AH=0: Versionsnummer erhalten (z.Z. AX=100, entspricht 1.0)
;
;     AH=1: Unterprogramm aufrufen
;	ES:BX=Programmadresse, muß global und far definiert sein, keine
;	Parameter, keine Bereichs- und Stackprüfungen, keine Far-Calls!
;	Rückgabe über DX:AX möglich
;	DS: Datensegment
;	EDX: Übergabeparameter
;	Ansprung der Routine erfolgt mit
;	 CS: Alias zu angegebenem ES
;	 DS: Alias zu angegebenem DS
;	 ES: 4-Gigabyte-VxD-Daten-Deskriptor
;	 EDX: Übergabeparameter
; Ein Umrechnen mit CB_High_Linear ist hierbei nicht nötig, da das
; Unterprogramm immer im Kontext mit der laufenden VM gerufen wird.
;
;     AH=2: Periodischer Unterprogrammaufruf, Timer-gesteuert
;	ES:BX=Programmadresse, wie bei AH=1, keine Rückgabe
;	DS: Datensegment
;	EDX=Aufruf-Frequenz, 100..44100 Hz (oder auch höher)
;	mit ES:BX=0 oder DX:CX=0 Abschalten des Aufrufs
;	Ansprung der Routine wie bei AH=1, aber ohne EDX
;	Nur ein Programm kann dieses API zu einer Zeit benutzen!
;
;     AH=3: Periodischer Unterprogrammaufruf, CMOS-Timer-gesteuert
;	Parameter wie AH=2, jedoch gültige Frequenzen:
;	EDX=2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768
;
BeginProc VCall0_API
	mov	al,[ebp.Client_AH]	;Funktions-Nummer
	or	al,al
	jz	@@GetVer
	dec	al
	jz	@@CallSR
	dec	al
	jz	@@High_Freq
@@error:
	SETFLAG	[ebp.Client_Flags],CF_Mask	;Nicht unterstützte Funktion
	ret
@@GetVer:
	mov	[ebp.Client_AX],VCall0_Ver
	jmp	@@okay

@@CallSR:
	call	SetupDescriptors
	jc	@@error
	mov	edx,[ebp.Client_EDX]	;als Notnagel: EDX als Parameter
	call	VCall0_CallSR

;Hier angekommen, werden die Rückgabeparameter weitergereicht, und alles OK.
	mov	[ebp.Client_EAX],eax
	mov	[ebp.Client_EDX],edx
@@okay:	MASKFLAG [ebp.Client_Flags],not CF_Mask
	ret

@@High_Freq:
	mov	eax,[ebp.Client_EDX]
	or	eax,eax
	jz	@@End_High_Freq
	cmp	eax,100
	jc	@@error
	cmp	eax,1000000	;das ist schon eine Zumutung!
	ja	@@error
	cmp	[OldInt08],0
	jnz	@@T0_only
	mov	[OwningVM],ebx
	call	SetupDescriptors
	jc	@@error
	cli
	call	HookTimerServices
	call	HookInt50
@@T0_only:
	mov	eax,[ebp.Client_EDX]
	call	hz2ticks
	mov	[TimerAdd],eax
	cli
	call	ProgT0
	sti
	jmp	@@okay
@@End_High_Freq:
	cmp	[OldInt08],0
	jz	@@error			;Schon ausgeschaltet!
	call	Kill_High_Freq
	jmp	@@okay
EndProc VCall0_API

BeginProc VCall0_Dynamic_Exit
	movzx	eax,word ptr [SR_RETF_FROM_16+2]
	VMMCall	_Free_GDT_Selector, <eax,0>
	VMMCall	_Free_GDT_Selector, <[SR_Ring0_DS],0>
	VMMCall	_Free_GDT_Selector, <[SR_Ring0_CS],0>
	clc
	ret
EndProc VCall0_Dynamic_Exit

BeginProc VMDies
	cmp	[OwningVM],ebx
	je	Kill_High_Freq
	ret
EndProc VMDies

VxD_CODE_ENDS

;****************************
;*** Initialisierungscode ***
;****************************
VxD_ICODE_SEG

;------------------------------------------------------------------------------
;	VCall0_Sys_Critical_Init
;	2 GDT-Selektoren belegen, jedoch noch nicht ausfüllen
;	1 GDT-Selektor für Rücksprung belegen und ausfüllen
;------------------------------------------------------------------------------

BeginProc VCall0_Init

	Trace_Out "VCall0: Device_Init"
VCall0_Dynamic_Init:
	pushad
;2 leere Deskriptortabellen-Einträge besorgen
	VMMCall _Allocate_GDT_Selector, <09A00h, 0, 0>
;1 PUSH 0 kostet selbst bei 32-bit-Flat nur 2 Bytes, XOR EAX,EAX lohnt nicht
	or	eax, eax
	jz	@@error
	mov	[SR_Ring0_CS],eax
	VMMCall _Allocate_GDT_Selector, <09200h, 0, 0>
	or	eax, eax
	jz	@@error1
	mov	[SR_Ring0_DS],eax

;1 gefüllten Deskriptortabelleneintrag besorgen für das obige kurze
; Codeschnipsel
	VMMCall _BuildDescriptorDWORDS, <<OFFSET32 VCall0_Switch_To_Flat>,\
		VCall0_STF_Limit, Code_Type+D_DPL0,\
		D_DEF32+D_GRAN_BYTE, BDDExplicitDPL>
	VMMCall _Allocate_GDT_Selector,<edx, eax, 0>
	or	eax, eax
	jz	@@error2
	mov	word ptr [SR_RETF_FROM_16+2],ax

	popad
	clc
	ret

@@error2:	;2 Deskriptoren aufräumen
	VMMCall	_Free_GDT_Selector, <[SR_Ring0_DS],0>
@@error1:	;1 Deskriptor aufräumen
	VMMCall	_Free_GDT_Selector, <[SR_Ring0_CS],0>
@@error:	;kein Deskriptor aufräumen, nur CY melden: VCALL0 nicht laden
	popad
	stc
	ret
EndProc VCall0_Init

VxD_ICODE_ENDS

	END

;**********************************
;*** Residente Daten (pageable) ***
;**********************************
VxD_DATA_SEG

IOCTL_Table	dd	OFFSET32 Retu		;(-1)
		dd	OFFSET32 Retu		;(0)
		dd	OFFSET32 IOCTL_GetVer	;(1)
		dd	OFFSET32 IOCTL_Call0	;(2)
		dd	OFFSET32 IOCTL_High_Freq;(3)
		dd	OFFSET32 IOCTL_CMOS_Freq;(4)

VxD_DATA_ENDS

;IOCTL (Win32)

BeginProc VCall0_IOCTL
	inc	ecx
	cmp	ecx,7
	jnc	@@err
	call	[IOCTL_Table+ecx*4]
	jnc	@@ok
@@err:	mov	eax,32h		;Error_Not_Supported
	jmp	@@e
@@ok:
	mov	edi,[esi.lpcbBytesReturned]
	or	edi,edi
	jz	@@1
	mov	[edi],eax
@@1:	xor	eax,eax		;Null = Fehlercode "OK" (??)
@@e:	ret
EndProc VCall0_IOCTL

BeginProc IOCTL_GetVer
	cmp	[esi.cbOutBuffer],2
	jc	@@e
	mov	edi,[esi.lpvOutBuffer]
	mov	word ptr [edi],VCall0_Ver
	push	2
	pop	eax		;2 Return-Bytes
@@e:	ret
EndProc IOCTL_GetVer

BeginProc IOCTL_Call0
	cmp	[esi.cbInBuffer],4
	jc	@@e
	mov	edi,[esi.lpvInBuffer]
	mov	edi,[edi]	;Adresse 0:32
	;wie weiter? Das 32-bit-Programm muß ggf. erst "zugeschaltet" werden!
@@e:	ret
EndProc IOCTL_Call0

Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded