Sweden-Number/dlls/wineusb.sys/wineusb.c

443 lines
14 KiB
C

/*
* USB root device enumerator using libusb
*
* Copyright 2020 Zebediah Figura
*
* 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 <assert.h>
#include <stdarg.h>
#include <stdlib.h>
#include <libusb.h>
#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winioctl.h"
#include "winternl.h"
#include "ddk/wdm.h"
#include "ddk/usb.h"
#include "ddk/usbioctl.h"
#include "wine/asm.h"
#include "wine/debug.h"
#include "wine/list.h"
#include "wine/unicode.h"
WINE_DEFAULT_DEBUG_CHANNEL(wineusb);
#if defined(__i386__) && !defined(_WIN32)
extern void * WINAPI wrap_fastcall_func1( void *func, const void *a );
__ASM_STDCALL_FUNC( wrap_fastcall_func1, 8,
"popl %ecx\n\t"
"popl %eax\n\t"
"xchgl (%esp),%ecx\n\t"
"jmp *%eax" );
#define call_fastcall_func1(func,a) wrap_fastcall_func1(func,a)
#else
#define call_fastcall_func1(func,a) func(a)
#endif
#define DECLARE_CRITICAL_SECTION(cs) \
static CRITICAL_SECTION cs; \
static CRITICAL_SECTION_DEBUG cs##_debug = \
{ 0, 0, &cs, { &cs##_debug.ProcessLocksList, &cs##_debug.ProcessLocksList }, \
0, 0, { (DWORD_PTR)(__FILE__ ": " # cs) }}; \
static CRITICAL_SECTION cs = { &cs##_debug, -1, 0, 0, 0, 0 };
DECLARE_CRITICAL_SECTION(wineusb_cs);
static struct list device_list = LIST_INIT(device_list);
struct usb_device
{
struct list entry;
DEVICE_OBJECT *device_obj;
libusb_device *libusb_device;
libusb_device_handle *handle;
};
static DRIVER_OBJECT *driver_obj;
static DEVICE_OBJECT *bus_fdo, *bus_pdo;
static libusb_hotplug_callback_handle hotplug_cb_handle;
static void add_usb_device(libusb_device *libusb_device)
{
static const WCHAR formatW[] = {'\\','D','e','v','i','c','e','\\','U','S','B','P','D','O','-','%','u',0};
struct libusb_device_descriptor device_desc;
static unsigned int name_index;
libusb_device_handle *handle;
struct usb_device *device;
DEVICE_OBJECT *device_obj;
UNICODE_STRING string;
NTSTATUS status;
WCHAR name[20];
int ret;
libusb_get_device_descriptor(libusb_device, &device_desc);
TRACE("Adding new device %p, vendor %04x, product %04x.\n", libusb_device,
device_desc.idVendor, device_desc.idProduct);
if ((ret = libusb_open(libusb_device, &handle)))
{
WARN("Failed to open device: %s\n", libusb_strerror(ret));
return;
}
sprintfW(name, formatW, name_index++);
RtlInitUnicodeString(&string, name);
if ((status = IoCreateDevice(driver_obj, sizeof(*device), &string,
FILE_DEVICE_USB, 0, FALSE, &device_obj)))
{
ERR("Failed to create device, status %#x.\n", status);
LeaveCriticalSection(&wineusb_cs);
libusb_close(handle);
return;
}
device = device_obj->DeviceExtension;
device->device_obj = device_obj;
device->libusb_device = libusb_ref_device(libusb_device);
device->handle = handle;
EnterCriticalSection(&wineusb_cs);
list_add_tail(&device_list, &device->entry);
LeaveCriticalSection(&wineusb_cs);
IoInvalidateDeviceRelations(bus_pdo, BusRelations);
}
static void remove_usb_device(libusb_device *libusb_device)
{
struct usb_device *device;
TRACE("Removing device %p.\n", libusb_device);
EnterCriticalSection(&wineusb_cs);
LIST_FOR_EACH_ENTRY(device, &device_list, struct usb_device, entry)
{
if (device->libusb_device == libusb_device)
{
libusb_unref_device(device->libusb_device);
libusb_close(device->handle);
list_remove(&device->entry);
IoInvalidateDeviceRelations(bus_pdo, BusRelations);
IoDeleteDevice(device->device_obj);
break;
}
}
LeaveCriticalSection(&wineusb_cs);
}
static BOOL thread_shutdown;
static HANDLE event_thread;
static int LIBUSB_CALL hotplug_cb(libusb_context *context, libusb_device *device,
libusb_hotplug_event event, void *user_data)
{
if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
add_usb_device(device);
else
remove_usb_device(device);
return 0;
}
static DWORD CALLBACK event_thread_proc(void *arg)
{
int ret;
TRACE("Starting event thread.\n");
while (!thread_shutdown)
{
if ((ret = libusb_handle_events(NULL)))
ERR("Error handling events: %s\n", libusb_strerror(ret));
}
TRACE("Shutting down event thread.\n");
return 0;
}
static NTSTATUS fdo_pnp(IRP *irp)
{
IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp);
NTSTATUS ret;
TRACE("irp %p, minor function %#x.\n", irp, stack->MinorFunction);
switch (stack->MinorFunction)
{
case IRP_MN_QUERY_DEVICE_RELATIONS:
{
struct usb_device *device;
DEVICE_RELATIONS *devices;
unsigned int i = 0;
if (stack->Parameters.QueryDeviceRelations.Type != BusRelations)
{
FIXME("Unhandled device relations type %#x.\n", stack->Parameters.QueryDeviceRelations.Type);
break;
}
EnterCriticalSection(&wineusb_cs);
if (!(devices = ExAllocatePool(PagedPool,
offsetof(DEVICE_RELATIONS, Objects[list_count(&device_list)]))))
{
LeaveCriticalSection(&wineusb_cs);
irp->IoStatus.Status = STATUS_NO_MEMORY;
break;
}
LIST_FOR_EACH_ENTRY(device, &device_list, struct usb_device, entry)
{
devices->Objects[i++] = device->device_obj;
call_fastcall_func1(ObfReferenceObject, device->device_obj);
}
LeaveCriticalSection(&wineusb_cs);
devices->Count = i;
irp->IoStatus.Information = (ULONG_PTR)devices;
irp->IoStatus.Status = STATUS_SUCCESS;
break;
}
case IRP_MN_START_DEVICE:
if ((ret = libusb_hotplug_register_callback(NULL,
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
LIBUSB_HOTPLUG_MATCH_ANY, hotplug_cb, NULL, &hotplug_cb_handle)))
{
ERR("Failed to register callback: %s\n", libusb_strerror(ret));
irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
break;
}
irp->IoStatus.Status = STATUS_SUCCESS;
break;
case IRP_MN_SURPRISE_REMOVAL:
irp->IoStatus.Status = STATUS_SUCCESS;
break;
case IRP_MN_REMOVE_DEVICE:
{
struct usb_device *device, *cursor;
libusb_hotplug_deregister_callback(NULL, hotplug_cb_handle);
thread_shutdown = TRUE;
libusb_interrupt_event_handler(NULL);
WaitForSingleObject(event_thread, INFINITE);
CloseHandle(event_thread);
EnterCriticalSection(&wineusb_cs);
LIST_FOR_EACH_ENTRY_SAFE(device, cursor, &device_list, struct usb_device, entry)
{
libusb_unref_device(device->libusb_device);
libusb_close(device->handle);
list_remove(&device->entry);
IoDeleteDevice(device->device_obj);
}
LeaveCriticalSection(&wineusb_cs);
irp->IoStatus.Status = STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(irp);
ret = IoCallDriver(bus_pdo, irp);
IoDetachDevice(bus_pdo);
IoDeleteDevice(bus_fdo);
return ret;
}
default:
FIXME("Unhandled minor function %#x.\n", stack->MinorFunction);
}
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(bus_pdo, irp);
}
static void get_device_id(const struct usb_device *device, WCHAR *buffer)
{
static const WCHAR formatW[] = {'U','S','B','\\','V','I','D','_','%','0','4','X',
'&','P','I','D','_','%','0','4','X',0};
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device->libusb_device, &desc);
sprintfW(buffer, formatW, desc.idVendor, desc.idProduct);
}
static void get_hardware_ids(const struct usb_device *device, WCHAR *buffer)
{
static const WCHAR formatW[] = {'U','S','B','\\','V','I','D','_','%','0','4','X',
'&','P','I','D','_','%','0','4','X','&','R','E','V','_','%','0','4','X',0};
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device->libusb_device, &desc);
buffer += sprintfW(buffer, formatW, desc.idVendor, desc.idProduct, desc.bcdDevice) + 1;
get_device_id(device, buffer);
buffer += strlenW(buffer) + 1;
*buffer = 0;
}
static void get_compatible_ids(const struct usb_device *device, WCHAR *buffer)
{
static const WCHAR prot_format[] = {'U','S','B','\\','C','l','a','s','s','_','%','0','2','x',
'&','S','u','b','C','l','a','s','s','_','%','0','2','x',
'&','P','r','o','t','_','%','0','2','x',0};
static const WCHAR subclass_format[] = {'U','S','B','\\','C','l','a','s','s','_','%','0','2','x',
'&','S','u','b','C','l','a','s','s','_','%','0','2','x',0};
static const WCHAR class_format[] = {'U','S','B','\\','C','l','a','s','s','_','%','0','2','x',0};
struct libusb_device_descriptor device_desc;
libusb_get_device_descriptor(device->libusb_device, &device_desc);
buffer += sprintfW(buffer, prot_format, device_desc.bDeviceClass, device_desc.bDeviceSubClass,
device_desc.bDeviceProtocol) + 1;
buffer += sprintfW(buffer, subclass_format, device_desc.bDeviceClass, device_desc.bDeviceSubClass) + 1;
buffer += sprintfW(buffer, class_format, device_desc.bDeviceClass) + 1;
*buffer = 0;
}
static NTSTATUS query_id(const struct usb_device *device, IRP *irp, BUS_QUERY_ID_TYPE type)
{
WCHAR *id = NULL;
switch (type)
{
case BusQueryDeviceID:
if ((id = ExAllocatePool(PagedPool, 28 * sizeof(WCHAR))))
get_device_id(device, id);
break;
case BusQueryInstanceID:
if ((id = ExAllocatePool(PagedPool, 2 * sizeof(WCHAR))))
{
id[0] = '0';
id[1] = 0;
}
break;
case BusQueryHardwareIDs:
if ((id = ExAllocatePool(PagedPool, (28 + 37 + 1) * sizeof(WCHAR))))
get_hardware_ids(device, id);
break;
case BusQueryCompatibleIDs:
if ((id = ExAllocatePool(PagedPool, (33 + 25 + 13 + 1) * sizeof(WCHAR))))
get_compatible_ids(device, id);
break;
default:
FIXME("Unhandled ID query type %#x.\n", type);
return irp->IoStatus.Status;
}
irp->IoStatus.Information = (ULONG_PTR)id;
return STATUS_SUCCESS;
}
static NTSTATUS pdo_pnp(DEVICE_OBJECT *device_obj, IRP *irp)
{
IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp);
struct usb_device *device = device_obj->DeviceExtension;
NTSTATUS ret = irp->IoStatus.Status;
TRACE("device_obj %p, irp %p, minor function %#x.\n", device_obj, irp, stack->MinorFunction);
switch (stack->MinorFunction)
{
case IRP_MN_QUERY_ID:
ret = query_id(device, irp, stack->Parameters.QueryId.IdType);
break;
case IRP_MN_START_DEVICE:
case IRP_MN_QUERY_CAPABILITIES:
ret = STATUS_SUCCESS;
break;
default:
FIXME("Unhandled minor function %#x.\n", stack->MinorFunction);
}
irp->IoStatus.Status = ret;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return ret;
}
static NTSTATUS WINAPI driver_pnp(DEVICE_OBJECT *device, IRP *irp)
{
if (device == bus_fdo)
return fdo_pnp(irp);
return pdo_pnp(device, irp);
}
static NTSTATUS WINAPI driver_add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *pdo)
{
NTSTATUS ret;
TRACE("driver %p, pdo %p.\n", driver, pdo);
if ((ret = IoCreateDevice(driver, 0, NULL, FILE_DEVICE_BUS_EXTENDER, 0, FALSE, &bus_fdo)))
{
ERR("Failed to create FDO, status %#x.\n", ret);
return ret;
}
IoAttachDeviceToDeviceStack(bus_fdo, pdo);
bus_pdo = pdo;
bus_fdo->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
static void WINAPI driver_unload(DRIVER_OBJECT *driver)
{
libusb_exit(NULL);
}
NTSTATUS WINAPI DriverEntry(DRIVER_OBJECT *driver, UNICODE_STRING *path)
{
int err;
TRACE("driver %p, path %s.\n", driver, debugstr_w(path->Buffer));
driver_obj = driver;
if ((err = libusb_init(NULL)))
{
ERR("Failed to initialize libusb: %s\n", libusb_strerror(err));
return STATUS_UNSUCCESSFUL;
}
event_thread = CreateThread(NULL, 0, event_thread_proc, NULL, 0, NULL);
driver->DriverExtension->AddDevice = driver_add_device;
driver->DriverUnload = driver_unload;
driver->MajorFunction[IRP_MJ_PNP] = driver_pnp;
return STATUS_SUCCESS;
}