From: Aric Stewart Subject: [PATCH v4 1/5] winebus.sys: Handle linux input event device via udev Message-Id: Date: Tue, 7 Nov 2017 08:17:55 -0600 v2: Updates from Sebastian Lackner v3: Cross bus duplicate device detection added for udev devices v4: Further refinements from Sebastian Lackner Signed-off-by: Aric Stewart --- dlls/winebus.sys/bus.h | 6 ++ dlls/winebus.sys/bus_udev.c | 172 +++++++++++++++++++++++++++++++++++++++++--- dlls/winebus.sys/main.c | 48 +++++++++++++ 3 files changed, 218 insertions(+), 8 deletions(-) diff --git a/dlls/winebus.sys/bus.h b/dlls/winebus.sys/bus.h index effbd4afd0..9e6493a519 100644 --- a/dlls/winebus.sys/bus.h +++ b/dlls/winebus.sys/bus.h @@ -16,6 +16,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +typedef int(*enum_func)(DEVICE_OBJECT *device, void *context); + /* Busses */ NTSTATUS WINAPI udev_driver_init(DRIVER_OBJECT *driver, UNICODE_STRING *registry_path) DECLSPEC_HIDDEN; NTSTATUS WINAPI iohid_driver_init(DRIVER_OBJECT *driver, UNICODE_STRING *registry_path) DECLSPEC_HIDDEN; @@ -43,3 +45,7 @@ DEVICE_OBJECT *bus_find_hid_device(const platform_vtbl *vtbl, void *platform_dev void bus_remove_hid_device(DEVICE_OBJECT *device) DECLSPEC_HIDDEN; NTSTATUS WINAPI hid_internal_dispatch(DEVICE_OBJECT *device, IRP *irp) DECLSPEC_HIDDEN; void process_hid_report(DEVICE_OBJECT *device, BYTE *report, DWORD length) DECLSPEC_HIDDEN; +DEVICE_OBJECT* bus_enumerate_hid_devices(const platform_vtbl *vtbl, enum_func function, void* context) DECLSPEC_HIDDEN; + +/* General Bus Functions */ +DWORD check_bus_option(UNICODE_STRING *registry_path, const UNICODE_STRING *option) DECLSPEC_HIDDEN; diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index 415bd3db08..365c6faae5 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -42,6 +42,14 @@ # include #endif +#ifdef HAVE_LINUX_INPUT_H +# include +# undef SW_MAX +# if defined(EVIOCGBIT) && defined(EV_ABS) && defined(BTN_PINKIE) +# define HAS_PROPER_INPUT_HEADER +# endif +#endif + #define NONAMELESSUNION #include "ntstatus.h" @@ -65,11 +73,15 @@ WINE_DECLARE_DEBUG_CHANNEL(hid_report); static struct udev *udev_context = NULL; static DRIVER_OBJECT *udev_driver_obj = NULL; +static DWORD disable_hidraw = 0; +static DWORD disable_input = 0; static const WCHAR hidraw_busidW[] = {'H','I','D','R','A','W',0}; +static const WCHAR lnxev_busidW[] = {'L','N','X','E','V',0}; #include "initguid.h" DEFINE_GUID(GUID_DEVCLASS_HIDRAW, 0x3def44ad,0x242e,0x46e5,0x82,0x6d,0x70,0x72,0x13,0xf3,0xaa,0x81); +DEFINE_GUID(GUID_DEVCLASS_LINUXEVENT, 0x1b932c0d,0xfea7,0x42cd,0x8e,0xaa,0x0e,0x48,0x79,0xb6,0x9e,0xaa); struct platform_private { @@ -392,14 +404,66 @@ static const platform_vtbl hidraw_vtbl = hidraw_set_feature_report, }; +#ifdef HAS_PROPER_INPUT_HEADER + +static NTSTATUS lnxev_get_reportdescriptor(DEVICE_OBJECT *device, BYTE *buffer, DWORD length, DWORD *out_length) +{ + return STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS lnxev_get_string(DEVICE_OBJECT *device, DWORD index, WCHAR *buffer, DWORD length) +{ + return STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS lnxev_begin_report_processing(DEVICE_OBJECT *device) +{ + return STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS lnxev_set_output_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written) +{ + *written = 0; + return STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS lnxev_get_feature_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *read) +{ + *read = 0; + return STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS lnxev_set_feature_report(DEVICE_OBJECT *device, UCHAR id, BYTE *report, DWORD length, ULONG_PTR *written) +{ + *written = 0; + return STATUS_NOT_IMPLEMENTED; +} + +static const platform_vtbl lnxev_vtbl = { + compare_platform_device, + lnxev_get_reportdescriptor, + lnxev_get_string, + lnxev_begin_report_processing, + lnxev_set_output_report, + lnxev_get_feature_report, + lnxev_set_feature_report, +}; +#endif + +static int check_same_device(DEVICE_OBJECT *device, void* context) +{ + return !compare_platform_device(device, context); +} + static void try_add_device(struct udev_device *dev) { DWORD vid = 0, pid = 0, version = 0; - struct udev_device *usbdev; + struct udev_device *usbdev = NULL; DEVICE_OBJECT *device = NULL; const char *subsystem; const char *devnode; WCHAR *serial = NULL; + const char* gamepad = NULL; int fd; if (!(devnode = udev_device_get_devnode(dev))) @@ -411,24 +475,68 @@ static void try_add_device(struct udev_device *dev) return; } + subsystem = udev_device_get_subsystem(dev); usbdev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"); if (usbdev) { + #ifdef HAS_PROPER_INPUT_HEADER + const platform_vtbl *other_vtbl = NULL; + DEVICE_OBJECT *dup = NULL; + if (strcmp(subsystem, "hidraw") == 0) + other_vtbl = &lnxev_vtbl; + else if (strcmp(subsystem, "input") == 0) + other_vtbl = &hidraw_vtbl; + + if (other_vtbl) + dup = bus_enumerate_hid_devices(other_vtbl, check_same_device, dev); + if (dup) + { + TRACE("Duplicate cross bus device (%p) found, not adding the new one\n", dup); + close(fd); + return; + } + #endif vid = get_sysattr_dword(usbdev, "idVendor", 16); pid = get_sysattr_dword(usbdev, "idProduct", 16); version = get_sysattr_dword(usbdev, "version", 10); serial = get_sysattr_string(usbdev, "serial"); } +#ifdef HAS_PROPER_INPUT_HEADER + else + { + struct input_id device_id = {0}; + char device_uid[255] = {0}; + + if (ioctl(fd, EVIOCGID, &device_id) < 0) + WARN("ioctl(EVIOCGID) failed: %d %s\n", errno, strerror(errno)); + if (ioctl(fd, EVIOCGUNIQ(254), device_uid) >= 0 && device_uid[0]) + serial = strdupAtoW(device_uid); + + gamepad = udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"); + vid = device_id.vendor; + pid = device_id.product; + version = device_id.version; + } +#else + else + WARN("Could not get device to query VID, PID, Version and Serial\n"); +#endif TRACE("Found udev device %s (vid %04x, pid %04x, version %u, serial %s)\n", debugstr_a(devnode), vid, pid, version, debugstr_w(serial)); - subsystem = udev_device_get_subsystem(dev); if (strcmp(subsystem, "hidraw") == 0) { device = bus_create_hid_device(udev_driver_obj, hidraw_busidW, vid, pid, version, 0, serial, FALSE, &GUID_DEVCLASS_HIDRAW, &hidraw_vtbl, sizeof(struct platform_private)); } +#ifdef HAS_PROPER_INPUT_HEADER + else if (strcmp(subsystem, "input") == 0) + { + device = bus_create_hid_device(udev_driver_obj, lnxev_busidW, vid, pid, version, 0, serial, (gamepad != NULL), + &GUID_DEVCLASS_LINUXEVENT, &lnxev_vtbl, sizeof(struct platform_private)); + } +#endif if (device) { @@ -448,8 +556,14 @@ static void try_add_device(struct udev_device *dev) static void try_remove_device(struct udev_device *dev) { - DEVICE_OBJECT *device = bus_find_hid_device(&hidraw_vtbl, dev); - struct platform_private *private; + DEVICE_OBJECT *device = NULL; + struct platform_private* private; + + device = bus_find_hid_device(&hidraw_vtbl, dev); +#ifdef HAS_PROPER_INPUT_HEADER + if (device == NULL) + device = bus_find_hid_device(&lnxev_vtbl, dev); +#endif if (!device) return; IoInvalidateDeviceRelations(device, RemovalRelations); @@ -483,8 +597,16 @@ static void build_initial_deviceset(void) return; } - if (udev_enumerate_add_match_subsystem(enumerate, "hidraw") < 0) - WARN("Failed to add subsystem 'hidraw' to enumeration\n"); + if (!disable_hidraw) + if (udev_enumerate_add_match_subsystem(enumerate, "hidraw") < 0) + WARN("Failed to add subsystem 'hidraw' to enumeration\n"); +#ifdef HAS_PROPER_INPUT_HEADER + if (!disable_input) + { + if (udev_enumerate_add_match_subsystem(enumerate, "input") < 0) + WARN("Failed to add subsystem 'input' to enumeration\n"); + } +#endif if (udev_enumerate_scan_devices(enumerate) < 0) WARN("Enumeration scan failed\n"); @@ -509,6 +631,7 @@ static void build_initial_deviceset(void) static struct udev_monitor *create_monitor(struct pollfd *pfd) { struct udev_monitor *monitor; + int systems = 0; monitor = udev_monitor_new_from_netlink(udev_context, "udev"); if (!monitor) @@ -517,8 +640,27 @@ static struct udev_monitor *create_monitor(struct pollfd *pfd) return NULL; } - if (udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", NULL) < 0) - WARN("Failed to add subsystem 'hidraw' to monitor\n"); + if (!disable_hidraw) + { + if (udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", NULL) < 0) + WARN("Failed to add 'hidraw' subsystem to monitor\n"); + else + systems++; + } +#ifdef HAS_PROPER_INPUT_HEADER + if (!disable_input) + { + if (udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", NULL) < 0) + WARN("Failed to add 'input' subsystem to monitor\n"); + else + systems++; + } +#endif + if (systems == 0) + { + WARN("No subsystems added to monitor\n"); + goto error; + } if (udev_monitor_enable_receiving(monitor) < 0) goto error; @@ -589,6 +731,10 @@ NTSTATUS WINAPI udev_driver_init(DRIVER_OBJECT *driver, UNICODE_STRING *registry { HANDLE events[2]; DWORD result; + static const WCHAR hidraw_disabledW[] = {'D','i','s','a','b','l','e',' ','h','i','d','r','a','w',0}; + static const UNICODE_STRING hidraw_disabled = {sizeof(hidraw_disabledW) - sizeof(WCHAR), sizeof(hidraw_disabledW), (WCHAR*)hidraw_disabledW}; + static const WCHAR input_disabledW[] = {'D','i','s','a','b','l','e',' ','i','n','p','u','t',0}; + static const UNICODE_STRING input_disabled = {sizeof(input_disabledW) - sizeof(WCHAR), sizeof(input_disabledW), (WCHAR*)input_disabledW}; TRACE("(%p, %s)\n", driver, debugstr_w(registry_path->Buffer)); @@ -602,6 +748,16 @@ NTSTATUS WINAPI udev_driver_init(DRIVER_OBJECT *driver, UNICODE_STRING *registry driver->MajorFunction[IRP_MJ_PNP] = common_pnp_dispatch; driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = hid_internal_dispatch; + disable_hidraw = check_bus_option(registry_path, &hidraw_disabled); + if (disable_hidraw) + TRACE("UDEV hidraw devices disabled in registry\n"); + +#ifdef HAS_PROPER_INPUT_HEADER + disable_input = check_bus_option(registry_path, &input_disabled); + if (disable_input) + TRACE("UDEV input devices disabled in registry\n"); +#endif + if (!(events[0] = CreateEventW(NULL, TRUE, FALSE, NULL))) goto error; if (!(events[1] = CreateThread(NULL, 0, deviceloop_thread, events[0], 0, NULL))) diff --git a/dlls/winebus.sys/main.c b/dlls/winebus.sys/main.c index 978540bb17..390f54f584 100644 --- a/dlls/winebus.sys/main.c +++ b/dlls/winebus.sys/main.c @@ -287,6 +287,28 @@ DEVICE_OBJECT *bus_find_hid_device(const platform_vtbl *vtbl, void *platform_dev return ret; } +DEVICE_OBJECT* bus_enumerate_hid_devices(const platform_vtbl *vtbl, enum_func function, void* context) +{ + struct pnp_device *dev; + DEVICE_OBJECT *ret = NULL; + + TRACE("(%p)\n", vtbl); + + EnterCriticalSection(&device_list_cs); + LIST_FOR_EACH_ENTRY(dev, &pnp_devset, struct pnp_device, entry) + { + struct device_extension *ext = (struct device_extension *)dev->device->DeviceExtension; + if (ext->vtbl != vtbl) continue; + if (function(dev->device, context) == 0) + { + ret = dev->device; + break; + } + } + LeaveCriticalSection(&device_list_cs); + return ret; +} + void bus_remove_hid_device(DEVICE_OBJECT *device) { struct device_extension *ext = (struct device_extension *)device->DeviceExtension; @@ -625,6 +647,32 @@ void process_hid_report(DEVICE_OBJECT *device, BYTE *report, DWORD length) LeaveCriticalSection(&ext->report_cs); } +DWORD check_bus_option(UNICODE_STRING *registry_path, const UNICODE_STRING *option) +{ + OBJECT_ATTRIBUTES attr; + HANDLE key; + DWORD output = 0; + + InitializeObjectAttributes(&attr, registry_path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + if (NtOpenKey(&key, KEY_ALL_ACCESS, &attr) == STATUS_SUCCESS) + { + DWORD size; + char buffer[FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data[sizeof(DWORD)])]; + + KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION*)buffer; + + if (NtQueryValueKey(key, option, KeyValuePartialInformation, info, sizeof(buffer), &size) == STATUS_SUCCESS) + { + if (info->Type == REG_DWORD) + output = *(DWORD*)info->Data; + } + + NtClose(key); + } + + return output; +} + NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) { static const WCHAR udevW[] = {'\\','D','r','i','v','e','r','\\','U','D','E','V',0};