windows-nt/Source/XPSP1/NT/drivers/input/joystick/anajoyst.c
2020-09-26 16:20:57 +08:00

1566 lines
44 KiB
C

#include <ntddk.h>
#include <windef.h>
#include <mmsystem.h>
#include <mmddk.h>
#include <ntddjoy.h>
#define ANAJOYST_VERSION 10
// Device extension data
typedef struct {
// JOYSTICKID0 or JOYDSTICKID1
DWORD DeviceNumber;
// Number of axes supported and configured for this device.
DWORD NumberOfAxes;
// TRUE if there are two joysticks installed
BOOL bTwoSticks;
// The I/O address of the device, usually 0x201
PUCHAR DeviceAddress;
// A Spinlock is used to synchronize access to this device. This is
// a pointer to the actual spinlock data area
PKSPIN_LOCK SpinLock;
// Actual SpinLock data area
KSPIN_LOCK SpinLockData;
} JOY_EXTENSION, *PJOY_EXTENSION;
// Debugging macros
#ifdef DEBUG
#define ENABLE_DEBUG_TRACE
#endif
#ifdef ENABLE_DEBUG_TRACE
#define DebugTrace(_x_) \
DbgPrint("Joystick: "); \
KdPrint(_x_); \
DbgPrint("\n");
#else
#define DebugTrace(_x_)
#endif
// Global values (mostly timing related)
JOY_STATISTICS JoyStatistics; // Debugging and performance testing
// The high resolution system clock (from KeQueryPerformanceCounter) is updated at this frequency
DWORD Frequency;
// min number of KeQueryPerformanceCounter ticks between polls
// Used to prevent too-frequent polling of joystick
DWORD nMinTicksBetweenPolls;
// Last good packet
BOOL bLastGoodPacket; // TRUE if there is a last good packet
JOY_DD_INPUT_DATA jjLastGoodPacket; // data in last good packet
// time at which the joystick was last polled
LARGE_INTEGER liLastPoll; // set whenever the joystick's polled
// The maximum duration of a polling cycle (expressed in ticks).
DWORD MaxTimeoutInTicks;
// The maximum duration of a polling cycle for use in quiesce wait
LONG nQuiesceLoop;
// The minimum resolution of a polling cycle. This is used to detect
// if we've been pre-empted or interrupted during a polling loop. If
// we have been, we can retry the operation.
DWORD ThresholdInTicks;
// End of Global Values
// Routine Prototypes
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING RegistryPathName
);
NTSTATUS
AnajoystCreateDevice(
PDRIVER_OBJECT pDriverObject,
PWSTR DeviceNameBase,
DWORD DeviceNumber,
DWORD ExtensionSize,
BOOLEAN Exclusive,
DWORD DeviceType,
PDEVICE_OBJECT *DeviceObject
);
NTSTATUS
AnajoystDispatch(
IN PDEVICE_OBJECT pDO,
IN PIRP pIrp
);
NTSTATUS
AnajoystReadRegistryParameterDWORD(
PUNICODE_STRING RegistryPathName,
PWSTR ParameterName,
PDWORD ParameterValue
);
NTSTATUS
AnajoystMapDevice(
DWORD PortBase,
DWORD NumberOfPorts,
PJOY_EXTENSION pJoyExtension
);
VOID
AnajoystUnload(
PDRIVER_OBJECT pDriverObject
);
BOOL
AnajoystQuiesce(
PUCHAR JoyPort,
UCHAR Mask
);
DWORD
TimeInMicroSeconds(
DWORD dwTime
);
DWORD
TimeInTicks(
DWORD dwTimeInMicroSeconds
);
int
lstrnicmpW(
LPWSTR pszA,
LPWSTR pszB,
size_t cch
);
void
AnajoystGetConfig(
LPJOYREGHWCONFIG pConfig,
PJOY_EXTENSION pJoyExtension
);
NTSTATUS
AnajoystAnalogPoll(
PDEVICE_OBJECT pDO,
PIRP pIrp
);
NTSTATUS
AnajoystPoll(
IN PDEVICE_OBJECT pDO,
IN PIRP pIrp
);
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING RegistryPathName
)
/*++
Routine Description:
This routine is called at system initialization time to initialize
this driver.
Arguments:
DriverObject - Supplies the driver object.
RegistryPath - Supplies the registry path for this driver.
Return Value:
STATUS_SUCCESS
STATUS_DEVICE_CONFIGURATION_ERROR - Wrong number of axi in the registry
or error status from NT itself
--*/
{
NTSTATUS Status;
PDEVICE_OBJECT pJoyDevice0;
PDEVICE_OBJECT pJoyDevice1;
DWORD NumberOfAxes;
BOOL bTwoSticks;
DWORD DeviceAddress;
PJOY_EXTENSION pext0, pext1;
//DbgBreakPoint();
JoyStatistics.Version = ANAJOYST_VERSION;
DebugTrace(("Anajoyst %d", JoyStatistics.Version));
// Read registry parameters. These parameters are set up by the driver
// installation program and can be modified by the control panel applets.
// Number of axes
Status = AnajoystReadRegistryParameterDWORD(
RegistryPathName,
JOY_DD_NAXES_U,
&NumberOfAxes
);
DebugTrace(("Number of axes returned from registry: %d", NumberOfAxes));
if (!NT_SUCCESS(Status))
{
AnajoystUnload(pDriverObject);
return Status;
}
if (( NumberOfAxes < 2) || (NumberOfAxes > 4))
{
AnajoystUnload(pDriverObject);
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
return Status;
}
// Device address (usually 0x201)
Status = AnajoystReadRegistryParameterDWORD(
RegistryPathName,
JOY_DD_DEVICE_ADDRESS_U,
&DeviceAddress
);
if (NT_SUCCESS(Status))
{
DebugTrace(("Registry specified device address of 0x%x", DeviceAddress));
}
else
{
DebugTrace(("Using default device address of 0x%x", JOY_IO_PORT_ADDRESS));
DeviceAddress = JOY_IO_PORT_ADDRESS;
}
// Number of joysticks
Status = AnajoystReadRegistryParameterDWORD(
RegistryPathName,
JOY_DD_TWOSTICKS_U,
&bTwoSticks
);
bTwoSticks = !!bTwoSticks;
DebugTrace(("bTwoSticks: %ld", bTwoSticks));
if (!NT_SUCCESS(Status))
{
AnajoystUnload(pDriverObject);
return Status;
}
// if two joysticks are installed, only support two axes per joystick
if (bTwoSticks)
{
NumberOfAxes = 2;
}
// Calculate time thresholds for analog device
{
//DWORD Remainder;
LARGE_INTEGER LargeFrequency;
//DWORD ulStart, ulTemp, ulEnd;
//DWORD dwTicks, dwTimems;
//int i;
//BYTE byteJoy, byteTmp;
// Get the system timer resolution expressed in Hertz.
KeQueryPerformanceCounter(&LargeFrequency);
Frequency = LargeFrequency.LowPart;
DebugTrace(("Frequency: %u", Frequency));
//ThresholdInTicks = RtlExtendedLargeIntegerDivide(
// RtlExtendedIntegerMultiply(
// LargeFrequency,
// ANALOG_POLL_RESOLUTION
// ),
// 1000000L,
// &Remainder).LowPart;
//DebugTrace(("ThresholdInTicks: %u", ThresholdInTicks));
ThresholdInTicks = (DWORD) (((__int64)Frequency * (__int64)ANALOG_POLL_RESOLUTION) / (__int64)1000000L);
DebugTrace(("ThresholdInTicks: %u", ThresholdInTicks));
//MaxTimeoutInTicks = RtlExtendedLargeIntegerDivide(
// RtlExtendedIntegerMultiply(
// LargeFrequency,
// ANALOG_POLL_TIMEOUT
// ),
// 1000000L,
// &Remainder).LowPart;
//DebugTrace(("MaxTimeoutInTicks: %u", MaxTimeoutInTicks));
MaxTimeoutInTicks = (DWORD) (((__int64)Frequency * (__int64)ANALOG_POLL_TIMEOUT) / (__int64)1000000L);
DebugTrace(("MaxTimeoutInTicks: %u", MaxTimeoutInTicks));
// need latency for KeQueryPerformanceCounter. While we're at it, let's
// get min time for delay and stall execution
//ulStart = KeQueryPerformanceCounter(NULL).LowPart;
//for (i = 0; i < 1000; i++) {
// ulTemp = KeQueryPerformanceCounter(NULL).LowPart;
//}
//dwTicks = ulTemp - ulStart;
//dwTimems = TimeInMicroSeconds (dwTicks);
}
// Create the device
Status = AnajoystCreateDevice(
pDriverObject,
JOY_DD_DEVICE_NAME_U, // device driver
0,
sizeof(JOY_EXTENSION),
FALSE, // exclusive access
FILE_DEVICE_UNKNOWN,
&pJoyDevice0);
if (!NT_SUCCESS(Status))
{
DebugTrace(("SwndrCreateDevice returned %x", Status));
AnajoystUnload(pDriverObject);
return Status;
}
//((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->DeviceNumber = JOYSTICKID1;
//((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->NumberOfAxes = NumberOfAxes;
//((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->DeviceAddress = (PUCHAR) 0;
pext0 = (PJOY_EXTENSION)pJoyDevice0->DeviceExtension;
pext0->DeviceNumber = JOYSTICKID1;
pext0->NumberOfAxes = NumberOfAxes;
pext0->bTwoSticks = bTwoSticks;
pext0->DeviceAddress = (PUCHAR) 0;
// Initialize the spinlock used to synchronize access to this device
// KeInitializeSpinLock(&((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->SpinLockData);
// ((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->SpinLock =
// &((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->SpinLockData;
KeInitializeSpinLock(&pext0->SpinLockData);
pext0->SpinLock = &pext0->SpinLockData;
// Get the device address into the device extension
Status = AnajoystMapDevice(
DeviceAddress,
1,
pext0);
// (PJOY_EXTENSION)pJoyDevice0->DeviceExtension);
// Calibrate nQuiesceLoop for spinning in read_port loops to timeout after 10ms
{
int i;
PBYTE JoyPort;
DWORD ulStart, ulEnd;
BYTE byteJoy;
int LoopTimeInMicroSeconds;
JoyPort = ((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->DeviceAddress;
ulStart = KeQueryPerformanceCounter(NULL).LowPart;
for (i = 0; i < 1000; i++) {
byteJoy = READ_PORT_UCHAR(JoyPort);
if ((byteJoy & X_AXIS_BITMASK)) {
;
}
}
ulEnd = KeQueryPerformanceCounter(NULL).LowPart;
LoopTimeInMicroSeconds = TimeInMicroSeconds (ulEnd - ulStart);
nQuiesceLoop = (DWORD) (((__int64)1000L * (__int64)ANALOG_POLL_TIMEOUT) / (__int64)LoopTimeInMicroSeconds);
DebugTrace(("READ_PORT_UCHAR loop, 1000 interations: %u ticks", ulEnd - ulStart));
DebugTrace(("nQuiesceLoop: %u", nQuiesceLoop));
}
// if 2 joysticks are installed, support a second device
if (bTwoSticks)
{
Status = AnajoystCreateDevice(
pDriverObject,
JOY_DD_DEVICE_NAME_U,
1, // device number
sizeof (JOY_EXTENSION),
FALSE, // exclusive access
FILE_DEVICE_UNKNOWN,
&pJoyDevice1);
if (!NT_SUCCESS(Status))
{
DebugTrace(("Create device for second device returned %x", Status));
AnajoystUnload(pDriverObject);
return Status;
}
// // Both devices share the same I/O address so just copy it from pJoyDevice0
// ((PJOY_EXTENSION)pJoyDevice1->DeviceExtension)->DeviceAddress =
// ((PJOY_EXTENSION)pJoyDevice0->DeviceExtension)->DeviceAddress;
// ((PJOY_EXTENSION)pJoyDevice1->DeviceExtension)->DeviceNumber = JOYSTICKID2;
// ((PJOY_EXTENSION)pJoyDevice1->DeviceExtension)->NumberOfAxes = NumberOfAxes;
//
// // Initialize the spinlock used to synchronize access to this device
// KeInitializeSpinLock(&((PJOY_EXTENSION)pJoyDevice1->DeviceExtension)->SpinLockData);
// ((PJOY_EXTENSION)pJoyDevice1->DeviceExtension)->SpinLock =
// &((PJOY_EXTENSION)pJoyDevice1->DeviceExtension)->SpinLockData;
pext1 = (PJOY_EXTENSION)pJoyDevice1->DeviceExtension;
// Both devices share the same I/O address so just copy it from pJoyDevice0
pext1->DeviceAddress = pext0->DeviceAddress;
pext1->DeviceNumber = JOYSTICKID2;
pext1->NumberOfAxes = NumberOfAxes;
pext1->bTwoSticks = bTwoSticks; // (will be TRUE)
// Initialize the spinlock used to synchronize access to this device
KeInitializeSpinLock(&pext1->SpinLockData);
pext1->SpinLock = &pext1->SpinLockData;
}
// Define entry points
pDriverObject->DriverUnload = AnajoystUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = AnajoystDispatch;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = AnajoystDispatch;
pDriverObject->MajorFunction[IRP_MJ_READ] = AnajoystDispatch;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AnajoystDispatch;
// Zero statistics, set misc globals
JoyStatistics.Polls = 0;
JoyStatistics.Timeouts = 0;
JoyStatistics.PolledTooSoon = 0;
JoyStatistics.Redo = 0;
// allow max of 100 polls/s (min time between polls 10ms), which reduces time spinning in the NT kernel
nMinTicksBetweenPolls = TimeInTicks (10000);
bLastGoodPacket = FALSE;
liLastPoll = KeQueryPerformanceCounter (NULL);
return STATUS_SUCCESS;
}
NTSTATUS
AnajoystCreateDevice(
PDRIVER_OBJECT pDriverObject,
PWSTR DeviceNameBase,
DWORD DeviceNumber,
DWORD ExtensionSize,
BOOLEAN Exclusive,
DWORD DeviceType,
PDEVICE_OBJECT *DeviceObject
)
/*++
Routine Description:
This routine is called at driver initialization time to create
the device. The device is created to use Buffered IO.
Arguments:
pDriverObject - Supplies the driver object.
DeviceNameBase - The base name of the device to which a number is appended
DeviceNumber - A number which will be appended to the device name
ExtensionSize - Size of the device extension area
Exclusive - True if exclusive access should be enforced
DeviceType - NT Device type this device is modeled after
DeviceObject - pointer to the device object
Return Value:
STATUS_SUCCESS
or error status from NT itself
--*/
{
WCHAR DeviceName[100];
WCHAR UnicodeDosDeviceName[200];
UNICODE_STRING UnicodeDeviceName;
NTSTATUS Status;
int Length;
(void) wcscpy(DeviceName, DeviceNameBase);
Length = wcslen(DeviceName);
DeviceName[Length + 1] = L'\0';
DeviceName[Length] = (USHORT) (L'0' + DeviceNumber);
(void) RtlInitUnicodeString(&UnicodeDeviceName, DeviceName);
Status = IoCreateDevice(
pDriverObject,
ExtensionSize,
&UnicodeDeviceName,
DeviceType,
0,
(BOOLEAN) Exclusive,
DeviceObject
);
if (!NT_SUCCESS(Status))
{
return Status;
}
// very crude hack here, do the right thing sometime
if (DeviceNumber == 0) {
RtlInitUnicodeString((PUNICODE_STRING) &UnicodeDosDeviceName, L"\\DosDevices\\Joy1");
}
else {
RtlInitUnicodeString((PUNICODE_STRING) &UnicodeDosDeviceName, L"\\DosDevices\\Joy2");
}
Status = IoCreateSymbolicLink(
(PUNICODE_STRING) &UnicodeDosDeviceName,
(PUNICODE_STRING) &UnicodeDeviceName
);
if (!NT_SUCCESS(Status))
{
return Status;
}
// Set the flag signifying that we will do buffered I/O. This causes NT
// to allocate a buffer on a ReadFile operation which will then be copied
// back to the calling application by the I/O subsystem
(*DeviceObject)->Flags |= DO_BUFFERED_IO;
return Status;
}
NTSTATUS
AnajoystReadRegistryParameterDWORD(
PUNICODE_STRING RegistryPathName,
PWSTR ParameterName,
PDWORD ParameterValue
)
/*++
Routine Description:
This routine reads registry values for the driver configuration
Arguments:
RegistryPathName - Registry path containing the desired parameters
ParameterName - The name of the parameter
ParameterValue - Variable to receive the parameter value
Return Value:
STATUS_SUCCESS --
STATUS_NO_MORE_ENTRIES -- Couldn't find any entries
STATUS_INSUFFICIENT_RESOURCES -- Couldn't allocate paged pool
STATUS_DEVICE_CONFIGURATION_ERROR -- Returned value wasn't a DWORD
or error status from NT itself
-- */
{
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
HANDLE ServiceKey;
HANDLE DeviceKey; // Key handle of service node
UNICODE_STRING DeviceName; // Key to parameter node
DWORD KeyIndex;
DWORD KeyValueLength;
PBYTE KeyData;
BOOL ValueWasFound;
PKEY_VALUE_FULL_INFORMATION KeyInfo;
InitializeObjectAttributes( &ObjectAttributes,
RegistryPathName,
OBJ_CASE_INSENSITIVE,
NULL,
(PSECURITY_DESCRIPTOR) NULL);
//
// Open a key for our services node entry
//
Status = ZwOpenKey( &ServiceKey,
KEY_READ | KEY_WRITE,
&ObjectAttributes);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// Open the key to our device subkey
//
RtlInitUnicodeString(&DeviceName, L"Parameters");
InitializeObjectAttributes( &ObjectAttributes,
&DeviceName,
OBJ_CASE_INSENSITIVE,
ServiceKey,
(PSECURITY_DESCRIPTOR) NULL);
Status = ZwOpenKey (&DeviceKey,
KEY_READ | KEY_WRITE,
&ObjectAttributes);
ZwClose(ServiceKey);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// Loop reading our key values
//
// TODO exit loop when value is found?
ValueWasFound = FALSE;
for (KeyIndex = 0; ; KeyIndex++)
{
KeyValueLength = 0;
//
// find out how much data we will get
//
Status = ZwEnumerateValueKey(
DeviceKey,
KeyIndex,
KeyValueFullInformation,
NULL,
0,
&KeyValueLength);
if (STATUS_NO_MORE_ENTRIES == Status)
{
break;
}
if (0 == KeyValueLength)
{
return Status;
}
//
// Read the data
//
KeyData = ExAllocatePool (PagedPool, KeyValueLength);
if (NULL == KeyData)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ZwEnumerateValueKey(
DeviceKey,
KeyIndex,
KeyValueFullInformation,
KeyData,
KeyValueLength,
&KeyValueLength);
if (!NT_SUCCESS(Status))
{
ExFreePool(KeyData);
return Status;
}
KeyInfo = (PKEY_VALUE_FULL_INFORMATION) KeyData;
if (0 == lstrnicmpW(KeyInfo->Name,
ParameterName,
KeyInfo->NameLength / sizeof(WCHAR)))
{
// check its a DWORD
if (REG_DWORD != KeyInfo->Type)
{
ExFreePool(KeyData);
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
ValueWasFound = TRUE;
*ParameterValue = *(PDWORD) (KeyData + KeyInfo->DataOffset);
}
ExFreePool(KeyData);
}
return (ValueWasFound) ? STATUS_SUCCESS : STATUS_DEVICE_CONFIGURATION_ERROR;
}
NTSTATUS
AnajoystDispatch(
IN PDEVICE_OBJECT pDO,
IN PIRP pIrp
)
/*++
Routine Description:
Driver dispatch routine. Processes IRPs based on IRP MajorFunction
Arguments:
pDO -- pointer to the device object
pIrp -- pointer to the IRP to process
Return Value:
Returns the value of the IRP IoStatus.Status
--*/
{
PIO_STACK_LOCATION pIrpStack;
KIRQL OldIrql;
NTSTATUS Status;
DWORD dwRetries = 0;
//DbgBreakPoint();
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
Status = STATUS_SUCCESS;
pIrp->IoStatus.Status = Status;
pIrp->IoStatus.Information = 0;
switch (pIrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
//
// perform synchronous I/O
//
//pIrpStack->FileObject->Flags |= FO_SYNCHRONOUS_IO;
//NB This is bad code -- we are simply one thread wandering off through the computer -- we should be queuing up a DPC,
//returning status_pending to the calling program, then finishing the job when the dpc goes. This is possible given
//the analog game port technology.
// don't slam it into digital mode
//Status = AnajoystReset (((PJOY_EXTENSION)pDO->DeviceExtension)->DeviceAddress);
//((PJOY_EXTENSION)pDO->DeviceExtension)->CurrentDeviceMode = NULL;
// KeDelayExecutionThread( KernelMode, FALSE, &LI10ms); //unnecessary since AnajoystReset has a delay in it?
pIrp->IoStatus.Status = Status;
break;
case IRP_MJ_CLOSE:
break;
case IRP_MJ_READ:
//
// Find out which device we are and read, but first make sure
// there is enough room
//
DebugTrace(("IRP_MJ_READ"));
//DbgBreakPoint();
if (pIrpStack->Parameters.Read.Length < sizeof(JOY_DD_INPUT_DATA))
{
Status = STATUS_BUFFER_TOO_SMALL;
pIrp->IoStatus.Status = Status;
break;
}
//
// Serialize and get the current device values
//
KeAcquireSpinLock(((PJOY_EXTENSION) pDO->DeviceExtension)->SpinLock,
& OldIrql);
Status = AnajoystPoll(pDO, pIrp);
//
// release the spinlock
//
KeReleaseSpinLock(((PJOY_EXTENSION)pDO->DeviceExtension)->SpinLock,
OldIrql);
pIrp->IoStatus.Status = Status;
pIrp->IoStatus.Information = sizeof (JOY_DD_INPUT_DATA);
break;
case IRP_MJ_DEVICE_CONTROL:
switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_JOY_GET_STATISTICS:
// report statistics
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Version = JoyStatistics.Version;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Polls = JoyStatistics.Polls;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Timeouts = JoyStatistics.Timeouts;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->PolledTooSoon = JoyStatistics.PolledTooSoon;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Redo = JoyStatistics.Redo;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->nQuiesceLoop = nQuiesceLoop;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Frequency = Frequency;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->NumberOfAxes = ((PJOY_EXTENSION)pDO->DeviceExtension)->NumberOfAxes;
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->bTwoSticks = ((PJOY_EXTENSION)pDO->DeviceExtension)->bTwoSticks;
Status = STATUS_SUCCESS;
pIrp->IoStatus.Status = Status;
pIrp->IoStatus.Information = sizeof(JOY_STATISTICS);
// reset statistics
JoyStatistics.Polls = 0;
JoyStatistics.Timeouts = 0;
JoyStatistics.PolledTooSoon = 0;
JoyStatistics.Redo = 0;
break;
case IOCTL_JOY_GET_JOYREGHWCONFIG:
AnajoystGetConfig (
(LPJOYREGHWCONFIG)(pIrp->AssociatedIrp.SystemBuffer),
((PJOY_EXTENSION)pDO->DeviceExtension)
);
pIrp->IoStatus.Information = sizeof(JOYREGHWCONFIG);
break;
default:
DebugTrace(("Unknown IoControlCode"));
break;
} // end switch on IOCTL code
break;
default:
DebugTrace(("Unknown IRP Major Function %d", pIrpStack->MajorFunction));
} // end switch on IRP_MAJOR_XXXX
// pIrp->IoStatus.Status must be set to Status by this point.
// pIrp->IoStatus.Information must be set to the correct size by this point.
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return Status;
}
VOID
AnajoystUnload(
PDRIVER_OBJECT pDriverObject
)
/*++
Routine Description:
Driver unload routine. Deletes the device objects
Arguments:
pDriverObject -- pointer to the driver object whose devices we
are about to delete.
Return Value:
Returns Nothing
--*/
{
DWORD DeviceNumber;
WCHAR UnicodeDosDeviceName[200];
//
// Delete all of our devices
//
while (pDriverObject->DeviceObject)
{
DeviceNumber =
((PJOY_EXTENSION)pDriverObject->DeviceObject->DeviceExtension)->
DeviceNumber;
//
// withdraw claims on hardware by reporting no resource utilization
//
if (pDriverObject->DeviceObject)
{
if (DeviceNumber == 0)
{
// This isn't really necessary since we never reported the usage in the first place.
// There's some unused code in the original driver that may have once reported usage,
// but it was never called in the version I received. But this doesn't seem to hurt,
// and "if it ain't broke, don't fix it," at least two weeks before RC1 target.
DebugTrace(("ReportNull place"));
//AnajoystReportNullResourceUsage(pDriverObject->DeviceObject);
}
}
if (DeviceNumber == 0) {
RtlInitUnicodeString((PUNICODE_STRING) &UnicodeDosDeviceName, L"\\DosDevices\\Joy1");
}
else {
RtlInitUnicodeString((PUNICODE_STRING) &UnicodeDosDeviceName, L"\\DosDevices\\Joy2");
}
IoDeleteSymbolicLink((PUNICODE_STRING) &UnicodeDosDeviceName);
DebugTrace(("Freeing device %d", DeviceNumber));
IoDeleteDevice(pDriverObject->DeviceObject);
}
}
NTSTATUS
AnajoystPoll(
IN PDEVICE_OBJECT pDO,
IN PIRP pIrp
)
/*++
Routine Description:
Polls the device for position and button information. The polling method
(analog, digital, enhanced) is selected by the CurrentDeviceMode variable
in the device extension.
Only enhanced digital allowed. If other modes are necessary, cut and paste
(and test!) the code from file analog3p.c
Arguments:
pDO -- pointer to the device object
pIrp -- pointer to the IRP to process
if successful, data is put into the pIrp
Return Value:
STATUS_SUCCESS -- if the poll succeeded,
STATUS_TIMEOUT -- if the poll failed
--*/
{
NTSTATUS Status;
PJOY_DD_INPUT_DATA pInput;
pInput = (PJOY_DD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;
Status = STATUS_TIMEOUT;
pIrp->IoStatus.Status = Status;
if (pInput != NULL)
{
pInput->Unplugged = TRUE; // until proven otherwise
}
if (KeQueryPerformanceCounter(NULL).QuadPart < liLastPoll.QuadPart + nMinTicksBetweenPolls) {
// Don't poll too frequently, instead return last good packet
JoyStatistics.PolledTooSoon++;
if (bLastGoodPacket) {
RtlCopyMemory (pInput, &jjLastGoodPacket, sizeof (JOY_DD_INPUT_DATA));
Status = STATUS_SUCCESS;
}
else {
// no last packet, too soon to poll, nothing we can do
Status = STATUS_TIMEOUT;
}
}
else {
// do the analog poll
liLastPoll = KeQueryPerformanceCounter (NULL);
++JoyStatistics.Polls;
Status = AnajoystAnalogPoll(pDO, pIrp);
if (Status != STATUS_SUCCESS) ++JoyStatistics.Timeouts;
}
pIrp->IoStatus.Status = Status;
return Status;
}
BOOL
AnajoystQuiesce(
PUCHAR JoyPort,
UCHAR Mask
)
/*++
Routine Description:
This routine attempts to insure that the joystick is not still active as a
result of an earlier operation. This is accomplished by repeatedly reading
the device and checking that no bits are set in the supplied mask. The idea
is to check that none of the analog bits (resistive bits) are in use.
Arguments:
JoyPort - the address of the port (as returned from hal)
Mask - the mask specifying which analog bits should be checked.
Return Value:
TRUE Quiesce operation succeeded
FALSE No quiesce within a reasonable period. This generally means
that the device is unplugged.
NB This is not a reliable test for "joystick unplugged"
This routine can return TRUE under some circumstances
even when there is no joystick
--*/
{
int i;
UCHAR PortVal;
// Wait for port to quiesce
for (i = 0; i < nQuiesceLoop; i++) {
PortVal = READ_PORT_UCHAR(JoyPort);
if ((PortVal & Mask) == 0) return TRUE;
}
// If poll timed out we have an uplugged joystick or something's wrong
DebugTrace(("AnajoystQuiesce failed!"));
return FALSE;
}
NTSTATUS
AnajoystMapDevice(
DWORD PortBase,
DWORD NumberOfPorts,
PJOY_EXTENSION pJoyExtension
)
{
DWORD MemType;
PHYSICAL_ADDRESS PortAddress;
PHYSICAL_ADDRESS MappedAddress;
MemType = 1; // IO space
PortAddress.LowPart = PortBase;
PortAddress.HighPart = 0;
HalTranslateBusAddress(
Isa,
0,
PortAddress,
&MemType,
&MappedAddress);
if (MemType == 0) {
//
// Map memory type IO space into our address space
//
pJoyExtension->DeviceAddress = (PUCHAR) MmMapIoSpace(MappedAddress,
NumberOfPorts,
FALSE);
}
else
{
pJoyExtension->DeviceAddress = (PUCHAR) MappedAddress.LowPart;
}
return STATUS_SUCCESS;
}
DWORD
TimeInMicroSeconds(
DWORD dwTime
)
{
DWORD Remainder;
return RtlExtendedLargeIntegerDivide(
RtlEnlargedUnsignedMultiply( dwTime, 1000000L),
Frequency,
&Remainder
).LowPart;
}
DWORD
TimeInTicks(
DWORD dwTimeInMicroSeconds
)
{
return (DWORD) (((__int64)dwTimeInMicroSeconds * (__int64)Frequency) / (__int64) 1000000L);
}
int lstrnicmpW (LPWSTR pszA, LPWSTR pszB, size_t cch)
{
if (!pszA || !pszB)
{
return (!pszB) - (!pszA); // A,!B:1, !A,B:-1, !A,!B:0
}
// while (cch--)
for ( ; cch > 0; cch--, pszA++, pszB++) // previous version did not increment string pointers [SteveZ]
{
if (!*pszA || !*pszB)
{
return (!*pszB) - (!*pszA); // A,!B:1, !A,B:-1, !A,!B:0
}
if (*pszA != *pszB)
{
return (int)(*pszA) - (int)(*pszB); // -1:A<B, 0:A==B, 1:A>B
}
}
return 0; // no differences before told to stop comparing, so A==B
}
void
AnajoystGetConfig (
LPJOYREGHWCONFIG pConfig,
PJOY_EXTENSION pJoyExtension
)
/*++
Routine Description:
This routine is called in response to the IOCTL_JOY_GET_JOYREGHWCONFIG
query. It fills out a JOYREGHWCONFIG structure with relevant information
about the given joystick.
Arguments:
pConfig - Specifies a JOYREGHWCONFIG structure, to be filled in
pJoyExtension - Specifies the joystick to query
Return Value:
void
--*/
{
pConfig->hwv.jrvHardware.jpMin.dwX = 20;
pConfig->hwv.jrvHardware.jpMin.dwY = 20;
pConfig->hwv.jrvHardware.jpMin.dwZ = 20;
pConfig->hwv.jrvHardware.jpMin.dwR = 20;
pConfig->hwv.jrvHardware.jpMin.dwU = 20;
pConfig->hwv.jrvHardware.jpMin.dwV = 20;
pConfig->hwv.jrvHardware.jpMax.dwX = 1600;
pConfig->hwv.jrvHardware.jpMax.dwY = 1600;
pConfig->hwv.jrvHardware.jpMax.dwZ = 1600;
pConfig->hwv.jrvHardware.jpMax.dwR = 1600;
pConfig->hwv.jrvHardware.jpMax.dwU = 1600;
pConfig->hwv.jrvHardware.jpMax.dwV = 1600;
pConfig->hwv.jrvHardware.jpCenter.dwX = 790;
pConfig->hwv.jrvHardware.jpCenter.dwY = 790;
pConfig->hwv.jrvHardware.jpCenter.dwZ = 790;
pConfig->hwv.jrvHardware.jpCenter.dwR = 790;
pConfig->hwv.jrvHardware.jpCenter.dwU = 790;
pConfig->hwv.jrvHardware.jpCenter.dwV = 790;
pConfig->hwv.dwCalFlags = 0;
pConfig->dwReserved = 0;
pConfig->dwUsageSettings = JOY_US_PRESENT;
switch( ((PJOY_EXTENSION)pJoyExtension)->NumberOfAxes )
{
case 2:
pConfig->hws.dwFlags = 0;
pConfig->hws.dwNumButtons = 2;
pConfig->dwType = JOY_HW_2A_2B_GENERIC;
break;
case 3:
pConfig->hws.dwFlags = JOY_HWS_HASR;
pConfig->hws.dwNumButtons = 4;
pConfig->dwType = JOY_HW_CUSTOM;
break;
case 4:
pConfig->hws.dwFlags = JOY_HWS_HASU | JOY_HWS_HASR;
pConfig->hws.dwNumButtons = 4;
pConfig->dwType = JOY_HW_CUSTOM;
break;
}
}
NTSTATUS
AnajoystAnalogPoll(
PDEVICE_OBJECT pDO,
PIRP pIrp
)
/*++
Do a good comment block here...
THIS MAY HANG UP IF NO JOYSTICK ATTACHED. DON'T RELEASE THIS CODE WITH ANALOG
JOYSTICK SUPPORT WITHOUT CAREFULLY CHECKING THE CODE.
Routine Description:
Polls the analog device for position and button information. The position
information in analog devices is coveyed by the duration of a pulse width.
Each axis occupies one bit position. The read operation is started by
writing a value to the joystick io address. Immediately thereafter we
begin examing the values returned and the elapsed time.
This sort of device has a few limitations:
First, button information is not latched by the device, so if a button press
which occurrs in between polls will be lost. There is really no way to prevent
this short of devoting the entire cpu to polling.
Secondly, although we raise IRQL to DISPATCH_LEVEL, other interrupts will
occur during the polling routines and this will have the effect of lengthening
the pulse width (by delaying our polling loop) and thus there will be some
fluctuation about the actual value. It might be possible to try another IRQL
to see if this helps, but ultimately, nothing short of disabling interrupts
altogether will insure success. This is too much of a price to pay. The
solution is a better device.
Third, when circumstances cause a poll to last too long, we abort it and
retry the operation. We have to do this to place an upper bound on the
time we poll, and an upper bound on the time we spend at an elevated IRQL.
But in this case both the position information and
the button press information is lost. Note that there is an upper bound
on the poll duration, beyond which we conclude that the device is disconnected.
Arguments:
pDO -- pointer to the device object
pIrp -- pointer to the requesing Irp
Return Value:
STATUS_SUCCESS -- if the poll succeeded,
STATUS_TIMEOUT -- if the poll failed (timeout), or the checksum was incorrect
--*/
{
UCHAR PortVal;
PBYTE JoyPort;
DWORD Id;
DWORD NumberOfAxes;
BOOL bTwoSticks;
PJOY_DD_INPUT_DATA pInput;
BOOL Redo;
UCHAR Buttons;
UCHAR xMask, yMask, zMask, rMask;
DWORD xTime, yTime, zTime, rTime;
int MaxRedos;
DebugTrace(("AnajoystAnalogPoll"));
pInput = (PJOY_DD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;
// If we fail we assume it's because we're unplugged
pInput->Unplugged = TRUE;
// Find where our port and data area are, and related parameters
JoyPort = ((PJOY_EXTENSION)pDO->DeviceExtension)->DeviceAddress;
Id = ((PJOY_EXTENSION)pDO->DeviceExtension)->DeviceNumber;
NumberOfAxes = ((PJOY_EXTENSION)pDO->DeviceExtension)->NumberOfAxes;
bTwoSticks = ((PJOY_EXTENSION)pDO->DeviceExtension)->bTwoSticks;
// Read port state
PortVal = READ_PORT_UCHAR(JoyPort);
Buttons = 0;
// Get current button states and build bitmasks for the resistive inputs
if (Id == JOYSTICKID1)
{
switch (NumberOfAxes)
{
case 2:
xMask = JOYSTICK1_X_MASK;
yMask = JOYSTICK1_Y_MASK;
zMask = 0;
rMask = 0;
if (!(PortVal & JOYSTICK1_BUTTON1))
{
Buttons |= JOY_BUTTON1;
}
if (!(PortVal & JOYSTICK1_BUTTON2))
{
Buttons |= JOY_BUTTON2;
}
if (!bTwoSticks)
{
if (!(PortVal & JOYSTICK2_BUTTON1))
{
Buttons |= JOY_BUTTON3;
}
if (!(PortVal & JOYSTICK2_BUTTON2))
{
Buttons |= JOY_BUTTON4;
}
}
break;
case 3:
xMask = JOYSTICK1_X_MASK;
yMask = JOYSTICK1_Y_MASK;
zMask = 0;
rMask = JOYSTICK1_R_MASK; // this is 0x08, typically the third axis on 3axis joysticks
if (!(PortVal & JOYSTICK1_BUTTON1))
{
Buttons |= JOY_BUTTON1;
}
if (!(PortVal & JOYSTICK1_BUTTON2))
{
Buttons |= JOY_BUTTON2;
}
if (!(PortVal & JOYSTICK2_BUTTON1))
{
Buttons |= JOY_BUTTON3;
}
if (!(PortVal & JOYSTICK2_BUTTON2))
{
Buttons |= JOY_BUTTON4;
}
break;
case 4:
// Note that we read all axi because we don't know which
// axis will be used by the joystick, and we read all the
// buttons because no other joystick could use them
xMask = JOYSTICK1_X_MASK;
yMask = JOYSTICK1_Y_MASK;
zMask = JOYSTICK1_Z_MASK;
rMask = JOYSTICK1_R_MASK;
if (!(PortVal & JOYSTICK1_BUTTON1))
{
Buttons |= JOY_BUTTON1;
}
if (!(PortVal & JOYSTICK1_BUTTON2))
{
Buttons |= JOY_BUTTON2;
}
if (!(PortVal & JOYSTICK2_BUTTON1))
{
Buttons |= JOY_BUTTON3;
}
if (!(PortVal & JOYSTICK2_BUTTON2))
{
Buttons |= JOY_BUTTON4;
}
break;
default:
break;
// $TODO - report invalid number of axi
}
}
else if ((Id == JOYSTICKID2) && (bTwoSticks))
{
xMask = JOYSTICK2_X_MASK;
yMask = JOYSTICK2_Y_MASK;
zMask = 0;
rMask = 0;
if (!(PortVal & JOYSTICK2_BUTTON1))
{
Buttons |= JOY_BUTTON1;
}
if (!(PortVal & JOYSTICK2_BUTTON2))
{
Buttons |= JOY_BUTTON2;
}
}
else
{
// $TODO - report unsupported configuration
}
// Insure that the resistive inputs are currently reset before performing
// the next read. If we find one or more hot inputs, wait briefly for
// them to reset. If they don't, we assume that the joystick is unplugged
if (!AnajoystQuiesce(JoyPort, (UCHAR) (xMask | yMask | zMask | rMask)))
{
DebugTrace(("AnajoystQuiesce: failed to quiesce resistive inputs"));
return STATUS_TIMEOUT;
}
// Note that timing is EXTREMELY critical in the loop below.
// Avoid calling complicated arithmetic (eg TimeInMicroSeconds)
// or we will decrease our accuracy
// Other problems with accuracy, probably larger than the delays caused
// by arithmetic, are the latency in calls to KeQueryPerformanceCounter
// (typically about 5 us), and delays that can occur on the bus when DMA
// is taking place.
// Now poll the device. We wait until the status bit(s) set(s)
// and note the time. If the time since the last poll was
// too great we ignore the answer and try again.
// Loop until we get a decent reading or exceed the threshold
for (Redo = TRUE, MaxRedos = 20; Redo && --MaxRedos != 0;)
{
ULONG StartTime;
ULONG CurrentTime;
ULONG PreviousTime;
ULONG PreviousTimeButOne;
UCHAR ResistiveInputMask;
ResistiveInputMask = xMask | yMask | zMask | rMask;
// Lock on to start time
StartTime = KeQueryPerformanceCounter(NULL).LowPart;
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
CurrentTime = KeQueryPerformanceCounter(NULL).LowPart - StartTime;
PortVal = READ_PORT_UCHAR(JoyPort);
// Now wait until our end times for each coordinate
PreviousTimeButOne = 0;
PreviousTime = CurrentTime;
for (Redo = FALSE;
ResistiveInputMask;
PreviousTimeButOne = PreviousTime,
PreviousTime = CurrentTime,
PortVal = READ_PORT_UCHAR(JoyPort)
) {
PortVal = ResistiveInputMask & ~PortVal;
CurrentTime = KeQueryPerformanceCounter(NULL).LowPart - StartTime;
if (CurrentTime > MaxTimeoutInTicks) {
DebugTrace(("Polling failed - ResistiveInputMask = %x, Time = %d",
(ULONG)ResistiveInputMask,
TimeInMicroSeconds(CurrentTime)));
return STATUS_TIMEOUT;
}
if (PortVal & xMask) {
ResistiveInputMask &= ~xMask;
xTime = PreviousTime;
}
if (PortVal & yMask) {
ResistiveInputMask &= ~yMask;
yTime = PreviousTime;
}
if (PortVal & zMask) {
ResistiveInputMask &= ~zMask;
zTime = PreviousTime;
}
if (PortVal & rMask){
ResistiveInputMask &= ~rMask;
rTime = PreviousTime;
}
if (PortVal && CurrentTime - PreviousTimeButOne > ThresholdInTicks) {
// Something (DMA or interrupts) delayed our read loop, start again.
DebugTrace(("Too long a gap between polls - %u us", TimeInMicroSeconds(CurrentTime - PreviousTimeButOne)));
JoyStatistics.Redo++;
Redo = TRUE;
break;
}
}
}
if (MaxRedos == 0)
{
DebugTrace(("Overran redos to get counters"));
pInput->Unplugged = TRUE;
return STATUS_TIMEOUT;
}
pInput->Unplugged = FALSE;
pInput->Buttons = Buttons;
pInput->XTime = TimeInMicroSeconds(xTime);
pInput->YTime = TimeInMicroSeconds(yTime);
pInput->ZTime = (zMask) ? TimeInMicroSeconds(zTime) : 0;
pInput->TTime = (rMask) ? TimeInMicroSeconds(rTime) : 0;
pInput->Axi = ((PJOY_EXTENSION)pDO->DeviceExtension)->NumberOfAxes;
// everything worked, save this info as last good packet
RtlCopyMemory (&jjLastGoodPacket, pInput, sizeof (JOY_DD_INPUT_DATA));
bLastGoodPacket = TRUE;
DebugTrace(("X = %x, Y = %x, Z = %x, R = %x, Buttons = %x",
pInput->XTime,
pInput->YTime,
pInput->ZTime,
pInput->TTime,
pInput->Buttons));
return STATUS_SUCCESS;
}