[PATCH 4/5] xinput.sys: Create an internal PDO, on the XINPUT bus.

Rémi Bernon rbernon at codeweavers.com
Wed Aug 18 02:31:13 CDT 2021


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 at 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));
 
-- 
2.32.0




More information about the wine-devel mailing list