770 lines
23 KiB
C
770 lines
23 KiB
C
/*
|
|
* QemuA6Ude - Windows USB Device Emulation controller for the QEMU A6 model.
|
|
*
|
|
* The UDE child is deliberately exposed as Apple's normal DFU PID (0x1227),
|
|
* which is what unmodified libirecovery/idevicerestore discovers on Windows.
|
|
* Class and vendor control transfers are relayed through \\.\QemuA6Ude0.
|
|
*/
|
|
|
|
#include <ntddk.h>
|
|
#include <wdf.h>
|
|
#include <usb.h>
|
|
#include <wdfusb.h>
|
|
#include <usbdlib.h>
|
|
#include <ude/1.0/UdeCx.h>
|
|
#include <initguid.h>
|
|
#include <usbioctl.h>
|
|
#include <wdmsec.h>
|
|
|
|
#include "../include/qemu_a6_usb_protocol.h"
|
|
|
|
#define QA6_POOL_TAG 'U6AQ'
|
|
#define QA6_DEVICE_NAME L"\\Device\\QemuA6Ude0"
|
|
#define QA6_SYMBOLIC_LINK L"\\DosDevices\\QemuA6Ude0"
|
|
#define QA6_HOST_INTERFACE_REF L"QemuA6UdeHost"
|
|
#define QA6_LANGUAGE_ID 0x0409
|
|
|
|
#define QA6_LOG_ERROR(...) \
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, \
|
|
"QemuA6Ude: " __VA_ARGS__)
|
|
#define QA6_LOG_INFO(...) \
|
|
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, \
|
|
"QemuA6Ude: " __VA_ARGS__)
|
|
|
|
typedef struct _QA6_CONTROLLER_CONTEXT {
|
|
WDFQUEUE DefaultQueue;
|
|
WDFQUEUE WaitingUserReads;
|
|
WDFSPINLOCK BridgeLock;
|
|
|
|
PUDECXUSBDEVICE_INIT ChildInit;
|
|
UDECXUSBDEVICE ChildDevice;
|
|
UDECXUSBENDPOINT ControlEndpoint;
|
|
WDFQUEUE ControlQueue;
|
|
BOOLEAN PluggedIn;
|
|
|
|
WDFREQUEST PendingUrb;
|
|
ULONG PendingRequestId;
|
|
ULONG PendingTransferLength;
|
|
BOOLEAN PendingDirectionIn;
|
|
|
|
BOOLEAN RequestFrameReady;
|
|
ULONG RequestFrameLength;
|
|
UCHAR RequestFrame[QA6_USB_MAX_FRAME_SIZE];
|
|
} QA6_CONTROLLER_CONTEXT, *PQA6_CONTROLLER_CONTEXT;
|
|
|
|
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QA6_CONTROLLER_CONTEXT,
|
|
Qa6GetControllerContext);
|
|
|
|
DRIVER_INITIALIZE DriverEntry;
|
|
EVT_WDF_DRIVER_DEVICE_ADD Qa6EvtDeviceAdd;
|
|
EVT_WDF_DEVICE_D0_ENTRY Qa6EvtDeviceD0Entry;
|
|
EVT_WDF_DEVICE_D0_EXIT Qa6EvtDeviceD0Exit;
|
|
EVT_WDF_OBJECT_CONTEXT_CLEANUP Qa6EvtControllerCleanup;
|
|
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Qa6EvtControllerIoctl;
|
|
EVT_WDF_IO_QUEUE_IO_READ Qa6EvtBridgeRead;
|
|
EVT_WDF_IO_QUEUE_IO_WRITE Qa6EvtBridgeWrite;
|
|
EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE Qa6EvtCanceledUserRead;
|
|
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Qa6EvtControlUrb;
|
|
EVT_UDECX_USB_ENDPOINT_RESET Qa6EvtEndpointReset;
|
|
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY Qa6EvtQueryUsbCapability;
|
|
|
|
static const USB_DEVICE_DESCRIPTOR Qa6DeviceDescriptor = {
|
|
sizeof(USB_DEVICE_DESCRIPTOR),
|
|
USB_DEVICE_DESCRIPTOR_TYPE,
|
|
0x0200,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x40,
|
|
0x05ac,
|
|
0x1227,
|
|
0x0000,
|
|
2,
|
|
3,
|
|
4,
|
|
1
|
|
};
|
|
|
|
static const UCHAR Qa6ConfigurationDescriptor[] = {
|
|
0x09, USB_CONFIGURATION_DESCRIPTOR_TYPE,
|
|
0x19, 0x00,
|
|
0x01,
|
|
0x01,
|
|
0x05,
|
|
0x80,
|
|
0xfa,
|
|
|
|
0x09, USB_INTERFACE_DESCRIPTOR_TYPE,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0xfe,
|
|
0x01,
|
|
0x00,
|
|
0x00,
|
|
|
|
0x07, 0x21,
|
|
0x01,
|
|
0x0a, 0x00,
|
|
0x00,
|
|
0x08
|
|
};
|
|
|
|
static const UCHAR Qa6LanguageDescriptor[] = { 4, USB_STRING_DESCRIPTOR_TYPE,
|
|
0x09, 0x04 };
|
|
|
|
DECLARE_CONST_UNICODE_STRING(Qa6ManufacturerString, L"Apple Inc.");
|
|
DECLARE_CONST_UNICODE_STRING(Qa6ProductString,
|
|
L"Apple Mobile Device (DFU Mode)");
|
|
DECLARE_CONST_UNICODE_STRING(
|
|
Qa6SerialString,
|
|
L"CPID:8950 CPRV:20 CPFM:03 SCEP:10 BDID:00 "
|
|
L"ECID:0000000000200000 IBFL:00 SRTG:[iBoot-1145.3]");
|
|
DECLARE_CONST_UNICODE_STRING(Qa6ConfigurationString, L"Apple Mobile Device");
|
|
|
|
static VOID
|
|
Qa6CompleteUserRead(
|
|
_In_ WDFREQUEST Request,
|
|
_In_reads_bytes_(Length) const UCHAR *Data,
|
|
_In_ ULONG Length
|
|
)
|
|
{
|
|
PVOID buffer;
|
|
size_t bufferLength;
|
|
NTSTATUS status;
|
|
|
|
status = WdfRequestRetrieveOutputBuffer(Request, Length, &buffer,
|
|
&bufferLength);
|
|
if (!NT_SUCCESS(status)) {
|
|
WdfRequestComplete(Request, status);
|
|
return;
|
|
}
|
|
|
|
if (bufferLength < Length) {
|
|
WdfRequestComplete(Request, STATUS_BUFFER_TOO_SMALL);
|
|
return;
|
|
}
|
|
|
|
RtlCopyMemory(buffer, Data, Length);
|
|
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, Length);
|
|
}
|
|
|
|
static VOID
|
|
Qa6PublishRequestFrame(
|
|
_In_ WDFDEVICE Controller
|
|
)
|
|
{
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
|
WDFREQUEST reader = NULL;
|
|
NTSTATUS status;
|
|
|
|
WdfSpinLockAcquire(context->BridgeLock);
|
|
status = WdfIoQueueRetrieveNextRequest(context->WaitingUserReads, &reader);
|
|
if (NT_SUCCESS(status)) {
|
|
context->RequestFrameReady = FALSE;
|
|
} else {
|
|
context->RequestFrameReady = TRUE;
|
|
}
|
|
WdfSpinLockRelease(context->BridgeLock);
|
|
|
|
if (reader != NULL) {
|
|
Qa6CompleteUserRead(reader, context->RequestFrame,
|
|
context->RequestFrameLength);
|
|
}
|
|
}
|
|
|
|
static NTSTATUS
|
|
Qa6InitializeUsbDescriptors(
|
|
_In_ WDFDEVICE Controller
|
|
)
|
|
{
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
|
UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
|
|
NTSTATUS status;
|
|
|
|
context->ChildInit = UdecxUsbDeviceInitAllocate(Controller);
|
|
if (context->ChildInit == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
|
|
UdecxUsbDeviceInitSetStateChangeCallbacks(context->ChildInit, &callbacks);
|
|
UdecxUsbDeviceInitSetSpeed(context->ChildInit, UdecxUsbHighSpeed);
|
|
UdecxUsbDeviceInitSetEndpointsType(context->ChildInit,
|
|
UdecxEndpointTypeSimple);
|
|
|
|
status = UdecxUsbDeviceInitAddDescriptor(
|
|
context->ChildInit, (PUCHAR)&Qa6DeviceDescriptor,
|
|
(USHORT)sizeof(Qa6DeviceDescriptor));
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
status = UdecxUsbDeviceInitAddDescriptorWithIndex(
|
|
context->ChildInit, (PUCHAR)Qa6LanguageDescriptor,
|
|
(USHORT)sizeof(Qa6LanguageDescriptor), 0);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
status = UdecxUsbDeviceInitAddStringDescriptor(
|
|
context->ChildInit, &Qa6ManufacturerString, 2, QA6_LANGUAGE_ID);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
status = UdecxUsbDeviceInitAddStringDescriptor(
|
|
context->ChildInit, &Qa6ProductString, 3, QA6_LANGUAGE_ID);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
status = UdecxUsbDeviceInitAddStringDescriptor(
|
|
context->ChildInit, &Qa6SerialString, 4, QA6_LANGUAGE_ID);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
return UdecxUsbDeviceInitAddStringDescriptor(
|
|
context->ChildInit, &Qa6ConfigurationString, 5, QA6_LANGUAGE_ID);
|
|
}
|
|
|
|
static NTSTATUS
|
|
Qa6CreateControlEndpoint(
|
|
_In_ WDFDEVICE Controller
|
|
)
|
|
{
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
|
WDF_IO_QUEUE_CONFIG queueConfig;
|
|
PUDECXUSBENDPOINT_INIT endpointInit = NULL;
|
|
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
|
|
NTSTATUS status;
|
|
|
|
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
|
|
queueConfig.EvtIoInternalDeviceControl = Qa6EvtControlUrb;
|
|
queueConfig.PowerManaged = WdfFalse;
|
|
status = WdfIoQueueCreate(Controller, &queueConfig,
|
|
WDF_NO_OBJECT_ATTRIBUTES,
|
|
&context->ControlQueue);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
endpointInit = UdecxUsbSimpleEndpointInitAllocate(context->ChildDevice);
|
|
if (endpointInit == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
UdecxUsbEndpointInitSetEndpointAddress(endpointInit,
|
|
USB_DEFAULT_ENDPOINT_ADDRESS);
|
|
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, Qa6EvtEndpointReset);
|
|
UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);
|
|
status = UdecxUsbEndpointCreate(&endpointInit,
|
|
WDF_NO_OBJECT_ATTRIBUTES,
|
|
&context->ControlEndpoint);
|
|
if (!NT_SUCCESS(status)) {
|
|
if (endpointInit != NULL) {
|
|
UdecxUsbEndpointInitFree(endpointInit);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
UdecxUsbEndpointSetWdfIoQueue(context->ControlEndpoint,
|
|
context->ControlQueue);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS
|
|
Qa6PlugInUsbDevice(
|
|
_In_ WDFDEVICE Controller
|
|
)
|
|
{
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Controller);
|
|
UDECX_USB_DEVICE_PLUG_IN_OPTIONS plugOptions;
|
|
WDF_OBJECT_ATTRIBUTES attributes;
|
|
NTSTATUS status;
|
|
|
|
if (context->PluggedIn) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
status = UdecxUsbDeviceInitAddDescriptor(
|
|
context->ChildInit, (PUCHAR)Qa6ConfigurationDescriptor,
|
|
(USHORT)sizeof(Qa6ConfigurationDescriptor));
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
|
status = UdecxUsbDeviceCreate(&context->ChildInit, &attributes,
|
|
&context->ChildDevice);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
status = Qa6CreateControlEndpoint(Controller);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&plugOptions);
|
|
plugOptions.Usb20PortNumber = 1;
|
|
status = UdecxUsbDevicePlugIn(context->ChildDevice, &plugOptions);
|
|
if (NT_SUCCESS(status)) {
|
|
context->PluggedIn = TRUE;
|
|
QA6_LOG_INFO("Apple DFU child plugged in as USB\\VID_05AC&PID_1227\n");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
DriverEntry(
|
|
_In_ PDRIVER_OBJECT DriverObject,
|
|
_In_ PUNICODE_STRING RegistryPath
|
|
)
|
|
{
|
|
WDF_DRIVER_CONFIG config;
|
|
WDF_OBJECT_ATTRIBUTES attributes;
|
|
|
|
WDF_DRIVER_CONFIG_INIT(&config, Qa6EvtDeviceAdd);
|
|
config.DriverPoolTag = QA6_POOL_TAG;
|
|
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
|
return WdfDriverCreate(DriverObject, RegistryPath, &attributes, &config,
|
|
WDF_NO_HANDLE);
|
|
}
|
|
|
|
NTSTATUS
|
|
Qa6EvtDeviceAdd(
|
|
_In_ WDFDRIVER Driver,
|
|
_Inout_ PWDFDEVICE_INIT DeviceInit
|
|
)
|
|
{
|
|
WDF_PNPPOWER_EVENT_CALLBACKS powerCallbacks;
|
|
WDF_FILEOBJECT_CONFIG fileConfig;
|
|
WDF_OBJECT_ATTRIBUTES attributes;
|
|
UDECX_WDF_DEVICE_CONFIG udeConfig;
|
|
WDF_IO_QUEUE_CONFIG queueConfig;
|
|
WDF_IO_QUEUE_CONFIG manualQueueConfig;
|
|
PQA6_CONTROLLER_CONTEXT context;
|
|
WDFDEVICE controller;
|
|
UNICODE_STRING deviceName;
|
|
UNICODE_STRING symbolicLink;
|
|
UNICODE_STRING reference;
|
|
NTSTATUS status;
|
|
|
|
UNREFERENCED_PARAMETER(Driver);
|
|
|
|
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&powerCallbacks);
|
|
powerCallbacks.EvtDeviceD0Entry = Qa6EvtDeviceD0Entry;
|
|
powerCallbacks.EvtDeviceD0Exit = Qa6EvtDeviceD0Exit;
|
|
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &powerCallbacks);
|
|
|
|
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK,
|
|
WDF_NO_EVENT_CALLBACK,
|
|
WDF_NO_EVENT_CALLBACK);
|
|
fileConfig.FileObjectClass = WdfFileObjectWdfCannotUseFsContexts;
|
|
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig,
|
|
WDF_NO_OBJECT_ATTRIBUTES);
|
|
|
|
status = WdfDeviceInitAssignSDDLString(
|
|
DeviceInit, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
status = UdecxInitializeWdfDeviceInit(DeviceInit);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
RtlInitUnicodeString(&deviceName, QA6_DEVICE_NAME);
|
|
status = WdfDeviceInitAssignName(DeviceInit, &deviceName);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
|
|
QA6_CONTROLLER_CONTEXT);
|
|
attributes.EvtCleanupCallback = Qa6EvtControllerCleanup;
|
|
attributes.ExecutionLevel = WdfExecutionLevelPassive;
|
|
status = WdfDeviceCreate(&DeviceInit, &attributes, &controller);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
RtlInitUnicodeString(&symbolicLink, QA6_SYMBOLIC_LINK);
|
|
status = WdfDeviceCreateSymbolicLink(controller, &symbolicLink);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
RtlInitUnicodeString(&reference, QA6_HOST_INTERFACE_REF);
|
|
status = WdfDeviceCreateDeviceInterface(
|
|
controller, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
|
|
&reference);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
UDECX_WDF_DEVICE_CONFIG_INIT(&udeConfig, Qa6EvtQueryUsbCapability);
|
|
status = UdecxWdfDeviceAddUsbDeviceEmulation(controller, &udeConfig);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
context = Qa6GetControllerContext(controller);
|
|
RtlZeroMemory(context, sizeof(*context));
|
|
|
|
status = WdfSpinLockCreate(WDF_NO_OBJECT_ATTRIBUTES,
|
|
&context->BridgeLock);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
WDF_IO_QUEUE_CONFIG_INIT(&manualQueueConfig, WdfIoQueueDispatchManual);
|
|
manualQueueConfig.EvtIoCanceledOnQueue = Qa6EvtCanceledUserRead;
|
|
manualQueueConfig.PowerManaged = WdfFalse;
|
|
status = WdfIoQueueCreate(controller, &manualQueueConfig,
|
|
WDF_NO_OBJECT_ATTRIBUTES,
|
|
&context->WaitingUserReads);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig,
|
|
WdfIoQueueDispatchSequential);
|
|
queueConfig.EvtIoDeviceControl = Qa6EvtControllerIoctl;
|
|
queueConfig.EvtIoRead = Qa6EvtBridgeRead;
|
|
queueConfig.EvtIoWrite = Qa6EvtBridgeWrite;
|
|
queueConfig.PowerManaged = WdfFalse;
|
|
status = WdfIoQueueCreate(controller, &queueConfig,
|
|
WDF_NO_OBJECT_ATTRIBUTES,
|
|
&context->DefaultQueue);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
return Qa6InitializeUsbDescriptors(controller);
|
|
}
|
|
|
|
NTSTATUS
|
|
Qa6EvtDeviceD0Entry(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ WDF_POWER_DEVICE_STATE PreviousState
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(PreviousState);
|
|
return Qa6PlugInUsbDevice(Device);
|
|
}
|
|
|
|
NTSTATUS
|
|
Qa6EvtDeviceD0Exit(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ WDF_POWER_DEVICE_STATE TargetState
|
|
)
|
|
{
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(Device);
|
|
|
|
if (TargetState == WdfPowerDeviceD3Final && context->PluggedIn) {
|
|
context->PluggedIn = FALSE;
|
|
return UdecxUsbDevicePlugOutAndDelete(context->ChildDevice);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtControllerCleanup(
|
|
_In_ WDFOBJECT Object
|
|
)
|
|
{
|
|
PQA6_CONTROLLER_CONTEXT context =
|
|
Qa6GetControllerContext((WDFDEVICE)Object);
|
|
|
|
if (context->ChildInit != NULL) {
|
|
UdecxUsbDeviceInitFree(context->ChildInit);
|
|
context->ChildInit = NULL;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtControllerIoctl(
|
|
_In_ WDFQUEUE Queue,
|
|
_In_ WDFREQUEST Request,
|
|
_In_ size_t OutputBufferLength,
|
|
_In_ size_t InputBufferLength,
|
|
_In_ ULONG IoControlCode
|
|
)
|
|
{
|
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
|
|
|
UNREFERENCED_PARAMETER(OutputBufferLength);
|
|
UNREFERENCED_PARAMETER(InputBufferLength);
|
|
UNREFERENCED_PARAMETER(IoControlCode);
|
|
|
|
if (!UdecxWdfDeviceTryHandleUserIoctl(controller, Request)) {
|
|
WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtBridgeRead(
|
|
_In_ WDFQUEUE Queue,
|
|
_In_ WDFREQUEST Request,
|
|
_In_ size_t Length
|
|
)
|
|
{
|
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
|
|
BOOLEAN ready;
|
|
ULONG frameLength;
|
|
NTSTATUS status;
|
|
|
|
UNREFERENCED_PARAMETER(Length);
|
|
|
|
WdfSpinLockAcquire(context->BridgeLock);
|
|
ready = context->RequestFrameReady;
|
|
frameLength = context->RequestFrameLength;
|
|
if (ready) {
|
|
context->RequestFrameReady = FALSE;
|
|
}
|
|
WdfSpinLockRelease(context->BridgeLock);
|
|
|
|
if (ready) {
|
|
Qa6CompleteUserRead(Request, context->RequestFrame, frameLength);
|
|
return;
|
|
}
|
|
|
|
status = WdfRequestForwardToIoQueue(Request, context->WaitingUserReads);
|
|
if (!NT_SUCCESS(status)) {
|
|
WdfRequestComplete(Request, status);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtBridgeWrite(
|
|
_In_ WDFQUEUE Queue,
|
|
_In_ WDFREQUEST Request,
|
|
_In_ size_t Length
|
|
)
|
|
{
|
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
|
|
const Qa6UsbFrameHeader *header;
|
|
PVOID rawBuffer;
|
|
size_t rawLength;
|
|
WDFREQUEST urb = NULL;
|
|
ULONG originalTransferLength = 0;
|
|
BOOLEAN directionIn = FALSE;
|
|
NTSTATUS status;
|
|
|
|
UNREFERENCED_PARAMETER(Length);
|
|
|
|
status = WdfRequestRetrieveInputBuffer(Request, QA6_USB_HEADER_SIZE,
|
|
&rawBuffer, &rawLength);
|
|
if (!NT_SUCCESS(status)) {
|
|
WdfRequestComplete(Request, status);
|
|
return;
|
|
}
|
|
|
|
header = (const Qa6UsbFrameHeader *)rawBuffer;
|
|
if (header->magic != QA6_USB_MAGIC ||
|
|
header->version != QA6_USB_PROTOCOL_VERSION ||
|
|
header->type != QA6_USB_MESSAGE_RESPONSE ||
|
|
header->payload_length > QA6_USB_MAX_PAYLOAD ||
|
|
rawLength != QA6_USB_HEADER_SIZE + header->payload_length) {
|
|
WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);
|
|
return;
|
|
}
|
|
|
|
WdfSpinLockAcquire(context->BridgeLock);
|
|
if (context->PendingUrb != NULL &&
|
|
context->PendingRequestId == header->request_id) {
|
|
urb = context->PendingUrb;
|
|
originalTransferLength = context->PendingTransferLength;
|
|
directionIn = context->PendingDirectionIn;
|
|
context->PendingUrb = NULL;
|
|
}
|
|
WdfSpinLockRelease(context->BridgeLock);
|
|
|
|
if (urb == NULL) {
|
|
WdfRequestComplete(Request, STATUS_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
if (header->status == QA6_USB_STATUS_SUCCESS) {
|
|
ULONG completed;
|
|
|
|
if (directionIn) {
|
|
PUCHAR transferBuffer;
|
|
ULONG transferBufferLength;
|
|
|
|
status = UdecxUrbRetrieveBuffer(urb, &transferBuffer,
|
|
&transferBufferLength);
|
|
if (NT_SUCCESS(status)) {
|
|
completed = header->payload_length;
|
|
if (completed > transferBufferLength) {
|
|
completed = transferBufferLength;
|
|
}
|
|
RtlCopyMemory(transferBuffer,
|
|
(const UCHAR *)rawBuffer + QA6_USB_HEADER_SIZE,
|
|
completed);
|
|
} else {
|
|
completed = 0;
|
|
}
|
|
} else {
|
|
completed = header->transfer_length;
|
|
if (completed > originalTransferLength) {
|
|
completed = originalTransferLength;
|
|
}
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
UdecxUrbSetBytesCompleted(urb, completed);
|
|
UdecxUrbCompleteWithNtStatus(urb, status);
|
|
} else if (header->status == QA6_USB_STATUS_STALL) {
|
|
UdecxUrbComplete(urb, USBD_STATUS_STALL_PID);
|
|
} else {
|
|
UdecxUrbCompleteWithNtStatus(urb, STATUS_DEVICE_NOT_CONNECTED);
|
|
}
|
|
|
|
WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, rawLength);
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtCanceledUserRead(
|
|
_In_ WDFQUEUE Queue,
|
|
_In_ WDFREQUEST Request
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Queue);
|
|
WdfRequestComplete(Request, STATUS_CANCELLED);
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtControlUrb(
|
|
_In_ WDFQUEUE Queue,
|
|
_In_ WDFREQUEST Request,
|
|
_In_ size_t OutputBufferLength,
|
|
_In_ size_t InputBufferLength,
|
|
_In_ ULONG IoControlCode
|
|
)
|
|
{
|
|
WDFDEVICE controller = WdfIoQueueGetDevice(Queue);
|
|
PQA6_CONTROLLER_CONTEXT context = Qa6GetControllerContext(controller);
|
|
Qa6UsbFrameHeader *header =
|
|
(Qa6UsbFrameHeader *)context->RequestFrame;
|
|
WDF_USB_CONTROL_SETUP_PACKET setup;
|
|
PUCHAR transferBuffer = NULL;
|
|
ULONG transferBufferLength = 0;
|
|
ULONG payloadLength = 0;
|
|
ULONG requestId;
|
|
NTSTATUS status;
|
|
|
|
UNREFERENCED_PARAMETER(OutputBufferLength);
|
|
UNREFERENCED_PARAMETER(InputBufferLength);
|
|
|
|
if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB) {
|
|
UdecxUrbCompleteWithNtStatus(Request, STATUS_INVALID_DEVICE_REQUEST);
|
|
return;
|
|
}
|
|
|
|
status = UdecxUrbRetrieveControlSetupPacket(Request, &setup);
|
|
if (!NT_SUCCESS(status)) {
|
|
UdecxUrbCompleteWithNtStatus(Request, status);
|
|
return;
|
|
}
|
|
|
|
/* UDE answers descriptor/enumeration requests from the registered sets. */
|
|
if ((setup.Generic.Bytes[0] & 0x60) == 0) {
|
|
UdecxUrbSetBytesCompleted(Request, 0);
|
|
UdecxUrbCompleteWithNtStatus(Request, STATUS_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
if (setup.Packet.wLength != 0) {
|
|
status = UdecxUrbRetrieveBuffer(Request, &transferBuffer,
|
|
&transferBufferLength);
|
|
if (!NT_SUCCESS(status)) {
|
|
UdecxUrbCompleteWithNtStatus(Request, status);
|
|
return;
|
|
}
|
|
if (transferBufferLength > QA6_USB_MAX_PAYLOAD) {
|
|
UdecxUrbCompleteWithNtStatus(Request,
|
|
STATUS_INVALID_BUFFER_SIZE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((setup.Generic.Bytes[0] & 0x80) == 0) {
|
|
payloadLength = transferBufferLength;
|
|
}
|
|
|
|
WdfSpinLockAcquire(context->BridgeLock);
|
|
if (context->PendingUrb != NULL) {
|
|
WdfSpinLockRelease(context->BridgeLock);
|
|
UdecxUrbCompleteWithNtStatus(Request, STATUS_DEVICE_BUSY);
|
|
return;
|
|
}
|
|
|
|
requestId = ++context->PendingRequestId;
|
|
if (requestId == 0) {
|
|
requestId = ++context->PendingRequestId;
|
|
}
|
|
|
|
RtlZeroMemory(header, sizeof(*header));
|
|
header->magic = QA6_USB_MAGIC;
|
|
header->version = QA6_USB_PROTOCOL_VERSION;
|
|
header->type = QA6_USB_MESSAGE_REQUEST;
|
|
header->request_id = requestId;
|
|
header->status = QA6_USB_STATUS_SUCCESS;
|
|
header->payload_length = payloadLength;
|
|
header->transfer_length = transferBufferLength;
|
|
RtlCopyMemory(header->setup, setup.Generic.Bytes, sizeof(header->setup));
|
|
if (payloadLength != 0) {
|
|
RtlCopyMemory(context->RequestFrame + QA6_USB_HEADER_SIZE,
|
|
transferBuffer, payloadLength);
|
|
}
|
|
|
|
context->RequestFrameLength = QA6_USB_HEADER_SIZE + payloadLength;
|
|
context->PendingUrb = Request;
|
|
context->PendingTransferLength = transferBufferLength;
|
|
context->PendingDirectionIn =
|
|
(setup.Generic.Bytes[0] & 0x80) != 0;
|
|
WdfSpinLockRelease(context->BridgeLock);
|
|
|
|
QA6_LOG_INFO("EP0 request %lu bm=%02x req=%02x len=%u\n",
|
|
requestId, setup.Generic.Bytes[0], setup.Generic.Bytes[1],
|
|
setup.Packet.wLength);
|
|
Qa6PublishRequestFrame(controller);
|
|
}
|
|
|
|
VOID
|
|
Qa6EvtEndpointReset(
|
|
_In_ UDECXUSBENDPOINT Endpoint,
|
|
_In_ WDFREQUEST Request
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Endpoint);
|
|
WdfRequestComplete(Request, STATUS_SUCCESS);
|
|
}
|
|
|
|
NTSTATUS
|
|
Qa6EvtQueryUsbCapability(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ PGUID CapabilityType,
|
|
_In_ ULONG OutputBufferLength,
|
|
_Out_writes_to_opt_(OutputBufferLength, *ResultLength) PVOID OutputBuffer,
|
|
_Out_ PULONG ResultLength
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Device);
|
|
UNREFERENCED_PARAMETER(OutputBufferLength);
|
|
UNREFERENCED_PARAMETER(OutputBuffer);
|
|
|
|
*ResultLength = 0;
|
|
if (RtlCompareMemory(
|
|
CapabilityType,
|
|
&GUID_USB_CAPABILITY_DEVICE_CONNECTION_HIGH_SPEED_COMPATIBLE,
|
|
sizeof(GUID)) == sizeof(GUID)) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|