GameEnum.sys

Summary

GameEnum is a WDM driver. It is a bus driver for a legacy Gameport. It works closely with Hidgame.sys, which controls the children GameEnum enumerates. Keywords include HID, WDM, and bus driver.

Building the Sample

The driver works on both x86 and Alpha platforms and is 64-bit compliant. It builds properly with Visual C 6.0 and supports Plug and Play, Power Management, and device interfaces.

The driver is installed when a Gameport is discovered via Plug and Play. There are no special .inf requirements; please see GamePort.inf in the system INF directory for an example. Once built, the sample produces one binary, GameEnum.sys. Necessary files include those in this directory and Gameport.h and Wdm.h. Both checked and free builds are available, and there are no known bugs or issues to be documented.

CODE TOUR

File Manifest

File		Description
GameSys.htm	The documentation for this sample (this file)
Gameenum.h	Definitions
Gameenum.c	External and internal IOCTL support
Pnp.c		Plug and Play functionality
Gameenum.rc	Resources

Programming Tour

This code tour discusses the steps an audio driver must complete to setup GameEnum.sys as a functioning FDO for the physical gameport.

The major topics covered in this tour include the following.

Resources requirements made by GameEnum

GameEnum acts as an FDO for a traditional gameport. USB joysticks do not need any additional drivers; DirectInput will open them directly. A kernel driver for an audio device can enumerate a child device and have GameEnum loaded on top of it. GameEnum will handle all the details in enumerating, starting, and handling joysticks plugged into the system.

The following assumptions are made by GameEnum with regards to its resources:

  1. If there are resources provided, it does not have to be the port 0x201. For example, the resources can be in PCI space.
  2. If no resources are assigned to the GameEnum stack, then the PDO must handle an IOCTL to supply GameEnum with the necessary data on how to access the gameport hardware. For details on how to implement this, see below.
  3. There are no interrupts assigned to it.

Definition of a lower filter

The term lower filter refers to a device object in the stack below GameEnum which handles the override IOCTL. This device object can be the PDO itself (if the audio driver is enumerating the PDO) or an actual filter object that is in between GameEnum and the PDO in the stack.

Functions that are supplied to GameEnum's PDOs

Once a PDO enumerated by GameEnum has started, it sends down an IOCTL to acquire the functions it needs to read from and write to the hardware. This information is sent via the GAMEENUM_PORT_PARAMETERS structure defined in gameport.h. If the gameport has non-standard ports, then the lower filter must override this information by handling the override IOCTL, which is explained later in this document.

Here is a brief description of fields that are relevant to a driver that would override the supplied GameEnum defaults:

Field Name Description Default Value
ReadAccessor The function is used by the joystick driver to read analog data from the gameport hardware / joystick READ_PORT_UCHAR
ReadAccessorDigital The function is used by the joystick driver to read data digitally from the gameport hardware / joystick. This function is not supplied by default; it is only supplied if the lower filter handles the override IOCTL. NONE
WriteAccessor The function is used to by the joystick driver write data to the gameport hardware / joystick WRITE_PORT_UCHAR
GameContext Context passed as a parameter to ReadAccessor, ReadAccessorDigital, and WriteAccessor Port resource assigned to the stack (0x201 for example)
AcquirePort

"Acquires" and locks the hardware. This function must be called before any of the Accessor functions listed above may be called. The Accessor functions must be functional between calls to AcquirePort and ReleasePort.

This function may fail, so the caller must check the return code before using the Accessor functions.

Game_AcquirePort
ReleasePort "Releases" and unlocks the hardware. This function will be called after the joystick driver has finished calling the Accessor functions. Game_ReleasePort
PortContext Context passed as a parameter to AcquirePort and ReleasePort. Context private to GameEnum

To override the Accessor functions, all the fields are required, except for ReadAccessorDigital, which is an optional field. Only override this function if you can support digital reads on your hardware. The AcquirePort and ReleasePort functions may be overridden only if the Accessor functions are overridden. All the fields must be overridden; there are no optional fields.

How to override the supplied functions

GameEnum will send an internal IOCTL, IOCTL_GAMEENUM_ACQUIRE_ACCESSORS, down the stack to the lower filter so that it may override GameEnum's default functionality. The lower filter must fill in the output buffer for this IOCTL, which is of the size GAMEENUM_ACQUIRE_ACCESSORS, defined in gameport.h. The fields in GAMEENUM_ACQUIRE_ACCESSORS match one to one with the relevant fields in GAMEENUM_PORT_PARAMETERS (the structure supplied to GameEnum's PDO).

When overriding the Accessor functions, you may provide any value for the GameContext. It does not have to be limited to a port resource value. For instance, you can pass your device extension as the context, and then pull data off of the extension to set up the hardware and then perform the actual read. In essence, you provide another level of indirection to this function call.

Here is a code snippet that shows how to handle the override IOCTL and perform special actions on a fictional gameport that requires special init before the port can be read or written.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, GameFilter_InternalIoctl)
#endif

typedef struct _DEVICE_EXTENSION {
    // ... 

    // Port address which will enable the game port
    PUCHAR GamePortEnableAddress;

    // status register
    PUCHAR GamePortStatusAddress;

    // Port address which we read the data from (ie 0x201 on a traditional ISA card)
    PUCHAR GamePortDataAddress;

    // Sanity check 
    BOOLEAN GamePortEnabled;

    // ...

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;


NTSTATUS
DriverEntry (
    IN  PDRIVER_OBJECT  DriverObject,
    IN  PUNICODE_STRING UniRegistryPath
    )
{
    UNREFERENCED_PARAMETER (UniRegistryPath);

    // ...
    DriverObject->MajorFunction [IRP_MJ_INTERNAL_DEVICE_CONTROL]
        = GameFilter_InternalIoctl;
    // ...

    return STATUS_SUCCESS;
}

NTSTATUS
GameFilter_InternalIoctl (
    PDEVICE_OBJECT  DeviceObject,
    IN  PIRP        Irp  
    )
{
    NTSTATUS status;
    PIO_STACK_LOCATION stack;
    PDEVICE_EXTENSION devExt; 
    PGAMEENUM_ACQUIRE_ACCESSORS pGameEnumAcquireAccessors;

    PAGED_CODE();

    stack = IoGetCurrentIrpStackLocation(Irp);
    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    switch (stack->Parameters.DeviceIoControl.IoControlCode) {
    case IOCTL_GAMEENUM_ACQUIRE_ACCESSORS:

        pGameEnumAcquireAccessors = (PGAMEENUM_ACQUIRE_ACCESSORS)
            Irp->AssociatedIrp.SystemBuffer;

        if (stack->Parameters.DeviceIoControl.OutputBufferLength < 
            sizeof(GAMEENUM_ACQUIRE_ACCESSORS) ||

            pGameEnumAcquireAccessors->Size < 
            sizeof(GAMEENUM_ACQUIRE_ACCESSORS) ) {

            status  = STATUS_BUFFER_TOO_SMALL;
        }
        else {
            //
            // Accessor functions
            //

            // digital read not supported at this time in this filter
            pGameEnumAcquireAccessors->ReadAccessorDigital = NULL;

            //
            // GameEnum does not know how to parse the data address out of the
            // range of ports assigned to this PDO, so we must do this on 
            // behalf of GameEnum.
            //
            pGameEnumAcquireAccessors->GameContext = (PVOID)
                devExt->GamePortDataAddress;

            //
            // TBD:  Need to abstract out the XXX_PORT functions so that we can
            // handle registers as well (ie, READ / WRITE_REGISTER_UCHAR)
            //

            //
            // We don't need to do anything special for reads or writes so 
            // supply the normal read / write functions.  Besides, adding 
            // another level of indirection kills performance because joysticks
            // are usually polled continuously.
            //
            pGameEnumAcquireAccessors->ReadAccessor = (PGAMEENUM_READPORT)
                READ_PORT_UCHAR; 
            pGameEnumAcquireAccessors->WriteAccessor = (PGAMEENUM_WRITEPORT)
                WRITE_PORT_UCHAR;

            //
            // Acquire / Release functions
            //
            pGameEnumAcquireAccessors->PortContext = (PVOID) devExt;

            pGameEnumAcquireAccessors->AcquirePort = (PGAMEENUM_ACQUIRE_PORT)
                GameFilter_AcquirePort;
            pGameEnumAcquireAccessors->ReleasePort = (PGAMEENUM_RELEASE_PORT)
                GameFilter_ReleasePort;
                
            status = STATUS_SUCCESS;
        }
        break;

    default:
        status = STATUS_NOT_SUPPORTED;
    }

    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return status;
}    

NTSTATUS
GameFilter_AcquirePort(
    PVOID Context
    )
{
    NTSTATUS            status = STATUS_NOT_SUPPORTED;
    PDEVICE_EXTENSION   devExt = (PDEVICE_EXTENSION) Context;
    UCHAR               statusValue;

    // function cannot be paged because we can be called at high IRQL
    // PAGED_CODE();

    //
    // TBD:  Need to abstract out the XXX_PORT functions so that we can handle
    // registers as well (ie, READ / WRITE_REGISTER_UCHAR)
    //

    if (devExt->GamePortEnabled) {
        //
        // Already enabled, nothing to do
        //
        return STATUS_SUCCESS;
    }

    //
    // devExt->GamePortEnableAddress and GamePortStatusAddress were set in 
    // start device when we parsed the resources assigned to the port
    //
    WRITE_PORT_UCHAR(devExt->GamePortEnableAddress, 0x1);
    statusValue = READ_PORT_UCHAR(devExt->GamePortStatusAddress);

    //
    // If statusValue's lower 4 bits are set (a magic value really) then the port
    // is enabled.  Otherwise, we can't enable it at this time.
    //
    if (statusValue & 0xF) {
        devExt->GamePortEnabled = TRUE;
        status = STATUS_SUCCESS;
    }
    else {
        //
        // Do something here if we want to retry enabling the gameport in the
        // "near" future (such as wait on an event or spin in a loop)
        //
        ;
    }

    return status;
}

VOID
GameFilter_ReleasePort(
    PVOID Context
    )
{
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION) Context;

    // function cannot be paged because we can be called at high IRQL
    // PAGED_CODE();

    ASSERT(devExt->GamePortEnabled);

    WRITE_PORT_UCHAR(devExt->GamePortEnableAddress, 0x0);
    devExt->GamePortEnabled = FALSE;
}

Top of page

© 1998 Microsoft Corporation