Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/dinput/tests/Makefile.in | 8 + dlls/dinput/tests/dinput_test.h | 1 + dlls/dinput/tests/driver_bus.c | 679 ++++++++++++++++++++++++- dlls/dinput/tests/driver_hid.c | 282 ++++++++++ dlls/dinput/tests/driver_hid.h | 19 +- dlls/dinput/tests/driver_hid.spec | 1 + dlls/dinput/tests/driver_hid_poll.c | 283 +++++++++++ dlls/dinput/tests/driver_hid_poll.spec | 1 + dlls/dinput/tests/hid.c | 188 ++++++- 9 files changed, 1446 insertions(+), 16 deletions(-) create mode 100644 dlls/dinput/tests/driver_hid.c create mode 100644 dlls/dinput/tests/driver_hid.spec create mode 100644 dlls/dinput/tests/driver_hid_poll.c create mode 100644 dlls/dinput/tests/driver_hid_poll.spec
diff --git a/dlls/dinput/tests/Makefile.in b/dlls/dinput/tests/Makefile.in index 013235c77d9..e2764e5f4d6 100644 --- a/dlls/dinput/tests/Makefile.in +++ b/dlls/dinput/tests/Makefile.in @@ -5,6 +5,10 @@ driver_IMPORTS = winecrt0 ntoskrnl hal hidclass driver_EXTRADLLFLAGS = -nodefaultlibs -nostartfiles -Wl,--subsystem,native driver_bus_IMPORTS = winecrt0 ntoskrnl hal driver_bus_EXTRADLLFLAGS = -nodefaultlibs -nostartfiles -Wl,--subsystem,native +driver_hid_IMPORTS = winecrt0 ntoskrnl hal hidclass +driver_hid_EXTRADLLFLAGS = -nodefaultlibs -nostartfiles -Wl,--subsystem,native +driver_hid_poll_IMPORTS = winecrt0 ntoskrnl hal hidclass +driver_hid_poll_EXTRADLLFLAGS = -nodefaultlibs -nostartfiles -Wl,--subsystem,native
SOURCES = \ device.c \ @@ -14,6 +18,10 @@ SOURCES = \ driver.spec \ driver_bus.c \ driver_bus.spec \ + driver_hid.c \ + driver_hid.spec \ + driver_hid_poll.c \ + driver_hid_poll.spec \ force_feedback.c \ hid.c \ hotplug.c \ diff --git a/dlls/dinput/tests/dinput_test.h b/dlls/dinput/tests/dinput_test.h index cb1c42a7e86..46ea8109c9a 100644 --- a/dlls/dinput/tests/dinput_test.h +++ b/dlls/dinput/tests/dinput_test.h @@ -49,6 +49,7 @@ extern const GUID expect_guid_product; extern const WCHAR expect_path[]; extern const WCHAR expect_path_end[];
+extern HANDLE device_added, device_removed; extern HINSTANCE instance; extern BOOL localized; /* object names get translated */
diff --git a/dlls/dinput/tests/driver_bus.c b/dlls/dinput/tests/driver_bus.c index 01f6e9ec9ee..959e620e8f6 100644 --- a/dlls/dinput/tests/driver_bus.c +++ b/dlls/dinput/tests/driver_bus.c @@ -28,6 +28,8 @@ #include "winternl.h" #include "winioctl.h" #include "ddk/wdm.h" +#include "ddk/hidsdi.h" +#include "ddk/hidport.h"
#include "wine/list.h"
@@ -37,6 +39,369 @@ typedef ULONG PNP_DEVICE_STATE; #define PNP_DEVICE_REMOVED 8
+#define check_buffer( a, b ) check_buffer_( __LINE__, a, b ) +static void check_buffer_( int line, HID_XFER_PACKET *packet, struct hid_expect *expect ) +{ + ULONG match_len, i; + + match_len = RtlCompareMemory( packet->reportBuffer, expect->report_buf, expect->report_len ); + ok( match_len == expect->report_len, "unexpected data:\n" ); + if (match_len == expect->report_len) return; + + for (i = 0; i < packet->reportBufferLen;) + { + char buffer[256], *buf = buffer; + buf += sprintf( buf, "%08lx ", i ); + do buf += sprintf( buf, " %02x", packet->reportBuffer[i] ); + while (++i % 16 && i < packet->reportBufferLen); + ok( 0, " %s\n", buffer ); + } +} + +#define EXPECT_QUEUE_BUFFER_SIZE (64 * sizeof(struct hid_expect)) + +struct expect_queue +{ + KSPIN_LOCK lock; + struct hid_expect *pos; + struct hid_expect *end; + struct hid_expect spurious; + struct hid_expect *buffer; + IRP *pending_wait; + char context[64]; +}; + +static void expect_queue_init( struct expect_queue *queue ) +{ + KeInitializeSpinLock( &queue->lock ); + queue->buffer = ExAllocatePool( PagedPool, EXPECT_QUEUE_BUFFER_SIZE ); + RtlSecureZeroMemory( queue->buffer, EXPECT_QUEUE_BUFFER_SIZE ); + queue->pos = queue->buffer; + queue->end = queue->buffer; +} + +static void expect_queue_cleanup( struct expect_queue *queue ) +{ + KIRQL irql; + IRP *irp; + + KeAcquireSpinLock( &queue->lock, &irql ); + if ((irp = queue->pending_wait)) + { + queue->pending_wait = NULL; + if (!IoSetCancelRoutine( irp, NULL )) irp = NULL; + } + KeReleaseSpinLock( &queue->lock, irql ); + + if (irp) + { + irp->IoStatus.Information = 0; + irp->IoStatus.Status = STATUS_DELETE_PENDING; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } + + ExFreePool( queue->buffer ); +} + +static void expect_queue_reset( struct expect_queue *queue, void *buffer, unsigned int size ) +{ + struct hid_expect *missing, *missing_end, *tmp; + char context[64]; + KIRQL irql; + + missing = ExAllocatePool( PagedPool, EXPECT_QUEUE_BUFFER_SIZE ); + RtlSecureZeroMemory( missing, EXPECT_QUEUE_BUFFER_SIZE ); + missing_end = missing; + + KeAcquireSpinLock( &queue->lock, &irql ); + tmp = queue->pos; + while (tmp < queue->end) *missing_end++ = *tmp++; + + queue->pos = queue->buffer; + queue->end = queue->buffer; + + if (size) memcpy( queue->end, buffer, size ); + queue->end = queue->end + size / sizeof(struct hid_expect); + memcpy( context, queue->context, sizeof(context) ); + KeReleaseSpinLock( &queue->lock, irql ); + + tmp = missing; + while (tmp != missing_end) + { + winetest_push_context( "%s expect[%Id]", context, tmp - missing ); + if (tmp->broken) + { + todo_wine_if( tmp->todo ) + win_skip( "broken (code %#lx id %u len %u)\n", tmp->code, tmp->report_id, tmp->report_len ); + } + else + { + todo_wine_if( tmp->todo ) + ok( tmp->wine_only, "missing (code %#lx id %u len %u)\n", tmp->code, tmp->report_id, tmp->report_len ); + } + winetest_pop_context(); + tmp++; + } + + ExFreePool( missing ); +} + +static void WINAPI wait_cancel_routine( DEVICE_OBJECT *device, IRP *irp ) +{ + struct expect_queue *queue = irp->Tail.Overlay.DriverContext[0]; + KIRQL irql; + + IoReleaseCancelSpinLock( irp->CancelIrql ); + + KeAcquireSpinLock( &queue->lock, &irql ); + queue->pending_wait = NULL; + KeReleaseSpinLock( &queue->lock, irql ); + + irp->IoStatus.Information = 0; + irp->IoStatus.Status = STATUS_CANCELLED; + IoCompleteRequest( irp, IO_NO_INCREMENT ); +} + +static NTSTATUS expect_queue_wait( struct expect_queue *queue, IRP *irp ) +{ + NTSTATUS status; + KIRQL irql; + + KeAcquireSpinLock( &queue->lock, &irql ); + if (queue->pos == queue->end) + status = STATUS_SUCCESS; + else + { + IoSetCancelRoutine( irp, wait_cancel_routine ); + if (irp->Cancel && !IoSetCancelRoutine( irp, NULL )) + status = STATUS_CANCELLED; + else + { + irp->Tail.Overlay.DriverContext[0] = queue; + IoMarkIrpPending( irp ); + queue->pending_wait = irp; + status = STATUS_PENDING; + } + } + KeReleaseSpinLock( &queue->lock, irql ); + + return status; +} + +static void expect_queue_next( struct expect_queue *queue, ULONG code, HID_XFER_PACKET *packet, LONG *index, + struct hid_expect *expect, BOOL compare_buf, char *context, ULONG context_size ) +{ + struct hid_expect *missing, *missing_end, *tmp; + ULONG len = packet->reportBufferLen; + BYTE *buf = packet->reportBuffer; + BYTE id = packet->reportId; + IRP *irp = NULL; + KIRQL irql; + + missing = ExAllocatePool( PagedPool, EXPECT_QUEUE_BUFFER_SIZE ); + RtlSecureZeroMemory( missing, EXPECT_QUEUE_BUFFER_SIZE ); + missing_end = missing; + + KeAcquireSpinLock( &queue->lock, &irql ); + tmp = queue->pos; + while (tmp < queue->end) + { + if (running_under_wine && !tmp->todo) break; + if (!running_under_wine && !tmp->broken && !tmp->wine_only) break; + if (tmp->code == code && tmp->report_id == id && tmp->report_len == len && + (!compare_buf || RtlCompareMemory( tmp->report_buf, buf, len ) == len)) + break; + *missing_end++ = *tmp++; + } + *index = tmp - queue->buffer; + if (tmp < queue->end) queue->pos = tmp + 1; + else tmp = &queue->spurious; + *expect = *tmp; + + while (queue->pos < queue->end) + { + if (running_under_wine || !queue->pos->wine_only) break; + queue->pos++; + } + if (queue->pos == queue->end && (irp = queue->pending_wait)) + { + queue->pending_wait = NULL; + if (!IoSetCancelRoutine( irp, NULL )) irp = NULL; + } + memcpy( context, queue->context, context_size ); + KeReleaseSpinLock( &queue->lock, irql ); + + if (irp) + { + irp->IoStatus.Information = 0; + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } + + ok( tmp != &queue->spurious, "%s got spurious packet\n", context ); + + winetest_push_context( "%s expect[%Id]", context, tmp - queue->buffer ); + todo_wine_if( tmp->todo ) + ok( !tmp->wine_only, "found code %#lx id %u len %u\n", tmp->code, tmp->report_id, tmp->report_len ); + winetest_pop_context(); + + tmp = missing; + while (tmp != missing_end) + { + winetest_push_context( "%s expect[%Id]", context, tmp - missing ); + if (tmp->broken) + { + todo_wine_if( tmp->todo ) + win_skip( "broken (code %#lx id %u len %u)\n", tmp->code, tmp->report_id, tmp->report_len ); + } + else + { + todo_wine_if( tmp->todo ) + ok( tmp->wine_only, "missing (code %#lx id %u len %u)\n", tmp->code, tmp->report_id, tmp->report_len ); + } + winetest_pop_context(); + tmp++; + } + + ExFreePool( missing ); +} + +struct irp_queue +{ + KSPIN_LOCK lock; + LIST_ENTRY list; +}; + +static IRP *irp_queue_pop( struct irp_queue *queue ) +{ + KIRQL irql; + IRP *irp; + + KeAcquireSpinLock( &queue->lock, &irql ); + if (IsListEmpty( &queue->list )) irp = NULL; + else irp = CONTAINING_RECORD( RemoveHeadList( &queue->list ), IRP, Tail.Overlay.ListEntry ); + KeReleaseSpinLock( &queue->lock, irql ); + + return irp; +} + +static void irp_queue_push( struct irp_queue *queue, IRP *irp ) +{ + KIRQL irql; + + KeAcquireSpinLock( &queue->lock, &irql ); + InsertTailList( &queue->list, &irp->Tail.Overlay.ListEntry ); + KeReleaseSpinLock( &queue->lock, irql ); +} + +static void irp_queue_clear( struct irp_queue *queue ) +{ + IRP *irp; + + while ((irp = irp_queue_pop( queue ))) + { + irp->IoStatus.Status = STATUS_DELETE_PENDING; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } +} + +static void irp_queue_init( struct irp_queue *queue ) +{ + KeInitializeSpinLock( &queue->lock ); + InitializeListHead( &queue->list ); +} + +struct input_queue +{ + KSPIN_LOCK lock; + BOOL is_polled; + struct hid_expect *pos; + struct hid_expect *end; + struct hid_expect *buffer; + struct irp_queue pending; +}; + +static void input_queue_init( struct input_queue *queue, BOOL is_polled ) +{ + KeInitializeSpinLock( &queue->lock ); + queue->is_polled = is_polled; + queue->buffer = ExAllocatePool( PagedPool, EXPECT_QUEUE_BUFFER_SIZE ); + RtlSecureZeroMemory( queue->buffer, EXPECT_QUEUE_BUFFER_SIZE ); + queue->pos = queue->buffer; + queue->end = queue->buffer; + irp_queue_init( &queue->pending ); +} + +static void input_queue_cleanup( struct input_queue *queue ) +{ + ExFreePool( queue->buffer ); +} + +static BOOL input_queue_read_locked( struct input_queue *queue, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG out_size = stack->Parameters.DeviceIoControl.OutputBufferLength; + struct hid_expect *tmp = queue->pos; + + if (tmp >= queue->end) return FALSE; + if (tmp->ret_length) out_size = tmp->ret_length; + + memcpy( irp->UserBuffer, tmp->report_buf, out_size ); + irp->IoStatus.Information = out_size; + irp->IoStatus.Status = tmp->ret_status; + if (tmp < queue->end) queue->pos = tmp + 1; + + /* loop on the queue data in polled mode */ + if (queue->is_polled && queue->pos == queue->end) queue->pos = queue->buffer; + return TRUE; +} + +static NTSTATUS input_queue_read( struct input_queue *queue, IRP *irp ) +{ + NTSTATUS status; + KIRQL irql; + + KeAcquireSpinLock( &queue->lock, &irql ); + if (input_queue_read_locked( queue, irp )) status = STATUS_SUCCESS; + else + { + IoMarkIrpPending( irp ); + irp_queue_push( &queue->pending, irp ); + status = STATUS_PENDING; + } + KeReleaseSpinLock( &queue->lock, irql ); + + return status; +} + +static void input_queue_reset( struct input_queue *queue, void *in_buf, ULONG in_size ) +{ + struct irp_queue completed; + ULONG remaining; + KIRQL irql; + IRP *irp; + + irp_queue_init( &completed ); + + KeAcquireSpinLock( &queue->lock, &irql ); + remaining = queue->end - queue->pos; + queue->pos = queue->buffer; + queue->end = queue->buffer; + memcpy( queue->end, in_buf, in_size ); + queue->end += in_size / sizeof(struct hid_expect); + + while (!queue->is_polled && queue->pos < queue->end && (irp = irp_queue_pop( &queue->pending ))) + { + input_queue_read_locked( queue, irp ); + irp_queue_push( &completed, irp ); + } + KeReleaseSpinLock( &queue->lock, irql ); + + if (!queue->is_polled) ok( !remaining, "unread input\n" ); + + while ((irp = irp_queue_pop( &completed ))) IoCompleteRequest( irp, IO_NO_INCREMENT ); +} + struct device { KSPIN_LOCK lock; @@ -56,6 +421,15 @@ struct phys_device
WCHAR instance_id[MAX_PATH]; WCHAR device_id[MAX_PATH]; + + BOOL use_report_id; + DWORD report_descriptor_len; + char report_descriptor_buf[1024]; + + HIDP_CAPS caps; + HID_DEVICE_ATTRIBUTES attributes; + struct expect_queue expect_queue; + struct input_queue input_queue; };
static inline struct phys_device *pdo_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) @@ -133,7 +507,9 @@ static DEVICE_OBJECT *find_child_device( struct func_device *impl, struct bus_de KIRQL irql; ULONG i;
- swprintf( device_id, MAX_PATH, L"WINETEST\VID_%04X&PID_%04X", desc->vid, desc->pid ); + swprintf( device_id, MAX_PATH, L"WINETEST\VID_%04X&PID_%04X", desc->attributes.VendorID, + desc->attributes.ProductID ); + if (desc->is_polled) wcscat( device_id, L"&POLL" );
KeAcquireSpinLock( &impl->base.lock, &irql ); devices = impl->devices->Objects; @@ -206,11 +582,18 @@ static WCHAR *query_hardware_ids( DEVICE_OBJECT *device )
static WCHAR *query_compatible_ids( DEVICE_OBJECT *device ) { - DWORD size = 0; + static const WCHAR hid_compat_id[] = L"WINETEST\WINE_COMP_HID"; + static const WCHAR hid_poll_compat_id[] = L"WINETEST\WINE_COMP_POLLHID"; + struct phys_device *impl = pdo_from_DEVICE_OBJECT( device ); + const WCHAR *compat_id = impl->input_queue.is_polled ? hid_poll_compat_id : hid_compat_id; + DWORD size = (wcslen( compat_id ) + 1) * sizeof(WCHAR); WCHAR *dst;
if ((dst = ExAllocatePool( PagedPool, size + sizeof(WCHAR) ))) + { + memcpy( dst, compat_id, size ); dst[size / sizeof(WCHAR)] = 0; + }
return dst; } @@ -283,6 +666,7 @@ static NTSTATUS pdo_pnp( DEVICE_OBJECT *device, IRP *irp ) state = (code == IRP_MN_START_DEVICE || code == IRP_MN_CANCEL_REMOVE_DEVICE) ? 0 : PNP_DEVICE_REMOVED; KeAcquireSpinLock( &impl->base.lock, &irql ); impl->base.state = state; + irp_queue_clear( &impl->input_queue.pending ); KeReleaseSpinLock( &impl->base.lock, irql ); if (code != IRP_MN_REMOVE_DEVICE) status = STATUS_SUCCESS; else @@ -291,6 +675,8 @@ static NTSTATUS pdo_pnp( DEVICE_OBJECT *device, IRP *irp ) IoCompleteRequest( irp, IO_NO_INCREMENT ); if (remove_child_device( fdo, device )) { + input_queue_cleanup( &impl->input_queue ); + expect_queue_cleanup( &impl->expect_queue ); IoDeleteDevice( device ); if (winetest_debug > 1) trace( "Deleted Bus PDO %p\n", device ); } @@ -410,6 +796,8 @@ static NTSTATUS create_child_pdo( DEVICE_OBJECT *device, struct bus_device_desc WCHAR name[MAX_PATH]; NTSTATUS status;
+ if (winetest_debug > 1) trace( "polled %u, report_id %u\n", desc->is_polled, desc->use_report_id ); + swprintf( name, MAX_PATH, L"\Device\WINETEST#%p&%p&%u", device->DriverObject, device, index++ ); RtlInitUnicodeString( &name_str, name );
@@ -421,11 +809,25 @@ static NTSTATUS create_child_pdo( DEVICE_OBJECT *device, struct bus_device_desc
impl = pdo_from_DEVICE_OBJECT( child ); KeInitializeSpinLock( &impl->base.lock ); - swprintf( impl->device_id, MAX_PATH, L"WINETEST\VID_%04X&PID_%04X", desc->vid, desc->pid ); + swprintf( impl->device_id, MAX_PATH, L"WINETEST\VID_%04X&PID_%04X", desc->attributes.VendorID, + desc->attributes.ProductID ); + /* use a different device ID so that driver cache select the polled driver */ + if (desc->is_polled) wcscat( impl->device_id, L"&POLL" ); swprintf( impl->instance_id, MAX_PATH, L"0&0000&0" ); impl->base.is_phys = TRUE; impl->fdo = fdo;
+ impl->use_report_id = desc->use_report_id; + impl->caps = desc->caps; + impl->attributes = desc->attributes; + impl->report_descriptor_len = desc->report_descriptor_len; + memcpy( impl->report_descriptor_buf, desc->report_descriptor_buf, desc->report_descriptor_len ); + input_queue_init( &impl->input_queue, desc->is_polled ); + input_queue_reset( &impl->input_queue, desc->input, desc->input_size ); + expect_queue_init( &impl->expect_queue ); + expect_queue_reset( &impl->expect_queue, desc->expect, desc->expect_size ); + memcpy( impl->expect_queue.context, desc->context, desc->context_size ); + if (winetest_debug > 1) trace( "Created Bus PDO %p for Bus FDO %p\n", child, device );
append_child_device( fdo, child ); @@ -541,14 +943,225 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) static NTSTATUS WINAPI pdo_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); - ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + struct phys_device *impl = pdo_from_DEVICE_OBJECT( device ); + const ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength; + ULONG out_size = stack->Parameters.DeviceIoControl.OutputBufferLength; + const ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + struct hid_expect expect = {0}; + char context[64]; + NTSTATUS status; + BOOL removed; + KIRQL irql; + LONG index;
if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_ioctl(code) ); - ok( 0, "unexpected call\n" );
- irp->IoStatus.Status = STATUS_NOT_SUPPORTED; - IoCompleteRequest( irp, IO_NO_INCREMENT ); - return STATUS_NOT_SUPPORTED; + KeAcquireSpinLock( &impl->base.lock, &irql ); + removed = impl->base.state == PNP_DEVICE_REMOVED; + KeReleaseSpinLock( &impl->base.lock, irql ); + + if (removed) + { + irp->IoStatus.Status = STATUS_DELETE_PENDING; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_DELETE_PENDING; + } + + winetest_push_context( "id %d%s", impl->use_report_id, impl->input_queue.is_polled ? " poll" : "" ); + + switch (code) + { + case IOCTL_HID_GET_DEVICE_DESCRIPTOR: + { + HID_DESCRIPTOR *desc = irp->UserBuffer; + + ok( !in_size, "got input size %lu\n", in_size ); + ok( out_size == sizeof(*desc), "got output size %lu\n", out_size ); + + if (out_size == sizeof(*desc)) + { + ok( !desc->bLength, "got size %u\n", desc->bLength ); + + desc->bLength = sizeof(*desc); + desc->bDescriptorType = HID_HID_DESCRIPTOR_TYPE; + desc->bcdHID = HID_REVISION; + desc->bCountry = 0; + desc->bNumDescriptors = 1; + desc->DescriptorList[0].bReportType = HID_REPORT_DESCRIPTOR_TYPE; + desc->DescriptorList[0].wReportLength = impl->report_descriptor_len; + irp->IoStatus.Information = sizeof(*desc); + } + status = STATUS_SUCCESS; + break; + } + + case IOCTL_HID_GET_REPORT_DESCRIPTOR: + ok( !in_size, "got input size %lu\n", in_size ); + ok( out_size == impl->report_descriptor_len, "got output size %lu\n", out_size ); + + if (out_size == impl->report_descriptor_len) + { + memcpy( irp->UserBuffer, impl->report_descriptor_buf, impl->report_descriptor_len ); + irp->IoStatus.Information = impl->report_descriptor_len; + } + status = STATUS_SUCCESS; + break; + + case IOCTL_HID_GET_DEVICE_ATTRIBUTES: + ok( !in_size, "got input size %lu\n", in_size ); + ok( out_size == sizeof(impl->attributes), "got output size %lu\n", out_size ); + + if (out_size == sizeof(impl->attributes)) + { + memcpy( irp->UserBuffer, &impl->attributes, sizeof(impl->attributes) ); + irp->IoStatus.Information = sizeof(impl->attributes); + } + status = STATUS_SUCCESS; + break; + + case IOCTL_HID_READ_REPORT: + { + ULONG expected_size = impl->caps.InputReportByteLength - (impl->use_report_id ? 0 : 1); + ok( !in_size, "got input size %lu\n", in_size ); + ok( out_size == expected_size, "got output size %lu\n", out_size ); + status = input_queue_read( &impl->input_queue, irp ); + break; + } + + case IOCTL_HID_WRITE_REPORT: + { + HID_XFER_PACKET *packet = irp->UserBuffer; + ULONG expected_size = impl->caps.OutputReportByteLength - (impl->use_report_id ? 0 : 1); + + ok( in_size == sizeof(*packet), "got input size %lu\n", in_size ); + ok( !out_size, "got output size %lu\n", out_size ); + ok( packet->reportBufferLen >= expected_size, "got report size %lu\n", packet->reportBufferLen ); + + expect_queue_next( &impl->expect_queue, code, packet, &index, &expect, TRUE, context, sizeof(context) ); + winetest_push_context( "%s expect[%ld]", context, index ); + ok( code == expect.code, "got %#lx, expected %#lx\n", code, expect.code ); + ok( packet->reportId == expect.report_id, "got id %u\n", packet->reportId ); + ok( packet->reportBufferLen == expect.report_len, "got len %lu\n", packet->reportBufferLen ); + check_buffer( packet, &expect ); + winetest_pop_context(); + + irp->IoStatus.Information = expect.ret_length ? expect.ret_length : expect.report_len; + status = expect.ret_status; + break; + } + + case IOCTL_HID_GET_INPUT_REPORT: + { + HID_XFER_PACKET *packet = irp->UserBuffer; + ULONG expected_size = impl->caps.InputReportByteLength - (impl->use_report_id ? 0 : 1); + ok( !in_size, "got input size %lu\n", in_size ); + ok( out_size == sizeof(*packet), "got output size %lu\n", out_size ); + + ok( packet->reportBufferLen >= expected_size, "got len %lu\n", packet->reportBufferLen ); + ok( !!packet->reportBuffer, "got buffer %p\n", packet->reportBuffer ); + + expect_queue_next( &impl->expect_queue, code, packet, &index, &expect, FALSE, context, sizeof(context) ); + winetest_push_context( "%s expect[%ld]", context, index ); + ok( code == expect.code, "got %#lx, expected %#lx\n", code, expect.code ); + ok( packet->reportId == expect.report_id, "got id %u\n", packet->reportId ); + ok( packet->reportBufferLen == expect.report_len, "got len %lu\n", packet->reportBufferLen ); + winetest_pop_context(); + + irp->IoStatus.Information = expect.ret_length ? expect.ret_length : expect.report_len; + memcpy( packet->reportBuffer, expect.report_buf, irp->IoStatus.Information ); + status = expect.ret_status; + break; + } + + case IOCTL_HID_SET_OUTPUT_REPORT: + { + HID_XFER_PACKET *packet = irp->UserBuffer; + ULONG expected_size = impl->caps.OutputReportByteLength - (impl->use_report_id ? 0 : 1); + ok( in_size == sizeof(*packet), "got input size %lu\n", in_size ); + ok( !out_size, "got output size %lu\n", out_size ); + + ok( packet->reportBufferLen >= expected_size, "got len %lu\n", packet->reportBufferLen ); + ok( !!packet->reportBuffer, "got buffer %p\n", packet->reportBuffer ); + + expect_queue_next( &impl->expect_queue, code, packet, &index, &expect, TRUE, context, sizeof(context) ); + winetest_push_context( "%s expect[%ld]", context, index ); + ok( code == expect.code, "got %#lx, expected %#lx\n", code, expect.code ); + ok( packet->reportId == expect.report_id, "got id %u\n", packet->reportId ); + ok( packet->reportBufferLen == expect.report_len, "got len %lu\n", packet->reportBufferLen ); + check_buffer( packet, &expect ); + winetest_pop_context(); + + irp->IoStatus.Information = expect.ret_length ? expect.ret_length : expect.report_len; + status = expect.ret_status; + break; + } + + case IOCTL_HID_GET_FEATURE: + { + HID_XFER_PACKET *packet = irp->UserBuffer; + ULONG expected_size = impl->caps.FeatureReportByteLength - (impl->use_report_id ? 0 : 1); + ok( !in_size, "got input size %lu\n", in_size ); + ok( out_size == sizeof(*packet), "got output size %lu\n", out_size ); + + ok( packet->reportBufferLen >= expected_size, "got len %lu\n", packet->reportBufferLen ); + ok( !!packet->reportBuffer, "got buffer %p\n", packet->reportBuffer ); + + expect_queue_next( &impl->expect_queue, code, packet, &index, &expect, FALSE, context, sizeof(context) ); + winetest_push_context( "%s expect[%ld]", context, index ); + ok( code == expect.code, "got %#lx, expected %#lx\n", code, expect.code ); + ok( packet->reportId == expect.report_id, "got id %u\n", packet->reportId ); + ok( packet->reportBufferLen == expect.report_len, "got len %lu\n", packet->reportBufferLen ); + winetest_pop_context(); + + irp->IoStatus.Information = expect.ret_length ? expect.ret_length : expect.report_len; + memcpy( packet->reportBuffer, expect.report_buf, irp->IoStatus.Information ); + status = expect.ret_status; + break; + } + + case IOCTL_HID_SET_FEATURE: + { + HID_XFER_PACKET *packet = irp->UserBuffer; + ULONG expected_size = impl->caps.FeatureReportByteLength - (impl->use_report_id ? 0 : 1); + ok( in_size == sizeof(*packet), "got input size %lu\n", in_size ); + ok( !out_size, "got output size %lu\n", out_size ); + + ok( packet->reportBufferLen >= expected_size, "got len %lu\n", packet->reportBufferLen ); + ok( !!packet->reportBuffer, "got buffer %p\n", packet->reportBuffer ); + + expect_queue_next( &impl->expect_queue, code, packet, &index, &expect, TRUE, context, sizeof(context) ); + winetest_push_context( "%s expect[%ld]", context, index ); + ok( code == expect.code, "got %#lx, expected %#lx\n", code, expect.code ); + ok( packet->reportId == expect.report_id, "got id %u\n", packet->reportId ); + ok( packet->reportBufferLen == expect.report_len, "got len %lu\n", packet->reportBufferLen ); + check_buffer( packet, &expect ); + winetest_pop_context(); + + irp->IoStatus.Information = expect.ret_length ? expect.ret_length : expect.report_len; + status = expect.ret_status; + break; + } + + case IOCTL_HID_GET_STRING: + memcpy( irp->UserBuffer, L"Wine Test", sizeof(L"Wine Test") ); + irp->IoStatus.Information = sizeof(L"Wine Test"); + status = STATUS_SUCCESS; + break; + + default: + ok( 0, "unexpected call\n" ); + status = irp->IoStatus.Status; + break; + } + + winetest_pop_context(); + + if (status != STATUS_PENDING) + { + irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } + return status; }
static NTSTATUS WINAPI fdo_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) @@ -574,14 +1187,52 @@ static NTSTATUS WINAPI driver_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) static NTSTATUS WINAPI pdo_ioctl( DEVICE_OBJECT *device, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + struct phys_device *impl = pdo_from_DEVICE_OBJECT( device ); + ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength; ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + NTSTATUS status; + KIRQL irql;
if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_ioctl(code) ); - ok( 0, "unexpected call\n" );
- irp->IoStatus.Status = STATUS_NOT_SUPPORTED; - IoCompleteRequest( irp, IO_NO_INCREMENT ); - return STATUS_NOT_SUPPORTED; + switch (code) + { + case IOCTL_WINETEST_HID_SET_EXPECT: + expect_queue_reset( &impl->expect_queue, irp->AssociatedIrp.SystemBuffer, in_size ); + status = STATUS_SUCCESS; + break; + case IOCTL_WINETEST_HID_WAIT_EXPECT: + status = expect_queue_wait( &impl->expect_queue, irp ); + break; + case IOCTL_WINETEST_HID_SEND_INPUT: + input_queue_reset( &impl->input_queue, irp->AssociatedIrp.SystemBuffer, in_size ); + status = STATUS_SUCCESS; + break; + case IOCTL_WINETEST_HID_SET_CONTEXT: + KeAcquireSpinLock( &impl->expect_queue.lock, &irql ); + memcpy( impl->expect_queue.context, irp->AssociatedIrp.SystemBuffer, in_size ); + KeReleaseSpinLock( &impl->expect_queue.lock, irql ); + status = STATUS_SUCCESS; + break; + case IOCTL_WINETEST_REMOVE_DEVICE: + KeAcquireSpinLock( &impl->base.lock, &irql ); + impl->base.state = PNP_DEVICE_REMOVED; + irp_queue_clear( &impl->input_queue.pending ); + KeReleaseSpinLock( &impl->base.lock, irql ); + status = STATUS_SUCCESS; + break; + case IOCTL_WINETEST_CREATE_DEVICE: + ok( 0, "unexpected call\n" ); + status = irp->IoStatus.Status; + break; + } + + if (status != STATUS_PENDING) + { + irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } + return status; }
static NTSTATUS WINAPI fdo_ioctl( DEVICE_OBJECT *device, IRP *irp ) @@ -603,7 +1254,11 @@ static NTSTATUS WINAPI fdo_ioctl( DEVICE_OBJECT *device, IRP *irp ) case IOCTL_WINETEST_REMOVE_DEVICE: if ((device = find_child_device( impl, irp->AssociatedIrp.SystemBuffer )) && !remove_child_device( impl, device )) + { + status = pdo_ioctl( device, irp ); IoInvalidateDeviceRelations( impl->pdo, BusRelations ); + return status; + } status = STATUS_SUCCESS; break; default: diff --git a/dlls/dinput/tests/driver_hid.c b/dlls/dinput/tests/driver_hid.c new file mode 100644 index 00000000000..9222c4b8f18 --- /dev/null +++ b/dlls/dinput/tests/driver_hid.c @@ -0,0 +1,282 @@ +/* + * Copyright 2022 Rémi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> +#include <stdio.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winioctl.h" +#include "ddk/wdm.h" +#include "hidusage.h" +#include "ddk/hidpi.h" +#include "ddk/hidport.h" + +#include "wine/list.h" + +#include "initguid.h" +#include "driver_hid.h" + +static DRIVER_OBJECT *expect_driver; + +struct hid_device +{ + DEVICE_OBJECT *expect_bus_pdo; + DEVICE_OBJECT *expect_hid_fdo; + struct hid_device *expect_hid_ext; + UNICODE_STRING control_symlink; +}; + +static void check_device( DEVICE_OBJECT *device ) +{ + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + struct hid_device *impl = ext->MiniDeviceExtension; + + ok( device == impl->expect_hid_fdo, "got device %p\n", device ); + ok( device->DriverObject == expect_driver, "got DriverObject %p\n", device->DriverObject ); + if (!device->NextDevice) ok( device == impl->expect_hid_fdo, "got device %p\n", device ); + else ok( device->NextDevice == impl->expect_hid_fdo, "got NextDevice %p\n", device->NextDevice ); + ok( !device->AttachedDevice, "got AttachedDevice %p\n", device->AttachedDevice ); + + ok( ext->MiniDeviceExtension == impl->expect_hid_ext, "got MiniDeviceExtension %p\n", ext->MiniDeviceExtension ); + ok( ext->PhysicalDeviceObject == impl->expect_bus_pdo, "got PhysicalDeviceObject %p\n", ext->PhysicalDeviceObject ); + ok( ext->NextDeviceObject == impl->expect_bus_pdo, "got NextDeviceObject %p\n", ext->NextDeviceObject ); +} + +#ifdef __ASM_USE_FASTCALL_WRAPPER +extern void *WINAPI wrap_fastcall_func1( void *func, const void *a ); +__ASM_STDCALL_FUNC( wrap_fastcall_func1, 8, + "popl %ecx\n\t" + "popl %eax\n\t" + "xchgl (%esp),%ecx\n\t" + "jmp *%eax" ); +#define call_fastcall_func1( func, a ) wrap_fastcall_func1( func, a ) +#else +#define call_fastcall_func1( func, a ) func( a ) +#endif + +static ULONG_PTR get_device_relations( DEVICE_OBJECT *device, DEVICE_RELATIONS *previous, + ULONG count, DEVICE_OBJECT **devices ) +{ + DEVICE_RELATIONS *relations; + ULONG new_count = count; + + if (previous) new_count += previous->Count; + if (!(relations = ExAllocatePool( PagedPool, offsetof( DEVICE_RELATIONS, Objects[new_count] ) ))) + { + ok( 0, "Failed to allocate memory\n" ); + return (ULONG_PTR)previous; + } + + if (!previous) relations->Count = 0; + else + { + memcpy( relations, previous, offsetof( DEVICE_RELATIONS, Objects[previous->Count] ) ); + ExFreePool( previous ); + } + + while (count--) + { + call_fastcall_func1( ObfReferenceObject, *devices ); + relations->Objects[relations->Count++] = *devices++; + } + + return (ULONG_PTR)relations; +} + +static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + struct hid_device *impl = ext->MiniDeviceExtension; + ULONG code = stack->MinorFunction; + + if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_pnp(code) ); + + switch (code) + { + case IRP_MN_START_DEVICE: + IoSetDeviceInterfaceState( &impl->control_symlink, TRUE ); + irp->IoStatus.Status = STATUS_SUCCESS; + break; + case IRP_MN_REMOVE_DEVICE: + IoSetDeviceInterfaceState( &impl->control_symlink, FALSE ); + RtlFreeUnicodeString( &impl->control_symlink ); + irp->IoStatus.Status = STATUS_SUCCESS; + break; + case IRP_MN_STOP_DEVICE: + case IRP_MN_SURPRISE_REMOVAL: + case IRP_MN_CANCEL_REMOVE_DEVICE: + case IRP_MN_QUERY_REMOVE_DEVICE: + irp->IoStatus.Status = STATUS_SUCCESS; + break; + + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + DEVICE_RELATION_TYPE type = stack->Parameters.QueryDeviceRelations.Type; + switch (type) + { + case BusRelations: + case EjectionRelations: + if (winetest_debug > 1) trace( "IRP_MN_QUERY_DEVICE_RELATIONS type %u not handled\n", type ); + break; + case RemovalRelations: + ok( !irp->IoStatus.Information, "got unexpected RemovalRelations relations\n" ); + irp->IoStatus.Information = get_device_relations( device, (void *)irp->IoStatus.Information, + 1, &ext->PhysicalDeviceObject ); + if (!irp->IoStatus.Information) irp->IoStatus.Status = STATUS_NO_MEMORY; + else irp->IoStatus.Status = STATUS_SUCCESS; + break; + default: ok( 0, "got unexpected IRP_MN_QUERY_DEVICE_RELATIONS type %#x\n", type ); break; + } + break; + } + } + + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( ext->NextDeviceObject, irp ); +} + +static NTSTATUS WINAPI driver_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + const ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + + if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_ioctl(code) ); + check_device( device ); + + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( ext->PhysicalDeviceObject, irp ); +} + +static NTSTATUS (WINAPI *hidclass_driver_ioctl)( DEVICE_OBJECT *device, IRP *irp ); +static NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + + if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_ioctl(code) ); + + switch (code) + { + case IOCTL_WINETEST_HID_SET_EXPECT: + case IOCTL_WINETEST_HID_WAIT_EXPECT: + case IOCTL_WINETEST_HID_SEND_INPUT: + case IOCTL_WINETEST_HID_SET_CONTEXT: + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( ext->PhysicalDeviceObject, irp ); + + case IOCTL_WINETEST_REMOVE_DEVICE: + case IOCTL_WINETEST_CREATE_DEVICE: + ok( 0, "unexpected call\n" ); + irp->IoStatus.Status = STATUS_NOT_SUPPORTED; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_NOT_SUPPORTED; + } + + return hidclass_driver_ioctl( device, irp ); +} + +static NTSTATUS WINAPI driver_add_device( DRIVER_OBJECT *driver, DEVICE_OBJECT *device ) +{ + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + struct hid_device *impl = ext->MiniDeviceExtension; + DEVICE_OBJECT *bus_pdo = ext->PhysicalDeviceObject; + NTSTATUS status; + + if (winetest_debug > 1) trace( "%s: driver %p, device %p\n", __func__, driver, device ); + + impl->expect_hid_fdo = device; + impl->expect_bus_pdo = ext->PhysicalDeviceObject; + impl->expect_hid_ext = ext->MiniDeviceExtension; + + todo_wine + ok( impl->expect_bus_pdo->AttachedDevice == device, "got AttachedDevice %p\n", bus_pdo->AttachedDevice ); + ok( driver == expect_driver, "got driver %p\n", driver ); + check_device( device ); + + status = IoRegisterDeviceInterface( ext->PhysicalDeviceObject, &control_class, NULL, &impl->control_symlink ); + ok( !status, "IoRegisterDeviceInterface returned %#lx\n", status ); + + if (winetest_debug > 1) trace( "Created HID FDO %p for Bus PDO %p\n", device, ext->PhysicalDeviceObject ); + + device->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI driver_create( DEVICE_OBJECT *device, IRP *irp ) +{ + if (winetest_debug > 1) trace( "%s: device %p\n", __func__, device ); + ok( 0, "unexpected call\n" ); + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI driver_close( DEVICE_OBJECT *device, IRP *irp ) +{ + if (winetest_debug > 1) trace( "%s: device %p\n", __func__, device ); + ok( 0, "unexpected call\n" ); + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; +} + +static void WINAPI driver_unload( DRIVER_OBJECT *driver ) +{ + if (winetest_debug > 1) trace( "%s: driver %p\n", __func__, driver ); + winetest_cleanup(); +} + +NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *registry ) +{ + HID_MINIDRIVER_REGISTRATION params = + { + .Revision = HID_REVISION, + .DriverObject = driver, + .DeviceExtensionSize = sizeof(struct hid_device), + .RegistryPath = registry, + }; + NTSTATUS status; + + expect_driver = driver; + + if ((status = winetest_init())) return status; + if (winetest_debug > 1) trace( "%s: driver %p\n", __func__, driver ); + + driver->DriverExtension->AddDevice = driver_add_device; + driver->DriverUnload = driver_unload; + driver->MajorFunction[IRP_MJ_PNP] = driver_pnp; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driver_ioctl; + driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = driver_internal_ioctl; + driver->MajorFunction[IRP_MJ_CREATE] = driver_create; + driver->MajorFunction[IRP_MJ_CLOSE] = driver_close; + + status = HidRegisterMinidriver( ¶ms ); + ok( !status, "got %#lx\n", status ); + + hidclass_driver_ioctl = driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driver_ioctl; + + return STATUS_SUCCESS; +} diff --git a/dlls/dinput/tests/driver_hid.h b/dlls/dinput/tests/driver_hid.h index 8ac91e5cc81..0bc8e34ccc9 100644 --- a/dlls/dinput/tests/driver_hid.h +++ b/dlls/dinput/tests/driver_hid.h @@ -35,6 +35,8 @@ #include "winternl.h"
#include "ddk/wdm.h" +#include "ddk/hidsdi.h" +#include "ddk/hidport.h" #include "ddk/hidclass.h"
DEFINE_GUID(control_class,0xdeadbeef,0x29ef,0x4538,0xa5,0xfd,0xb6,0x95,0x73,0xa3,0x62,0xc0); @@ -62,8 +64,21 @@ struct hid_expect /* create/remove device */ struct bus_device_desc { - WORD vid; - WORD pid; + BOOL is_polled; + BOOL use_report_id; + + DWORD report_descriptor_len; + char report_descriptor_buf[1024]; + + HIDP_CAPS caps; + HID_DEVICE_ATTRIBUTES attributes; + + ULONG input_size; + struct hid_expect input[64]; + ULONG expect_size; + struct hid_expect expect[64]; + ULONG context_size; + char context[64]; };
/* kernel/user shared data */ diff --git a/dlls/dinput/tests/driver_hid.spec b/dlls/dinput/tests/driver_hid.spec new file mode 100644 index 00000000000..ad33444716a --- /dev/null +++ b/dlls/dinput/tests/driver_hid.spec @@ -0,0 +1 @@ +# nothing here yet diff --git a/dlls/dinput/tests/driver_hid_poll.c b/dlls/dinput/tests/driver_hid_poll.c new file mode 100644 index 00000000000..7d9c215a0f0 --- /dev/null +++ b/dlls/dinput/tests/driver_hid_poll.c @@ -0,0 +1,283 @@ +/* + * Copyright 2022 Rémi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> +#include <stdio.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winioctl.h" +#include "ddk/wdm.h" +#include "hidusage.h" +#include "ddk/hidpi.h" +#include "ddk/hidport.h" + +#include "wine/list.h" + +#include "initguid.h" +#include "driver_hid.h" + +static DRIVER_OBJECT *expect_driver; + +struct hid_device +{ + DEVICE_OBJECT *expect_bus_pdo; + DEVICE_OBJECT *expect_hid_fdo; + struct hid_device *expect_hid_ext; + UNICODE_STRING control_symlink; +}; + +static void check_device( DEVICE_OBJECT *device ) +{ + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + struct hid_device *impl = ext->MiniDeviceExtension; + + ok( device == impl->expect_hid_fdo, "got device %p\n", device ); + ok( device->DriverObject == expect_driver, "got DriverObject %p\n", device->DriverObject ); + if (!device->NextDevice) ok( device == impl->expect_hid_fdo, "got device %p\n", device ); + else ok( device->NextDevice == impl->expect_hid_fdo, "got NextDevice %p\n", device->NextDevice ); + ok( !device->AttachedDevice, "got AttachedDevice %p\n", device->AttachedDevice ); + + ok( ext->MiniDeviceExtension == impl->expect_hid_ext, "got MiniDeviceExtension %p\n", ext->MiniDeviceExtension ); + ok( ext->PhysicalDeviceObject == impl->expect_bus_pdo, "got PhysicalDeviceObject %p\n", ext->PhysicalDeviceObject ); + ok( ext->NextDeviceObject == impl->expect_bus_pdo, "got NextDeviceObject %p\n", ext->NextDeviceObject ); +} + +#ifdef __ASM_USE_FASTCALL_WRAPPER +extern void *WINAPI wrap_fastcall_func1( void *func, const void *a ); +__ASM_STDCALL_FUNC( wrap_fastcall_func1, 8, + "popl %ecx\n\t" + "popl %eax\n\t" + "xchgl (%esp),%ecx\n\t" + "jmp *%eax" ); +#define call_fastcall_func1( func, a ) wrap_fastcall_func1( func, a ) +#else +#define call_fastcall_func1( func, a ) func( a ) +#endif + +static ULONG_PTR get_device_relations( DEVICE_OBJECT *device, DEVICE_RELATIONS *previous, + ULONG count, DEVICE_OBJECT **devices ) +{ + DEVICE_RELATIONS *relations; + ULONG new_count = count; + + if (previous) new_count += previous->Count; + if (!(relations = ExAllocatePool( PagedPool, offsetof( DEVICE_RELATIONS, Objects[new_count] ) ))) + { + ok( 0, "Failed to allocate memory\n" ); + return (ULONG_PTR)previous; + } + + if (!previous) relations->Count = 0; + else + { + memcpy( relations, previous, offsetof( DEVICE_RELATIONS, Objects[previous->Count] ) ); + ExFreePool( previous ); + } + + while (count--) + { + call_fastcall_func1( ObfReferenceObject, *devices ); + relations->Objects[relations->Count++] = *devices++; + } + + return (ULONG_PTR)relations; +} + +static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + struct hid_device *impl = ext->MiniDeviceExtension; + ULONG code = stack->MinorFunction; + + if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_pnp(code) ); + + switch (code) + { + case IRP_MN_START_DEVICE: + IoSetDeviceInterfaceState( &impl->control_symlink, TRUE ); + irp->IoStatus.Status = STATUS_SUCCESS; + break; + case IRP_MN_REMOVE_DEVICE: + IoSetDeviceInterfaceState( &impl->control_symlink, FALSE ); + RtlFreeUnicodeString( &impl->control_symlink ); + irp->IoStatus.Status = STATUS_SUCCESS; + break; + case IRP_MN_STOP_DEVICE: + case IRP_MN_SURPRISE_REMOVAL: + case IRP_MN_CANCEL_REMOVE_DEVICE: + case IRP_MN_QUERY_REMOVE_DEVICE: + irp->IoStatus.Status = STATUS_SUCCESS; + break; + + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + DEVICE_RELATION_TYPE type = stack->Parameters.QueryDeviceRelations.Type; + switch (type) + { + case BusRelations: + case EjectionRelations: + if (winetest_debug > 1) trace( "IRP_MN_QUERY_DEVICE_RELATIONS type %u not handled\n", type ); + break; + case RemovalRelations: + ok( !irp->IoStatus.Information, "got unexpected RemovalRelations relations\n" ); + irp->IoStatus.Information = get_device_relations( device, (void *)irp->IoStatus.Information, + 1, &ext->PhysicalDeviceObject ); + if (!irp->IoStatus.Information) irp->IoStatus.Status = STATUS_NO_MEMORY; + else irp->IoStatus.Status = STATUS_SUCCESS; + break; + default: ok( 0, "got unexpected IRP_MN_QUERY_DEVICE_RELATIONS type %#x\n", type ); break; + } + break; + } + } + + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( ext->NextDeviceObject, irp ); +} + +static NTSTATUS WINAPI driver_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + const ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + + if (code != IOCTL_HID_READ_REPORT && winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_ioctl(code) ); + check_device( device ); + + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( ext->PhysicalDeviceObject, irp ); +} + +static NTSTATUS (WINAPI *hidclass_driver_ioctl)( DEVICE_OBJECT *device, IRP *irp ); +static NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + + if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_ioctl(code) ); + + switch (code) + { + case IOCTL_WINETEST_HID_SET_EXPECT: + case IOCTL_WINETEST_HID_WAIT_EXPECT: + case IOCTL_WINETEST_HID_SEND_INPUT: + case IOCTL_WINETEST_HID_SET_CONTEXT: + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( ext->PhysicalDeviceObject, irp ); + + case IOCTL_WINETEST_REMOVE_DEVICE: + case IOCTL_WINETEST_CREATE_DEVICE: + ok( 0, "unexpected call\n" ); + irp->IoStatus.Status = STATUS_NOT_SUPPORTED; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_NOT_SUPPORTED; + } + + return hidclass_driver_ioctl( device, irp ); +} + +static NTSTATUS WINAPI driver_add_device( DRIVER_OBJECT *driver, DEVICE_OBJECT *device ) +{ + HID_DEVICE_EXTENSION *ext = device->DeviceExtension; + struct hid_device *impl = ext->MiniDeviceExtension; + DEVICE_OBJECT *bus_pdo = ext->PhysicalDeviceObject; + NTSTATUS status; + + if (winetest_debug > 1) trace( "%s: driver %p, device %p\n", __func__, driver, device ); + + impl->expect_hid_fdo = device; + impl->expect_bus_pdo = ext->PhysicalDeviceObject; + impl->expect_hid_ext = ext->MiniDeviceExtension; + + todo_wine + ok( impl->expect_bus_pdo->AttachedDevice == device, "got AttachedDevice %p\n", bus_pdo->AttachedDevice ); + ok( driver == expect_driver, "got driver %p\n", driver ); + check_device( device ); + + status = IoRegisterDeviceInterface( ext->PhysicalDeviceObject, &control_class, NULL, &impl->control_symlink ); + ok( !status, "IoRegisterDeviceInterface returned %#lx\n", status ); + + if (winetest_debug > 1) trace( "Created HID FDO %p for Bus PDO %p\n", device, ext->PhysicalDeviceObject ); + + device->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI driver_create( DEVICE_OBJECT *device, IRP *irp ) +{ + if (winetest_debug > 1) trace( "%s: device %p\n", __func__, device ); + ok( 0, "unexpected call\n" ); + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI driver_close( DEVICE_OBJECT *device, IRP *irp ) +{ + if (winetest_debug > 1) trace( "%s: device %p\n", __func__, device ); + ok( 0, "unexpected call\n" ); + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; +} + +static void WINAPI driver_unload( DRIVER_OBJECT *driver ) +{ + if (winetest_debug > 1) trace( "%s: driver %p\n", __func__, driver ); + winetest_cleanup(); +} + +NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *registry ) +{ + HID_MINIDRIVER_REGISTRATION params = + { + .Revision = HID_REVISION, + .DriverObject = driver, + .DeviceExtensionSize = sizeof(struct hid_device), + .RegistryPath = registry, + .DevicesArePolled = TRUE, + }; + NTSTATUS status; + + expect_driver = driver; + + if ((status = winetest_init())) return status; + if (winetest_debug > 1) trace( "%s: driver %p\n", __func__, driver ); + + driver->DriverExtension->AddDevice = driver_add_device; + driver->DriverUnload = driver_unload; + driver->MajorFunction[IRP_MJ_PNP] = driver_pnp; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driver_ioctl; + driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = driver_internal_ioctl; + driver->MajorFunction[IRP_MJ_CREATE] = driver_create; + driver->MajorFunction[IRP_MJ_CLOSE] = driver_close; + + status = HidRegisterMinidriver( ¶ms ); + ok( !status, "got %#lx\n", status ); + + hidclass_driver_ioctl = driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = driver_ioctl; + + return STATUS_SUCCESS; +} diff --git a/dlls/dinput/tests/driver_hid_poll.spec b/dlls/dinput/tests/driver_hid_poll.spec new file mode 100644 index 00000000000..ad33444716a --- /dev/null +++ b/dlls/dinput/tests/driver_hid_poll.spec @@ -0,0 +1 @@ +# nothing here yet diff --git a/dlls/dinput/tests/hid.c b/dlls/dinput/tests/hid.c index e61ff11b925..0ee6ab61fea 100644 --- a/dlls/dinput/tests/hid.c +++ b/dlls/dinput/tests/hid.c @@ -39,6 +39,7 @@ #include "setupapi.h" #include "cfgmgr32.h" #include "newdev.h" +#include "dbt.h"
#include "objbase.h"
@@ -65,8 +66,11 @@ const WCHAR expect_vidpid_str[] = L"VID_1209&PID_0001"; const GUID expect_guid_product = {EXPECT_VIDPID, 0x0000, 0x0000, {0x00, 0x00, 'P', 'I', 'D', 'V', 'I', 'D'}}; const WCHAR expect_path[] = L"\\?\hid#winetest#1&2fafeb0&"; const WCHAR expect_path_end[] = L"&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"; +HANDLE device_added, device_removed; +static BOOL hid_device_created;
static struct winetest_shared_data *test_data; +static HANDLE monitor_thread, monitor_stop; static HANDLE test_data_mapping; static HANDLE okfile;
@@ -316,6 +320,8 @@ static const char inf_text[] = "[mfg_section.NT" EXT "]\n" "Wine test root driver=device_section,test_hardware_id\n" "Wine Test Bus Device=bus_section,WINETEST\BUS\n" + "Wine Test HID Device=hid_section,WINETEST\WINE_COMP_HID\n" + "Wine Test HID Polled Device=hid_poll_section,WINETEST\WINE_COMP_POLLHID\n"
"[device_section.NT" EXT "]\n" "CopyFiles=file_section\n" @@ -329,17 +335,35 @@ static const char inf_text[] = "[bus_section.NT" EXT ".Services]\n" "AddService=winetest_bus,0x2,bus_service\n"
+ "[hid_section.NT" EXT "]\n" + "CopyFiles=file_section\n" + + "[hid_section.NT" EXT ".Services]\n" + "AddService=winetest_hid,0x2,hid_service\n" + + "[hid_poll_section.NT" EXT "]\n" + "CopyFiles=file_section\n" + + "[hid_poll_section.NT" EXT ".Services]\n" + "AddService=winetest_hid_poll,0x2,hid_poll_service\n" + "[file_section]\n" "winetest.sys\n" "winetest_bus.sys\n" + "winetest_hid.sys\n" + "winetest_hid_poll.sys\n"
"[SourceDisksFiles]\n" "winetest.sys=1\n" "winetest_bus.sys=1\n" + "winetest_hid.sys=1\n" + "winetest_hid_poll.sys=1\n"
"[SourceDisksNames]\n" "1=,winetest.sys\n" "1=,winetest_bus.sys\n" + "1=,winetest_hid.sys\n" + "1=,winetest_hid_poll.sys\n"
"[DestinationDirs]\n" "DefaultDestDir=12\n" @@ -359,6 +383,22 @@ static const char inf_text[] = "ErrorControl=1\n" "LoadOrderGroup=WinePlugPlay\n" "DisplayName="Wine Test Bus Driver"\n" + + "[hid_service]\n" + "ServiceBinary=%12%\winetest_hid.sys\n" + "ServiceType=1\n" + "StartType=3\n" + "ErrorControl=1\n" + "LoadOrderGroup=WinePlugPlay\n" + "DisplayName="Wine Test HID Driver"\n" + + "[hid_poll_service]\n" + "ServiceBinary=%12%\winetest_hid_poll.sys\n" + "ServiceType=1\n" + "StartType=3\n" + "ErrorControl=1\n" + "LoadOrderGroup=WinePlugPlay\n" + "DisplayName="Wine Test HID Polled Driver"\n" "; they don't sleep anymore, on the beach\n";
static void add_file_to_catalog( HANDLE catalog, const WCHAR *file ) @@ -505,11 +545,20 @@ static void pnp_driver_stop( BOOL bus ) ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); ret = DeleteFileW( L"winetest_bus.sys" ); ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); + ret = DeleteFileW( L"winetest_hid.sys" ); + ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); + ret = DeleteFileW( L"winetest_hid_poll.sys" ); + ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); /* Windows 10 apparently deletes the image in SetupUninstallOEMInf(). */ ret = DeleteFileW( L"C:/windows/system32/drivers/winetest.sys" ); ok( ret || GetLastError() == ERROR_FILE_NOT_FOUND, "Failed to delete file, error %lu\n", GetLastError() ); ret = DeleteFileW( L"C:/windows/system32/drivers/winetest_bus.sys" ); ok( ret || GetLastError() == ERROR_FILE_NOT_FOUND, "Failed to delete file, error %lu\n", GetLastError() ); + ret = DeleteFileW( L"C:/windows/system32/drivers/winetest_hid.sys" ); + todo_wine_if(!ret && GetLastError() == ERROR_ACCESS_DENIED) /* Wine doesn't unload device drivers correctly */ + ok( ret || GetLastError() == ERROR_FILE_NOT_FOUND, "Failed to delete file, error %lu\n", GetLastError() ); + ret = DeleteFileW( L"C:/windows/system32/drivers/winetest_hid_poll.sys" ); + ok( ret || GetLastError() == ERROR_FILE_NOT_FOUND, "Failed to delete file, error %lu\n", GetLastError() ); }
static BOOL find_hid_device_path( WCHAR *device_path ) @@ -527,8 +576,11 @@ static BOOL find_hid_device_path( WCHAR *device_path )
for (i = 0; SetupDiEnumDeviceInfo( set, i, &device ); ++i) { + SetLastError( 0xdeadbeef ); ret = SetupDiEnumDeviceInterfaces( set, &device, &GUID_DEVINTERFACE_HID, 0, &iface ); + todo_wine_if(!ret && GetLastError() == ERROR_NO_MORE_ITEMS) /* Wine doesn't unload device drivers correctly */ ok( ret, "Failed to get interface, error %#lx\n", GetLastError() ); + if (!ret) continue; ok( IsEqualGUID( &iface.InterfaceClassGuid, &GUID_DEVINTERFACE_HID ), "wrong class %s\n", debugstr_guid( &iface.InterfaceClassGuid ) ); ok( iface.Flags == SPINT_ACTIVE, "got flags %#lx\n", iface.Flags ); @@ -574,6 +626,14 @@ static BOOL pnp_driver_start( BOOL bus ) ret = MoveFileExW( filename, L"winetest_bus.sys", MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING ); ok( ret, "failed to move file, error %lu\n", GetLastError() );
+ load_resource( L"driver_hid.dll", filename ); + ret = MoveFileExW( filename, L"winetest_hid.sys", MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING ); + ok( ret, "failed to move file, error %lu\n", GetLastError() ); + + load_resource( L"driver_hid_poll.dll", filename ); + ret = MoveFileExW( filename, L"winetest_hid_poll.sys", MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING ); + ok( ret, "failed to move file, error %lu\n", GetLastError() ); + f = fopen( "winetest.inf", "w" ); ok( !!f, "failed to open winetest.inf: %s\n", strerror( errno ) ); fputs( inf_text, f ); @@ -586,6 +646,8 @@ static BOOL pnp_driver_start( BOOL bus )
add_file_to_catalog( catalog, L"winetest.sys" ); add_file_to_catalog( catalog, L"winetest_bus.sys" ); + add_file_to_catalog( catalog, L"winetest_hid.sys" ); + add_file_to_catalog( catalog, L"winetest_hid_poll.sys" ); add_file_to_catalog( catalog, L"winetest.inf" );
ret = CryptCATPersistStore( catalog ); @@ -603,6 +665,10 @@ static BOOL pnp_driver_start( BOOL bus ) ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); ret = DeleteFileW( L"winetest_bus.sys" ); ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); + ret = DeleteFileW( L"winetest_hid.sys" ); + ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); + ret = DeleteFileW( L"winetest_hid_poll.sys" ); + ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); ret = DeleteFileW( L"winetest.sys" ); ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); winetest_mute_threshold = old_mute_threshold; @@ -3408,10 +3474,74 @@ BOOL dinput_driver_start_( const char *file, int line, const BYTE *desc_buf, ULO return hid_device_start(); }
+static LRESULT CALLBACK monitor_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + if (msg == WM_DEVICECHANGE) + { + DEV_BROADCAST_DEVICEINTERFACE_W *iface = (DEV_BROADCAST_DEVICEINTERFACE_W *)lparam; + if (wparam == DBT_DEVICEREMOVECOMPLETE && IsEqualGUID( &iface->dbcc_classguid, &control_class )) + SetEvent( device_removed ); + if (wparam == DBT_DEVICEARRIVAL && IsEqualGUID( &iface->dbcc_classguid, &GUID_DEVINTERFACE_HID )) + SetEvent( device_added ); + } + + return DefWindowProcW( hwnd, msg, wparam, lparam ); +} + +DWORD WINAPI monitor_thread_proc( void *stop_event ) +{ + DEV_BROADCAST_DEVICEINTERFACE_A iface_filter_a = + { + .dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_A), + .dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE, + }; + WNDCLASSEXW class = + { + .cbSize = sizeof(WNDCLASSEXW), + .hInstance = GetModuleHandleW( NULL ), + .lpszClassName = L"device_monitor", + .lpfnWndProc = monitor_wndproc, + }; + HDEVNOTIFY devnotify; + HANDLE hwnd; + MSG msg; + + RegisterClassExW( &class ); + hwnd = CreateWindowW( class.lpszClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL ); + ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); + devnotify = RegisterDeviceNotificationA( hwnd, &iface_filter_a, DEVICE_NOTIFY_ALL_INTERFACE_CLASSES ); + ok( !!devnotify, "RegisterDeviceNotificationA failed, error %lu\n", GetLastError() ); + + do + { + while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessageW( &msg ); + } + } while (MsgWaitForMultipleObjects( 1, &stop_event, FALSE, INFINITE, QS_ALLINPUT )); + + UnregisterDeviceNotification( devnotify ); + DestroyWindow( hwnd ); + UnregisterClassW( class.lpszClassName, class.hInstance ); + + CloseHandle( stop_event ); + return 0; +} + BOOL dinput_test_init_( const char *file, int line ) { BOOL is_wow64;
+ monitor_stop = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!monitor_stop, "CreateEventW failed, error %lu\n", GetLastError() ); + device_added = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!device_added, "CreateEventW failed, error %lu\n", GetLastError() ); + device_removed = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!device_removed, "CreateEventW failed, error %lu\n", GetLastError() ); + monitor_thread = CreateThread( NULL, 0, monitor_thread_proc, monitor_stop, 0, NULL ); + ok( !!monitor_thread, "CreateThread failed, error %lu\n", GetLastError() ); + subtest_(file, line)( "hid" ); instance = GetModuleHandleW( NULL ); localized = GetUserDefaultLCID() != MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT); @@ -3442,6 +3572,8 @@ BOOL dinput_test_init_( const char *file, int line )
subtest( "driver" ); subtest( "driver_bus" ); + subtest( "driver_hid" ); + subtest( "driver_hid_poll" ); return TRUE; }
@@ -3451,6 +3583,13 @@ void dinput_test_exit(void) CloseHandle( test_data_mapping ); CloseHandle( okfile ); DeleteFileW( L"C:\windows\winetest_dinput_okfile" ); + + SetEvent( monitor_stop ); + WaitForSingleObject( monitor_thread, INFINITE ); + CloseHandle( monitor_thread ); + CloseHandle( monitor_stop ); + CloseHandle( device_removed ); + CloseHandle( device_added ); }
BOOL CALLBACK find_test_device( const DIDEVICEINSTANCEW *devinst, void *context ) @@ -3612,10 +3751,39 @@ DWORD WINAPI dinput_test_device_thread( void *stop_event )
static void test_bus_driver(void) { - struct bus_device_desc desc = +#include "psh_hid_macros.h" + const unsigned char report_desc[] = { - .vid = LOWORD(EXPECT_VIDPID), .pid = HIWORD(EXPECT_VIDPID), + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + USAGE(1, HID_USAGE_GENERIC_JOYSTICK), + COLLECTION(1, Application), + USAGE(1, HID_USAGE_GENERIC_X), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + END_COLLECTION, }; +#include "pop_hid_macros.h" + + static const HID_DEVICE_ATTRIBUTES attributes = + { + .Size = sizeof(HID_DEVICE_ATTRIBUTES), + .VendorID = LOWORD(EXPECT_VIDPID), + .ProductID = HIWORD(EXPECT_VIDPID), + .VersionNumber = 0x0100, + }; + const HIDP_CAPS caps = + { + .Usage = HID_USAGE_GENERIC_JOYSTICK, + .UsagePage = HID_USAGE_PAGE_GENERIC, + .InputReportByteLength = 2, + .NumberLinkCollectionNodes = 1, + .NumberInputValueCaps = 1, + .NumberInputDataIndices = 1, + }; + struct bus_device_desc desc = { .caps = caps, .attributes = attributes, }; + + WCHAR device_path[MAX_PATH]; HANDLE control; BOOL ret;
@@ -3634,16 +3802,32 @@ static void test_bus_driver(void)
bus_device_start();
+ desc.report_descriptor_len = sizeof(report_desc); + memcpy( desc.report_descriptor_buf, report_desc, sizeof(report_desc) ); + + ResetEvent( device_added ); + control = CreateFileW( L"\\?\root#winetest#0#{deadbeef-29ef-4538-a5fd-b69573a362c0}", 0, 0, NULL, OPEN_EXISTING, 0, NULL ); ok( control != INVALID_HANDLE_VALUE, "CreateFile failed, error %lu\n", GetLastError() ); ret = sync_ioctl( control, IOCTL_WINETEST_CREATE_DEVICE, &desc, sizeof(desc), NULL, 0, INFINITE ); ok( ret, "IOCTL_WINETEST_CREATE_DEVICE failed, last error %lu\n", GetLastError() );
+ WaitForSingleObject( device_added, INFINITE ); + hid_device_created = TRUE; + + swprintf( device_path, MAX_PATH, L"\\?\hid#vid_%04x&pid_%04x", LOWORD(EXPECT_VIDPID), HIWORD(EXPECT_VIDPID) ); + ret = find_hid_device_path( device_path ); + ok( ret, "Failed to find HID device matching %s\n", debugstr_w(device_path) ); + + ResetEvent( device_removed ); + ret = sync_ioctl( control, IOCTL_WINETEST_REMOVE_DEVICE, &desc, sizeof(desc), NULL, 0, INFINITE ); ok( ret, "IOCTL_WINETEST_REMOVE_DEVICE failed, last error %lu\n", GetLastError() ); CloseHandle( control );
+ WaitForSingleObject( device_removed, INFINITE ); + done: bus_device_stop(); }