/* * WINE HID Pseudo-Plug and Play support * * Copyright 2015 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 */ #define NONAMELESSUNION #include #include #include "hid.h" #include "ddk/hidtypes.h" #include "regstr.h" #include "wine/debug.h" #include "wine/unicode.h" #include "wine/list.h" WINE_DEFAULT_DEBUG_CHANNEL(hid); static const WCHAR device_enumeratorW[] = {'H','I','D',0}; static const WCHAR device_deviceid_fmtW[] = {'%','s','\\', 'v','i','d','_','%','0','4','x','&','p','i','d','_','%', '0','4','x'}; static const WCHAR device_instanceid_fmtW[] = {'%','s','\\', 'v','i','d','_','%','0','4','x','&','p','i','d','_','%', '0','4','x','&','%','s','\\','%','i','&','%','s',0}; typedef struct _NATIVE_DEVICE { struct list entry; DWORD vidpid; DEVICE_OBJECT *PDO; DEVICE_OBJECT *FDO; HID_MINIDRIVER_REGISTRATION *minidriver; } NATIVE_DEVICE; static struct list tracked_devices = LIST_INIT(tracked_devices); static NTSTATUS WINAPI internalComplete(DEVICE_OBJECT *deviceObject, IRP *irp, void *context ) { SetEvent(irp->UserEvent); return STATUS_MORE_PROCESSING_REQUIRED; } static NTSTATUS SendDeviceIRP(DEVICE_OBJECT* device, IRP *irp) { NTSTATUS status; IO_STACK_LOCATION *irpsp; HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL); irp->UserEvent = event; irpsp = IoGetNextIrpStackLocation(irp); irpsp->CompletionRoutine = internalComplete; irpsp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR; IoCallDriver(device, irp); if (irp->IoStatus.u.Status == STATUS_PENDING) WaitForSingleObject(event, INFINITE); status = irp->IoStatus.u.Status; IoCompleteRequest(irp, IO_NO_INCREMENT ); CloseHandle(event); return status; } static NTSTATUS PNP_SendPnPIRP(DEVICE_OBJECT *device, UCHAR minor) { IO_STACK_LOCATION *irpsp; IO_STATUS_BLOCK irp_status; IRP *irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, device, NULL, 0, NULL, NULL, &irp_status); irpsp = IoGetNextIrpStackLocation(irp); irpsp->MinorFunction = minor; irpsp->Parameters.StartDevice.AllocatedResources = NULL; irpsp->Parameters.StartDevice.AllocatedResourcesTranslated = NULL; return SendDeviceIRP(device, irp); } static NTSTATUS PNP_SendPowerIRP(DEVICE_OBJECT *device, DEVICE_POWER_STATE power) { IO_STATUS_BLOCK irp_status; IO_STACK_LOCATION *irpsp; IRP *irp = IoBuildSynchronousFsdRequest(IRP_MJ_POWER, device, NULL, 0, NULL, NULL, &irp_status); irpsp = IoGetNextIrpStackLocation(irp); irpsp->MinorFunction = IRP_MN_SET_POWER; irpsp->Parameters.Power.Type = DevicePowerState; irpsp->Parameters.Power.State.DeviceState = power; return SendDeviceIRP(device, irp); } NTSTATUS WINAPI PNP_AddDevice(DRIVER_OBJECT *driver, DEVICE_OBJECT *PDO) { DEVICE_OBJECT *device = NULL; NTSTATUS status; minidriver *minidriver; HID_DEVICE_ATTRIBUTES attr; BASE_DEVICE_EXTENSION *ext = NULL; WCHAR serial[256]; WCHAR interface[256]; DWORD index = HID_STRING_ID_ISERIALNUMBER; NATIVE_DEVICE *tracked_device, *ptr; INT interface_index = 1; HID_DESCRIPTOR descriptor; BYTE *reportDescriptor; INT i; static const WCHAR ig_fmtW[] = {'I','G','_','%','i',0}; static const WCHAR im_fmtW[] = {'I','M','_','%','i',0}; TRACE("PDO add device(%p)\n", PDO); minidriver = find_minidriver(driver); status = HID_CreateDevice(PDO, &minidriver->minidriver, &device); if (status != STATUS_SUCCESS) { ERR("Failed to create HID object (%x)\n",status); return status; } ext = device->DeviceExtension; InitializeListHead(&ext->irp_queue); TRACE("Created device %p\n",device); status = minidriver->AddDevice(minidriver->minidriver.DriverObject, device); if (status != STATUS_SUCCESS) { ERR("Minidriver AddDevice failed (%x)\n",status); HID_DeleteDevice(&minidriver->minidriver, device); return status; } status = PNP_SendPnPIRP(device, IRP_MN_START_DEVICE); if (status != STATUS_SUCCESS) { ERR("Minidriver IRP_MN_START_DEVICE failed (%x)\n",status); HID_DeleteDevice(&minidriver->minidriver, device); return status; } status = call_minidriver(IOCTL_HID_GET_DEVICE_ATTRIBUTES, device, NULL, 0, &attr, sizeof(attr)); if (status != STATUS_SUCCESS) { ERR("Minidriver failed to get Attributes(%x)\n",status); PNP_SendPnPIRP(device, IRP_MN_REMOVE_DEVICE); HID_DeleteDevice(&minidriver->minidriver, device); return status; } ext->information.VendorID = attr.VendorID; ext->information.ProductID = attr.ProductID; ext->information.VersionNumber = attr.VersionNumber; ext->information.Polled = minidriver->minidriver.DevicesArePolled; tracked_device = HeapAlloc(GetProcessHeap(), 0, sizeof(*tracked_device)); tracked_device->vidpid = MAKELONG(attr.VendorID, attr.ProductID); tracked_device->PDO = PDO; tracked_device->FDO = device; tracked_device->minidriver = &minidriver->minidriver; LIST_FOR_EACH_ENTRY(ptr, &tracked_devices, NATIVE_DEVICE, entry) if (ptr->vidpid == tracked_device->vidpid) interface_index++; list_add_tail(&tracked_devices, &tracked_device->entry); status = call_minidriver(IOCTL_HID_GET_DEVICE_DESCRIPTOR, device, NULL, 0, &descriptor, sizeof(descriptor)); if (status != STATUS_SUCCESS) { ERR("Cannot get Device Descriptor(%x)\n",status); PNP_SendPnPIRP(device, IRP_MN_REMOVE_DEVICE); HID_DeleteDevice(&minidriver->minidriver, device); return status; } for (i = 0; i < descriptor.bNumDescriptors; i++) if (descriptor.DescriptorList[i].bReportType == HID_REPORT_DESCRIPTOR_TYPE) break; if (i >= descriptor.bNumDescriptors) { ERR("No Report Descriptor found in reply\n"); PNP_SendPnPIRP(device, IRP_MN_REMOVE_DEVICE); HID_DeleteDevice(&minidriver->minidriver, device); return status; } reportDescriptor = HeapAlloc(GetProcessHeap(), 0, descriptor.DescriptorList[i].wReportLength); status = call_minidriver(IOCTL_HID_GET_REPORT_DESCRIPTOR, device, NULL, 0, reportDescriptor, descriptor.DescriptorList[i].wReportLength); if (status != STATUS_SUCCESS) { ERR("Cannot get Report Descriptor(%x)\n",status); HID_DeleteDevice(&minidriver->minidriver, device); HeapFree(GetProcessHeap(), 0, reportDescriptor); return status; } ext->preparseData = ParseDescriptor(reportDescriptor, descriptor.DescriptorList[0].wReportLength); HeapFree(GetProcessHeap(), 0, reportDescriptor); if (!ext->preparseData) { ERR("Cannot parse Report Descriptor\n"); HID_DeleteDevice(&minidriver->minidriver, device); return STATUS_NOT_SUPPORTED; } ext->information.DescriptorSize = ext->preparseData->dwSize; serial[0] = 0; status = call_minidriver(IOCTL_HID_GET_STRING, device, &index, sizeof(DWORD), serial, sizeof(serial)); if (serial[0] == 0) { static const WCHAR wZeroSerial[]= {'0','0','0','0',0}; lstrcpyW(serial, wZeroSerial); } if (ext->preparseData->caps.UsagePage == HID_USAGE_PAGE_GENERIC && (ext->preparseData->caps.Usage == HID_USAGE_GENERIC_GAMEPAD || ext->preparseData->caps.Usage == HID_USAGE_GENERIC_JOYSTICK)) sprintfW(interface, ig_fmtW, interface_index); else sprintfW(interface, im_fmtW, interface_index); sprintfW(ext->instance_id, device_instanceid_fmtW, device_enumeratorW, ext->information.VendorID, ext->information.ProductID, interface, ext->information.VersionNumber, serial); sprintfW(ext->device_id, device_deviceid_fmtW, device_enumeratorW, ext->information.VendorID, ext->information.ProductID); HID_LinkDevice(device); ext->poll_interval = DEFAULT_POLL_INTERVAL; ext->ring_buffer = RingBuffer_Create(sizeof(HID_XFER_PACKET) + ext->preparseData->caps.InputReportByteLength); HID_StartDeviceThread(device); PNP_SendPowerIRP(device, PowerDeviceD0); return STATUS_SUCCESS; } void PNP_CleanupPNP(DRIVER_OBJECT *driver) { NATIVE_DEVICE *tracked_device, *ptr; LIST_FOR_EACH_ENTRY_SAFE(tracked_device, ptr, &tracked_devices, NATIVE_DEVICE, entry) { if (tracked_device->minidriver->DriverObject == driver) { list_remove(&tracked_device->entry); PNP_SendPowerIRP(tracked_device->FDO, PowerDeviceD3); PNP_SendPnPIRP(tracked_device->FDO, IRP_MN_REMOVE_DEVICE); HID_DeleteDevice(tracked_device->minidriver, tracked_device->FDO); HeapFree(GetProcessHeap(), 0, tracked_device); } } } NTSTATUS WINAPI HID_PNP_Dispatch(DEVICE_OBJECT *device, IRP *irp) { NTSTATUS rc = STATUS_NOT_SUPPORTED; IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation(irp); TRACE("%p, %p\n", device, irp); switch(irpsp->MinorFunction) { case IRP_MN_QUERY_ID: { BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; WCHAR *id = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR)*REGSTR_VAL_MAX_HCID_LEN); TRACE("IRP_MN_QUERY_ID[%i]\n", irpsp->Parameters.QueryId.IdType); switch (irpsp->Parameters.QueryId.IdType) { case BusQueryHardwareIDs: case BusQueryCompatibleIDs: { WCHAR *ptr; ptr = id; /* Instance ID */ strcpyW(ptr, ext->instance_id); ptr += lstrlenW(ext->instance_id) + 1; /* Device ID */ strcpyW(ptr, ext->device_id); ptr += lstrlenW(ext->device_id) + 1; /* Bus ID */ strcpyW(ptr, device_enumeratorW); ptr += lstrlenW(device_enumeratorW) + 1; *ptr = 0; irp->IoStatus.Information = (ULONG_PTR)id; rc = STATUS_SUCCESS; break; } case BusQueryDeviceID: strcpyW(id, ext->device_id); irp->IoStatus.Information = (ULONG_PTR)id; rc = STATUS_SUCCESS; break; case BusQueryInstanceID: strcpyW(id, ext->instance_id); irp->IoStatus.Information = (ULONG_PTR)id; rc = STATUS_SUCCESS; break; case BusQueryDeviceSerialNumber: FIXME("BusQueryDeviceSerialNumber not implemented\n"); HeapFree(GetProcessHeap(), 0, id); break; } break; } default: { /* Forward IRP to the minidriver */ minidriver *minidriver = find_minidriver(device->DriverObject); return minidriver->PNPDispatch(device, irp); } } irp->IoStatus.u.Status = rc; IoCompleteRequest( irp, IO_NO_INCREMENT ); return rc; }