[idd] input: purge report reads asynchronously

WDF forbids WdfIoQueuePurgeSynchronously from an I/O callback.
Keep HID deactivation pending while the report queue is purged
asynchronously, then complete it from the queue-state callback.

Reject activation during the transition. Complete any remaining
deactivation request during queue cleanup.
This commit is contained in:
Geoffrey McRae
2026-08-15 18:00:01 +10:00
parent 066d64393d
commit fd2357565a

View File

@@ -80,6 +80,7 @@ struct HIDDeviceContext
HIDStatistics statistics; HIDStatistics statistics;
HIDQueuedReport reports[REPORT_QUEUE_LENGTH]; HIDQueuedReport reports[REPORT_QUEUE_LENGTH];
CInputPipeClient * inputPipe; CInputPipeClient * inputPipe;
WDFREQUEST deactivateRequest;
}; };
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(HIDDeviceContext, HIDGetDeviceContext); WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(HIDDeviceContext, HIDGetDeviceContext);
@@ -88,6 +89,7 @@ static CSRWLock s_deviceLock;
static HIDDeviceContext * s_device = nullptr; static HIDDeviceContext * s_device = nullptr;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL HIDEvtIoDeviceControl; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL HIDEvtIoDeviceControl;
EVT_WDF_IO_QUEUE_STATE HIDEvtReportQueuePurged;
EVT_WDF_OBJECT_CONTEXT_CLEANUP HIDEvtReportQueueCleanup; EVT_WDF_OBJECT_CONTEXT_CLEANUP HIDEvtReportQueueCleanup;
EVT_WDF_OBJECT_CONTEXT_CLEANUP HIDEvtDeviceCleanup; EVT_WDF_OBJECT_CONTEXT_CLEANUP HIDEvtDeviceCleanup;
EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT HIDEvtSelfManagedIoInit; EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT HIDEvtSelfManagedIoInit;
@@ -338,7 +340,7 @@ static NTSTATUS ActivateDevice(_Inout_ HIDDeviceContext * context)
{ {
CSRWExclusiveLock lifecycleLock(context->lifecycleLock); CSRWExclusiveLock lifecycleLock(context->lifecycleLock);
CSRWExclusiveLock lock(context->reportLock); CSRWExclusiveLock lock(context->reportLock);
if (context->stopping) if (context->stopping || context->deactivateRequest)
return STATUS_DEVICE_NOT_READY; return STATUS_DEVICE_NOT_READY;
WdfIoQueueStart(context->reportQueue); WdfIoQueueStart(context->reportQueue);
@@ -346,21 +348,36 @@ static NTSTATUS ActivateDevice(_Inout_ HIDDeviceContext * context)
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
static NTSTATUS DeactivateDevice(_Inout_ HIDDeviceContext * context) static NTSTATUS DeactivateDevice(
_Inout_ HIDDeviceContext * context,
_In_ WDFREQUEST request,
_Out_ bool * complete)
{
{ {
CSRWExclusiveLock lifecycleLock(context->lifecycleLock); CSRWExclusiveLock lifecycleLock(context->lifecycleLock);
{ {
CSRWExclusiveLock lock(context->reportLock); CSRWExclusiveLock lock(context->reportLock);
if (context->stopping) if (context->stopping)
return STATUS_DEVICE_NOT_READY; return STATUS_DEVICE_NOT_READY;
if (context->deactivateRequest)
return STATUS_DEVICE_BUSY;
context->active = false; context->active = false;
context->reportHead = 0; context->reportHead = 0;
context->reportCount = 0; context->reportCount = 0;
context->consumerUsage = UINT16_MAX; context->consumerUsage = UINT16_MAX;
} }
WdfIoQueuePurgeSynchronously(context->reportQueue);
return STATUS_SUCCESS; context->deactivateRequest = request;
}
// WDF forbids synchronously purging any queue from EvtIoDeviceControl.
// Keep the HID deactivation request pending until the asynchronous purge
// has canceled every outstanding read report.
*complete = false;
WdfIoQueuePurge(
context->reportQueue, HIDEvtReportQueuePurged, context);
return STATUS_PENDING;
} }
static NTSTATUS CreateQueues(_In_ WDFDEVICE device) static NTSTATUS CreateQueues(_In_ WDFDEVICE device)
@@ -737,7 +754,7 @@ VOID HIDEvtIoDeviceControl(
break; break;
case IOCTL_HID_DEACTIVATE_DEVICE: case IOCTL_HID_DEACTIVATE_DEVICE:
status = DeactivateDevice(context); status = DeactivateDevice(context, request, &complete);
break; break;
default: default:
@@ -749,21 +766,51 @@ VOID HIDEvtIoDeviceControl(
WdfRequestComplete(request, status); WdfRequestComplete(request, status);
} }
VOID HIDEvtReportQueuePurged(
_In_ WDFQUEUE queue,
_In_ WDFCONTEXT callbackContext)
{
UNREFERENCED_PARAMETER(queue);
HIDDeviceContext * context =
static_cast<HIDDeviceContext *>(callbackContext);
WDFREQUEST request = nullptr;
{
CSRWExclusiveLock lifecycleLock(context->lifecycleLock);
request = context->deactivateRequest;
context->deactivateRequest = nullptr;
}
if (request)
WdfRequestComplete(request, STATUS_SUCCESS);
}
VOID HIDEvtReportQueueCleanup(_In_ WDFOBJECT object) VOID HIDEvtReportQueueCleanup(_In_ WDFOBJECT object)
{ {
HIDDeviceContext * context = HIDDeviceContext * context =
HIDGetDeviceContext(WdfIoQueueGetDevice((WDFQUEUE)object)); HIDGetDeviceContext(WdfIoQueueGetDevice((WDFQUEUE)object));
{
CSRWExclusiveLock deviceLock(s_deviceLock); CSRWExclusiveLock deviceLock(s_deviceLock);
if (s_device == context) if (s_device == context)
s_device = nullptr; s_device = nullptr;
}
WDFREQUEST deactivateRequest = nullptr;
{
CSRWExclusiveLock lifecycleLock(context->lifecycleLock);
{ {
CSRWExclusiveLock reportLock(context->reportLock); CSRWExclusiveLock reportLock(context->reportLock);
context->stopping = true; context->stopping = true;
context->reportHead = 0; context->reportHead = 0;
context->reportCount = 0; context->reportCount = 0;
} }
deactivateRequest = context->deactivateRequest;
context->deactivateRequest = nullptr;
}
if (deactivateRequest)
WdfRequestComplete(deactivateRequest, STATUS_DEVICE_REMOVED);
} }
VOID HIDEvtDeviceCleanup(_In_ WDFOBJECT object) VOID HIDEvtDeviceCleanup(_In_ WDFOBJECT object)