/* * usbshare - IOCTL handling and URB forwarding */ #include "usbshare.h" static VOID UsbShareCompleteTransfer( _In_ WDFREQUEST Request, _In_ WDFIOTARGET Target, _In_ PWDF_REQUEST_COMPLETION_PARAMS Params, _In_ WDFCONTEXT Context); static NTSTATUS UsbShareHandleClaim(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request); static NTSTATUS UsbShareHandleGetDescriptors(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request); static NTSTATUS UsbShareHandleSubmit(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request); static NTSTATUS UsbShareHandleCancel(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request); static NTSTATUS UsbShareHandleSetInterface(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request); static NTSTATUS UsbShareHandleClearHalt(_In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request); VOID UsbShareEvtIoDeviceControl( _In_ WDFQUEUE Queue, _In_ WDFREQUEST Request, _In_ size_t OutputBufferLength, _In_ size_t InputBufferLength, _In_ ULONG IoControlCode ) { WDFDEVICE device = WdfIoQueueGetDevice(Queue); PDEVICE_CONTEXT context = GetDeviceContext(device); NTSTATUS status; UNREFERENCED_PARAMETER(OutputBufferLength); UNREFERENCED_PARAMETER(InputBufferLength); switch (IoControlCode) { case IOCTL_USBSHARE_CLAIM: status = UsbShareHandleClaim(context, Request); break; case IOCTL_USBSHARE_RELEASE: UsbShareReleaseClaim(context, WdfRequestGetFileObject(Request)); status = STATUS_SUCCESS; break; case IOCTL_USBSHARE_GET_DESCRIPTORS: status = UsbShareHandleGetDescriptors(context, Request); break; case IOCTL_USBSHARE_SUBMIT: status = UsbShareHandleSubmit(context, Request); /* * A submitted transfer completes asynchronously; the completion * routine owns the request from here. */ if (status == STATUS_PENDING) { return; } break; case IOCTL_USBSHARE_CANCEL: status = UsbShareHandleCancel(context, Request); break; case IOCTL_USBSHARE_SET_INTERFACE: status = UsbShareHandleSetInterface(context, Request); break; case IOCTL_USBSHARE_CLEAR_HALT: status = UsbShareHandleClearHalt(context, Request); break; case IOCTL_USBSHARE_RESET: status = WdfUsbTargetDeviceResetPortSynchronously(context->UsbDevice); break; default: /* * Not ours. As a filter we must pass it on rather than fail it — * some other component in the stack may be waiting for the answer. */ { WDF_REQUEST_SEND_OPTIONS options; WDF_REQUEST_SEND_OPTIONS_INIT(&options, WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET); WdfRequestFormatRequestUsingCurrentType(Request); if (!WdfRequestSend(Request, WdfDeviceGetIoTarget(device), &options)) { WdfRequestComplete(Request, WdfRequestGetStatus(Request)); } return; } } WdfRequestComplete(Request, status); } static NTSTATUS UsbShareHandleClaim( _In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request ) { NTSTATUS status; PUSBSHARE_DEVICE_INFO info; WDFFILEOBJECT fileObject = WdfRequestGetFileObject(Request); status = WdfRequestRetrieveOutputBuffer(Request, sizeof(USBSHARE_DEVICE_INFO), (PVOID *)&info, NULL); if (!NT_SUCCESS(status)) { return status; } WdfSpinLockAcquire(Context->ClaimLock); if (Context->Claimed && Context->ClaimOwner != fileObject) { WdfSpinLockRelease(Context->ClaimLock); return STATUS_DEVICE_BUSY; } Context->Claimed = TRUE; Context->ClaimOwner = fileObject; WdfSpinLockRelease(Context->ClaimLock); *info = Context->Info; WdfRequestSetInformation(Request, sizeof(USBSHARE_DEVICE_INFO)); KdPrint(("usbshare: device claimed (%04x:%04x)\n", info->VendorId, info->ProductId)); return STATUS_SUCCESS; } static NTSTATUS UsbShareHandleGetDescriptors( _In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request ) { NTSTATUS status; PVOID buffer; size_t bufferLength; if (Context->Descriptors == NULL) { status = UsbShareBuildDescriptorBlob(Context); if (!NT_SUCCESS(status)) { return status; } } status = WdfRequestRetrieveOutputBuffer(Request, 1, &buffer, &bufferLength); if (!NT_SUCCESS(status)) { return status; } if (bufferLength < Context->DescriptorsLength) { /* Report the needed size so the caller can retry. */ WdfRequestSetInformation(Request, Context->DescriptorsLength); return STATUS_BUFFER_TOO_SMALL; } RtlCopyMemory(buffer, Context->Descriptors, Context->DescriptorsLength); WdfRequestSetInformation(Request, Context->DescriptorsLength); return STATUS_SUCCESS; } /* * Builds the descriptor blob: device descriptor followed by every * configuration descriptor, matching what Linux returns when reading a * usbdevfs file. The client parses both with the same code. */ NTSTATUS UsbShareBuildDescriptorBlob( _In_ PDEVICE_CONTEXT Context ) { NTSTATUS status; USB_DEVICE_DESCRIPTOR deviceDescriptor; PUCHAR blob = NULL; ULONG blobSize = 0; ULONG offset; UCHAR configIndex; WdfUsbTargetDeviceGetDeviceDescriptor(Context->UsbDevice, &deviceDescriptor); /* First pass: total up the sizes. */ blobSize = sizeof(USB_DEVICE_DESCRIPTOR); for (configIndex = 0; configIndex < deviceDescriptor.bNumConfigurations; configIndex++) { USHORT configSize = 0; status = WdfUsbTargetDeviceRetrieveConfigDescriptor(Context->UsbDevice, NULL, &configSize); if (status != STATUS_BUFFER_TOO_SMALL && !NT_SUCCESS(status)) { return status; } blobSize += configSize; /* * Only configuration 0 can be retrieved through this API; devices * with several configurations would need a raw control transfer per * configuration. They are rare enough to leave for later. */ break; } blob = (PUCHAR)ExAllocatePool2(POOL_FLAG_NON_PAGED, blobSize, USBSHARE_POOL_TAG); if (blob == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlCopyMemory(blob, &deviceDescriptor, sizeof(USB_DEVICE_DESCRIPTOR)); offset = sizeof(USB_DEVICE_DESCRIPTOR); { USHORT configSize = (USHORT)(blobSize - offset); status = WdfUsbTargetDeviceRetrieveConfigDescriptor(Context->UsbDevice, blob + offset, &configSize); if (!NT_SUCCESS(status)) { ExFreePoolWithTag(blob, USBSHARE_POOL_TAG); return status; } } Context->Descriptors = blob; Context->DescriptorsLength = blobSize; return STATUS_SUCCESS; } static NTSTATUS UsbShareHandleSubmit( _In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request ) { NTSTATUS status; PUSBSHARE_TRANSFER transfer; size_t inputLength; PUCHAR payload; WDFMEMORY urbMemory; PURB urb; WDFUSBPIPE pipe; PREQUEST_CONTEXT reqContext; WDF_OBJECT_ATTRIBUTES attributes; WDFIOTARGET target; if (!UsbShareIsClaimed(Context)) { return STATUS_INVALID_DEVICE_STATE; } status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_TRANSFER), (PVOID *)&transfer, &inputLength); if (!NT_SUCCESS(status)) { return status; } if (inputLength < sizeof(USBSHARE_TRANSFER) + transfer->BufferLength) { return STATUS_BUFFER_TOO_SMALL; } payload = (PUCHAR)transfer + sizeof(USBSHARE_TRANSFER); /* Attach a context so a later cancel can find this request. */ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT); status = WdfObjectAllocateContext(Request, &attributes, (PVOID *)&reqContext); if (!NT_SUCCESS(status)) { return status; } reqContext->TransferId = transfer->Id; reqContext->ExpectedLength = transfer->BufferLength; WDF_OBJECT_ATTRIBUTES_INIT(&attributes); attributes.ParentObject = Request; if (transfer->Type == USBSHARE_TRANSFER_CONTROL) { status = WdfUsbTargetDeviceCreateUrb(Context->UsbDevice, &attributes, &urbMemory, &urb); if (!NT_SUCCESS(status)) { return status; } /* * The setup packet arrives in USB wire order and is copied verbatim. * Reinterpreting the fields here would only introduce a chance to get * the endianness wrong. */ UsbBuildVendorRequest(urb, URB_FUNCTION_VENDOR_DEVICE, sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST), (transfer->Direction == USBSHARE_DIR_IN) ? USBD_TRANSFER_DIRECTION_IN : 0, 0, transfer->Setup[0], /* bmRequestType */ transfer->Setup[1], /* bRequest */ *(USHORT *)&transfer->Setup[2], /* wValue */ *(USHORT *)&transfer->Setup[4], /* wIndex */ payload, NULL, transfer->BufferLength, NULL); } else { pipe = Context->Pipes[transfer->EndpointAddress]; if (pipe == NULL) { return STATUS_INVALID_PARAMETER; } status = WdfUsbTargetDeviceCreateUrb(Context->UsbDevice, &attributes, &urbMemory, &urb); if (!NT_SUCCESS(status)) { return status; } /* * Bulk and interrupt share one URB function; the pipe handle decides * which it actually is. */ urb->UrbBulkOrInterruptTransfer.Hdr.Length = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER); urb->UrbBulkOrInterruptTransfer.Hdr.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER; urb->UrbBulkOrInterruptTransfer.PipeHandle = WdfUsbTargetPipeWdmGetPipeHandle(pipe); urb->UrbBulkOrInterruptTransfer.TransferBuffer = payload; urb->UrbBulkOrInterruptTransfer.TransferBufferLength = transfer->BufferLength; urb->UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL; urb->UrbBulkOrInterruptTransfer.UrbLink = NULL; urb->UrbBulkOrInterruptTransfer.TransferFlags = (transfer->Direction == USBSHARE_DIR_IN) ? (USBD_TRANSFER_DIRECTION_IN | USBD_SHORT_TRANSFER_OK) : 0; } reqContext->UrbMemory = urbMemory; reqContext->Urb = urb; target = WdfUsbTargetDeviceGetIoTarget(Context->UsbDevice); status = WdfUsbTargetDeviceFormatRequestForUrb(Context->UsbDevice, Request, urbMemory, NULL); if (!NT_SUCCESS(status)) { return status; } WdfRequestSetCompletionRoutine(Request, UsbShareCompleteTransfer, Context); /* Track it so a cancel can find it. */ WdfSpinLockAcquire(Context->PendingLock); WdfCollectionAdd(Context->PendingTransfers, Request); WdfSpinLockRelease(Context->PendingLock); if (!WdfRequestSend(Request, target, WDF_NO_SEND_OPTIONS)) { status = WdfRequestGetStatus(Request); WdfSpinLockAcquire(Context->PendingLock); WdfCollectionRemove(Context->PendingTransfers, Request); WdfSpinLockRelease(Context->PendingLock); return status; } return STATUS_PENDING; } static VOID UsbShareCompleteTransfer( _In_ WDFREQUEST Request, _In_ WDFIOTARGET Target, _In_ PWDF_REQUEST_COMPLETION_PARAMS Params, _In_ WDFCONTEXT CompletionContext ) { PDEVICE_CONTEXT context = (PDEVICE_CONTEXT)CompletionContext; PREQUEST_CONTEXT reqContext = GetRequestContext(Request); PUSBSHARE_TRANSFER_RESULT result; NTSTATUS status; size_t outputLength; ULONG transferred = 0; PUCHAR outPayload; UNREFERENCED_PARAMETER(Target); WdfSpinLockAcquire(context->PendingLock); WdfCollectionRemove(context->PendingTransfers, Request); WdfSpinLockRelease(context->PendingLock); if (reqContext->Urb != NULL) { transferred = reqContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength; } status = WdfRequestRetrieveOutputBuffer(Request, sizeof(USBSHARE_TRANSFER_RESULT), (PVOID *)&result, &outputLength); if (!NT_SUCCESS(status)) { WdfRequestComplete(Request, status); return; } result->Id = reqContext->TransferId; result->Status = Params->IoStatus.Status; result->UsbdStatus = (reqContext->Urb != NULL) ? reqContext->Urb->UrbHeader.Status : 0; result->ActualLength = transferred; /* * Copy the received payload after the result header, but only as much as * the output buffer holds — a device may return more than expected. */ if (transferred > 0 && outputLength > sizeof(USBSHARE_TRANSFER_RESULT)) { ULONG room = (ULONG)(outputLength - sizeof(USBSHARE_TRANSFER_RESULT)); ULONG copy = (transferred < room) ? transferred : room; outPayload = (PUCHAR)result + sizeof(USBSHARE_TRANSFER_RESULT); RtlCopyMemory(outPayload, reqContext->Urb->UrbBulkOrInterruptTransfer.TransferBuffer, copy); WdfRequestSetInformation(Request, sizeof(USBSHARE_TRANSFER_RESULT) + copy); } else { WdfRequestSetInformation(Request, sizeof(USBSHARE_TRANSFER_RESULT)); } /* * Always complete successfully: the transfer's own outcome travels in the * result structure. Failing the IOCTL would lose the distinction between * "the ioctl did not work" and "the device stalled". */ WdfRequestComplete(Request, STATUS_SUCCESS); } static NTSTATUS UsbShareHandleCancel( _In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request ) { NTSTATUS status; PUSBSHARE_CANCEL cancel; ULONG i, count; WDFREQUEST target = NULL; status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_CANCEL), (PVOID *)&cancel, NULL); if (!NT_SUCCESS(status)) { return status; } WdfSpinLockAcquire(Context->PendingLock); count = WdfCollectionGetCount(Context->PendingTransfers); for (i = 0; i < count; i++) { WDFREQUEST candidate = (WDFREQUEST)WdfCollectionGetItem(Context->PendingTransfers, i); PREQUEST_CONTEXT candidateContext = GetRequestContext(candidate); if (candidateContext != NULL && candidateContext->TransferId == cancel->Id) { target = candidate; break; } } WdfSpinLockRelease(Context->PendingLock); if (target == NULL) { /* Already finished. Not an error: the caller gets its result anyway. */ return STATUS_SUCCESS; } WdfRequestCancelSentRequest(target); return STATUS_SUCCESS; } static NTSTATUS UsbShareHandleSetInterface( _In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request ) { NTSTATUS status; PUSBSHARE_SET_INTERFACE params; WDF_USB_INTERFACE_SELECT_SETTING_PARAMS settingParams; if (!UsbShareIsClaimed(Context)) { return STATUS_INVALID_DEVICE_STATE; } status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_SET_INTERFACE), (PVOID *)¶ms, NULL); if (!NT_SUCCESS(status)) { return status; } if (Context->UsbInterface == NULL) { return STATUS_INVALID_DEVICE_STATE; } /* * Going through the framework rather than sending a raw SET_INTERFACE is * essential: the USB stack has to re-open the pipes and, for isochronous * endpoints, reserve bandwidth. A raw control transfer changes the device * without telling the stack, after which every later transfer fails. */ WDF_USB_INTERFACE_SELECT_SETTING_PARAMS_INIT_SETTING(&settingParams, params->AlternateSetting); status = WdfUsbInterfaceSelectSetting(Context->UsbInterface, WDF_NO_OBJECT_ATTRIBUTES, &settingParams); if (!NT_SUCCESS(status)) { return status; } /* Pipe handles change with the setting, so rebuild the map. */ RtlZeroMemory(Context->Pipes, sizeof(Context->Pipes)); { BYTE pipeCount = WdfUsbInterfaceGetNumConfiguredPipes(Context->UsbInterface); BYTE i; for (i = 0; i < pipeCount; i++) { WDF_USB_PIPE_INFORMATION pipeInfo; WDFUSBPIPE pipe; WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo); pipe = WdfUsbInterfaceGetConfiguredPipe(Context->UsbInterface, i, &pipeInfo); if (pipe != NULL) { Context->Pipes[pipeInfo.EndpointAddress] = pipe; WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe); } } } return STATUS_SUCCESS; } static NTSTATUS UsbShareHandleClearHalt( _In_ PDEVICE_CONTEXT Context, _In_ WDFREQUEST Request ) { NTSTATUS status; PUSBSHARE_CLEAR_HALT params; WDFUSBPIPE pipe; if (!UsbShareIsClaimed(Context)) { return STATUS_INVALID_DEVICE_STATE; } status = WdfRequestRetrieveInputBuffer(Request, sizeof(USBSHARE_CLEAR_HALT), (PVOID *)¶ms, NULL); if (!NT_SUCCESS(status)) { return status; } pipe = Context->Pipes[params->EndpointAddress]; if (pipe == NULL) { return STATUS_INVALID_PARAMETER; } return WdfUsbTargetPipeResetSynchronously(pipe, WDF_NO_HANDLE, NULL); }