This internal PDO is an HID compatible pass-through device, but it needs to be kept private and is listed on the internal XINPUT device interface class, instead of the public HID device interface class.
This is a Wine extension for convenience and native XInput driver uses a different, undocumented, device interface.
This internal PDO filters the report read requests and copies the input reports to the pending xinput PDO read requests, completing them at the same time.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/hidclass.sys/hid.h | 1 + dlls/hidclass.sys/pnp.c | 6 +- dlls/xinput.sys/Makefile.in | 2 +- dlls/xinput.sys/main.c | 230 +++++++++++++++++++++++++++++++++++- 4 files changed, 233 insertions(+), 6 deletions(-)
diff --git a/dlls/hidclass.sys/hid.h b/dlls/hidclass.sys/hid.h index 1ca6a926872..4fa9e72e615 100644 --- a/dlls/hidclass.sys/hid.h +++ b/dlls/hidclass.sys/hid.h @@ -84,6 +84,7 @@ typedef struct _BASE_DEVICE_EXTENSION * for convenience. */ WCHAR device_id[MAX_DEVICE_ID_LEN]; WCHAR instance_id[MAX_DEVICE_ID_LEN]; + const GUID *interface_guid;
BOOL is_fdo; } BASE_DEVICE_EXTENSION; diff --git a/dlls/hidclass.sys/pnp.c b/dlls/hidclass.sys/pnp.c index db45aea2fd3..60aaa2e4a6f 100644 --- a/dlls/hidclass.sys/pnp.c +++ b/dlls/hidclass.sys/pnp.c @@ -38,6 +38,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(hid);
DEFINE_DEVPROPKEY(DEVPROPKEY_HID_HANDLE, 0xbc62e415, 0xf4fe, 0x405c, 0x8e, 0xda, 0x63, 0x6f, 0xb5, 0x9f, 0x08, 0x98, 2); +DEFINE_GUID(GUID_DEVINTERFACE_XINPUT, 0xec87f1e3, 0xc13b, 0x4100, 0xb5, 0xf7, 0x8b, 0x84, 0xd5, 0x42, 0x60, 0xcb);
#if defined(__i386__) && !defined(_WIN32)
@@ -165,6 +166,8 @@ static NTSTATUS WINAPI driver_add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *b ext->u.fdo.hid_ext.NextDeviceObject = bus_pdo; swprintf(ext->device_id, ARRAY_SIZE(ext->device_id), L"HID\%s", wcsrchr(device_id, '\') + 1); wcscpy(ext->instance_id, instance_id); + ext->interface_guid = !wcsncmp(device_id, L"XINPUT\", 7) ? &GUID_DEVINTERFACE_XINPUT + : &GUID_DEVINTERFACE_HID;
status = minidriver->AddDevice(minidriver->minidriver.DriverObject, fdo); if (status != STATUS_SUCCESS) @@ -220,6 +223,7 @@ static void create_child(minidriver *minidriver, DEVICE_OBJECT *fdo) KeInitializeSpinLock(&pdo_ext->u.pdo.irp_queue_lock); wcscpy(pdo_ext->device_id, fdo_ext->device_id); wcscpy(pdo_ext->instance_id, fdo_ext->instance_id); + pdo_ext->interface_guid = fdo_ext->interface_guid;
pdo_ext->u.pdo.information.VendorID = attr.VendorID; pdo_ext->u.pdo.information.ProductID = attr.ProductID; @@ -445,7 +449,7 @@ static NTSTATUS pdo_pnp(DEVICE_OBJECT *device, IRP *irp) }
case IRP_MN_START_DEVICE: - if ((status = IoRegisterDeviceInterface(device, &GUID_DEVINTERFACE_HID, NULL, &ext->u.pdo.link_name))) + if ((status = IoRegisterDeviceInterface(device, ext->interface_guid, NULL, &ext->u.pdo.link_name))) { ERR("Failed to register interface, status %#x.\n", status); break; diff --git a/dlls/xinput.sys/Makefile.in b/dlls/xinput.sys/Makefile.in index 52b00ef0528..f75c92a189f 100644 --- a/dlls/xinput.sys/Makefile.in +++ b/dlls/xinput.sys/Makefile.in @@ -1,5 +1,5 @@ MODULE = xinput.sys -IMPORTS = ntoskrnl +IMPORTS = ntoskrnl hidparse EXTRADLLFLAGS = -mno-cygwin -Wl,--subsystem,native
C_SRCS = \ diff --git a/dlls/xinput.sys/main.c b/dlls/xinput.sys/main.c index b97ec49b325..aaf4b78dc17 100644 --- a/dlls/xinput.sys/main.c +++ b/dlls/xinput.sys/main.c @@ -31,6 +31,7 @@ #include "cfgmgr32.h" #include "ddk/wdm.h" #include "ddk/hidport.h" +#include "ddk/hidpddi.h"
#include "wine/debug.h"
@@ -52,17 +53,26 @@ struct device_state { DEVICE_OBJECT *bus_pdo; DEVICE_OBJECT *xinput_pdo; + DEVICE_OBJECT *internal_pdo;
WCHAR bus_id[MAX_DEVICE_ID_LEN]; WCHAR instance_id[MAX_DEVICE_ID_LEN]; + + /* everything below requires holding the cs */ + CRITICAL_SECTION cs; + IRP *pending_read; + ULONG report_len; + char *report_buf; };
struct device_extension { BOOL is_fdo; + BOOL is_xinput; BOOL removed;
WCHAR device_id[MAX_DEVICE_ID_LEN]; + struct device_state *state; };
@@ -71,12 +81,114 @@ static inline struct device_extension *ext_from_DEVICE_OBJECT(DEVICE_OBJECT *dev return (struct device_extension *)device->DeviceExtension; }
+static NTSTATUS sync_ioctl(DEVICE_OBJECT *device, DWORD code, void *in_buf, DWORD in_len, void *out_buf, DWORD out_len) +{ + IO_STATUS_BLOCK io; + KEVENT event; + IRP *irp; + + KeInitializeEvent(&event, NotificationEvent, FALSE); + irp = IoBuildDeviceIoControlRequest(code, device, in_buf, in_len, out_buf, out_len, TRUE, &event, &io); + if (IoCallDriver(device, irp) == STATUS_PENDING) KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + return io.Status; +} + +static BOOL set_pending_read(struct device_state *state, IRP *irp) +{ + IRP *previous; + + RtlEnterCriticalSection(&state->cs); + if (!(previous = state->pending_read)) + { + state->pending_read = irp; + IoMarkIrpPending(irp); + } + RtlLeaveCriticalSection(&state->cs); + + return previous == NULL; +} + +static IRP *pop_pending_read(struct device_state *state) +{ + IRP *pending; + + RtlEnterCriticalSection(&state->cs); + pending = state->pending_read; + state->pending_read = NULL; + RtlLeaveCriticalSection(&state->cs); + + return pending; +} + +static NTSTATUS WINAPI xinput_ioctl(DEVICE_OBJECT *device, IRP *irp) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + struct device_state *state = ext->state; + + TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo); + + switch (code) + { + case IOCTL_HID_READ_REPORT: + if (!set_pending_read(state, irp)) + { + ERR("another read IRP was already pending!\n"); + irp->IoStatus.Status = STATUS_UNSUCCESSFUL; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_UNSUCCESSFUL; + } + return STATUS_PENDING; + + case IOCTL_HID_GET_INPUT_REPORT: + { + HID_XFER_PACKET *packet = (HID_XFER_PACKET *)irp->UserBuffer; + + RtlEnterCriticalSection(&state->cs); + memcpy(packet->reportBuffer, state->report_buf, state->report_len); + irp->IoStatus.Information = state->report_len; + RtlLeaveCriticalSection(&state->cs); + + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; + } + + default: + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(state->bus_pdo, irp); + } + + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI hid_read_report_completion(DEVICE_OBJECT *device, IRP *irp, void *context) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + ULONG read_len = irp->IoStatus.Information; + struct device_state *state = ext->state; + char *read_buf = irp->UserBuffer; + + TRACE("device %p, irp %p, bus_pdo %p.\n", device, irp, state->bus_pdo); + + RtlEnterCriticalSection(&state->cs); + if (!state->report_buf) WARN("report buffer not created yet.\n"); + else if (read_len <= state->report_len) memcpy(state->report_buf, read_buf, read_len); + else ERR("report length mismatch %u, expected %u\n", read_len, state->report_len); + RtlLeaveCriticalSection(&state->cs); + + if (irp->PendingReturned) IoMarkIrpPending(irp); + return STATUS_SUCCESS; +} + static NTSTATUS WINAPI internal_ioctl(DEVICE_OBJECT *device, IRP *irp) { struct device_extension *ext = ext_from_DEVICE_OBJECT(device); IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; struct device_state *state = ext->state; + IRP *pending;
if (InterlockedOr(&ext->removed, FALSE)) { @@ -85,12 +197,86 @@ static NTSTATUS WINAPI internal_ioctl(DEVICE_OBJECT *device, IRP *irp) IoCompleteRequest(irp, IO_NO_INCREMENT); }
+ if (ext->is_xinput) return xinput_ioctl(device, irp); + TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo);
- IoSkipCurrentIrpStackLocation(irp); + if (code == IOCTL_HID_READ_REPORT) + { + /* we completed the previous internal read, send the fixed up report to xinput pdo */ + if ((pending = pop_pending_read(state))) + { + RtlEnterCriticalSection(&state->cs); + memcpy(pending->UserBuffer, state->report_buf, state->report_len); + pending->IoStatus.Information = state->report_len; + RtlLeaveCriticalSection(&state->cs); + + pending->IoStatus.Status = irp->IoStatus.Status; + IoCompleteRequest(pending, IO_NO_INCREMENT); + } + + IoCopyCurrentIrpStackLocationToNext(irp); + IoSetCompletionRoutine(irp, hid_read_report_completion, NULL, TRUE, FALSE, FALSE); + } + else IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(state->bus_pdo, irp); }
+static NTSTATUS xinput_pdo_start_device(DEVICE_OBJECT *device) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + struct device_state *state = ext->state; + PHIDP_REPORT_DESCRIPTOR report_desc; + PHIDP_PREPARSED_DATA preparsed; + HIDP_DEVICE_DESC device_desc; + HID_DESCRIPTOR hid_desc; + ULONG report_desc_len; + NTSTATUS status; + HIDP_CAPS caps; + + if ((status = sync_ioctl(state->bus_pdo, IOCTL_HID_GET_DEVICE_DESCRIPTOR, NULL, 0, &hid_desc, sizeof(hid_desc)))) + return status; + + if (!(report_desc_len = hid_desc.DescriptorList[0].wReportLength)) return STATUS_UNSUCCESSFUL; + if (!(report_desc = malloc(report_desc_len))) return STATUS_NO_MEMORY; + + status = sync_ioctl(state->bus_pdo, IOCTL_HID_GET_REPORT_DESCRIPTOR, NULL, 0, report_desc, report_desc_len); + if (!status) status = HidP_GetCollectionDescription(report_desc, report_desc_len, PagedPool, &device_desc); + free(report_desc); + if (status != HIDP_STATUS_SUCCESS) return status; + + preparsed = device_desc.CollectionDesc->PreparsedData; + status = HidP_GetCaps(preparsed, &caps); + if (status != HIDP_STATUS_SUCCESS) WARN("HidP_GetCaps returned %#x\n", status); + + status = STATUS_SUCCESS; + RtlEnterCriticalSection(&state->cs); + state->report_len = caps.InputReportByteLength; + if (!device_desc.ReportIDs[0].ReportID) state->report_len--; + if (!(state->report_buf = malloc(state->report_len))) status = STATUS_NO_MEMORY; + RtlLeaveCriticalSection(&state->cs); + + HidP_FreeCollectionDescription(&device_desc); + return status; +} + +static NTSTATUS xinput_pdo_remove_device(DEVICE_OBJECT *device) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + struct device_state *state = ext->state; + char *report_buf; + + RtlEnterCriticalSection(&state->cs); + report_buf = state->report_buf; + state->report_buf = NULL; + state->report_len = 0; + RtlLeaveCriticalSection(&state->cs); + + free(report_buf); + + return STATUS_SUCCESS; +} + static WCHAR *query_instance_id(DEVICE_OBJECT *device) { struct device_extension *ext = ext_from_DEVICE_OBJECT(device); @@ -152,21 +338,33 @@ static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) struct device_state *state = ext->state; ULONG code = stack->MinorFunction; NTSTATUS status; + IRP *pending;
TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo);
switch (code) { case IRP_MN_START_DEVICE: - status = STATUS_SUCCESS; + if (ext->is_xinput) status = xinput_pdo_start_device(device); + else status = STATUS_SUCCESS; break;
case IRP_MN_SURPRISE_REMOVAL: status = STATUS_SUCCESS; if (InterlockedExchange(&ext->removed, TRUE)) break; + + if (!ext->is_xinput) break; + + if ((pending = pop_pending_read(state))) + { + pending->IoStatus.Status = STATUS_DELETE_PENDING; + pending->IoStatus.Information = 0; + IoCompleteRequest(pending, IO_NO_INCREMENT); + } break;
case IRP_MN_REMOVE_DEVICE: + if (ext->is_xinput) xinput_pdo_remove_device(device); irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(irp, IO_NO_INCREMENT); IoDeleteDevice(device); @@ -210,7 +408,7 @@ static void create_child_pdos(DEVICE_OBJECT *fdo) { struct device_extension *fdo_ext = fdo->DeviceExtension, *pdo_ext; struct device_state *state = fdo_ext->state; - DEVICE_OBJECT *xinput_pdo; + DEVICE_OBJECT *internal_pdo, *xinput_pdo; UNICODE_STRING string; WCHAR *tmp, pdo_name[255]; NTSTATUS status; @@ -223,16 +421,33 @@ static void create_child_pdos(DEVICE_OBJECT *fdo) return; }
+ swprintf(pdo_name, ARRAY_SIZE(pdo_name), L"\Device\XINPUT#%p&%p", fdo->DriverObject, state->bus_pdo); + RtlInitUnicodeString(&string, pdo_name); + if ((status = IoCreateDevice(fdo->DriverObject, sizeof(*pdo_ext), &string, 0, 0, FALSE, &internal_pdo))) + { + ERR( "failed to create internal PDO, status %#x.\n", status ); + IoDeleteDevice(xinput_pdo); + return; + } + pdo_ext = xinput_pdo->DeviceExtension; pdo_ext->is_fdo = FALSE; + pdo_ext->is_xinput = TRUE; pdo_ext->state = state; wcscpy(pdo_ext->device_id, fdo_ext->device_id); if ((tmp = wcsstr(pdo_ext->device_id, L"&MI_"))) memcpy(tmp, L"&IG", 6); else wcscat(pdo_ext->device_id, L"&IG_00");
+ pdo_ext = internal_pdo->DeviceExtension; + pdo_ext->is_fdo = FALSE; + pdo_ext->is_xinput = FALSE; + pdo_ext->state = state; + swprintf(pdo_ext->device_id, MAX_DEVICE_ID_LEN, L"XINPUT\%s", wcsrchr(fdo_ext->device_id, '\') + 1); + state->xinput_pdo = xinput_pdo; + state->internal_pdo = internal_pdo;
- TRACE("fdo %p, xinput PDO %p.\n", fdo, xinput_pdo); + TRACE("fdo %p, internal PDO %p, xinput PDO %p.\n", fdo, internal_pdo, xinput_pdo);
IoInvalidateDeviceRelations(state->bus_pdo, BusRelations); } @@ -262,6 +477,12 @@ static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) }
devices->Count = 0; + if ((child = state->internal_pdo)) + { + devices->Objects[devices->Count] = child; + call_fastcall_func1(ObfReferenceObject, child); + devices->Count++; + } if ((child = state->xinput_pdo)) { devices->Objects[devices->Count] = child; @@ -373,6 +594,7 @@ static NTSTATUS WINAPI add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *bus_pdo) wcscpy(state->bus_id, bus_id); swprintf(ext->device_id, MAX_DEVICE_ID_LEN, L"%s\%s", bus_id, device_id); wcscpy(state->instance_id, instance_id); + RtlInitializeCriticalSection(&state->cs);
TRACE("fdo %p, bus %s, device %s, instance %s.\n", fdo, debugstr_w(state->bus_id), debugstr_w(ext->device_id), debugstr_w(state->instance_id));