![]() Last edit: 98-06-04 Graham Wideman |
Delphi |
gwiopm.pas and gwiopm.sys documentation Article created: 98-06-01 |
The basic model is shown below:
The keys to the whole picture are the kernel functions highlighted in yellow. These are what the driver uses to install our desired I/O permissions map. (Some comments on this below.)
The unit gwiopm.pas contains an object GWIOPM_Driver with a number of methods to install the driver, prepare an I/O permissions map and hand that to the kernel. An I/O permissions map is 8K bytes, with each bit controlling access to one port in the x86's 64K I/O port address space. Bit 0 of Byte 0 controls I/O address 0, and so on.
There are other auxiliary functions to provide some diagnostics and general hacking satisfaction.
Task/Function | Description |
Connect to Service Control Manager | |
OpenSCM: DWORD; CloseSCM: DWORD; |
Open and close connection to Service Control Manager. Basic function of OpenSCM is to get a handle, which is retained by the GWIOPM_Driver object. |
Manage Driver | |
Install(newdriverpath: string): DWORD; Start: DWORD; Stop: DWORD; Remove: DWORD; |
Functions to install, start, stop and remove driver. Install() takes either a driver path or '' for default (gwiopm.sys in the application's home directory.) |
Device functions | |
DeviceOpen: DWORD; DeviceClose: DWORD; |
First you must open a device (this fetches a device handle for the future use of GWIOPM_Driver's other driver functions. Later, you must close the device if you want to remove the driver. Note that closing the device or removing the driver does not remove the IOPM that you may have provided to the NT kernel (though presumably that's forgotten after the application using it quits.) |
Test functions | |
IOCTL_IOPMD_READ_TEST (var RetVal: DWORD): DWORD; IOCTL_IOPMD_READ_VERSION (var RetVal: DWORD): DWORD; |
Diagnostics to check that driver is installed and functioning. READ_TEST returns a RetVal of $123 while READ_VERSION returns a decimal number somewhere over 100 for version 1.x |
Manipulate driver's local IOPM (LIOPM) | |
IOCTL_IOPMD_CLEAR_LIOPM: DWORD; IOCTL_IOPMD_SET_LIOPM (Addr: Word; B: byte): DWORD; |
Clear the driver's preparatory map array, or set specific bytes to particular values. |
IOCTL_IOPMD_GET_LIOPMB (Addr: Word; var B: byte): DWORD; IOCTL_IOPMD_GET_LIOPMA(var A: TIOPM): DWORD; |
Fetch either a particular byte, or the whole map from the driver. Note that this is the driver's map-in-preparation, not the one that the kernel is actually using. |
LIOPM_Set_Ports (BeginPort: word; EndPort: word; Enable: Boolean): DWORD; | Enable/disable bits for specific ports. (Preferable to calling IOCTL_IOPMD_SET_LIOPM.) |
Interact with kernel IOPM (KIOPM) | |
IOCTL_IOPMD_ACTIVATE_KIOPM: DWORD; |
Give the driver's map to the kernel. |
IOCTL_IOPMD_DEACTIVATE_KIOPM: DWORD; |
Tell kernel to forget about our process-specific map. |
IOCTL_IOPMD_QUERY_KIOPM: DWORD; |
Readback the kernel's map to the driver. (If you then use GET_LIOPMB your app can see the map the kernel is currently using.) |
Return values: | Functions generally return ERROR_SUCCESS or other error code that can be examined with the ErrorLookup function |
ErrorLookup(ErrorNum: DWORD): string; |
These were partly documented by Dale Roberts, and I have experimented with them a little more. So here's what I think they do:
Kernel function | Description or speculation |
void Ke386IoSetAccessProcess(PEPROCESS, int); |
This provides the identity of the current process to the kernel, and apparently lets the kernel know that we want to use a special I/O permissions map (presumably the kernel needs to reserve us some space.) The int argument apparently enables (1) or disables (0) the special map (or blanket enables/disables I/O, I'm not sure which.) |
void Ke386SetIoAccessMap(int, IOPM *); |
This is the function that the driver uses to provide a new map (8K bytes) to the kernel. The int argument chooses between "copy our map" (1) or "fill kernel's map with 0xFF" (0). Note that a 1 bit in the map disables access to a particular port, so the int=0 option effectively clears the kernel's map to all-disabled. |
void Ke386QueryIoAccessMap(int, IOPM *); |
This function copies the kernel's map back to the driver's buffer. I have no idea what the int argument does, but when set to 1 it works. Perhaps set to 0 it clears the driver's buffer to 0xFF's. |
In each case you can look at the gwiopm.c source to see how I used them, which follows how Dale Roberts used them, with a little bit of extra experimentation on the Query function.
Client-specific IOPM: The gwiopm.sys driver was written with only a single client application in mind. In fact the whole idea of bypassing NT's resource arbitration/allocation philosophy and programming directly to the I/O ports presupposes that only one application (or applications that are very friendly) will be using the driver. However, it would make some sense to modestly redesign gwiopm so that the local IOPM table is client-app specific, rather than global for the driver. This could be done by defining a device extension to hold the port-enable info for each client.
I/O Port Driver: The port I/O approach shown here takes the point of view that if you're wanting to manipulate ports directly, you probably want as few extraneous mechanisms in the picture as possible. Hence, the actual cpu I/O instructions are right in your Delphi/assembler code. However, another point of view would be that even though you are manipulating the hardware, you would still like a "good citizen" driver to arbitrate your port usage. This, of course, requires a more sophisticated driver that essentially translates your app's "requests" for I/O into actual I/O actions. Such an approach was being taken by Victor I. Ishikeev in his TVicHW_95 control for Win95 with a promise for an NT version sometime. Or one could look at the samples in the NT DDK.
Writing Drivers in Delphi: On the surface, it would appear that this can't be done. The primary reason is that Delphi lacks the compile/link functions to produce a .sys file, not to mention missing the Delphi-ized DDK header files. However, you can get generic "adapter" drivers which will callback your Delphi code. There is some overhead involved, but for some situations it could be just the ticket. Other products offer "Wizard"-style driver development, again avoiding the necessity to understand device drivers in depth (one hopes) See:
Start at Cherry Hill: