You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2417 lines
63 KiB
2417 lines
63 KiB
//TODO check return and irp->status returns for all routines. Trace 'em as far as necessary.
|
|
|
|
/*++ BUILD Version: 0001 // Increment this if a change has global effects
|
|
|
|
|
|
Copyright (c) 1995, 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
swndr3p.c
|
|
|
|
Abstract:
|
|
|
|
Kernel mode device driver for Microsoft SideWinder 3p joystick device
|
|
|
|
|
|
Author:
|
|
|
|
edbriggs 30-Nov-95
|
|
|
|
|
|
Revision History:
|
|
|
|
stevez May 96
|
|
removed unused code, including analog and 1-bit digital modes.
|
|
See analog3p.c, .h for original version
|
|
May need 1-bit digital mode for Aztec game cards, may want analog
|
|
for future release.
|
|
NB there is still a lot of unnecessary code left in this driver
|
|
|
|
RtlLargeIntegerX calls are historical and can be replaced by __int64
|
|
compiler supported arithmetic.
|
|
|
|
6/10/96 registry variables now being used for port address
|
|
6/10/96 resets enhanced digital mode if joystick goes to analog mode during
|
|
use (for example if user toggles "emulation" switch)
|
|
6/13/96 limits polling to 100/s by setting min time between polls to 10ms
|
|
6/13/96 code structure revised in SidewndrPoll and subroutines
|
|
|
|
|
|
--*/
|
|
|
|
|
|
|
|
#include <ntddk.h>
|
|
#include <windef.h>
|
|
#include <mmsystem.h>
|
|
#include <mmddk.h>
|
|
#include <ntddsjoy.h>
|
|
//#include "joylog.h"
|
|
|
|
|
|
|
|
//
|
|
// Device extension data
|
|
//
|
|
|
|
typedef struct {
|
|
|
|
//
|
|
// JOYSTICKID0 or JOYDSTICKID1
|
|
//
|
|
|
|
DWORD DeviceNumber;
|
|
|
|
//
|
|
// Number of axes supported and configured for this device. The
|
|
// Sidewinder 3P supports a maximum of 4 axes
|
|
//
|
|
|
|
DWORD NumberOfAxes;
|
|
|
|
//
|
|
// Current operating mode of the device:
|
|
// { Invalid | Analog | Digital | Enhanced | Maximum }
|
|
//
|
|
|
|
DWORD CurrentDeviceMode;
|
|
|
|
//
|
|
// The I/O address of the device. Note, this may be a memory mapped
|
|
// address
|
|
//
|
|
|
|
PUCHAR DeviceAddress;
|
|
|
|
//
|
|
// Boolean denoting whether this address is mapped (TRUE) or not)
|
|
//
|
|
|
|
BOOL DeviceIsMapped;
|
|
|
|
//
|
|
// A fast mutext is used to synchronize access to this device.
|
|
//
|
|
|
|
FAST_MUTEX Lock;
|
|
|
|
} JOY_EXTENSION, *PJOY_EXTENSION;
|
|
|
|
|
|
|
|
|
|
//
|
|
// Debugging macros
|
|
//
|
|
|
|
#ifdef DEBUG
|
|
#define ENABLE_DEBUG_TRACE
|
|
#endif
|
|
|
|
#ifdef ENABLE_DEBUG_TRACE
|
|
#define DebugTrace(_x_) \
|
|
DbgPrint("SideWndr : "); \
|
|
KdPrint(_x_); \
|
|
DbgPrint("\n");
|
|
#else
|
|
#define DebugTrace(_x_)
|
|
#endif
|
|
|
|
//
|
|
// Condition Compilation Directives
|
|
//
|
|
|
|
|
|
|
|
|
|
//
|
|
// Global values used to speed up calculations in sampling loops
|
|
// Also calibration constants set in DriverEntry
|
|
// -------------------------------------------------------------
|
|
//
|
|
|
|
JOY_STATISTICS JoyStatistics; // These are used for debugging and performance testing
|
|
|
|
//
|
|
// The high resolution system clock (from KeQueryPerformanceCounter)
|
|
// is updated at this frequency
|
|
//
|
|
|
|
DWORD Frequency;
|
|
|
|
//
|
|
// The latency in a call to KeQueryPerformanceCounter in microseconds
|
|
//
|
|
|
|
DWORD dwQPCLatency;
|
|
|
|
//
|
|
// After a write to the joystick port, we spin in a read-port loop, waiting
|
|
// for a bit to go high.
|
|
// This is the number of iterations to spin before timing out. Set
|
|
// to timeout after about 2 milliseconds
|
|
|
|
LONG nReadLoopMax;
|
|
|
|
//
|
|
// Values for KeDelayExecutionThread
|
|
//
|
|
|
|
LARGE_INTEGER LI10ms;
|
|
|
|
//
|
|
// number of KeQueryPerformanceCounter ticks in 1 millisecond
|
|
// (used to prevent too-frequent polling of joystick)
|
|
//
|
|
|
|
DWORD nMinTicksBetweenPolls;
|
|
|
|
//
|
|
// Assembly area for digital packets
|
|
//
|
|
|
|
#define MAX_PACKET_SIZE 23
|
|
BYTE NormalPacket[8];
|
|
BYTE EnhancedPacket[MAX_PACKET_SIZE];
|
|
|
|
//
|
|
// Last good packet
|
|
//
|
|
|
|
BOOL bLastGoodPacket;
|
|
JOY_DD_INPUT_DATA jjLastGoodPacket;
|
|
|
|
//
|
|
// time at which the joystick was last polled
|
|
//
|
|
|
|
LARGE_INTEGER liLastPoll; // set whenever the joystick's polled
|
|
DWORD PollLength;
|
|
DWORD PollLengthMax;
|
|
|
|
#define MAX_POLL_LENGTH 0 // 310
|
|
#define USE_CLI 1
|
|
|
|
|
|
//
|
|
// End of Global Values
|
|
// ---------------------
|
|
//
|
|
|
|
|
|
|
|
//
|
|
// Routine Prototypes
|
|
//
|
|
|
|
|
|
NTSTATUS
|
|
DriverEntry(
|
|
IN PDRIVER_OBJECT pDriverObject,
|
|
IN PUNICODE_STRING RegistryPathName
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrCreateDevice(
|
|
PDRIVER_OBJECT pDriverObject,
|
|
PWSTR DeviceNameBase,
|
|
DWORD DeviceNumber,
|
|
DWORD ExtensionSize,
|
|
BOOLEAN Exclusive,
|
|
DWORD DeviceType,
|
|
PDEVICE_OBJECT *DeviceObject
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrDispatch(
|
|
IN PDEVICE_OBJECT pDO,
|
|
IN PIRP pIrp
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrReportNullResourceUsage(
|
|
PDEVICE_OBJECT DeviceObject
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrReadRegistryParameterDWORD(
|
|
PUNICODE_STRING RegistryPathName,
|
|
PWSTR ParameterName,
|
|
PDWORD ParameterValue
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrMapDevice(
|
|
DWORD PortBase,
|
|
DWORD NumberOfPorts,
|
|
PJOY_EXTENSION pJoyExtension
|
|
);
|
|
|
|
|
|
VOID
|
|
SidewndrUnload(
|
|
PDRIVER_OBJECT pDriverObject
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrPoll(
|
|
IN PDEVICE_OBJECT pDO,
|
|
IN PIRP pIrp
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrEnhancedDigitalPoll(
|
|
IN PDEVICE_OBJECT pDO,
|
|
IN PIRP pIrp
|
|
);
|
|
|
|
|
|
BOOL
|
|
SidewndrQuiesce(
|
|
PUCHAR JoyPort,
|
|
UCHAR Mask
|
|
);
|
|
|
|
|
|
DWORD
|
|
TimeInMicroSeconds(
|
|
DWORD dwTime
|
|
);
|
|
|
|
|
|
DWORD
|
|
TimeInTicks(
|
|
DWORD dwTimeInMicroSeconds
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrWaitForClockEdge(
|
|
DWORD edge,
|
|
BYTE *pByte,
|
|
PUCHAR JoyPort
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrReset(
|
|
PUCHAR JoyPort
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrStartAnalogMode(
|
|
PUCHAR JoyPort
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrStartDigitalMode(
|
|
PUCHAR JoyPort
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrStartEnhancedMode(
|
|
PUCHAR JoyPort
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrGetEnhancedPacket(
|
|
PUCHAR joyPort
|
|
);
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrInterpretEnhancedPacket(
|
|
PJOY_DD_INPUT_DATA pInput
|
|
);
|
|
|
|
|
|
int
|
|
lstrnicmpW(
|
|
LPWSTR pszA,
|
|
LPWSTR pszB,
|
|
size_t cch
|
|
);
|
|
|
|
|
|
VOID
|
|
SidewndrWait (
|
|
DWORD TotalWait // in uS
|
|
);
|
|
|
|
|
|
BOOL
|
|
SidewndrReadWait (
|
|
PUCHAR JoyPort,
|
|
UCHAR Mask
|
|
);
|
|
|
|
|
|
void
|
|
SidewndrGetConfig(
|
|
LPJOYREGHWCONFIG pConfig,
|
|
PJOY_EXTENSION pJoyExtension
|
|
);
|
|
|
|
|
|
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 JoyDevice0;
|
|
PDEVICE_OBJECT JoyDevice1;
|
|
DWORD NumberOfAxes;
|
|
DWORD DeviceAddress;
|
|
DWORD DeviceType;
|
|
|
|
|
|
//
|
|
// See how many axes we have from the registry parameters. These parameters
|
|
// are set up by the driver installation program, and can be modified by
|
|
// control panel
|
|
//
|
|
|
|
//DbgBreakPoint();
|
|
JoyStatistics.nVersion = 16; // global, initialize it first thing so we for sure what we're running
|
|
DebugTrace(("Sidewndr %d", JoyStatistics.nVersion));
|
|
|
|
Status = SidewndrReadRegistryParameterDWORD(
|
|
RegistryPathName,
|
|
JOY_DD_NAXES_U,
|
|
&NumberOfAxes
|
|
);
|
|
|
|
DebugTrace(("Number of axes returned from registry: %d", NumberOfAxes));
|
|
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SidewndrUnload(pDriverObject);
|
|
return Status;
|
|
}
|
|
|
|
|
|
if (( NumberOfAxes < 2) || (NumberOfAxes > 4))
|
|
{
|
|
SidewndrUnload(pDriverObject);
|
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// See if the registry contains a device address other than the
|
|
// default of 0x201
|
|
//
|
|
|
|
Status = SidewndrReadRegistryParameterDWORD(
|
|
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;
|
|
}
|
|
|
|
|
|
//
|
|
// See if there is a device type specified in the registry
|
|
//
|
|
|
|
Status = SidewndrReadRegistryParameterDWORD(
|
|
RegistryPathName,
|
|
JOY_DD_DEVICE_TYPE_U,
|
|
&DeviceType
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugTrace(("No device type entry for joystick"));
|
|
SidewndrUnload(pDriverObject);
|
|
Status = STATUS_DEVICE_CONFIGURATION_ERROR;
|
|
return Status;
|
|
}
|
|
|
|
DebugTrace(("Joystick device type %d", DeviceType));
|
|
|
|
// set global large_integers for KeDelayExecutionThread (negative numbers for relative time)
|
|
// NB KeDelayExecutionThread calls typically take at least 10 milliseconds on the pentium75 I used for testing,
|
|
// no matter how little time is requested
|
|
LI10ms = RtlConvertLongToLargeInteger(-100000);
|
|
|
|
//
|
|
// 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));
|
|
|
|
// 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);
|
|
dwQPCLatency = (dwTimems / 1000) + 1; // round up
|
|
|
|
/* following code used only for testing timing of kernel timing routines
|
|
ulStart = KeQueryPerformanceCounter(NULL).LowPart;
|
|
KeDelayExecutionThread( KernelMode, FALSE, &LI2ms);
|
|
ulEnd = KeQueryPerformanceCounter(NULL).LowPart;
|
|
DebugTrace(("QPC latency in uS: %u, DET(2ms) in ticks: %u ticks",
|
|
dwQPCLatency,
|
|
ulEnd - ulStart));
|
|
|
|
ulStart = KeQueryPerformanceCounter(NULL).LowPart;
|
|
for (i = 0; i < 1000; i++) {
|
|
KeStallExecutionProcessor(1); // 1 microsecond (Hah!)
|
|
}
|
|
ulEnd = KeQueryPerformanceCounter(NULL).LowPart;
|
|
DebugTrace(("KeStallExecutionProcessor(1) called 1000 times, in ticks: %u",
|
|
ulEnd - ulStart));
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Attempt to create the device
|
|
//
|
|
|
|
Status = SidewndrCreateDevice(
|
|
pDriverObject,
|
|
JOY_DD_DEVICE_NAME_U, // device driver
|
|
0,
|
|
sizeof(JOY_EXTENSION),
|
|
FALSE, // exclusive access
|
|
FILE_DEVICE_UNKNOWN,
|
|
&JoyDevice0);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugTrace(("SwndrCreateDevice returned %x", Status));
|
|
SidewndrUnload(pDriverObject);
|
|
return Status;
|
|
}
|
|
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->DeviceNumber = JOYSTICKID1;
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->NumberOfAxes = NumberOfAxes;
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->CurrentDeviceMode =
|
|
SIDEWINDER3P_ANALOG_MODE;
|
|
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->DeviceIsMapped = FALSE;
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->DeviceAddress = (PUCHAR) 0;
|
|
|
|
//
|
|
// Initialize the fast mutext used to synchronize access to this device
|
|
//
|
|
|
|
ExInitializeFastMutex(&((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->Lock);
|
|
|
|
//
|
|
// Get the device address into the device extension
|
|
//
|
|
|
|
Status = SidewndrMapDevice(
|
|
DeviceAddress,
|
|
1,
|
|
(PJOY_EXTENSION)JoyDevice0->DeviceExtension);
|
|
|
|
|
|
// Calibrate nReadLoopMax for spinning in read_port loops to timeout after 2ms
|
|
{
|
|
int i;
|
|
PBYTE JoyPort;
|
|
DWORD ulStart, ulEnd;
|
|
BYTE byteJoy;
|
|
int LoopTimeInMicroSeconds;
|
|
|
|
i = 1000;
|
|
JoyPort = ((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->DeviceAddress;
|
|
|
|
ulStart = KeQueryPerformanceCounter(NULL).LowPart;
|
|
while (i--){
|
|
byteJoy = READ_PORT_UCHAR(JoyPort);
|
|
if ((byteJoy & X_AXIS_BITMASK)) {
|
|
;
|
|
}
|
|
}
|
|
ulEnd = KeQueryPerformanceCounter(NULL).LowPart;
|
|
LoopTimeInMicroSeconds = TimeInMicroSeconds (ulEnd - ulStart);
|
|
nReadLoopMax = (1000 * 2000) / LoopTimeInMicroSeconds; // want 2 mS for nReadLoopMax iterations
|
|
DebugTrace(("READ_PORT_UCHAR loop, 1000 interations: %u ticks", ulEnd - ulStart));
|
|
DebugTrace(("nReadLoopMax: %u", nReadLoopMax));
|
|
}
|
|
//
|
|
// if only 2 axes were requested, we can support a second device
|
|
//
|
|
|
|
// Number of axed should be 4 here, since we're only supporting sidewinder
|
|
// in enhanced digital mode. Leave this code in just for safety.
|
|
|
|
if (2 == NumberOfAxes)
|
|
{
|
|
Status = SidewndrCreateDevice(
|
|
pDriverObject,
|
|
JOY_DD_DEVICE_NAME_U,
|
|
1, // device number
|
|
sizeof (JOY_EXTENSION),
|
|
FALSE, // exclusive access
|
|
FILE_DEVICE_UNKNOWN,
|
|
&JoyDevice1);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugTrace(("Create device for second device returned %x", Status));
|
|
SidewndrUnload(pDriverObject);
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// In the analog world (which we are in if there are 2 devices, both
|
|
// devices share the same I/O address so just copy it from JoyDevice0
|
|
//
|
|
|
|
((PJOY_EXTENSION)JoyDevice1->DeviceExtension)->DeviceIsMapped =
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->DeviceIsMapped;
|
|
|
|
((PJOY_EXTENSION)JoyDevice1->DeviceExtension)->DeviceAddress =
|
|
((PJOY_EXTENSION)JoyDevice0->DeviceExtension)->DeviceAddress;
|
|
|
|
}
|
|
|
|
//
|
|
// Place the enty points in our driver object
|
|
//
|
|
|
|
pDriverObject->DriverUnload = SidewndrUnload;
|
|
pDriverObject->MajorFunction[IRP_MJ_CREATE] = SidewndrDispatch;
|
|
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = SidewndrDispatch;
|
|
pDriverObject->MajorFunction[IRP_MJ_READ] = SidewndrDispatch;
|
|
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SidewndrDispatch;
|
|
|
|
//
|
|
// Zero statistics, set misc globals
|
|
//
|
|
|
|
JoyStatistics.EnhancedPolls = 0;
|
|
JoyStatistics.EnhancedPollTimeouts = 0;
|
|
JoyStatistics.EnhancedPollErrors = 0;
|
|
JoyStatistics.nPolledTooSoon = 0;
|
|
JoyStatistics.nReset = 0;
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_ENHANCEDMODE_ATTEMPTS; i++) {
|
|
JoyStatistics.Retries[i] = 0;
|
|
}
|
|
}
|
|
|
|
bLastGoodPacket = FALSE;
|
|
liLastPoll = KeQueryPerformanceCounter (NULL);
|
|
// allow max of 100 polls/s (min time between polls 10ms), which reduces time spinning in the NT kernel
|
|
nMinTicksBetweenPolls = TimeInTicks (10000);
|
|
PollLengthMax = TimeInTicks (MAX_POLL_LENGTH);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrCreateDevice(
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
RtlInitUnicodeString((PUNICODE_STRING) &UnicodeDosDeviceName, L"\\DosDevices\\Joy1");
|
|
|
|
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
|
|
SidewndrReadRegistryParameterDWORD(
|
|
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
|
|
SidewndrDispatch(
|
|
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;
|
|
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.
|
|
|
|
Status = SidewndrReset (((PJOY_EXTENSION)pDO->DeviceExtension)->DeviceAddress);
|
|
|
|
((PJOY_EXTENSION)pDO->DeviceExtension)->CurrentDeviceMode =
|
|
SIDEWINDER3P_ENHANCED_DIGITAL_MODE;
|
|
|
|
//KeDelayExecutionThread( KernelMode, FALSE, &LI10ms); //unnecessary since SidewndrReset 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
|
|
//
|
|
|
|
ExAcquireFastMutex(&((PJOY_EXTENSION)pDO->DeviceExtension)->Lock);
|
|
|
|
|
|
Status = SidewndrPoll(pDO, pIrp);
|
|
|
|
//
|
|
// release the lock
|
|
//
|
|
|
|
ExReleaseFastMutex(&((PJOY_EXTENSION)pDO->DeviceExtension)->Lock);
|
|
|
|
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)->nVersion = JoyStatistics.nVersion;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->EnhancedPolls = JoyStatistics.EnhancedPolls;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->EnhancedPollTimeouts = JoyStatistics.EnhancedPollTimeouts;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->EnhancedPollErrors = JoyStatistics.EnhancedPollErrors;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->nPolledTooSoon = JoyStatistics.nPolledTooSoon;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->nReset = JoyStatistics.nReset;
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_ENHANCEDMODE_ATTEMPTS; i++) {
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Retries[i] = JoyStatistics.Retries[i];
|
|
}
|
|
}
|
|
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->dwQPCLatency = dwQPCLatency;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->nReadLoopMax = nReadLoopMax;
|
|
((PJOY_STATISTICS)pIrp->AssociatedIrp.SystemBuffer)->Frequency = Frequency;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
pIrp->IoStatus.Status = Status;
|
|
pIrp->IoStatus.Information = sizeof(JOY_STATISTICS);
|
|
|
|
// reset statistics
|
|
JoyStatistics.EnhancedPolls = 0;
|
|
JoyStatistics.EnhancedPollTimeouts = 0;
|
|
JoyStatistics.EnhancedPollErrors = 0;
|
|
JoyStatistics.nPolledTooSoon = 0;
|
|
JoyStatistics.nReset = 0;
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_ENHANCEDMODE_ATTEMPTS; i++) {
|
|
JoyStatistics.Retries[i] = 0;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case IOCTL_JOY_GET_JOYREGHWCONFIG:
|
|
|
|
SidewndrGetConfig (
|
|
(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
|
|
SidewndrUnload(
|
|
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)
|
|
{
|
|
SidewndrReportNullResourceUsage(pDriverObject->DeviceObject);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
RtlInitUnicodeString(
|
|
(PUNICODE_STRING) &UnicodeDosDeviceName,
|
|
L"\\DosDevices\\Joy1");
|
|
|
|
IoDeleteSymbolicLink(
|
|
(PUNICODE_STRING) &UnicodeDosDeviceName);
|
|
|
|
|
|
|
|
DebugTrace(("Freeing device %d", DeviceNumber));
|
|
|
|
IoDeleteDevice(pDriverObject->DeviceObject);
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrPoll(
|
|
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
|
|
}
|
|
|
|
switch (((PJOY_EXTENSION)pDO->DeviceExtension)->CurrentDeviceMode)
|
|
{
|
|
case SIDEWINDER3P_INVALID_MODE:
|
|
break;
|
|
|
|
case SIDEWINDER3P_ANALOG_MODE:
|
|
break;
|
|
|
|
case SIDEWINDER3P_DIGITAL_MODE:
|
|
break;
|
|
|
|
case SIDEWINDER3P_ENHANCED_DIGITAL_MODE:
|
|
|
|
// Don't poll too frequently, instead return last good packet
|
|
if (KeQueryPerformanceCounter(NULL).QuadPart < liLastPoll.QuadPart + nMinTicksBetweenPolls) {
|
|
JoyStatistics.nPolledTooSoon++;
|
|
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;
|
|
}
|
|
break;
|
|
}
|
|
// Poll the joystick
|
|
Status = SidewndrEnhancedDigitalPoll(pDO, pIrp);
|
|
if (Status == STATUS_SUCCESS) {
|
|
// Everything's fine
|
|
break;
|
|
}
|
|
else {
|
|
// timed out, maybe user switched to analog mode?
|
|
Status = SidewndrReset ( (PUCHAR) ((PJOY_EXTENSION)pDO->DeviceExtension)->DeviceAddress);
|
|
JoyStatistics.nReset++;
|
|
if (Status != STATUS_SUCCESS) {
|
|
// won't go digital, maybe unplugged, nothing we can do
|
|
break;
|
|
}
|
|
}
|
|
// Now in enhanced digital mode, try polling it again (if user switches joystick between prev lines and
|
|
// this line, we'll time out, next query to the joystick will find and solve the problem)
|
|
Status = SidewndrEnhancedDigitalPoll(pDO, pIrp);
|
|
break;
|
|
|
|
case SIDEWINDER3P_MAXIMUM_MODE:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
pIrp->IoStatus.Status = Status;
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrEnhancedDigitalPoll(
|
|
IN PDEVICE_OBJECT pDO,
|
|
IN PIRP pIrp
|
|
)
|
|
{
|
|
PUCHAR joyPort;
|
|
NTSTATUS PollStatus;
|
|
NTSTATUS DecodeStatus;
|
|
DWORD MaxRetries;
|
|
|
|
joyPort = ((PJOY_EXTENSION)pDO->DeviceExtension)->DeviceAddress;
|
|
|
|
// Try to get a good enhanced mode packet up to MAX_ENHANCEDMODE_ATTEMPTS
|
|
// If there is a timeout, or if the data are invalid (bad checksum or sync
|
|
// bits) wait 1ms for the joystick to reset itself, and try again.
|
|
//
|
|
// Note that although this should eventually get a good packet, packets
|
|
// discarded in the interim (because of errors) will cause button presses
|
|
// to be lost.
|
|
//
|
|
// Although this loses data, it keeps bad data from reaching the caller,
|
|
// which seem to be about the best we can do at this stage.
|
|
//
|
|
// We keep a count of all the errors so that we keep track of just
|
|
// how bad the situation really is.
|
|
//
|
|
|
|
for( MaxRetries = 0; MaxRetries < MAX_ENHANCEDMODE_ATTEMPTS; MaxRetries++)
|
|
{
|
|
// try to read (poll) the device
|
|
|
|
liLastPoll = KeQueryPerformanceCounter (NULL);
|
|
PollStatus = SidewndrGetEnhancedPacket(joyPort);
|
|
++JoyStatistics.EnhancedPolls;
|
|
|
|
if (PollStatus != STATUS_SUCCESS)
|
|
{
|
|
// There was a timeout of some sort on the device read.
|
|
++JoyStatistics.EnhancedPollTimeouts;
|
|
}
|
|
else
|
|
{
|
|
// The device read completed. Process the data and verify the checksum
|
|
// and sync bits. The processed data will be in AssociatedIrp.SystemBuffer
|
|
DecodeStatus = SidewndrInterpretEnhancedPacket(
|
|
(PJOY_DD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer);
|
|
if (DecodeStatus != STATUS_SUCCESS)
|
|
{
|
|
// The data was bad, most likely because we missed some of the nibbles.
|
|
++JoyStatistics.EnhancedPollErrors;
|
|
}
|
|
else
|
|
{
|
|
// Everything worked as we had hoped. The data has already been
|
|
// deposited in the AssociatedIrp.SystemBuffer.
|
|
JoyStatistics.Retries[MaxRetries]++;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// We did not succeed in reading the packet. Wait 1 ms for the device to
|
|
// stabilize before re-trying the read
|
|
//KeDelayExecutionThread( KernelMode, FALSE, &LI1ms); // cannot use KeDelayExecutionThread here
|
|
// because we're at dispatch level, thanks
|
|
// to the spin lock we hold
|
|
// Mail from manolito says (64-48)*10us = 160us should be enough. But I seem to recall reading 21 packets out of 66 sent.
|
|
// Pending answer from manolito, set to 450us.
|
|
SidewndrWait (600); // this is bad because it monopolizes the cpu, but since we're spinlocked anyway, what the heck, do it.
|
|
|
|
}
|
|
|
|
// We exceeded MAX_ENHANCEDMODE_ATTEMPTS. Something is pretty badly wrong;
|
|
// in any case, a higher level caller will have to decide what to do
|
|
return STATUS_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrReportNullResourceUsage(
|
|
PDEVICE_OBJECT DeviceObject
|
|
)
|
|
{
|
|
BOOLEAN ResourceConflict;
|
|
CM_RESOURCE_LIST ResourceList;
|
|
NTSTATUS Status;
|
|
|
|
ResourceList.Count = 0;
|
|
|
|
//
|
|
// Report our usage and detect conflicts
|
|
//
|
|
|
|
Status = IoReportResourceUsage( NULL,
|
|
DeviceObject->DriverObject,
|
|
&ResourceList,
|
|
sizeof(DWORD),
|
|
DeviceObject,
|
|
NULL,
|
|
0,
|
|
FALSE,
|
|
&ResourceConflict);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (ResourceConflict)
|
|
{
|
|
return STATUS_DEVICE_CONFIGURATION_ERROR;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
SidewndrQuiesce(
|
|
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 the stuff to quiesce
|
|
//
|
|
|
|
for (i = 0; i < ANALOG_POLL_TIMEOUT; i++) {
|
|
|
|
PortVal = READ_PORT_UCHAR(JoyPort);
|
|
if ((PortVal & Mask) == 0){
|
|
return TRUE;
|
|
} else {
|
|
KeStallExecutionProcessor(1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If poll timed out we have an uplugged joystick
|
|
//
|
|
|
|
DebugTrace(("SidewndrQuiesce failed!"));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrMapDevice(
|
|
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);
|
|
pJoyExtension->DeviceIsMapped = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pJoyExtension->DeviceAddress = (PUCHAR) MappedAddress.LowPart;
|
|
pJoyExtension->DeviceIsMapped = FALSE;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrWaitForClockEdge(
|
|
DWORD edge,
|
|
BYTE *pByte,
|
|
PUCHAR JoyPort
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits for the clock line to go high, or low depending on a the supplied
|
|
parameter (edge). If edge is CLOCK_RISING_EDGE, waits for rising edge,
|
|
else if edge is CLOCK_FALLING_EDGE
|
|
|
|
An upper bound for the wait duration is set at 1000 iterations.
|
|
|
|
Arguments:
|
|
|
|
edge -- CLOCK_RISING_EDGE or CLOCK_FALLING Edge to specify what to await
|
|
|
|
pByte -- The contents of the device register are returned for other use
|
|
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS -- the specified edge was detected before timeout
|
|
|
|
STATUS_TIMEOUT -- timeout before detecting specified edge.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD maxTimeout;
|
|
BYTE joyByte;
|
|
|
|
maxTimeout = nReadLoopMax;
|
|
|
|
if (CLOCK_RISING_EDGE == edge)
|
|
{
|
|
while (maxTimeout--)
|
|
{
|
|
joyByte = READ_PORT_UCHAR(JoyPort);
|
|
if (joyByte & CLOCK_BITMASK)
|
|
{
|
|
*pByte = joyByte;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
*pByte = joyByte;
|
|
return STATUS_TIMEOUT;
|
|
}
|
|
else
|
|
{
|
|
while (maxTimeout--)
|
|
{
|
|
joyByte = READ_PORT_UCHAR(JoyPort);
|
|
if (!(joyByte & CLOCK_BITMASK))
|
|
{
|
|
*pByte = joyByte;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
*pByte = joyByte;
|
|
return STATUS_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrReset(
|
|
PUCHAR JoyPort
|
|
)
|
|
// This resets the joystick to enhanced digital mode.
|
|
{
|
|
DWORD dwRetries;
|
|
NTSTATUS Status;
|
|
|
|
dwRetries = 0;
|
|
|
|
do {
|
|
++dwRetries;
|
|
|
|
Status = SidewndrStartAnalogMode(JoyPort);
|
|
if (Status == STATUS_TIMEOUT) continue;
|
|
//KeDelayExecutionThread( KernelMode, FALSE, &LI10ms); //MarkSV thinks this is unnecessary
|
|
|
|
Status = SidewndrStartDigitalMode(JoyPort);
|
|
if (Status == STATUS_TIMEOUT) continue;
|
|
//KeDelayExecutionThread( KernelMode, FALSE, &LI10ms); //MarkSV thinks this is unnecessary
|
|
|
|
Status = SidewndrStartEnhancedMode(JoyPort);
|
|
|
|
} while ((Status == STATUS_TIMEOUT) && (dwRetries < 10) );
|
|
|
|
// give the joystick time to stabilize MarkSV thinks this is unnecessary
|
|
//KeDelayExecutionThread( KernelMode, FALSE, &LI10ms);
|
|
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrStartAnalogMode(
|
|
PUCHAR JoyPort
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
if(! SidewndrQuiesce(JoyPort, 0x01))
|
|
{
|
|
return STATUS_TIMEOUT;
|
|
}
|
|
|
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
if (!SidewndrReadWait(JoyPort, X_AXIS_BITMASK)) goto timeout;
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
if (!SidewndrReadWait(JoyPort, X_AXIS_BITMASK)) goto timeout;
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
|
|
KeLowerIrql(OldIrql);
|
|
|
|
//
|
|
// Wait 1ms to let port settle out
|
|
//
|
|
|
|
KeDelayExecutionThread( KernelMode, FALSE, &LI10ms); // MarkSV says 1 ms is enough, original code had 8 ms
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
timeout:
|
|
KeLowerIrql(OldIrql);
|
|
return STATUS_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrStartDigitalMode(
|
|
PUCHAR JoyPort
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
DWORD dwStart, dwX0, dwX1, dwX2, dwX3;
|
|
|
|
|
|
DebugTrace(("Sidewndr: Digital Mode Requested"));
|
|
|
|
SidewndrQuiesce(JoyPort, 0x01);
|
|
|
|
|
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
|
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
if (!SidewndrReadWait(JoyPort, X_AXIS_BITMASK)) goto timeout;
|
|
SidewndrWait (75);
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
if (!SidewndrReadWait(JoyPort, X_AXIS_BITMASK)) goto timeout;
|
|
SidewndrWait (75 + 726);
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
if (!SidewndrReadWait(JoyPort, X_AXIS_BITMASK)) goto timeout;
|
|
SidewndrWait (75 + 300);
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
if (!SidewndrReadWait(JoyPort, X_AXIS_BITMASK)) goto timeout;
|
|
|
|
KeLowerIrql(OldIrql);
|
|
|
|
SidewndrQuiesce(JoyPort, 0x01);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
timeout:
|
|
KeLowerIrql(OldIrql);
|
|
return STATUS_TIMEOUT;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
SidewndrStartEnhancedMode(
|
|
PUCHAR JoyPort
|
|
)
|
|
{
|
|
DWORD byteIndex;
|
|
DWORD bitIndex;
|
|
BYTE JoyByte;
|
|
NTSTATUS Status;
|
|
KIRQL OldIrql;
|
|
|
|
|
|
|
|
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
|
|
// Wait for serial clock to go high, probably already there.
|
|
Status = SidewndrWaitForClockEdge(CLOCK_RISING_EDGE, &JoyByte, JoyPort);
|
|
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
KeLowerIrql(OldIrql);
|
|
DebugTrace(("SidewndrStartEnhancedMode: timeout in first spin"));
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
|
|
for (byteIndex = 0; byteIndex < 6; byteIndex++)
|
|
{
|
|
for (bitIndex = 0; bitIndex < 8; bitIndex++)
|
|
{
|
|
// look for falling edge of serial clock.
|
|
|
|
Status = SidewndrWaitForClockEdge(CLOCK_FALLING_EDGE, &JoyByte, JoyPort);
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
KeLowerIrql(OldIrql);
|
|
DebugTrace(("SidewndrStartEnhancedMode: timeout in second spin byteIndex %d bitIndex %d", byteIndex, bitIndex));
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
|
|
// Wait for serial clock to go high.
|
|
Status = SidewndrWaitForClockEdge(CLOCK_RISING_EDGE, &JoyByte, JoyPort);
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
KeLowerIrql(OldIrql);
|
|
DebugTrace(("SidewndrStartEnhancedMode: timeout in third spin"));
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Interrupt the processor again, telling it to send an ID packet.
|
|
// After getting the ID packet it knows to go into enhanced mode.
|
|
// This does not affect the packet currently going.
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, JOY_START_TIMERS);
|
|
|
|
|
|
// Wait out the rest of the packet so we can figure out how long this takes.
|
|
for (byteIndex = 6; byteIndex < 8; byteIndex++)
|
|
{
|
|
for (bitIndex = 0; bitIndex < 8; bitIndex++)
|
|
{
|
|
// look for falling edge of serial clock.
|
|
Status = SidewndrWaitForClockEdge(CLOCK_FALLING_EDGE, &JoyByte, JoyPort);
|
|
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
KeLowerIrql(OldIrql);
|
|
DebugTrace(("SidewndrStartEnhancedMode Timeout in 4th spin"));
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
|
|
// Wait for serial clock to go high.
|
|
|
|
Status = SidewndrWaitForClockEdge(CLOCK_RISING_EDGE, &JoyByte, JoyPort);
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
KeLowerIrql(OldIrql);
|
|
DebugTrace(("SidewndrStartEnhancedMode Timeout in 5th spin"));
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
KeLowerIrql(OldIrql);
|
|
|
|
//m_tmPacketTime = SystemTime() - tmStartTime;
|
|
|
|
// The joystick ID comes across on 20 bytes and we just did 8 bytes,
|
|
// so wait (with interrupts enabled) long enough for the ID packet to
|
|
// complete. After that we should be in enhanced mode. Each nibble takes
|
|
// about 10us, so 1ms should be plenty of time for everything.
|
|
KeDelayExecutionThread( KernelMode, FALSE, &LI10ms);
|
|
|
|
return(STATUS_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
*******************************************************************************
|
|
Routine:
|
|
|
|
CSidewinder::GetEnhancedPacket
|
|
|
|
Description:
|
|
|
|
If the joystick is in digital enhanced mode, you can call this to
|
|
get a digital packet and store the data into the class' m_enhancedPacket
|
|
member variable. Call InterpretEnhancedPacket to turn the raw data into
|
|
joystick info.
|
|
|
|
Note that while you can get an enhanced packet in 1/3 the time of a normal
|
|
packet (and can thus turn back on interrputs much sooner), you can not get
|
|
enhanced packets any faster than you can get normal packets. This function
|
|
will check to make sure sufficient time has passed since the last time it
|
|
was called and if it hasn't it will wait (with interrupts ENABLED) until
|
|
that is true before asking for another packet.
|
|
|
|
This assumes the joystick is in digital enhanced mode and there is no way
|
|
to tell if this is not the case. If the joystick is just in digital
|
|
(non-enhanced) mode then this will return successfully. However, the
|
|
checksum and/or sync bits will not be correct.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
successful if it worked.
|
|
not_digital_mode if the joystick is not in digital mode.
|
|
|
|
*******************************************************************************
|
|
--*/
|
|
NTSTATUS
|
|
SidewndrGetEnhancedPacket(
|
|
PUCHAR JoyPort
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
DWORD byteIndex;
|
|
DWORD maxTimeout;
|
|
BYTE joyByte;
|
|
NTSTATUS Status;
|
|
|
|
// While enhanced packets come across faster than normal packets,
|
|
// they can not be called any more frequently. This makes sure
|
|
// we've let enough time since the last packet go by before calling
|
|
// for another.
|
|
|
|
|
|
#if USE_CLI && defined(_X86_)
|
|
__asm {
|
|
cli
|
|
}
|
|
|
|
#else
|
|
KeRaiseIrql(HIGH_LEVEL, &OldIrql);
|
|
#endif
|
|
|
|
// Start the retrieval operation
|
|
|
|
WRITE_PORT_UCHAR(JoyPort, 0);
|
|
|
|
// Wait for serial clock to go high, probably already there.
|
|
|
|
maxTimeout = nReadLoopMax;
|
|
while (maxTimeout--)
|
|
{
|
|
joyByte = READ_PORT_UCHAR(JoyPort);
|
|
if (joyByte & CLOCK_BITMASK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (maxTimeout == 0)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
for (byteIndex = 0; byteIndex < MAX_PACKET_SIZE; byteIndex++)
|
|
{
|
|
// look for falling edge of serial clock.
|
|
|
|
maxTimeout = nReadLoopMax;
|
|
while (maxTimeout--)
|
|
{
|
|
joyByte = READ_PORT_UCHAR(JoyPort);
|
|
if (!(joyByte & CLOCK_BITMASK))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (maxTimeout == 0)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
|
|
// Wait for serial clock to go high.
|
|
|
|
maxTimeout = nReadLoopMax;
|
|
while (maxTimeout--)
|
|
{
|
|
joyByte = READ_PORT_UCHAR(JoyPort);
|
|
if (joyByte & CLOCK_BITMASK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (maxTimeout == 0)
|
|
{
|
|
goto Done;
|
|
}
|
|
|
|
EnhancedPacket[byteIndex] = (joyByte & ALLDATA_BITMASK) >> 5;
|
|
}
|
|
|
|
Done:
|
|
|
|
#if USE_CLI && defined(_X86_)
|
|
__asm {
|
|
sti
|
|
}
|
|
#else
|
|
KeLowerIrql(OldIrql);
|
|
#endif
|
|
|
|
if (maxTimeout == 0)
|
|
{
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
|
|
#if MAX_POLL_LENGTH
|
|
|
|
PollLength = KeQueryPerformanceCounter(NULL).QuadPart - liLastPoll.QuadPart;
|
|
|
|
if (PollLength > PollLengthMax) {
|
|
// DbgPrint( "Swndr: Poll Length exceed %d.\n", TimeInMicroSeconds(PollLength) );
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
#endif
|
|
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*++
|
|
*******************************************************************************
|
|
Routine:
|
|
|
|
CSidewinder::InterpretEnhancedPacket
|
|
|
|
Description:
|
|
|
|
Call this after getting an enhanced packet. It converts the raw data into
|
|
normal joystick data, filling out the class' m_data structure.
|
|
|
|
The encoding of the raw Data bits (D1-D3) is given below.
|
|
|
|
Data packet format for Enhanced Mode transmission (4 line)
|
|
Byte D3 D2 D1 D0
|
|
0 Y9 Y8 Y7 SCLK
|
|
1 X9 X8 X7 SCLK
|
|
2 B0 1 H3 SCLK
|
|
3 B3 B2 B1 SCLK
|
|
4 B6 B5 B4 SCLK
|
|
5 X1 X0 0 SCLK
|
|
6 X4 X3 X2 SCLK
|
|
7 0 X6 X5 SCLK
|
|
8 Y2 Y1 Y0 SCLK
|
|
9 Y5 Y4 Y3 SCLK
|
|
10 T7 0 Y6 SCLK
|
|
11 R7 T9 T8 SCLK
|
|
12 B7 CH/TM R8 SCLK
|
|
13 R1 R0 0 SCLK
|
|
14 R4 R3 R2 SCLK
|
|
15 0 R6 R5 SCLK
|
|
16 T2 T1 T0 SCLK
|
|
17 T5 T4 T3 SCLK
|
|
18 CHKSUM0 0 T6 SCLK
|
|
19 CHKSUM3 CHKSUM2 CHKSUM1 SCLK
|
|
20 H2 H1 H0 SCLK
|
|
21 0 0 0 SCLK
|
|
22 Y9 Y8 Y7 SCLK
|
|
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
successful if the data was valid.
|
|
bad_packet if either the checksum or sync bits were incorrect.
|
|
|
|
|
|
*******************************************************************************
|
|
--*/
|
|
NTSTATUS
|
|
SidewndrInterpretEnhancedPacket(
|
|
PJOY_DD_INPUT_DATA pInput
|
|
)
|
|
{
|
|
WORD temp16;
|
|
BYTE temp8;
|
|
BYTE checksum;
|
|
|
|
pInput->Unplugged = FALSE;
|
|
pInput->Mode = SIDEWINDER3P_ENHANCED_DIGITAL_MODE;
|
|
|
|
//Get xOffset.
|
|
temp16 = 0x0000;
|
|
temp16 |= (EnhancedPacket[1] & 0x07) << 7;
|
|
temp16 |= (EnhancedPacket[7] & 0x03) << 5;
|
|
temp16 |= (EnhancedPacket[6] & 0x07) << 2;
|
|
temp16 |= (EnhancedPacket[5] & 0x06) >> 1;
|
|
pInput->u.DigitalData.XOffset = temp16;
|
|
|
|
|
|
//Get yOffset.
|
|
temp16 = 0x0000;
|
|
temp16 |= (EnhancedPacket[0] & 0x07) << 7;
|
|
temp16 |= (EnhancedPacket[10] & 0x01) << 6;
|
|
temp16 |= (EnhancedPacket[9] & 0x07) << 3;
|
|
temp16 |= (EnhancedPacket[5] & 0x07);
|
|
pInput->u.DigitalData.YOffset = temp16;
|
|
|
|
|
|
//Get rzOffset: Only 9 bits (others are 10)
|
|
temp16 = 0x0000;
|
|
temp16 |= (EnhancedPacket[12] & 0x01) << 8;
|
|
temp16 |= (EnhancedPacket[11] & 0x04) << 5;
|
|
temp16 |= (EnhancedPacket[15] & 0x03) << 5;
|
|
temp16 |= (EnhancedPacket[14] & 0x07) << 2;
|
|
temp16 |= (EnhancedPacket[13] & 0x06) >> 1;
|
|
pInput->u.DigitalData.RzOffset = temp16;
|
|
|
|
//Get tOffset.
|
|
temp16 = 0x0000;
|
|
temp16 |= (EnhancedPacket[11] & 0x03) << 8;
|
|
temp16 |= (EnhancedPacket[10] & 0x04) << 5;
|
|
temp16 |= (EnhancedPacket[18] & 0x01) << 6;
|
|
temp16 |= (EnhancedPacket[17] & 0x07) << 3;
|
|
temp16 |= (EnhancedPacket[16] & 0x07);
|
|
pInput->u.DigitalData.TOffset = temp16;
|
|
|
|
|
|
//Get Hat
|
|
temp8 = 0x00;
|
|
temp8 |= (EnhancedPacket[2] & 0x01) << 3;
|
|
temp8 |= (EnhancedPacket[20] & 0x07);
|
|
pInput->u.DigitalData.Hat = temp8;
|
|
|
|
//Get Buttons
|
|
temp8 = 0x00;
|
|
temp8 |= (EnhancedPacket[2] & 0x04) >> 2;
|
|
temp8 |= (EnhancedPacket[3] & 0x07) << 1;
|
|
temp8 |= (EnhancedPacket[4] & 0x07) << 4;
|
|
temp8 |= (EnhancedPacket[12] & 0x04) << 5;
|
|
temp8 = ~temp8; // Buttons are 1 = off, 0 = on. Want the opposite.
|
|
pInput->u.DigitalData.Buttons = temp8;
|
|
|
|
|
|
// Get CH/TM switch.
|
|
pInput->u.DigitalData.Switch_CH_TM =
|
|
((EnhancedPacket[12] & 0x02) == 0) ? 1 : 2;
|
|
|
|
|
|
// Get Checksum
|
|
temp8 = 0x00;
|
|
temp8 |= (EnhancedPacket[18] & 0x04) >> 2;
|
|
temp8 |= (EnhancedPacket[19] & 0x07) << 1;
|
|
pInput->u.DigitalData.Checksum = temp8;
|
|
|
|
|
|
//
|
|
// Check the checksum. Because the enhance mode retrieves the data packet
|
|
// 3 bits at a time, the data is not in the same order that it arrives in
|
|
// in the normal mode. Thus, calculating the checksum requires additional
|
|
// manipulation.
|
|
//
|
|
|
|
checksum = pInput->u.DigitalData.Checksum;
|
|
checksum += 0x08 | ((EnhancedPacket[2] & 0x01) << 2) |
|
|
((EnhancedPacket[1] & 0x06) >> 1);
|
|
checksum += ((EnhancedPacket[1] & 0x01) << 3) |
|
|
(EnhancedPacket[0] & 0x07);
|
|
checksum += (EnhancedPacket[4] & 0x07);
|
|
checksum += ((EnhancedPacket[3] & 0x07) << 1) |
|
|
((EnhancedPacket[2] & 0x04) >> 2);
|
|
checksum += ((EnhancedPacket[7] & 0x03) << 1) |
|
|
((EnhancedPacket[6] & 0x04) >> 2);
|
|
checksum += ((EnhancedPacket[6] & 0x03) << 2) |
|
|
((EnhancedPacket[5] & 0x06) >> 1);
|
|
checksum += ((EnhancedPacket[10] & 0x01) << 2) |
|
|
((EnhancedPacket[9] & 0x06) >> 1);
|
|
checksum += ((EnhancedPacket[9] & 0x01) << 3) |
|
|
(EnhancedPacket[8] & 0x07);
|
|
checksum += (EnhancedPacket[12] & 0x07);
|
|
checksum += ((EnhancedPacket[11] & 0x07) << 1) |
|
|
((EnhancedPacket[10] & 0x04) >> 2);
|
|
checksum += ((EnhancedPacket[15] & 0x03) << 1) |
|
|
((EnhancedPacket[14] & 0x04) >> 2);
|
|
checksum += ((EnhancedPacket[14] & 0x03) << 2) |
|
|
((EnhancedPacket[13] & 0x06) >> 1);
|
|
checksum += ((EnhancedPacket[18] & 0x01) << 2) |
|
|
((EnhancedPacket[17] & 0x06) >> 1);
|
|
checksum += ((EnhancedPacket[17] & 0x01) << 3) |
|
|
(EnhancedPacket[16] & 0x07);
|
|
checksum += (EnhancedPacket[20] & 0x07);
|
|
|
|
checksum &= 0x0F;
|
|
if (checksum == 0)
|
|
{
|
|
pInput->u.DigitalData.fChecksumCorrect = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pInput->u.DigitalData.fChecksumCorrect = FALSE;
|
|
DebugTrace(("Enhanced packet checksum failed.\n"));
|
|
}
|
|
|
|
|
|
//
|
|
// Check SyncBits
|
|
// The routine gathers 2 extra bytes from the joystick. These
|
|
// should be zero and equal to the first byte.
|
|
//
|
|
|
|
if ((EnhancedPacket[2] & 0x02) != 0 &&
|
|
EnhancedPacket[0] == EnhancedPacket[22])
|
|
{
|
|
checksum =
|
|
(EnhancedPacket[5] & 0x01) + (EnhancedPacket[7] & 0x04) +
|
|
(EnhancedPacket[10] & 0x02) + (EnhancedPacket[13] & 0x01) +
|
|
(EnhancedPacket[15] & 0x04) + (EnhancedPacket[18] & 0x02) +
|
|
EnhancedPacket[21];
|
|
|
|
if (checksum == 0)
|
|
{
|
|
pInput->u.DigitalData.fSyncBitsCorrect = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pInput->u.DigitalData.fSyncBitsCorrect = FALSE;
|
|
DebugTrace(("Enhanced packet sync bits incorrect.\n"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pInput->u.DigitalData.fSyncBitsCorrect = FALSE;
|
|
}
|
|
|
|
if (pInput->u.DigitalData.fChecksumCorrect == TRUE &&
|
|
pInput->u.DigitalData.fSyncBitsCorrect == TRUE )
|
|
{
|
|
// everything worked, save this info as last good packet
|
|
RtlCopyMemory (&jjLastGoodPacket, pInput, sizeof (JOY_DD_INPUT_DATA));
|
|
bLastGoodPacket = TRUE;
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
return(STATUS_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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
|
|
SidewndrWait (
|
|
DWORD TotalWait // in uS
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine waits for the specified number of microseconds. Tolerances for
|
|
the joystick are smaller than NT typically provide, so all timing is isolated
|
|
into this routine, where we can do crude things and play nasty hacks as
|
|
necessary. This routine locks up the cpu, so only use it for putting the joystick
|
|
into digital mode.
|
|
|
|
Arguments:
|
|
|
|
TotalWait - time to wait in microseconds
|
|
|
|
--*/
|
|
{
|
|
DWORD ulStartTime, ulEndTime;
|
|
int nTicks;
|
|
|
|
// dwQPCLatency is the calibrated-for-this-machine latency for a call to KeQueryPerfomanceCounter (in uS).
|
|
|
|
nTicks = TimeInTicks (TotalWait - dwQPCLatency);
|
|
if (nTicks <= 0) return;
|
|
|
|
ulStartTime = KeQueryPerformanceCounter(NULL).LowPart;
|
|
ulEndTime = ulStartTime + nTicks;
|
|
|
|
|
|
while (KeQueryPerformanceCounter(NULL).LowPart < ulEndTime) {
|
|
;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
SidewndrReadWait (
|
|
PUCHAR JoyPort,
|
|
UCHAR Mask
|
|
)
|
|
{
|
|
/*++
|
|
read a port and wait until it gives correct answer based on mask.
|
|
timeout after nReadLoopMax iterations (about 2 mS).
|
|
--*/
|
|
|
|
int i;
|
|
for (i = 0; i < nReadLoopMax; i++) {
|
|
if ( ! (READ_PORT_UCHAR(JoyPort) & Mask) )
|
|
return TRUE; // port went high
|
|
}
|
|
return FALSE; // timed out
|
|
}
|
|
|
|
|
|
void
|
|
SidewndrGetConfig (
|
|
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->hws.dwNumButtons = 4;
|
|
|
|
switch (pJoyExtension->CurrentDeviceMode)
|
|
{
|
|
case SIDEWINDER3P_ANALOG_MODE:
|
|
{
|
|
pConfig->hws.dwFlags = JOY_HWS_HASPOV |
|
|
JOY_HWS_POVISBUTTONCOMBOS |
|
|
JOY_HWS_HASR |
|
|
JOY_HWS_HASZ;
|
|
|
|
pConfig->dwUsageSettings = JOY_US_HASRUDDER |
|
|
JOY_US_PRESENT |
|
|
JOY_US_ISOEM;
|
|
|
|
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 = 0;
|
|
pConfig->hwv.jrvHardware.jpMin.dwV = 0;
|
|
|
|
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 = 0;
|
|
pConfig->hwv.jrvHardware.jpMax.dwV = 0;
|
|
|
|
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 = 0;
|
|
pConfig->hwv.jrvHardware.jpCenter.dwV = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
case SIDEWINDER3P_DIGITAL_MODE:
|
|
case SIDEWINDER3P_ENHANCED_DIGITAL_MODE:
|
|
{
|
|
pConfig->hws.dwFlags = JOY_HWS_HASPOV |
|
|
JOY_HWS_POVISBUTTONCOMBOS |
|
|
JOY_HWS_HASR |
|
|
JOY_HWS_HASZ;
|
|
|
|
pConfig->dwUsageSettings = JOY_US_HASRUDDER |
|
|
JOY_US_PRESENT |
|
|
JOY_US_ISOEM;
|
|
|
|
pConfig->hwv.jrvHardware.jpMin.dwX = 0;
|
|
pConfig->hwv.jrvHardware.jpMin.dwY = 0;
|
|
pConfig->hwv.jrvHardware.jpMin.dwZ = 0;
|
|
pConfig->hwv.jrvHardware.jpMin.dwR = 0;
|
|
pConfig->hwv.jrvHardware.jpMin.dwU = 0;
|
|
pConfig->hwv.jrvHardware.jpMin.dwV = 0;
|
|
|
|
pConfig->hwv.jrvHardware.jpMax.dwX = 1024;
|
|
pConfig->hwv.jrvHardware.jpMax.dwY = 1024;
|
|
pConfig->hwv.jrvHardware.jpMax.dwZ = 1024;
|
|
pConfig->hwv.jrvHardware.jpMax.dwR = 512;
|
|
pConfig->hwv.jrvHardware.jpMax.dwU = 0;
|
|
pConfig->hwv.jrvHardware.jpMax.dwV = 0;
|
|
|
|
pConfig->hwv.jrvHardware.jpCenter.dwX = 512;
|
|
pConfig->hwv.jrvHardware.jpCenter.dwY = 512;
|
|
pConfig->hwv.jrvHardware.jpCenter.dwZ = 512;
|
|
pConfig->hwv.jrvHardware.jpCenter.dwR = 256;
|
|
pConfig->hwv.jrvHardware.jpCenter.dwU = 0;
|
|
pConfig->hwv.jrvHardware.jpCenter.dwV = 0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
pConfig->hwv.dwCalFlags = JOY_ISCAL_POV;
|
|
|
|
pConfig->dwType = JOY_HW_CUSTOM;
|
|
|
|
pConfig->dwReserved = 0;
|
|
}
|
|
|