/* * 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 "ddk/wdm.h" #include "regstr.h" #include "wine/debug.h" #include "wine/list.h" WINE_DEFAULT_DEBUG_CHANNEL(hid); static const WCHAR device_enumeratorW[] = {'H','I','D',0}; static const WCHAR separator_W[] = {'\\',0}; static NTSTATUS WINAPI internalComplete(DEVICE_OBJECT *deviceObject, IRP *irp, void *context) { HANDLE event = context; SetEvent(event); return STATUS_MORE_PROCESSING_REQUIRED; } static NTSTATUS get_device_id(DEVICE_OBJECT *device, BUS_QUERY_ID_TYPE type, WCHAR *id) { NTSTATUS status; IO_STACK_LOCATION *irpsp; IO_STATUS_BLOCK irp_status; HANDLE event; IRP *irp; irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, device, NULL, 0, NULL, NULL, &irp_status); if (irp == NULL) return STATUS_NO_MEMORY; event = CreateEventA(NULL, FALSE, FALSE, NULL); irpsp = IoGetNextIrpStackLocation(irp); irpsp->MinorFunction = IRP_MN_QUERY_ID; irpsp->Parameters.QueryId.IdType = type; IoSetCompletionRoutine(irp, internalComplete, event, TRUE, TRUE, TRUE); status = IoCallDriver(device, irp); if (status == STATUS_PENDING) WaitForSingleObject(event, INFINITE); lstrcpyW(id, (WCHAR *)irp->IoStatus.Information); ExFreePool( (WCHAR *)irp->IoStatus.Information ); status = irp->IoStatus.u.Status; IoCompleteRequest(irp, IO_NO_INCREMENT ); CloseHandle(event); return status; } NTSTATUS WINAPI PNP_AddDevice(DRIVER_OBJECT *driver, DEVICE_OBJECT *PDO) { WCHAR device_id[MAX_DEVICE_ID_LEN], instance_id[MAX_DEVICE_ID_LEN]; hid_device *hiddev; DEVICE_OBJECT *device = NULL; NTSTATUS status; minidriver *minidriver; HID_DEVICE_ATTRIBUTES attr; BASE_DEVICE_EXTENSION *ext = NULL; HID_DESCRIPTOR descriptor; BYTE *reportDescriptor; INT i; if ((status = get_device_id(PDO, BusQueryDeviceID, device_id))) { ERR("Failed to get PDO device id, status %#x.\n", status); return status; } if ((status = get_device_id(PDO, BusQueryInstanceID, instance_id))) { ERR("Failed to get PDO instance id, status %#x.\n", status); return status; } TRACE("Adding device to PDO %p, id %s\\%s.\n", PDO, debugstr_w(device_id), debugstr_w(instance_id)); minidriver = find_minidriver(driver); hiddev = HeapAlloc(GetProcessHeap(), 0, sizeof(*hiddev)); if (!hiddev) return STATUS_NO_MEMORY; status = HID_CreateDevice(PDO, &minidriver->minidriver, &hiddev->device); if (status != STATUS_SUCCESS) { ERR("Failed to create HID object (%x)\n",status); HeapFree(GetProcessHeap(), 0, hiddev); return status; } device = hiddev->device; ext = device->DeviceExtension; InitializeListHead(&ext->irp_queue); KeInitializeSpinLock(&ext->irp_queue_lock); 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(device); HeapFree(GetProcessHeap(), 0, hiddev); 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); HID_DeleteDevice(device); HeapFree(GetProcessHeap(), 0, hiddev); return status; } ext->information.VendorID = attr.VendorID; ext->information.ProductID = attr.ProductID; ext->information.VersionNumber = attr.VersionNumber; ext->information.Polled = minidriver->minidriver.DevicesArePolled; 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); HID_DeleteDevice(device); HeapFree(GetProcessHeap(), 0, hiddev); 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"); HID_DeleteDevice(device); HeapFree(GetProcessHeap(), 0, hiddev); 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(device); HeapFree(GetProcessHeap(), 0, reportDescriptor); HeapFree(GetProcessHeap(), 0, hiddev); 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(device); HeapFree(GetProcessHeap(), 0, hiddev); return STATUS_NOT_SUPPORTED; } list_add_tail(&(minidriver->device_list), &hiddev->entry); ext->information.DescriptorSize = ext->preparseData->dwSize; lstrcpyW(ext->instance_id, instance_id); lstrcpyW(ext->device_id, device_enumeratorW); lstrcatW(ext->device_id, separator_W); lstrcatW(ext->device_id, wcschr(device_id, '\\') + 1); 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); return STATUS_SUCCESS; } NTSTATUS PNP_RemoveDevice(minidriver *minidriver, DEVICE_OBJECT *device, IRP *irp) { BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; hid_device *hiddev; NTSTATUS rc = STATUS_NOT_SUPPORTED; rc = IoSetDeviceInterfaceState(&ext->link_name, FALSE); if (rc) { FIXME("failed to disable interface %x\n", rc); return rc; } if (ext->is_mouse) IoSetDeviceInterfaceState(&ext->mouse_link_name, FALSE); if (irp) rc = minidriver->PNPDispatch(device, irp); HID_DeleteDevice(device); LIST_FOR_EACH_ENTRY(hiddev, &minidriver->device_list, hid_device, entry) { if (hiddev->device == device) { list_remove(&hiddev->entry); HeapFree(GetProcessHeap(), 0, hiddev); break; } } return rc; } NTSTATUS WINAPI HID_PNP_Dispatch(DEVICE_OBJECT *device, IRP *irp) { NTSTATUS rc = STATUS_NOT_SUPPORTED; IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation(irp); minidriver *minidriver = find_minidriver(device->DriverObject); TRACE("%p, %p\n", device, irp); switch(irpsp->MinorFunction) { case IRP_MN_QUERY_ID: { BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; WCHAR *id = ExAllocatePool(PagedPool, 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; /* Device instance ID */ lstrcpyW(ptr, ext->device_id); ptr += lstrlenW(ext->device_id); lstrcpyW(ptr, separator_W); ptr += 1; lstrcpyW(ptr, ext->instance_id); ptr += lstrlenW(ext->instance_id) + 1; /* Device ID */ lstrcpyW(ptr, ext->device_id); ptr += lstrlenW(ext->device_id) + 1; /* Bus ID */ lstrcpyW(ptr, device_enumeratorW); ptr += lstrlenW(device_enumeratorW) + 1; *ptr = 0; irp->IoStatus.Information = (ULONG_PTR)id; rc = STATUS_SUCCESS; break; } case BusQueryDeviceID: lstrcpyW(id, ext->device_id); irp->IoStatus.Information = (ULONG_PTR)id; rc = STATUS_SUCCESS; break; case BusQueryInstanceID: lstrcpyW(id, ext->instance_id); irp->IoStatus.Information = (ULONG_PTR)id; rc = STATUS_SUCCESS; break; case BusQueryDeviceSerialNumber: FIXME("BusQueryDeviceSerialNumber not implemented\n"); ExFreePool(id); break; } break; } case IRP_MN_START_DEVICE: { BASE_DEVICE_EXTENSION *ext = device->DeviceExtension; rc = minidriver->PNPDispatch(device, irp); IoSetDeviceInterfaceState(&ext->link_name, TRUE); if (ext->is_mouse) IoSetDeviceInterfaceState(&ext->mouse_link_name, TRUE); return rc; } case IRP_MN_REMOVE_DEVICE: { return PNP_RemoveDevice(minidriver, device, irp); } default: { /* Forward IRP to the minidriver */ return minidriver->PNPDispatch(device, irp); } } irp->IoStatus.u.Status = rc; IoCompleteRequest( irp, IO_NO_INCREMENT ); return rc; }