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>
This commit is contained in:
parent
2a09548b59
commit
1a81022f4e
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
|
|
Loading…
Reference in New Issue