winebus.sys: Handle device reports for hidraw devices.

Signed-off-by: Aric Stewart <aric@codeweavers.com>
Signed-off-by: Sebastian Lackner <sebastian@fds-team.de>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
oldstable
Aric Stewart 2016-10-26 10:49:41 -05:00 committed by Alexandre Julliard
parent 2a09548b59
commit 1a81022f4e
3 changed files with 238 additions and 12 deletions

View File

@ -25,6 +25,7 @@ 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);
} platform_vtbl;
void *get_platform_private(DEVICE_OBJECT *device) DECLSPEC_HIDDEN;
@ -37,3 +38,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, BYTE *report, DWORD length) DECLSPEC_HIDDEN;

View File

@ -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,9 @@ struct platform_private
{
struct udev_device *udev_device;
int device_fd;
HANDLE report_thread;
int control_pipe[2];
};
static inline struct platform_private *impl_from_DEVICE_OBJECT(DEVICE_OBJECT *device)
@ -222,11 +227,69 @@ 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;
BYTE report_buffer[1024];
if (poll(plfds, 2, -1) <= 0) continue;
if (plfds[1].revents)
break;
size = read(plfds[0].fd, report_buffer, sizeof(report_buffer));
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, report_buffer, size);
}
return 0;
}
static NTSTATUS begin_report_processing(DEVICE_OBJECT *device)
{
struct platform_private *private = impl_from_DEVICE_OBJECT(device);
if (private->report_thread)
return STATUS_SUCCESS;
if (pipe(private->control_pipe) != 0)
{
ERR("Control pipe creation failed\n");
return STATUS_UNSUCCESSFUL;
}
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 const platform_vtbl hidraw_vtbl =
{
compare_platform_device,
hidraw_get_reportdescriptor,
hidraw_get_string,
begin_report_processing,
};
static void try_add_device(struct udev_device *dev)
@ -289,7 +352,19 @@ static void try_remove_device(struct udev_device *dev)
struct platform_private *private;
if (!device) return;
IoInvalidateDeviceRelations(device, RemovalRelations);
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]);
CloseHandle(private->report_thread);
}
dev = private->udev_device;
close(private->device_fd);
bus_remove_hid_device(device);

View File

@ -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;
BYTE *last_report;
DWORD last_report_size;
BOOL last_report_read;
DWORD buffer_size;
LIST_ENTRY irp_queue;
CRITICAL_SECTION report_cs;
BYTE platform_private[1];
};
@ -203,16 +212,26 @@ DEVICE_OBJECT *bus_create_hid_device(DRIVER_OBJECT *driver, const WCHAR *busidW,
/* fill out device_extension struct */
ext = (struct device_extension *)device->DeviceExtension;
ext->pnp_device = pnp_dev;
ext->vid = vid;
ext->pid = pid;
ext->uid = uid;
ext->version = version;
ext->index = get_vidpid_index(vid, pid);
ext->is_gamepad = is_gamepad;
ext->serial = strdupW(serialW);
ext->busid = busidW;
ext->vtbl = vtbl;
ext->pnp_device = pnp_dev;
ext->vid = vid;
ext->pid = pid;
ext->uid = uid;
ext->version = version;
ext->index = get_vidpid_index(vid, pid);
ext->is_gamepad = is_gamepad;
ext->serial = strdupW(serialW);
ext->busid = busidW;
ext->vtbl = vtbl;
ext->last_report = NULL;
ext->last_report_size = 0;
ext->last_report_read = TRUE;
ext->buffer_size = 0;
memset(ext->platform_private, 0, platform_data_size);
InitializeListHead(&ext->irp_queue);
InitializeCriticalSection(&ext->report_cs);
ext->report_cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": report_cs");
/* add to list of pnp devices */
pnp_dev->device = device;
@ -271,6 +290,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);
@ -278,8 +299,22 @@ void bus_remove_hid_device(DEVICE_OBJECT *device)
list_remove(&pnp_device->entry);
LeaveCriticalSection(&device_list_cs);
IoInvalidateDeviceRelations(device, RemovalRelations);
/* Cancel pending IRPs */
EnterCriticalSection(&ext->report_cs);
while ((entry = RemoveHeadList(&ext->irp_queue)) != &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);
}
LeaveCriticalSection(&ext->report_cs);
ext->report_cs.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&ext->report_cs);
HeapFree(GetProcessHeap(), 0, ext->serial);
HeapFree(GetProcessHeap(), 0, ext->last_report);
IoDeleteDevice(device);
/* pnp_device must be released after the device is gone */
@ -348,6 +383,22 @@ NTSTATUS WINAPI common_pnp_dispatch(DEVICE_OBJECT *device, IRP *irp)
return status;
}
static NTSTATUS deliver_last_report(struct device_extension *ext, DWORD buffer_length, BYTE* buffer, ULONG_PTR *out_length)
{
if (buffer_length < ext->last_report_size)
{
*out_length = 0;
return STATUS_BUFFER_TOO_SMALL;
}
else
{
if (ext->last_report)
memcpy(buffer, ext->last_report, ext->last_report_size);
*out_length = ext->last_report_size;
return STATUS_SUCCESS;
}
}
NTSTATUS WINAPI hid_internal_dispatch(DEVICE_OBJECT *device, IRP *irp)
{
NTSTATUS status = irp->IoStatus.u.Status;
@ -432,6 +483,54 @@ 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);
status = ext->vtbl->begin_report_processing(device);
if (status != STATUS_SUCCESS)
{
irp->IoStatus.u.Status = status;
LeaveCriticalSection(&ext->report_cs);
break;
}
irp->IoStatus.u.Status = status = deliver_last_report(ext,
packet->reportBufferLen, packet->reportBuffer,
&irp->IoStatus.Information);
if (status == STATUS_SUCCESS)
packet->reportBufferLen = irp->IoStatus.Information;
LeaveCriticalSection(&ext->report_cs);
break;
}
case IOCTL_HID_READ_REPORT:
{
TRACE_(hid_report)("IOCTL_HID_READ_REPORT\n");
EnterCriticalSection(&ext->report_cs);
status = ext->vtbl->begin_report_processing(device);
if (status != STATUS_SUCCESS)
{
irp->IoStatus.u.Status = status;
LeaveCriticalSection(&ext->report_cs);
break;
}
if (!ext->last_report_read)
{
irp->IoStatus.u.Status = status = deliver_last_report(ext,
irpsp->Parameters.DeviceIoControl.OutputBufferLength,
irp->UserBuffer, &irp->IoStatus.Information);
ext->last_report_read = TRUE;
}
else
{
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 +540,61 @@ 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, BYTE *report, DWORD length)
{
struct device_extension *ext = (struct device_extension*)device->DeviceExtension;
IRP *irp;
LIST_ENTRY *entry;
if (!length || !report)
return;
EnterCriticalSection(&ext->report_cs);
if (length > ext->buffer_size)
{
HeapFree(GetProcessHeap(), 0, ext->last_report);
ext->last_report = HeapAlloc(GetProcessHeap(), 0, length);
if (!ext->last_report)
{
ERR_(hid_report)("Failed to alloc last report\n");
ext->buffer_size = 0;
ext->last_report_size = 0;
ext->last_report_read = TRUE;
LeaveCriticalSection(&ext->report_cs);
return;
}
else
ext->buffer_size = length;
}
if (!ext->last_report_read)
ERR_(hid_report)("Device reports coming in too fast, last report not read yet!\n");
memcpy(ext->last_report, report, length);
ext->last_report_size = length;
ext->last_report_read = FALSE;
while ((entry = RemoveHeadList(&ext->irp_queue)) != &ext->irp_queue)
{
IO_STACK_LOCATION *irpsp;
TRACE_(hid_report)("Processing Request\n");
irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
irpsp = IoGetCurrentIrpStackLocation(irp);
irp->IoStatus.u.Status = deliver_last_report(ext,
irpsp->Parameters.DeviceIoControl.OutputBufferLength,
irp->UserBuffer, &irp->IoStatus.Information);
ext->last_report_read = TRUE;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
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};