On 17.10.2016 15:29, Aric Stewart wrote:
> v2: Suggestions from Sebastian Lackner
> Signed-off-by: Aric Stewart <aric(a)codeweavers.com>
> ---
> dlls/winebus.sys/bus.h | 3 ++
> dlls/winebus.sys/bus_udev.c | 73 ++++++++++++++++++++++++++
> dlls/winebus.sys/main.c | 123 +++++++++++++++++++++++++++++++++++++++++++-
> 3 files changed, 198 insertions(+), 1 deletion(-)
>
>
>
> v2-0001-winebus.sys-Handle-device-reports-for-hidraw-device.txt
>
>
> diff --git a/dlls/winebus.sys/bus.h b/dlls/winebus.sys/bus.h
> index 7ce54bf..bcb1163 100644
> --- a/dlls/winebus.sys/bus.h
> +++ b/dlls/winebus.sys/bus.h
> @@ -25,6 +25,8 @@ typedef struct
> int (*compare_platform_device)(DEVICE_OBJECT *device, void *platform_dev);
> NTSTATUS (*get_reportdescriptor)(DEVICE_OBJECT *device, BYTE *buffer, DWORD length, DWORD *out_length);
> NTSTATUS (*get_string)(DEVICE_OBJECT *device, DWORD index, WCHAR *buffer, DWORD length);
> + NTSTATUS (*begin_report_processing)(DEVICE_OBJECT *device, BYTE **report_buffer, DWORD *buffer_length);
> + void (*end_report_processing)(DEVICE_OBJECT *device, BYTE *report_buffer);
> } platform_vtbl;
>
> void *get_platform_private(DEVICE_OBJECT *device) DECLSPEC_HIDDEN;
> @@ -37,3 +39,4 @@ DEVICE_OBJECT *bus_create_hid_device(DRIVER_OBJECT *driver, const WCHAR *busidW,
> DEVICE_OBJECT *bus_find_hid_device(const platform_vtbl *vtbl, void *platform_dev) DECLSPEC_HIDDEN;
> 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, const WCHAR *busidW, BYTE *report, DWORD length) DECLSPEC_HIDDEN;
> diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c
> index 243aabb..37c8412 100644
> --- a/dlls/winebus.sys/bus_udev.c
> +++ b/dlls/winebus.sys/bus_udev.c
> @@ -61,6 +61,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(plugplay);
>
> #ifdef HAVE_UDEV
>
> +WINE_DECLARE_DEBUG_CHANNEL(hid_report);
> +
> static struct udev *udev_context = NULL;
> static DRIVER_OBJECT *udev_driver_obj = NULL;
>
> @@ -73,6 +75,11 @@ struct platform_private
> {
> struct udev_device *udev_device;
> int device_fd;
> +
> + BYTE *report_buffer;
> + int buffer_length;
> + HANDLE report_thread;
> + int control_pipe[2];
> };
>
> static inline struct platform_private *impl_from_DEVICE_OBJECT(DEVICE_OBJECT *device)
> @@ -222,11 +229,77 @@ static NTSTATUS hidraw_get_string(DEVICE_OBJECT *device, DWORD index, WCHAR *buf
> return STATUS_SUCCESS;
> }
>
> +static DWORD CALLBACK device_report_thread(void *args)
> +{
> + DEVICE_OBJECT *device = (DEVICE_OBJECT*)args;
> + struct platform_private *private = impl_from_DEVICE_OBJECT(device);
> + struct pollfd plfds[2];
> +
> + plfds[0].fd = private->device_fd;
> + plfds[0].events = POLLIN;
> + plfds[0].revents = 0;
> + plfds[1].fd = private->control_pipe[0];
> + plfds[1].events = POLLIN;
> + plfds[1].revents = 0;
> +
> + while (1)
> + {
> + int size;
> + if (poll(plfds, 2, -1) <= 0) continue;
> + if (plfds[1].revents || !private->report_buffer || private->buffer_length == 0)
> + break;
> + size = read(plfds[0].fd, private->report_buffer, private->buffer_length);
This could still cause problems when another thread copies the last_report while it is updated.
I don't know how you are planning to implement it for other platforms, but wouldn't it be better
to use a separate buffer in this thread, and let process_hid_report() do the copying?
> + if (size == -1)
> + TRACE_(hid_report)("Read failed. Likely an unplugged device\n");
> + else if (size == 0)
> + TRACE_(hid_report)("Failed to read report\n");
> + else
> + process_hid_report(device, hidraw_busidW, private->report_buffer, size);
> + }
> + return 0;
> +}
> +
> +static NTSTATUS begin_report_processing(DEVICE_OBJECT *device, BYTE **buffer, DWORD *length)
> +{
> + struct platform_private *private = impl_from_DEVICE_OBJECT(device);
> + *length = private->buffer_length = 1024;
> + *buffer = private->report_buffer = HeapAlloc(GetProcessHeap(), 0, *length);
> +
> + pipe(private->control_pipe);
> + private->report_thread = CreateThread(NULL, 0, device_report_thread, device, 0, NULL);
> + if (!private->report_thread)
> + {
> + ERR("Unable to create device report thread\n");
> + close(private->control_pipe[0]);
> + close(private->control_pipe[1]);
> + return STATUS_UNSUCCESSFUL;
> + }
> + else
> + return STATUS_SUCCESS;
> +}
> +
> +static void end_report_processing(DEVICE_OBJECT *device, BYTE *buffer)
> +{
> + struct platform_private *private = impl_from_DEVICE_OBJECT(device);
> + if (private->report_thread)
> + {
> + write(private->control_pipe[1], "q", 1);
> + WaitForSingleObject(private->report_thread, INFINITE);
> + close(private->control_pipe[0]);
> + close(private->control_pipe[1]);
> + }
> + private->buffer_length = 0;
> + private->report_buffer = NULL;
> + HeapFree(GetProcessHeap(), 0, buffer);
> +}
> +
> static const platform_vtbl hidraw_vtbl =
> {
> compare_platform_device,
> hidraw_get_reportdescriptor,
> hidraw_get_string,
> + begin_report_processing,
> + end_report_processing,
> };
>
> static void try_add_device(struct udev_device *dev)
> diff --git a/dlls/winebus.sys/main.c b/dlls/winebus.sys/main.c
> index 86adbf5..49dcc97 100644
> --- a/dlls/winebus.sys/main.c
> +++ b/dlls/winebus.sys/main.c
> @@ -40,6 +40,7 @@
> #include "bus.h"
>
> WINE_DEFAULT_DEBUG_CHANNEL(plugplay);
> +WINE_DECLARE_DEBUG_CHANNEL(hid_report);
>
> struct pnp_device
> {
> @@ -58,6 +59,14 @@ struct device_extension
> const WCHAR *busid; /* Expected to be a static constant */
>
> const platform_vtbl *vtbl;
> +
> + BOOL processing_started;
> + BYTE *last_report;
> + DWORD last_report_size;
> + DWORD buffer_size;
> + LIST_ENTRY irp_queue;
> + CRITICAL_SECTION report_cs;
> +
> BYTE platform_private[1];
> };
>
> @@ -214,6 +223,9 @@ DEVICE_OBJECT *bus_create_hid_device(DRIVER_OBJECT *driver, const WCHAR *busidW,
> ext->busid = busidW;
> ext->vtbl = vtbl;
>
> + InitializeListHead(&ext->irp_queue);
> + InitializeCriticalSection(&ext->report_cs);
You might want to give this CS a name, like done in other parts of wine. For example:
--- snip ---
InitializeCriticalSection(&src->lock);
src->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": XA2SourceImpl.lock");
--- snip ---
> +
> /* add to list of pnp devices */
> pnp_dev->device = device;
> list_add_tail(&pnp_devset, &pnp_dev->entry);
> @@ -271,6 +283,8 @@ void bus_remove_hid_device(DEVICE_OBJECT *device)
> {
> struct device_extension *ext = (struct device_extension *)device->DeviceExtension;
> struct pnp_device *pnp_device = ext->pnp_device;
> + LIST_ENTRY *entry;
> + IRP *irp;
>
> TRACE("(%p)\n", device);
>
> @@ -279,6 +293,23 @@ void bus_remove_hid_device(DEVICE_OBJECT *device)
> LeaveCriticalSection(&device_list_cs);
>
> IoInvalidateDeviceRelations(device, RemovalRelations);
> +
> + if (ext->processing_started)
> + ext->vtbl->end_report_processing(device, ext->last_report);
> +
> + /* Cancel pending IRPs */
> + EnterCriticalSection(&ext->report_cs);
> + 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);
> + }
> + LeaveCriticalSection(&ext->report_cs);
> +
You will have to delete the CS here. Also make sure to reset Spare[0] if it was previously
set to a debug string. For example:
--- snip ---
src->lock.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&src->lock);
--- snip ---
> HeapFree(GetProcessHeap(), 0, ext->serial);
> IoDeleteDevice(device);
>
> @@ -432,6 +463,58 @@ NTSTATUS WINAPI hid_internal_dispatch(DEVICE_OBJECT *device, IRP *irp)
> irp->IoStatus.Information = (strlenW((WCHAR *)irp->UserBuffer) + 1) * sizeof(WCHAR);
> break;
> }
> + case IOCTL_HID_GET_INPUT_REPORT:
> + {
> + HID_XFER_PACKET *packet = (HID_XFER_PACKET*)(irp->UserBuffer);
> + TRACE_(hid_report)("IOCTL_HID_GET_INPUT_REPORT\n");
> + EnterCriticalSection(&ext->report_cs);
> + if (!ext->processing_started)
> + {
> + irp->IoStatus.u.Status = status = ext->vtbl->begin_report_processing(device, &ext->last_report, &ext->buffer_size);
> + if (status == STATUS_SUCCESS)
> + ext->processing_started = TRUE;
> + else
> + {
> + LeaveCriticalSection(&ext->report_cs);
> + break;
> + }
> + }
> +
> + if (packet->reportBufferLen < ext->last_report_size)
> + {
> + irp->IoStatus.u.Status = status = STATUS_BUFFER_TOO_SMALL;
> + LeaveCriticalSection(&ext->report_cs);
> + break;
> + }
> +
> + ZeroMemory(packet->reportBuffer, packet->reportBufferLen);
Do we need this line? Usually callers do not expect valid data after the length
provided by irp->IoStatus.Information. Is it different in this case?
> + memcpy(packet->reportBuffer, ext->last_report, ext->last_report_size);
> + packet->reportBufferLen = ext->last_report_size;
> + irp->IoStatus.Information = ext->last_report_size;
> + irp->IoStatus.u.Status = status = STATUS_SUCCESS;
> + LeaveCriticalSection(&ext->report_cs);
> + break;
> + }
> + case IOCTL_HID_READ_REPORT:
> + {
> + TRACE_(hid_report)("IOCTL_HID_READ_REPORT\n");
> + EnterCriticalSection(&ext->report_cs);
> + if (!ext->processing_started)
> + {
> + if (ext->vtbl->begin_report_processing(device, &ext->last_report, &ext->buffer_size) == STATUS_SUCCESS)
> + ext->processing_started = TRUE;
> + else
> + {
> + irp->IoStatus.u.Status = status;
> + LeaveCriticalSection(&ext->report_cs);
> + break;
> + }
> + }
> + InsertTailList(&ext->irp_queue, &irp->Tail.Overlay.ListEntry);
> + status = STATUS_PENDING;
> + LeaveCriticalSection(&ext->report_cs);
> + break;
> + }
> default:
> {
> ULONG code = irpsp->Parameters.DeviceIoControl.IoControlCode;
> @@ -441,11 +524,49 @@ NTSTATUS WINAPI hid_internal_dispatch(DEVICE_OBJECT *device, IRP *irp)
> }
> }
>
> - IoCompleteRequest(irp, IO_NO_INCREMENT);
> + if (status != STATUS_PENDING)
> + IoCompleteRequest(irp, IO_NO_INCREMENT);
>
> return status;
> }
>
> +void process_hid_report(DEVICE_OBJECT *device, const WCHAR *busidW, BYTE *report, DWORD length)
> +{
> + struct device_extension *ext = (struct device_extension*)device->DeviceExtension;
> + IRP *irp;
> + LIST_ENTRY *entry;
> +
> + EnterCriticalSection(&ext->report_cs);
> + if (ext->last_report && report != ext->last_report && ext->buffer_size <= length)
Shouldn't it be "length <= ext->buffer_size" ?
> + {
> + memcpy(ext->last_report, report, length);
> + ext->last_report_size = length;
> + }
> + entry = RemoveHeadList(&ext->irp_queue);
> + while(entry != &ext->irp_queue)
> + {
> + IO_STACK_LOCATION *irpsp;
> + TRACE_(hid_report)("Processing Request\n");
> + irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
> + irpsp = IoGetCurrentIrpStackLocation(irp);
> +
> + if (irpsp->Parameters.DeviceIoControl.OutputBufferLength < length)
> + {
> + irp->IoStatus.Information = 0;
> + irp->IoStatus.u.Status = STATUS_BUFFER_TOO_SMALL;
> + }
> + else
> + {
> + memcpy(irp->UserBuffer, report, length);
> + irp->IoStatus.Information = length;
> + irp->IoStatus.u.Status = STATUS_SUCCESS;
> + }
> + IoCompleteRequest(irp, IO_NO_INCREMENT);
> + entry = RemoveHeadList(&ext->irp_queue);
> + }
> + LeaveCriticalSection(&ext->report_cs);
> +}
> +
> NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path )
> {
> static const WCHAR udevW[] = {'\\','D','r','i','v','e','r','\\','U','D','E','V',0};
>
>
>