From 7869d759d2a2ba9904b4ef2f343ccc03379e0335 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:33:21 -0400 Subject: [PATCH] fix(driver): sleep recovery --- docs/windows-driver.md | 7 +++++ .../windows/driver/libvirtualhid_umdf.cpp | 26 +++++++++++++++++++ tests/unit/test_windows_consumers.cpp | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/windows-driver.md b/docs/windows-driver.md index 1f690af..54a45a9 100644 --- a/docs/windows-driver.md +++ b/docs/windows-driver.md @@ -142,6 +142,13 @@ and explicitly wait for pending completion instead of mixing synchronous calls with an asynchronous handle. Each caller thread reuses its event to avoid creating a kernel handle for every input report. +The pending output read also participates in the UMDF power-managed queue +lifecycle. When the system sleeps, the driver acknowledges the queue stop while +retaining the cancelable request, then resumes that same request after the +control device returns to D0. This allows sleep to complete without waiting for +controller feedback and keeps the runtime's control handle and virtual devices +valid across resume. + The driver opens a separate VHF source target for each virtual HID device and parents that target to the control-file handle that created it. If the creating process exits or crashes, Windows cleans up devices that were not explicitly diff --git a/src/platform/windows/driver/libvirtualhid_umdf.cpp b/src/platform/windows/driver/libvirtualhid_umdf.cpp index 74abfc1..992e480 100644 --- a/src/platform/windows/driver/libvirtualhid_umdf.cpp +++ b/src/platform/windows/driver/libvirtualhid_umdf.cpp @@ -70,6 +70,8 @@ EVT_WDF_DEVICE_PREPARE_HARDWARE LvhEvtDevicePrepareHardware; EVT_WDF_DEVICE_RELEASE_HARDWARE LvhEvtDeviceReleaseHardware; EVT_WDF_FILE_CLEANUP LvhEvtFileCleanup; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL LvhEvtIoDeviceControl; +EVT_WDF_IO_QUEUE_IO_RESUME LvhEvtIoResume; +EVT_WDF_IO_QUEUE_IO_STOP LvhEvtIoStop; EVT_WDF_OBJECT_CONTEXT_CLEANUP LvhEvtDeviceCleanup; EVT_WDF_REQUEST_CANCEL LvhEvtOutputReadCanceled; EVT_VHF_ASYNC_OPERATION LvhEvtVhfGetFeature; @@ -1270,6 +1272,8 @@ NTSTATUS LvhEvtDeviceAdd(WDFDRIVER driver, PWDFDEVICE_INIT device_init) { WDF_IO_QUEUE_CONFIG queue_config; WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue_config, WdfIoQueueDispatchParallel); queue_config.EvtIoDeviceControl = LvhEvtIoDeviceControl; + queue_config.EvtIoStop = LvhEvtIoStop; + queue_config.EvtIoResume = LvhEvtIoResume; status = WdfIoQueueCreate(device, &queue_config, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE); trace_status("EvtDeviceAdd WdfIoQueueCreate", status); @@ -1321,6 +1325,28 @@ void LvhEvtOutputReadCanceled(WDFREQUEST request) { } } +void LvhEvtIoStop(WDFQUEUE queue, WDFREQUEST request, ULONG action_flags) { + UNREFERENCED_PARAMETER(queue); + UNREFERENCED_PARAMETER(action_flags); + + trace_status("EvtIoStop suspend output request"); + + // Output reads intentionally remain pending until VHF produces feedback. + // Keep the cancelable request under driver ownership while the power-managed + // queue stops so it cannot block the system power transition. WDF calls the + // matching resume callback after the device returns to D0. + WdfRequestStopAcknowledge(request, FALSE); +} + +void LvhEvtIoResume(WDFQUEUE queue, WDFREQUEST request) { + UNREFERENCED_PARAMETER(queue); + UNREFERENCED_PARAMETER(request); + + // The pending output read does not access hardware while it waits, so no + // restart operation is necessary after WDF restores the queue to D0. + trace_status("EvtIoResume output request"); +} + void LvhEvtVhfReadyForNextReadReport(VhfContext vhf_client_context) { auto *record = static_cast(vhf_client_context); if (record == nullptr) { diff --git a/tests/unit/test_windows_consumers.cpp b/tests/unit/test_windows_consumers.cpp index 2b99db7..fc0401e 100644 --- a/tests/unit/test_windows_consumers.cpp +++ b/tests/unit/test_windows_consumers.cpp @@ -763,7 +763,7 @@ TEST_F(WindowsConsumerTest, SdlExposesSubmittedBatteryStateForNonXboxProfiles) { SDL_UpdateGamepads(); SDL_PumpEvents(); power_state = SDL_GetGamepadPowerInfo(gamepad.get(), &percentage); - if (power_state != SDL_POWERSTATE_UNKNOWN && percentage >= 0) { + if (power_state == SDL_POWERSTATE_ON_BATTERY && percentage == expected_percentage) { break; } std::this_thread::sleep_for(20ms);