1566 lines
44 KiB
C
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;
|
|
}
|