xbox-kernel/private/ntos/dd/usb/xidex/xidinp.cpp

729 lines
22 KiB
C++
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
input.c
Abstract:
Implementation of the Input API
Environment:
Designed for XBOX.
Notes:
Revision History:
03-07-00 created by Mitchell Dernis (mitchd)
12-06-00 changed synchronization scheme
--*/
//
// Pull in OS headers
//
#define _XAPI_
#define _KERNEL32_ //since we are linked to XAPI for now.
extern "C" {
#include <ntos.h>
}
#include <ntrtl.h>
#include <nturtl.h>
#include <xtl.h>
#include <xboxp.h>
#include <xapidrv.h>
#include <xdbg.h>
#include <stdio.h>
//
// Setup the debug information for this file (see ..\inc\debug.h)
//
#define MODULE_POOL_TAG '_DIX'
#include <debug.h>
DEFINE_USB_DEBUG_FUNCTIONS("INPUT");
//
// Pull in public usb headers
//
#include <usb.h>
//
// Pull in xid headers
//
#include "xid.h"
//--------------------------------------------------------------
// Handle Verification
//--------------------------------------------------------------
#if DBG //Debug version verifies handles.
#define XID_VERIFY_HANDLES
#endif
#ifdef XID_VERIFY_HANDLES
PXID_OPEN_DEVICE XID_OpenDeviceList = NULL;
VOID XidInsertHandle(PXID_OPEN_DEVICE OpenDevice);
VOID XidRemoveHandle(PXID_OPEN_DEVICE OpenDevice);
VOID XidCheckHandle(PCHAR ApiName, PXID_OPEN_DEVICE OpenDevice);
#define XID_INSERT_HANDLE(OpenDevice) XidInsertHandle(OpenDevice)
#define XID_REMOVE_HANDLE(OpenDevice) XidRemoveHandle(OpenDevice)
#define XID_CHECK_HANDLE(ApiName, OpenDevice) XidCheckHandle(ApiName, OpenDevice)
#else
#define XID_INSERT_HANDLE(OpenDevice) do{}while(0)
#define XID_REMOVE_HANDLE(OpenDevice) do{}while(0)
#define XID_CHECK_HANDLE(ApiName,OpenDevice) do{}while(0)
#endif
//--------------------------------------------------------------
// Implementation
//--------------------------------------------------------------
XBOXAPI
HANDLE
WINAPI
XInputOpen(
IN PXPP_DEVICE_TYPE XppDeviceType,
IN DWORD dwPort,
IN DWORD dwSlot,
IN PXINPUT_POLLING_PARAMETERS pPollingParameters OPTIONAL
)
/*++
Routine Description:
Public API to open a handle to an input device.
Arguments:
XppDeviceType - device type to open.
dwPort - Port of device to open.
dwSlot - Slot of device to open.
pPollingParameters - [optional] polling parameters to use with device.
Return Value:
On success, handle to be used to access device.
On failure, NULL. Call GetLastError for specific error code.
ERROR_SHARING_VIOLATION - attempt to open more than one handle to a single device.
ERROR_OUTOFMEMORY - there was not enough memory to open the handle.
ERROR_INVALID_PARAMETER - the device type was not known.
ERROR_DEVICE_NOT_CONNECTED - a device was not found of the given type in the specified
port and slot.
ERROR_IO_DEVICE - an error occured talking to the device.
--*/
{
DWORD errorCode;
PXID_OPEN_DEVICE openDevice = NULL;
ASSERT_LESS_THAN_DISPATCH_LEVEL();
RIP_ON_NOT_TRUE_WITH_MESSAGE(XPP_XInitDevicesHasBeenCalled, "XInputOpen: XInitDevices must be called first!");
XID_TYPE_INFORMATION *typeInformation = GetTypeInformation(XppDeviceType);
if(!typeInformation)
{
RIP("XInputOpen: XppDeviceType is not a valid type for the XInputXXX API\n");
SetLastError(ERROR_INVALID_PARAMETER);
return NULL;
}
//
// RIP port and slot parameters
//
RIP_ON_NOT_TRUE(XInputOpen, dwPort<XGetPortCount());
// At this time all XID devices are only direct connect, so this RIP applies.
// In the future, we may want to add a slot type parameter to the type table.
RIP_ON_NOT_TRUE(XInputOpen, (dwSlot == XDEVICE_NO_SLOT));
//
// If the polling parameters are NULL, look up the defaults based on type.
//
if(NULL == pPollingParameters)
{
pPollingParameters = typeInformation->DefaultPollingParameters;
}
//
// Open the device
//
errorCode = XID_fOpenDevice(
typeInformation,
(dwSlot != XDEVICE_BOTTOM_SLOT) ? dwPort : dwPort +16,
&openDevice,
pPollingParameters
);
//
// If the open failed, set the error code.
//
if(NULL==openDevice)
{
SetLastError(errorCode);
}
else
{
//
// Insert the handle into the list of
// valid handles.
//
XID_INSERT_HANDLE(openDevice);
}
return (HANDLE)openDevice;
}
XBOXAPI
void
WINAPI
XInputClose(
IN HANDLE hDevice
)
/*++
Routine Description:
Public API to close a handle to an input device.
Arguments:
XppDeviceType - device type to open.
dwPort - Port of device to open.
dwSlot - Slot of device to open.
pPollingParameters - [optional] polling parameters to use with device.
Return Value:
None
Remarks:
Close can block for several milliseconds while resources are being cleaned up.
It is the callers responsibility not to use a handle after XInputClose has been
called. It is particularly risky with multiple threads.
* No other call to an XInputXXX API using the handle should be started after
XInputClose has been called, even if XInputClose has not yet returned.
* XInputClose must not be called until all other XInputXXX API's using the handle
have returned.
In debug builds, an attempt is made to detect these conditions and RIP, however, the
RIP's are not full-proof, and under some circumstances, may miss such a condition.
If these rules are violated the result is undefined, in many cases this will lead to
memory corruption and system crash.
One should normally wait for asynchronous I/O (started with XInputSetState) to complete
before calling XInputClose, however, it is not an error to call XInputClose with
asynchronous I/O pending. The result is that pending I/O is guaranteed to complete,
usually with status set to ERROR_CANCELLED, before XInputClose returns.
--*/
{
PXID_OPEN_DEVICE openDevice = (PXID_OPEN_DEVICE)hDevice;
ASSERT_LESS_THAN_DISPATCH_LEVEL();
XID_REMOVE_HANDLE(openDevice);
XID_fCloseDevice(openDevice);
return;
}
XBOXAPI
DWORD
WINAPI
XInputGetCapabilities(
IN HANDLE hDevice,
OUT PXINPUT_CAPABILITIES pCapabilities
)
/*++
Routine Description:
Public API to retrieve capabilities from a device.
Arguments:
hDevice - handle to device for which to get capabilities.
pCapabilities - pointer to buffer to get capabilities.
Return Value:
ERROR_SUCCESS - on success.
ERROR_DEVICE_NOT_CONNECTED - if the device is no longer connected.
ERROR_IO_DEVICE - problem communicating with the device.
Remarks:
This routine blocks while the device is queried. This can take
a few milliseconds.
--*/
{
PCHAR pReportBuffer;
ULONG length;
URB_CONTROL_TRANSFER urb;
KEVENT event;
KIRQL oldIrql;
PXID_DEVICE_NODE xidNode;
DWORD errorCode = ERROR_SUCCESS;
PXID_OPEN_DEVICE openDevice = (PXID_OPEN_DEVICE)hDevice;
ASSERT_LESS_THAN_DISPATCH_LEVEL();
//
// Raise Irql for synchronization
//
oldIrql = KeRaiseIrqlToDpcLevel();
//
// Verify handle.
//
XID_CHECK_HANDLE("XInputGetCapabilities", openDevice);
//
// Make sure device is connected
//
xidNode = openDevice->XidNode;
if( NULL == xidNode || xidNode->PendingRemove)
{
errorCode = ERROR_DEVICE_NOT_CONNECTED;
goto exit_input_get_caps;
}
//
// Zero the whole output buffer
//
RtlZeroMemory(pCapabilities, sizeof(XINPUT_CAPABILITIES));
//
// Fill in the subtype, that doesn't depend on the report being queried
// so even if this fails with invalid parameter, we can check it.
//
pCapabilities->SubType = xidNode->SubType;
//
// Make sure that the device supports GET_CAPABILITIES.
// This check is primarily for legacy devices, in particular
// legacy keyboards.
//
if(xidNode->TypeInformation->ulFlags&XID_BSF_NO_CAPABILITIES)
{
errorCode = ERROR_ACCESS_DENIED;
}
else
{
//
// Get the output portion
//
pReportBuffer = (PCHAR)&pCapabilities->Out;
length = xidNode->TypeInformation->pOutputReportInfoList[0].bCurrentSize;
//
// Build the URB
//
KeInitializeEvent(&event, NotificationEvent, FALSE);
USB_BUILD_CONTROL_TRANSFER(
&urb,
NULL,
(PVOID)(pReportBuffer-2),
length+2,
USB_TRANSFER_DIRECTION_IN,
(PURB_COMPLETE_PROC)XID_SyncComplete,
&event,
TRUE,
USB_DEVICE_TO_HOST | USB_VENDOR_COMMAND | USB_COMMAND_TO_INTERFACE,
XID_COMMAND_GET_CAPABILITIES,
0x0200,
xidNode->InterfaceNumber,
length+2
);
//
// Submit the request.
//
xidNode->Device->SubmitRequest((PURB)&urb);
//
// Wait for transfer to complete
//
KeLowerIrql(oldIrql);
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
oldIrql = KeRaiseIrqlToDpcLevel();
//
// Recheck the handle (should not have been closed)
//
XID_CHECK_HANDLE("XInputGetCapabilities", openDevice);
//
// Make sure the device wasn't removed
// in the interm.
//
if( NULL == openDevice->XidNode || xidNode->PendingRemove)
{
errorCode = ERROR_DEVICE_NOT_CONNECTED;
goto exit_input_get_caps;
}
//
// Check for error
//
if(USBD_ERROR(urb.Hdr.Status))
{
errorCode = IUsbDevice::Win32FromUsbdStatus(urb.Hdr.Status);
goto exit_input_get_caps;
}
//
// Now get the input portion
//
pReportBuffer = (PCHAR)&pCapabilities->In;
length = xidNode->TypeInformation->pInputReportInfoList[0].bCurrentSize;
//
// Build the URB
//
USB_BUILD_CONTROL_TRANSFER(
&urb,
NULL,
(PVOID)(pReportBuffer-2),
length+2,
USB_TRANSFER_DIRECTION_IN,
(PURB_COMPLETE_PROC)XID_SyncComplete,
&event,
TRUE,
USB_DEVICE_TO_HOST | USB_VENDOR_COMMAND | USB_COMMAND_TO_INTERFACE,
XID_COMMAND_GET_CAPABILITIES,
0x0100,
xidNode->InterfaceNumber,
length+2
);
KeInitializeEvent(&event, NotificationEvent, FALSE);
xidNode->Device->SubmitRequest((PURB)&urb);
//
// Wait for transfer to complete
//
KeLowerIrql(oldIrql);
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
oldIrql = KeRaiseIrqlToDpcLevel();
//
// Make sure the device wasn't removed
// in the interm.
//
if( NULL == openDevice->XidNode || xidNode->PendingRemove)
{
errorCode = ERROR_DEVICE_NOT_CONNECTED;
goto exit_input_get_caps;
}
//
// Check for error
//
if(USBD_ERROR(urb.Hdr.Status))
{
errorCode = IUsbDevice::Win32FromUsbdStatus(urb.Hdr.Status);
goto exit_input_get_caps;
}
}
exit_input_get_caps:
//Bug 2578, make sure nobody tries to falsely divine information
//from the reserved field.
pCapabilities->Reserved = 0;
KeLowerIrql(oldIrql);
return errorCode;
}
XBOXAPI
DWORD
WINAPI
XInputGetState(
IN HANDLE hDevice,
OUT PXINPUT_STATE pState
)
/*++
Routine Description:
Public API for retrieving the latest known state of the
device. This routine does not actually perform I/O it
just copies the latest known state into the caller's buffer.
Arguments:
hDevice - handle of device to get state for.
pState - buffer to receive state.
Return Value:
ERROR_SUCCESS - on success.
ERROR_DEVICE_NOT_CONNECTED - device no longer connected (buffer is still copied).
--*/
{
PXID_OPEN_DEVICE openDevice = (PXID_OPEN_DEVICE) hDevice;
DWORD errorCode = ERROR_SUCCESS;
KIRQL oldIrql;
ULONG length;
oldIrql = KeRaiseIrqlToDpcLevel();
//
// Verify handle.
//
XID_CHECK_HANDLE("XInputGetState", openDevice);
//
// Don't allow keyboards to work
//
#ifdef DEBUG_KEYBOARD
if(XDEVICE_TYPE_DEBUG_KEYBOARD == openDevice->TypeInformation->XppType)
{
KeLowerIrql(oldIrql);
RIP("XInputGetState does not work for keyboards.");
return ERROR_INVALID_PARAMETER;
}
#endif //DEBUG_KEYBOARD
//
// If the device is not connected, mark it, but copy the
// data anyway.
//
if(NULL == openDevice->XidNode || openDevice->XidNode->PendingRemove)
{
errorCode = ERROR_DEVICE_NOT_CONNECTED;
}
//
// Copy packet number
//
pState->dwPacketNumber = openDevice->PacketNumber;
//
// Copy the size indicated in the capatibility table.
//
length = openDevice->TypeInformation->pInputReportInfoList[0].bCurrentSize;
//
// Copy the latest report (We are using GameReport, but we could
// use any member of the union of report types)
RtlCopyMemory( (PVOID)&pState->Gamepad, (PVOID)openDevice->Report, length);
KeLowerIrql(oldIrql);
return errorCode;
}
XBOXAPI
DWORD
WINAPI
XInputSetState(
IN HANDLE hDevice,
OUT PXINPUT_FEEDBACK pFeedback
)
/*++
Routine Description:
Public API for sending data to the device.
THIS IS ASYNCHRONOUS!!!
Arguments:
hDevice - handle of device to send data to.
pFeedback - feedback data to send
Return Value:
ERROR_IO_PENDING - on sucess.
ERROR_DEVICE_NOT_CONNECTED - the device is no longer connected,
Remarks:
Calling XInputSetState programs output to the device. This output could a few to many
milliseconds to complete depending on the pPollingParameters passed to XInputOpen.
During this time XAPI owns pFeedback, the caller should consider it read only.
There are two options for synchronization:
1) If the caller sets pFeedback->Header.hEvent to a valid event handle, it will
be signaled when the I/O completes, otherwise hEvent should be NULL.
2) The caller may poll pFeedback->Header.dwStatus. This will be ERROR_IO_PENDING
when XInputSetState returns, it will change to ERROR_SUCCESS or another (non-pending)
error code when the transfer completes, successfully or otherwuse.
Calling XInputClose before the I/O complete cancels the I/O. pFeedback->Header.hEvent
(if a valid event) will be signaled before XInputClose returns,and pFeedback->Header.dwStatus
will change to ERROR_CANCELLED.
--*/
{
PXID_OPEN_DEVICE openDevice = (PXID_OPEN_DEVICE) hDevice;
PXINPUT_FEEDBACK_INTERNAL feedback = (PXINPUT_FEEDBACK_INTERNAL)pFeedback;
//
// Verify handle.
//
XID_CHECK_HANDLE("XInputSetState", openDevice);
//
// Don't allow keyboards to work
//
#ifdef DEBUG_KEYBOARD
if(XDEVICE_TYPE_DEBUG_KEYBOARD == openDevice->TypeInformation->XppType)
{
RIP("XInputSetState does not work for keyboards.");
return ERROR_INVALID_PARAMETER;
}
#endif //DEBUG_KEYBOARD
//
// Check Handle the report ID, before
// passing down.
//
feedback->Internal.bReportId = 0;
feedback->Internal.bSize = openDevice->TypeInformation->pOutputReportInfoList[0].bCurrentSize + XID_REPORT_HEADER;
return XID_fSendDeviceReport(openDevice, feedback);
}
XBOXAPI
DWORD
WINAPI
XInputPoll(
IN HANDLE hDevice
)
/*++
Routine Description:
Public API for manually polling an input device.
Arguments:
hDevice - handle of device to poll.
Return Value:
ERROR_SUCCESS - on success.
ERROR_DEVICE_NOT_CONNECTED - if the device is no longer connected.
Remarks:
This API only needs to be called if XInputOpen was called with
pPollingParameter->fAutoPoll set to FALSE. Otherwise, it is
a no-op.
XInputGetState should be updated to return the latest state of the device
no later than (pPollingParameter->bInputInterval + 1) ms, after XInputPoll
is called.
--*/
{
DWORD errorCode = ERROR_SUCCESS;
PXID_OPEN_DEVICE openDevice = (PXID_OPEN_DEVICE)hDevice;
PXID_DEVICE_NODE xidNode;
KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
//
// Verify handle.
//
XID_CHECK_HANDLE("XInputPoll", openDevice);
//
// Make sure device is connected
//
xidNode = openDevice->XidNode;
if(NULL == xidNode || xidNode->PendingRemove)
{
errorCode = ERROR_DEVICE_NOT_CONNECTED;
} else
{
if(!openDevice->AutoPoll)
{
if(!openDevice->OutstandingPoll)
{
openDevice->OutstandingPoll = 1;
#ifdef ALAMO_RAW_DATA_HACK
if(xidNode->AlamoHack) XID_AlamoStartPoll(openDevice);
else {
#endif
openDevice->Urb.BulkOrInterruptTransfer.TransferBufferLength =
openDevice->XidNode->bMaxInputReportSize;
openDevice->XidNode->Device->SubmitRequest(&openDevice->Urb);
#ifdef ALAMO_RAW_DATA_HACK
}
#endif
}
}
}
KeLowerIrql(oldIrql);
return errorCode;
}
//---------------------------------------------------------------------
// Debug routine that keep track of open handles, and generate
// RIP's when they are not valid.
//---------------------------------------------------------------------
#ifdef XID_VERIFY_HANDLES
VOID XidInsertHandle(PXID_OPEN_DEVICE OpenDevice)
{
KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
OpenDevice->NextOpenDevice = XID_OpenDeviceList;
XID_OpenDeviceList = OpenDevice;
KeLowerIrql(oldIrql);
}
VOID XidRemoveHandle(PXID_OPEN_DEVICE OpenDevice)
{
KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
PXID_OPEN_DEVICE previousDevice = NULL;
PXID_OPEN_DEVICE device = XID_OpenDeviceList;
BOOL found = FALSE;
while(device)
{
if(device == OpenDevice)
{
if(previousDevice)
{
previousDevice->NextOpenDevice = device->NextOpenDevice;
} else
{
XID_OpenDeviceList = device->NextOpenDevice;
}
found = TRUE;
break;
}
previousDevice = device;
device = device->NextOpenDevice;
}
KeLowerIrql(oldIrql);
if(!found)
{
RIP("XInputClose: Invalid Handle\n");
}
return;
}
VOID XidCheckHandle(CHAR *ApiName, PXID_OPEN_DEVICE OpenDevice)
{
KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
PXID_OPEN_DEVICE device = XID_OpenDeviceList;
BOOL found = FALSE;
while(device)
{
if(device == OpenDevice)
{
found = TRUE;
break;
}
device = device->NextOpenDevice;
}
//
// BUG 9292 - cannot RIP at DISPATCH_LEVEL. This hangs the machine when debugging
// with VC. We were lowering IRQL to oldIrql before the RIP, but since
// this routine is usually called at DISPATCH_LEVEL, this is not good enough.
// Note that since this routine is NEVER called from a true DPC, it is always
// possible to explictly lower IRQL to PASSIVE_LEVEL. We only want to do this
// though when we are going to RIP. In that case, the system is already hosed
// and we do not lose any sleep over completely hosing it.
//
if(!found)
{
KeLowerIrql(PASSIVE_LEVEL);
CHAR buffer[255];
sprintf(buffer, "%s: Invalid Handle(0x%0.8x)\n", ApiName, OpenDevice);
RIP(buffer);
KIRQL dummyIrql;
KeRaiseIrql(oldIrql, &dummyIrql); // A feeble attempt to keep the system running after the RIP.
} else
{
KeLowerIrql(oldIrql);
}
return;
}
#endif //XID_VERIFY_HANDLES