Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/dinput/tests/Makefile.in | 4 + dlls/dinput/tests/dinput_test.h | 2 + dlls/dinput/tests/driver_bus.c | 294 ++++++++++++++++++++++++++++++ dlls/dinput/tests/driver_bus.spec | 1 + dlls/dinput/tests/hid.c | 90 ++++++++- 5 files changed, 382 insertions(+), 9 deletions(-) create mode 100644 dlls/dinput/tests/driver_bus.c create mode 100644 dlls/dinput/tests/driver_bus.spec
diff --git a/dlls/dinput/tests/Makefile.in b/dlls/dinput/tests/Makefile.in index c1b887f8dcb..013235c77d9 100644 --- a/dlls/dinput/tests/Makefile.in +++ b/dlls/dinput/tests/Makefile.in @@ -3,6 +3,8 @@ IMPORTS = dinput dinput8 ole32 version user32 advapi32 hid uuid crypt32 newdev
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
SOURCES = \ device.c \ @@ -10,6 +12,8 @@ SOURCES = \ dinput.c \ driver.c \ driver.spec \ + driver_bus.c \ + driver_bus.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 c0027c07094..cb1c42a7e86 100644 --- a/dlls/dinput/tests/dinput_test.h +++ b/dlls/dinput/tests/dinput_test.h @@ -54,6 +54,8 @@ extern BOOL localized; /* object names get translated */
BOOL hid_device_start(void); void hid_device_stop(void); +BOOL bus_device_start(void); +void bus_device_stop(void);
void cleanup_registry_keys(void);
diff --git a/dlls/dinput/tests/driver_bus.c b/dlls/dinput/tests/driver_bus.c new file mode 100644 index 00000000000..aad7f3e2889 --- /dev/null +++ b/dlls/dinput/tests/driver_bus.c @@ -0,0 +1,294 @@ +/* + * Plug and Play test driver + * + * 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 "wine/list.h" + +#include "initguid.h" +#include "driver_hid.h" + +typedef ULONG PNP_DEVICE_STATE; +#define PNP_DEVICE_REMOVED 8 + +struct device +{ + KSPIN_LOCK lock; + PNP_DEVICE_STATE state; +}; + +static inline struct device *impl_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) +{ + return (struct device *)device->DeviceExtension; +} + +struct func_device +{ + struct device base; + DEVICE_OBJECT *pdo; /* lower PDO */ + UNICODE_STRING control_iface; +}; + +static inline struct func_device *fdo_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) +{ + struct device *impl = impl_from_DEVICE_OBJECT( device ); + return CONTAINING_RECORD( impl, struct func_device, base ); +} + +#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 fdo_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + struct func_device *impl = fdo_from_DEVICE_OBJECT( device ); + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->MinorFunction; + PNP_DEVICE_STATE state; + NTSTATUS status; + KIRQL irql; + + 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: + case IRP_MN_CANCEL_REMOVE_DEVICE: + case IRP_MN_QUERY_REMOVE_DEVICE: + case IRP_MN_SURPRISE_REMOVAL: + case IRP_MN_REMOVE_DEVICE: + 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; + KeReleaseSpinLock( &impl->base.lock, irql ); + IoSetDeviceInterfaceState( &impl->control_iface, state != PNP_DEVICE_REMOVED ); + if (code != IRP_MN_REMOVE_DEVICE) status = STATUS_SUCCESS; + else + { + IoSkipCurrentIrpStackLocation( irp ); + status = IoCallDriver( impl->pdo, irp ); + IoDetachDevice( impl->pdo ); + RtlFreeUnicodeString( &impl->control_iface ); + IoDeleteDevice( device ); + if (winetest_debug > 1) trace( "Deleted Bus FDO %p from PDO %p, status %#lx\n", impl, impl->pdo, status ); + return status; + } + break; + case IRP_MN_QUERY_PNP_DEVICE_STATE: + KeAcquireSpinLock( &impl->base.lock, &irql ); + irp->IoStatus.Information = impl->base.state; + KeReleaseSpinLock( &impl->base.lock, irql ); + status = STATUS_SUCCESS; + break; + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + DEVICE_RELATION_TYPE type = stack->Parameters.QueryDeviceRelations.Type; + switch (type) + { + case BusRelations: + if (winetest_debug > 1) trace( "fdo_pnp IRP_MN_QUERY_DEVICE_RELATIONS BusRelations\n" ); + ok( !irp->IoStatus.Information, "got unexpected BusRelations relations\n" ); + irp->IoStatus.Information = get_device_relations( device, (void *)irp->IoStatus.Information, 0, NULL ); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + case RemovalRelations: + if (winetest_debug > 1) trace( "fdo_pnp IRP_MN_QUERY_DEVICE_RELATIONS RemovalRelations\n" ); + ok( !irp->IoStatus.Information, "got unexpected RemovalRelations relations\n" ); + irp->IoStatus.Information = get_device_relations( device, (void *)irp->IoStatus.Information, + 1, &impl->pdo ); + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + default: + ok( 0, "got unexpected IRP_MN_QUERY_DEVICE_RELATIONS type %#x\n", type ); + status = irp->IoStatus.Status; + break; + } + break; + } + case IRP_MN_QUERY_CAPABILITIES: + { + DEVICE_CAPABILITIES *caps = stack->Parameters.DeviceCapabilities.Capabilities; + caps->EjectSupported = TRUE; + caps->Removable = TRUE; + caps->SilentInstall = TRUE; + caps->SurpriseRemovalOK = TRUE; + status = STATUS_SUCCESS; + break; + } + default: + if (winetest_debug > 1) trace( "fdo_pnp code %#lx %s, not implemented!\n", code, debugstr_pnp(code) ); + status = irp->IoStatus.Status; + break; + } + + irp->IoStatus.Status = status; + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( impl->pdo, irp ); +} + +static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + return fdo_pnp( device, irp ); +} + +static NTSTATUS WINAPI driver_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + + 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; +} + +static NTSTATUS WINAPI driver_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + + 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; +} + +static NTSTATUS WINAPI driver_add_device( DRIVER_OBJECT *driver, DEVICE_OBJECT *device ) +{ + struct func_device *impl; + DEVICE_OBJECT *child; + NTSTATUS status; + + if (winetest_debug > 1) trace( "%s: driver %p, device %p\n", __func__, driver, device ); + + if ((status = IoCreateDevice( driver, sizeof(struct func_device), NULL, FILE_DEVICE_BUS_EXTENDER, 0, FALSE, &child ))) + { + ok( 0, "Failed to create FDO, status %#lx.\n", status ); + return status; + } + + impl = (struct func_device *)child->DeviceExtension; + KeInitializeSpinLock( &impl->base.lock ); + impl->pdo = device; + + status = IoRegisterDeviceInterface( impl->pdo, &control_class, NULL, &impl->control_iface ); + ok( !status, "IoRegisterDeviceInterface returned %#lx\n", status ); + + if (winetest_debug > 1) trace( "Created Bus FDO %p for PDO %p\n", child, device ); + + IoAttachDeviceToDeviceStack( child, device ); + child->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, irp %p\n", __func__, device, irp ); + + 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, irp %p\n", __func__, device, irp ); + + 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 ) +{ + NTSTATUS status; + + 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; + + return STATUS_SUCCESS; +} diff --git a/dlls/dinput/tests/driver_bus.spec b/dlls/dinput/tests/driver_bus.spec new file mode 100644 index 00000000000..ad33444716a --- /dev/null +++ b/dlls/dinput/tests/driver_bus.spec @@ -0,0 +1 @@ +# nothing here yet diff --git a/dlls/dinput/tests/hid.c b/dlls/dinput/tests/hid.c index 6203f67bb25..bcaf949eb9c 100644 --- a/dlls/dinput/tests/hid.c +++ b/dlls/dinput/tests/hid.c @@ -315,6 +315,7 @@ 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"
"[device_section.NT" EXT "]\n" "CopyFiles=file_section\n" @@ -322,14 +323,23 @@ static const char inf_text[] = "[device_section.NT" EXT ".Services]\n" "AddService=winetest,0x2,svc_section\n"
+ "[bus_section.NT" EXT "]\n" + "CopyFiles=file_section\n" + + "[bus_section.NT" EXT ".Services]\n" + "AddService=winetest_bus,0x2,bus_service\n" + "[file_section]\n" "winetest.sys\n" + "winetest_bus.sys\n"
"[SourceDisksFiles]\n" "winetest.sys=1\n" + "winetest_bus.sys=1\n"
"[SourceDisksNames]\n" "1=,winetest.sys\n" + "1=,winetest_bus.sys\n"
"[DestinationDirs]\n" "DefaultDestDir=12\n" @@ -341,6 +351,14 @@ static const char inf_text[] = "ErrorControl=1\n" "LoadOrderGroup=WinePlugPlay\n" "DisplayName="winetest bus driver"\n" + + "[bus_service]\n" + "ServiceBinary=%12%\winetest_bus.sys\n" + "ServiceType=1\n" + "StartType=3\n" + "ErrorControl=1\n" + "LoadOrderGroup=WinePlugPlay\n" + "DisplayName="Wine Test Bus Driver"\n" "; they don't sleep anymore, on the beach\n";
static void add_file_to_catalog( HANDLE catalog, const WCHAR *file ) @@ -415,9 +433,9 @@ static void unload_driver( SC_HANDLE service ) CloseServiceHandle( service ); }
-static void pnp_driver_stop(void) +static void pnp_driver_stop( BOOL bus ) { - const WCHAR *service_name = L"winetest"; + const WCHAR *service_name = bus ? L"winetest_bus" : L"winetest"; SP_DEVINFO_DATA device = {sizeof(SP_DEVINFO_DATA)}; WCHAR path[MAX_PATH], dest[MAX_PATH], *filepart; SC_HANDLE manager, service; @@ -484,9 +502,13 @@ static void pnp_driver_stop(void) 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() ); + ret = DeleteFileW( L"winetest_bus.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() ); }
static BOOL find_hid_device_path( WCHAR *device_path ) @@ -525,12 +547,13 @@ static BOOL find_hid_device_path( WCHAR *device_path ) return ret; }
-static BOOL pnp_driver_start(void) +static BOOL pnp_driver_start( BOOL bus ) { static const WCHAR hardware_id[] = L"test_hardware_id\0"; + static const WCHAR bus_hardware_id[] = L"WINETEST\BUS"; + const WCHAR *service_name = bus ? L"winetest_bus" : L"winetest"; SP_DEVINFO_DATA device = {sizeof(SP_DEVINFO_DATA)}; WCHAR path[MAX_PATH], filename[MAX_PATH]; - const WCHAR *service_name = L"winetest"; SC_HANDLE manager, service; const CERT_CONTEXT *cert; int old_mute_threshold; @@ -546,6 +569,10 @@ static BOOL pnp_driver_start(void) ret = MoveFileExW( filename, L"winetest.sys", MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING ); ok( ret, "failed to move file, error %lu\n", GetLastError() );
+ load_resource( L"driver_bus.dll", filename ); + ret = MoveFileExW( filename, L"winetest_bus.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 ); @@ -557,6 +584,7 @@ static BOOL pnp_driver_start(void) ok( catalog != INVALID_HANDLE_VALUE, "Failed to create catalog, error %lu\n", GetLastError() );
add_file_to_catalog( catalog, L"winetest.sys" ); + add_file_to_catalog( catalog, L"winetest_bus.sys" ); add_file_to_catalog( catalog, L"winetest.inf" );
ret = CryptCATPersistStore( catalog ); @@ -572,6 +600,8 @@ static BOOL pnp_driver_start(void) ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); ret = DeleteFileW( L"winetest.inf" ); 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.sys" ); ok( ret, "Failed to delete file, error %lu\n", GetLastError() ); winetest_mute_threshold = old_mute_threshold; @@ -586,8 +616,9 @@ static BOOL pnp_driver_start(void) ret = SetupDiCreateDeviceInfoW( set, L"root\winetest\0", &GUID_NULL, NULL, NULL, 0, &device ); ok( ret, "failed to create device, error %#lx\n", GetLastError() );
- ret = SetupDiSetDeviceRegistryPropertyW( set, &device, SPDRP_HARDWAREID, (const BYTE *)hardware_id, - sizeof(hardware_id) ); + ret = SetupDiSetDeviceRegistryPropertyW( set, &device, SPDRP_HARDWAREID, + bus ? (const BYTE *)bus_hardware_id : (const BYTE *)hardware_id, + bus ? sizeof(bus_hardware_id) : sizeof(hardware_id) ); ok( ret, "failed to create set hardware ID, error %lu\n", GetLastError() );
ret = SetupDiCallClassInstaller( DIF_REGISTERDEVICE, set, &device ); @@ -598,7 +629,8 @@ static BOOL pnp_driver_start(void)
GetFullPathNameW( L"winetest.inf", ARRAY_SIZE(path), path, NULL );
- ret = UpdateDriverForPlugAndPlayDevicesW( NULL, hardware_id, path, INSTALLFLAG_FORCE, &need_reboot ); + ret = UpdateDriverForPlugAndPlayDevicesW( NULL, bus ? bus_hardware_id : hardware_id, path, + INSTALLFLAG_FORCE, &need_reboot ); ok( ret, "failed to install device, error %lu\n", GetLastError() ); ok( !need_reboot, "expected no reboot necessary\n" );
@@ -630,12 +662,22 @@ static BOOL pnp_driver_start(void)
void hid_device_stop(void) { - pnp_driver_stop(); + pnp_driver_stop( FALSE ); }
BOOL hid_device_start(void) { - return pnp_driver_start(); + return pnp_driver_start( FALSE ); +} + +void bus_device_stop(void) +{ + pnp_driver_stop( TRUE ); +} + +BOOL bus_device_start(void) +{ + return pnp_driver_start( TRUE ); }
#define check_hidp_caps( a, b ) check_hidp_caps_( __LINE__, a, b ) @@ -3398,6 +3440,7 @@ BOOL dinput_test_init_( const char *file, int line ) ok( okfile != INVALID_HANDLE_VALUE, "failed to create file, error %lu\n", GetLastError() );
subtest( "driver" ); + subtest( "driver_bus" ); return TRUE; }
@@ -3566,10 +3609,39 @@ DWORD WINAPI dinput_test_device_thread( void *stop_event ) return 0; }
+static void test_bus_driver(void) +{ + HANDLE control; + + if (!bus_device_start()) goto done; + + 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() ); + CloseHandle( control ); + + bus_device_stop(); + + control = CreateFileW( L"\\?\root#winetest#0#{deadbeef-29ef-4538-a5fd-b69573a362c0}", 0, 0, + NULL, OPEN_EXISTING, 0, NULL ); + ok( control == INVALID_HANDLE_VALUE, "CreateFile succeeded\n" ); + + bus_device_start(); + + 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() ); + CloseHandle( control ); + +done: + bus_device_stop(); +} + START_TEST( hid ) { if (!dinput_test_init()) return;
+ test_bus_driver(); test_hidp_kdr(); test_hid_driver( 0, FALSE ); test_hid_driver( 1, FALSE );