From: Aric Stewart Subject: [PATCH 4/5] winehidminidriver.sys: add linuxraw minidriver Message-Id: <56337208.6040803@codeweavers.com> Date: Fri, 30 Oct 2015 08:35:04 -0500 Signed-off-by: Aric Stewart --- configure | 1 + configure.ac | 1 + dlls/winehidminidriver.sys/Makefile.in | 1 + dlls/winehidminidriver.sys/hid.h | 1 + dlls/winehidminidriver.sys/main.c | 3 + dlls/winehidminidriver.sys/minidriver_linuxraw.c | 461 +++++++++++++++++++++++ include/config.h.in | 3 + 7 files changed, 471 insertions(+) create mode 100644 dlls/winehidminidriver.sys/minidriver_linuxraw.c diff --git a/configure b/configure index ba3a057..24f8886 100755 --- a/configure +++ b/configure @@ -6684,6 +6684,7 @@ for ac_header in \ linux/compiler.h \ linux/filter.h \ linux/hdreg.h \ + linux/hidraw.h \ linux/input.h \ linux/ioctl.h \ linux/joystick.h \ diff --git a/configure.ac b/configure.ac index b0013a2..535c89d 100644 --- a/configure.ac +++ b/configure.ac @@ -429,6 +429,7 @@ AC_CHECK_HEADERS(\ linux/compiler.h \ linux/filter.h \ linux/hdreg.h \ + linux/hidraw.h \ linux/input.h \ linux/ioctl.h \ linux/joystick.h \ diff --git a/dlls/winehidminidriver.sys/Makefile.in b/dlls/winehidminidriver.sys/Makefile.in index 2110847..885e5e6 100644 --- a/dlls/winehidminidriver.sys/Makefile.in +++ b/dlls/winehidminidriver.sys/Makefile.in @@ -7,4 +7,5 @@ C_SRCS = \ main.c \ pnp_bus.c \ pnp_manager.c \ + minidriver_linuxraw.c \ minidriver_osx.c diff --git a/dlls/winehidminidriver.sys/hid.h b/dlls/winehidminidriver.sys/hid.h index 4424cf0..265720e 100644 --- a/dlls/winehidminidriver.sys/hid.h +++ b/dlls/winehidminidriver.sys/hid.h @@ -18,6 +18,7 @@ /* Minidrivers */ NTSTATUS WINAPI osx_DriverInit(DRIVER_OBJECT *driver, UNICODE_STRING *RegistryPath) DECLSPEC_HIDDEN; +NTSTATUS WINAPI linuxraw_DriverInit(DRIVER_OBJECT *driver, UNICODE_STRING *RegistryPath) DECLSPEC_HIDDEN; /* Plug and Play Manager */ VOID HID_IoInvalidateDeviceRelations(DRIVER_OBJECT* driver, void *DeviceObject, DEVICE_RELATION_TYPE Type) DECLSPEC_HIDDEN; diff --git a/dlls/winehidminidriver.sys/main.c b/dlls/winehidminidriver.sys/main.c index 2efbdec..048e065 100644 --- a/dlls/winehidminidriver.sys/main.c +++ b/dlls/winehidminidriver.sys/main.c @@ -37,12 +37,15 @@ WINE_DEFAULT_DEBUG_CHANNEL(hid_minidriver); NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) { static const WCHAR osxhidW[] = {'\\','D','r','i','v','e','r','\\','O','S','X','H','i','d',0}; + static const WCHAR rawhidW[] = {'\\','D','r','i','v','e','r','\\','H','I','D','R','A','W','H','i','d',0}; UNICODE_STRING nameW; ERR( "%s\n", debugstr_w(path->Buffer) ); RtlInitUnicodeString( &nameW, osxhidW); IoCreateDriver(&nameW, osx_DriverInit); + RtlInitUnicodeString( &nameW, rawhidW); + IoCreateDriver(&nameW, linuxraw_DriverInit); return STATUS_SUCCESS; } diff --git a/dlls/winehidminidriver.sys/minidriver_linuxraw.c b/dlls/winehidminidriver.sys/minidriver_linuxraw.c new file mode 100644 index 0000000..b76a938 --- /dev/null +++ b/dlls/winehidminidriver.sys/minidriver_linuxraw.c @@ -0,0 +1,461 @@ +/* HID pseudo-mindriver connections for Linux Hidraw + * + * Copyright 2015 CodeWeavers, Aric Stewart + * + * 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 "config.h" +#include "wine/port.h" +#include + +#ifdef HAVE_LINUX_HIDRAW_H +# include +#endif + +#ifdef HAVE_SELECT +# include +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include +#endif + +#include + +#define NONAMELESSUNION + +#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/hidtypes.h" +#include "ddk/hidclass.h" +#include "ddk/hidport.h" +#include "wine/debug.h" +#include "wine/unicode.h" +#include "wine/list.h" +#include "hid.h" + +WINE_DEFAULT_DEBUG_CHANNEL(hid_minidriver); + +#ifdef HAVE_LINUX_HIDRAW_H +WINE_DECLARE_DEBUG_CHANNEL(hid_report); + +typedef struct _HR_DEVICE_EXTENSTION { + short *last_report; + int report_size; + + int joyfd; + + LIST_ENTRY irp_queue; +} HR_DEVICE_EXTENSION; + +typedef NTSTATUS WINAPI (*pAddDevice)(DRIVER_OBJECT *DriverObject, DEVICE_OBJECT *PhysicalDeviceObject); + +static DRIVER_OBJECT *hr_driver_obj = NULL; +static HANDLE device_loop_handle; +static HID_MINIDRIVER_REGISTRATION registration; + +static void CleanupDevice(DEVICE_OBJECT *device) +{ + IRP *irp; + LIST_ENTRY *entry; + HR_DEVICE_EXTENSION *ext = (HR_DEVICE_EXTENSION*)(((HID_DEVICE_EXTENSION*)device->DeviceExtension)->MiniDeviceExtension); + + /* Clean up our extension information */ + entry = RemoveHeadList(&ext->irp_queue); + while(entry != &ext->irp_queue) + { + irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry); + irp->IoStatus.u.Status = STATUS_CANCELLED; + irp->IoStatus.Information = 0; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + entry = RemoveHeadList(&ext->irp_queue); + } + HeapFree(GetProcessHeap(), 0, ext->last_report); +} + +static NTSTATUS WINAPI dispatchIoctl( DEVICE_OBJECT *device, IRP *irp ) +{ + NTSTATUS rc = STATUS_SUCCESS; + IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); + HR_DEVICE_EXTENSION *extension = (HR_DEVICE_EXTENSION*)(((HID_DEVICE_EXTENSION*)device->DeviceExtension)->MiniDeviceExtension); + + irp->IoStatus.Information = 0; + + switch (irpsp->Parameters.DeviceIoControl.IoControlCode) + { + case IOCTL_GET_PHYSICAL_DESCRIPTOR: + TRACE("IOCTL_GET_PHYSICAL_DESCRIPTOR\n"); + break; + case IOCTL_HID_ACTIVATE_DEVICE: + TRACE("IOCTL_HID_ACTIVATE_DEVICE\n"); + break; + case IOCTL_HID_DEACTIVATE_DEVICE: + TRACE("IOCTL_HID_DEACTIVATE_DEVICE\n"); + break; + case IOCTL_HID_GET_DEVICE_ATTRIBUTES: + { + PHID_DEVICE_ATTRIBUTES attr = (PHID_DEVICE_ATTRIBUTES)irp->UserBuffer; + struct hidraw_devinfo devinfo; + + TRACE("IOCTL_HID_GET_DEVICE_ATTRIBUTES\n"); + + RtlZeroMemory(attr, sizeof(HID_DEVICE_ATTRIBUTES)); + attr->Size = sizeof(HID_DEVICE_ATTRIBUTES); + + if (ioctl(extension->joyfd, HIDIOCGRAWINFO, &devinfo) == -1) + WARN("ioctl(HIDIOCGRAWINFO) failed: %d %s\n", errno, strerror(errno)); + else + { + attr->VendorID = devinfo.vendor; + attr->ProductID = devinfo.product; + attr->VersionNumber = 0; + } + + irp->IoStatus.u.Status = STATUS_SUCCESS; + irp->IoStatus.Information = sizeof(HID_DEVICE_ATTRIBUTES); + break; + } + case IOCTL_HID_GET_DEVICE_DESCRIPTOR: + { + int length = 0; + HID_DESCRIPTOR *descriptor = (HID_DESCRIPTOR*)irp->UserBuffer; + + TRACE("IOCTL_HID_GET_DEVICE_DESCRIPTOR\n"); + + if (irpsp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(HID_DESCRIPTOR)) + { + irp->IoStatus.u.Status = STATUS_BUFFER_TOO_SMALL; + break; + } + + if (ioctl(extension->joyfd, HIDIOCGRDESCSIZE, &length) == -1) + { + WARN("ioctl(HIDIOCGRDESCSIZE) failed: %d %s\n", errno, strerror(errno)); + irp->IoStatus.u.Status = STATUS_UNSUCCESSFUL; + break; + } + ZeroMemory(descriptor, sizeof(*descriptor)); + descriptor->bLength = sizeof(*descriptor); + descriptor->bDescriptorType = HID_HID_DESCRIPTOR_TYPE; + descriptor->bcdHID = HID_REVISION; + descriptor->bCountry = 0; + descriptor->bNumDescriptors = 1; + descriptor->DescriptorList[0].bReportType = HID_REPORT_DESCRIPTOR_TYPE; + descriptor->DescriptorList[0].wReportLength = length; + + irp->IoStatus.Information = sizeof(*descriptor); + irp->IoStatus.u.Status = STATUS_SUCCESS; + break; + } + case IOCTL_HID_GET_FEATURE: + TRACE("IOCTL_HID_GET_FEATURE\n"); + break; + case IOCTL_HID_GET_INDEXED_STRING: + TRACE("IOCTL_HID_GET_INDEXED_STRING\n"); + break; + case IOCTL_HID_GET_INPUT_REPORT: + TRACE_(hid_report)("IOCTL_HID_GET_INPUT_REPORT\n"); + ZeroMemory(((PHID_XFER_PACKET)(irp->UserBuffer))->reportBuffer, + ((PHID_XFER_PACKET)(irp->UserBuffer))->reportBufferLen); + memcpy(irp->UserBuffer, extension->last_report, extension->report_size); + irp->IoStatus.u.Status = STATUS_SUCCESS; + irp->IoStatus.Information = sizeof(HID_XFER_PACKET); + break; + case IOCTL_HID_GET_REPORT_DESCRIPTOR: + { + struct hidraw_report_descriptor descriptor; + TRACE("IOCTL_HID_GET_REPORT_DESCRIPTOR\n"); + + if (ioctl(extension->joyfd, HIDIOCGRDESCSIZE, &descriptor.size) == -1) + { + WARN("ioctl(HIDIOCGRDESCSIZE) failed: %d %s\n", errno, strerror(errno)); + irp->IoStatus.u.Status = STATUS_UNSUCCESSFUL; + break; + } + if (irpsp->Parameters.DeviceIoControl.OutputBufferLength < descriptor.size) + { + irp->IoStatus.u.Status = STATUS_BUFFER_TOO_SMALL; + break; + } + if (ioctl(extension->joyfd, HIDIOCGRDESC , &descriptor) == -1) + { + WARN("ioctl(HIDIOCGRDESC) failed: %d %s\n", errno, strerror(errno)); + irp->IoStatus.u.Status = STATUS_UNSUCCESSFUL; + break; + } + memcpy(irp->UserBuffer, descriptor.value, descriptor.size); + irp->IoStatus.Information = descriptor.size; + irp->IoStatus.u.Status = STATUS_SUCCESS; + break; + } + case IOCTL_HID_GET_STRING: + { + DWORD index = (DWORD)irpsp->Parameters.DeviceIoControl.Type3InputBuffer; + char str[MAX_PATH] = {0}; + + TRACE("IOCTL_HID_GET_STRING\n"); + + /* TODO: udev probibly gives us better strings */ + switch (index) + { + case HID_STRING_ID_IPRODUCT: + ioctl(extension->joyfd, HIDIOCGRAWNAME(MAX_PATH), str); + break; + case HID_STRING_ID_IMANUFACTURER: + break; + case HID_STRING_ID_ISERIALNUMBER: + break; + default: + ERR("Unknown string index\n"); + } + + if (str[0]) + { + WCHAR *buffer = (WCHAR*)(irp->UserBuffer); + int length = irpsp->Parameters.DeviceIoControl.OutputBufferLength/sizeof(WCHAR); + MultiByteToWideChar(CP_ACP, 0, str, -1, buffer, length); + irp->IoStatus.Information = (lstrlenW(buffer)+1) * sizeof(WCHAR); + } + + irp->IoStatus.u.Status = STATUS_SUCCESS; + break; + } + case IOCTL_HID_READ_REPORT: + { + TRACE_(hid_report)("IOCTL_HID_READ_REPORT\n"); + InsertTailList(&extension->irp_queue, &irp->Tail.Overlay.ListEntry); + rc = STATUS_PENDING; + break; + } + case IOCTL_HID_SET_FEATURE: + TRACE("IOCTL_HID_SET_FEATURE\n"); + break; + case IOCTL_HID_SET_OUTPUT_REPORT: + TRACE("IOCTL_HID_SET_OUTPUT_REPORT\n"); + break; + case IOCTL_HID_WRITE_REPORT: + TRACE("IOCTL_HID_WRITE_REPORT\n"); + break; + default: + { + ULONG code = irpsp->Parameters.DeviceIoControl.IoControlCode; + FIXME("Unsupported ioctl %i %x (device=%x access=%x func=%x method=%x)\n", + extension->joyfd, code, code >> 16, (code >> 14) & 3, (code >> 2) & 0xfff, code & 3); + irp->IoStatus.u.Status = STATUS_NOT_SUPPORTED; + rc = STATUS_UNSUCCESSFUL; + break; + } + } + + if (rc != STATUS_PENDING) + IoCompleteRequest( irp, IO_NO_INCREMENT ); + + return rc; +} + +static NTSTATUS WINAPI dispatchPNP( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); + + switch(irpsp->MinorFunction) + { + case IRP_MN_START_DEVICE: + TRACE("IRP_MN_START_DEVICE\n"); + irp->IoStatus.u.Status = STATUS_SUCCESS; + break; + case IRP_MN_REMOVE_DEVICE: + TRACE("IRP_MN_REMOVE_DEVICE\n"); + CleanupDevice(device); + irp->IoStatus.u.Status = STATUS_SUCCESS; + break; + } + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI dispatchPower( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); + TRACE("Device entering power state %i\n", + irpsp->Parameters.Power.State.DeviceState); + irp->IoStatus.u.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; +} + +VOID WINAPI Unload(DRIVER_OBJECT *driver) +{ + TRACE("Linux Hidraw Driver Unload\n"); + + /* TODO: Signal the device loop to stop */ + + WaitForSingleObject(device_loop_handle, INFINITE); +} + +NTSTATUS WINAPI AddDevice(DRIVER_OBJECT *driver, DEVICE_OBJECT *device) +{ + HR_DEVICE_EXTENSION *ext; + HID_DEVICE_EXTENSION *bext; + + TRACE("Linux Hidraw Driver AddDevice\n"); + + bext = (HID_DEVICE_EXTENSION*)device->DeviceExtension; + ext = (HR_DEVICE_EXTENSION*)(((HID_DEVICE_EXTENSION*)device->DeviceExtension)->MiniDeviceExtension); + + ext->joyfd = (int)bext->NextDeviceObject; + + InitializeListHead(&ext->irp_queue); + + return STATUS_SUCCESS; +} + +/* This is our main even loop for reading devices */ +static DWORD CALLBACK deviceloop_thread(VOID *args) +{ + fd_set fds; + int nfds; + struct timeval timeout; + DEVICE_OBJECT *device; + HANDLE *event = (HANDLE*)args; + + WaitForSingleObject(*event, INFINITE); + CloseHandle(event); + + /* Run the event loop */ + while (1) + { + device = hr_driver_obj->DeviceObject; + nfds = 0; + FD_ZERO(&fds); + while (device) + { + HR_DEVICE_EXTENSION *extension = (HR_DEVICE_EXTENSION*)(((HID_DEVICE_EXTENSION*)device->DeviceExtension)->MiniDeviceExtension); + FD_SET(extension->joyfd, &fds); + if (extension->joyfd > nfds) + nfds = extension->joyfd; + device = device->NextDevice; + } + if (nfds) + { + int rc; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + rc = select(nfds + 1, &fds, NULL, NULL, &timeout); + ERR("ARIC Select returned %i\n",rc); + if (rc > 0) + { + device = hr_driver_obj->DeviceObject; + while (device) + { + LIST_ENTRY *entry; + IRP *irp; + HR_DEVICE_EXTENSION *extension = (HR_DEVICE_EXTENSION*)(((HID_DEVICE_EXTENSION*)device->DeviceExtension)->MiniDeviceExtension); + if (FD_ISSET(extension->joyfd, &fds)) + { + TRACE_(hid_report)("event on device %p\n", device); + + if (!extension->last_report) + { + /* most hid reports are constrained to 64 bytes + however complicated devices try to push that + nothin should exceed this 1024 bytes */ + extension->last_report = HeapAlloc(GetProcessHeap(), 0, 1024); + } + extension->report_size = 1024; + extension->report_size = read(extension->joyfd, extension->last_report, + extension->report_size); + + if (extension->report_size == -1) + { + TRACE_(hid_report)("Read failed. Likely an unplugged device\n"); + extension->report_size = 0; + continue; + } + if (extension->report_size == 0) + { + ERR("Failed to read report\n"); + continue; + } + + entry = RemoveHeadList(&extension->irp_queue); + while(entry != &extension->irp_queue) + { + TRACE_(hid_report)("Processing Request\n"); + irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry); + /* Get Report */ + memcpy(irp->UserBuffer, extension->last_report, + extension->report_size); + irp->IoStatus.Information = extension->report_size; + + irp->IoStatus.u.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + entry = RemoveHeadList(&extension->irp_queue); + } + } + device = device->NextDevice; + } + } + } + else + sleep(1); + } + + return 1; +} + +NTSTATUS WINAPI linuxraw_DriverInit(DRIVER_OBJECT *driver, UNICODE_STRING *RegistryPath) +{ + HANDLE event; + + TRACE("Linux Hidraw Driver Init\n"); + + event = CreateEventW(NULL, FALSE, FALSE, NULL); + + device_loop_handle = CreateThread(NULL, 0, deviceloop_thread, &event, 0, NULL); + + hr_driver_obj = driver; + driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = dispatchIoctl; + driver->MajorFunction[IRP_MJ_PNP] = dispatchPNP; + driver->MajorFunction[IRP_MJ_POWER] = dispatchPower; + + driver->DriverUnload = Unload; + driver->DriverExtension->AddDevice = AddDevice; + + registration.DriverObject = driver; + registration.RegistryPath = RegistryPath; + registration.DeviceExtensionSize = sizeof(HR_DEVICE_EXTENSION) + sizeof(HID_DEVICE_EXTENSION); + registration.DevicesArePolled = FALSE; + HidRegisterMinidriver(®istration); + + SetEvent(event); + return STATUS_SUCCESS; +} + +#else + +NTSTATUS WINAPI linuxraw_DriverInit(DRIVER_OBJECT *driver, UNICODE_STRING *RegistryPath) +{ + TRACE("Dummy Linux Hidraw Driver Init\n"); + return STATUS_SUCCESS; +} + +#endif /* HAVE_LINUX_HIDRAW_H */ diff --git a/include/config.h.in b/include/config.h.in index eb61a94..00d5e34 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -414,6 +414,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_HDREG_H +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_HIDRAW_H + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_INPUT_H