From f4c40c238d15275d9582344157c22f7573ccd229 Mon Sep 17 00:00:00 2001 From: ManOnTheMountainTech Date: Tue, 20 Feb 2024 13:01:14 -0800 Subject: [PATCH] Went to the simplest approach. - Look for intelliouse taillight arrival - Start a worker thread to thunk to pageable code - Set the taillight black --- TailLight/SetBlack.h | 4 + TailLight/SetTaillightBlack.cpp | 250 ++++++++++++++++++++++++++++++++ TailLight/TailLight.vcxproj | 3 + TailLight/debug.h | 10 ++ TailLight/device.cpp | 92 ++++++++---- TailLight/device.h | 24 +++ TailLight/driver.h | 10 +- 7 files changed, 363 insertions(+), 30 deletions(-) create mode 100644 TailLight/SetBlack.h create mode 100644 TailLight/SetTaillightBlack.cpp create mode 100644 TailLight/debug.h diff --git a/TailLight/SetBlack.h b/TailLight/SetBlack.h new file mode 100644 index 0000000..a33d100 --- /dev/null +++ b/TailLight/SetBlack.h @@ -0,0 +1,4 @@ +#pragma once + +NTSTATUS CreateWorkItemForIoTargetOpenDevice(WDFDEVICE device); +NTSTATUS SetBlackAsync(WDFDEVICE device); \ No newline at end of file diff --git a/TailLight/SetTaillightBlack.cpp b/TailLight/SetTaillightBlack.cpp new file mode 100644 index 0000000..6f4488c --- /dev/null +++ b/TailLight/SetTaillightBlack.cpp @@ -0,0 +1,250 @@ +#include "driver.h" +#include + +#include "debug.h" +#include "SetBlack.h" + +EVT_WDF_REQUEST_COMPLETION_ROUTINE SetBlackCompletionRoutine; +EVT_WDF_WORKITEM SetBlackWorkItem; + +NTSTATUS CreateWorkItemForIoTargetOpenDevice(WDFDEVICE device) + /*++ + + Routine Description: + + Creates a WDF workitem to do the SetBlack() call after the driver + stack has initialized. + + Arguments: + + Device - Handle to a pre-allocated WDF work item. + + Requirements: + Must be synchronized to the device. + + --*/ +{ + + //TRACE_FN_ENTRY + + WDFWORKITEM hWorkItem = 0; + NTSTATUS status = STATUS_PNP_DRIVER_CONFIGURATION_INCOMPLETE; + { + WDF_WORKITEM_CONFIG workItemConfig; + WDF_OBJECT_ATTRIBUTES workItemAttributes; + WDF_OBJECT_ATTRIBUTES_INIT(&workItemAttributes); + workItemAttributes.ParentObject = device; + + DEVICE_CONTEXT* pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(device); + + // It's possible to get called twice. Been there, done that? + if (pDeviceContext->fSetBlackSuccess) { + return STATUS_SUCCESS; + } + + WDF_WORKITEM_CONFIG_INIT(&workItemConfig, SetBlackWorkItem); + + status = WdfWorkItemCreate(&workItemConfig, + &workItemAttributes, + &hWorkItem); + + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, + DPFLTR_ERROR_LEVEL, + "TailLight: Workitem creation failure 0x%x\n", + status)); + return status; // Maybe better luck next time. + } + + InterlockedIncrement((PLONG)(&pDeviceContext->fSetBlackSuccess)); + } + + WdfWorkItemEnqueue(hWorkItem); + + //TRACE_FN_EXIT + + return status; +} + +static NTSTATUS TryToOpenIoTarget(WDFIOTARGET target, + DEVICE_CONTEXT& DeviceContext) { + + PAGED_CODE(); + + WDF_IO_TARGET_OPEN_PARAMS openParams = {}; + WDF_IO_TARGET_OPEN_PARAMS_INIT_CREATE_BY_NAME( + &openParams, + &DeviceContext.PdoName, + FILE_WRITE_ACCESS); + + openParams.ShareAccess = FILE_SHARE_WRITE | FILE_SHARE_READ; + + NTSTATUS status = STATUS_UNSUCCESSFUL; + + // Ensure freed if fails. + status = WdfIoTargetOpen(target, &openParams); + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, + DPFLTR_ERROR_LEVEL, + "TailLight: 0x%x while opening the I/O target from worker thread\n", + status)); + NukeWdfHandle(target); + } + + return status; +} + +void SetBlackCompletionRoutine( + _In_ WDFREQUEST Request, + _In_ WDFIOTARGET Target, + _In_ PWDF_REQUEST_COMPLETION_PARAMS Params, + _In_ WDFCONTEXT Context) +{ + UNREFERENCED_PARAMETER(Target); + UNREFERENCED_PARAMETER(Params); + UNREFERENCED_PARAMETER(Context); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + status = WdfRequestGetStatus(Request); + KdPrint(("TailLight: %s WdfRequestSend status: 0x%x\n", __func__, status)); + + // One-shot and top of stack, so delete and pray. + WdfObjectDelete(Request); +} + +VOID SetBlackWorkItem( + WDFWORKITEM workItem) + /*++ + + Routine Description: + + Creates a WDF workitem to do the SetBlack() call after the driver + stack has initialized. + + Arguments: + + workItem - Handle to a pre-allocated WDF work item. + --*/ +{ + TRACE_FN_ENTRY + + NTSTATUS status = STATUS_UNSUCCESSFUL; + WDFDEVICE device = static_cast(WdfWorkItemGetParentObject(workItem)); + + status = SetBlackAsync(device); + NukeWdfHandle(workItem); + + //TRACE_FN_EXIT +} + +NTSTATUS SetBlackAsync(WDFDEVICE device) { + + TRACE_FN_ENTRY + + NTSTATUS status = STATUS_FAILED_DRIVER_ENTRY; + WDFIOTARGET hidTarget = NULL; + + DEVICE_CONTEXT* pDeviceContext = NULL; + + pDeviceContext = + WdfObjectGet_DEVICE_CONTEXT(device); + + if (pDeviceContext == NULL) { + return STATUS_DEVICE_NOT_READY; + } + { + // Ensure freed if fails. + status = WdfIoTargetCreate( + device, + WDF_NO_OBJECT_ATTRIBUTES, + &hidTarget); + + if (!NT_SUCCESS(status)) { + KdPrintEx((DPFLTR_IHVDRIVER_ID, + DPFLTR_ERROR_LEVEL, + "TailLight: 0x%x while creating I/O target from worker thread\n", + status)); + return status; + } + + status = TryToOpenIoTarget(hidTarget, *pDeviceContext); + } + + if (NT_SUCCESS(status)) { + + WDFREQUEST request = NULL; + + status = WdfRequestCreate(WDF_NO_OBJECT_ATTRIBUTES, + hidTarget, + &request); + + if (!NT_SUCCESS(status)) { + KdPrint(("TailLight: WdfRequestCreate failed 0x%x\n", status)); + goto ExitAndFree; + } + + WdfRequestSetCompletionRoutine( + request, + SetBlackCompletionRoutine, + WDF_NO_CONTEXT); + + TailLightReport report = {}; + report.Blue = 0x0; + report.Green = 0x0; + report.Red = 0x0; + + // Set up a WDF memory object for the IOCTL request + WDF_OBJECT_ATTRIBUTES mem_attrib = {}; + WDF_OBJECT_ATTRIBUTES_INIT(&mem_attrib); + mem_attrib.ParentObject = request; // auto-delete with request*/ + + WDFMEMORY InBuffer = 0; + BYTE* pInBuffer = nullptr; + + status = WdfMemoryCreate(&mem_attrib, + NonPagedPoolNx, + POOL_TAG, + sizeof(TailLightReport), + &InBuffer, + (void**)&pInBuffer); + + if (!NT_SUCCESS(status)) { + KdPrint(("TailLight: WdfMemoryCreate failed: 0x%x\n", status)); + goto ExitAndFree; + } + + // TODO: Wondering if we just cant cast pInBuffr as a TailLightReport + RtlCopyMemory(pInBuffer, &report, sizeof(TailLightReport)); + + // Format the request as write operation + status = WdfIoTargetFormatRequestForIoctl(hidTarget, + request, + IOCTL_HID_SET_FEATURE, + InBuffer, + NULL, + 0, + 0); + + if (!NT_SUCCESS(status)) { + KdPrint(("TailLight: WdfIoTargetFormatRequestForIoctl failed: 0x%x\n", status)); + goto ExitAndFree; + } + + pDeviceContext->previousThread = KeGetCurrentThread(); + + if (!WdfRequestSend(request, hidTarget, WDF_NO_SEND_OPTIONS)) { + WdfObjectDelete(request); + request = NULL; + } + } + +ExitAndFree: + if (hidTarget != NULL) { + WdfObjectDelete(hidTarget); + hidTarget = NULL; + } + + TRACE_FN_EXIT + + return status; +} \ No newline at end of file diff --git a/TailLight/TailLight.vcxproj b/TailLight/TailLight.vcxproj index c9cf577..a931254 100644 --- a/TailLight/TailLight.vcxproj +++ b/TailLight/TailLight.vcxproj @@ -109,6 +109,7 @@ copy "$(WDKBinRoot_x64)\certmgr.exe" $(PackageDir) + @@ -119,9 +120,11 @@ copy "$(WDKBinRoot_x64)\certmgr.exe" $(PackageDir) + + diff --git a/TailLight/debug.h b/TailLight/debug.h new file mode 100644 index 0000000..08575a3 --- /dev/null +++ b/TailLight/debug.h @@ -0,0 +1,10 @@ +#pragma once + +#pragma once + +#define TRACE_FN_ENTRY KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "TailLight: Entry %s\n", __func__)); +#define TRACE_FN_EXIT KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, "TailLight: Exit: %s\n", __func__)); +#define TRACE_REQUEST_BOOL(ret) KdPrint(("TailLight: WdfRequestSend returned bool: 0x%x\n", ret)); +#define TRACE_REQUEST_FAILURE(status) KdPrint(("TailLight: WdfRequestSend failed. Status=: 0x%x\n", ret)); + +NTSTATUS DumpTarget(WDFIOTARGET target, WDFMEMORY& memory); \ No newline at end of file diff --git a/TailLight/device.cpp b/TailLight/device.cpp index 89aad26..41e9685 100644 --- a/TailLight/device.cpp +++ b/TailLight/device.cpp @@ -1,46 +1,83 @@ #include "driver.h" #include +#include "debug.h" +#include "SetBlack.h" + EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControlFilter; +NTSTATUS PnpNotifyDeviceInterfaceChange( + _In_ PVOID pvNotificationStructure, + _Inout_opt_ PVOID pvContext) { + + //KdPrint(("TailLight: PnpNotifyDeviceInterfaceChange enter\n")); + NT_ASSERTMSG("WDFDEVICE not passed in!", pvContext); -VOID EvtSetBlackTimer(_In_ WDFTIMER Timer) { - KdPrint(("TailLight: EvtSetBlackTimer begin\n")); + if (pvNotificationStructure == NULL) { + return STATUS_SUCCESS; + } - WDFDEVICE device = (WDFDEVICE)WdfTimerGetParentObject(Timer); - NT_ASSERTMSG("EvtSetBlackTimer device NULL\n", device); + PDEVICE_INTERFACE_CHANGE_NOTIFICATION pDevInterface = + (PDEVICE_INTERFACE_CHANGE_NOTIFICATION)pvNotificationStructure; - NTSTATUS status = SetFeatureColor(device, 0); - if (!NT_SUCCESS(status)) { - KdPrint(("TailLight: EvtSetBlackTimer failure NTSTATUS=0x%x\n", status)); - return; + WDFDEVICE& device = (WDFDEVICE&)pvContext; + + ASSERT(IsEqualGUID(*(_GUID*)&(pDevInterface->InterfaceClassGuid), + *(_GUID*)&GUID_DEVINTERFACE_HID)); + + auto& symLinkName = pDevInterface->SymbolicLinkName; + if (symLinkName->Length < sizeof(MSINTELLIMOUSE_USBINTERFACE5_PREFIX)) { + return STATUS_SUCCESS; + } + + // Ensure that the Microsoft Mouse USB interface #5 has arrived. + if (!memcmp((PVOID)MSINTELLIMOUSE_USBINTERFACE5_PREFIX, + symLinkName->Buffer, + sizeof(MSINTELLIMOUSE_USBINTERFACE5_PREFIX) - 2)) { + + //auto& notification = pDevInterface->Event; + //KdPrint(("TailLight: Notification 0x%x 0x%x 0x%x 0x%x\n", notification.Data1, notification.Data2, notification.Data3, notification.Data4)); + + // Assumption: Device will arrive before removal. + if (IsEqualGUID(*(LPGUID)&(pDevInterface->Event), + *(LPGUID)&GUID_DEVICE_INTERFACE_ARRIVAL)) { + + // Opening a device may trigger PnP operations. Ensure that either a + // timer or a work item is used when opening up a device. + // Refer to p356 of Oney and IoGetDeviceObjectPointer. + // + // NOTE: It is possible for us to get blocked waiting for a system + // thread. One solution would be to use a timer that spawns a + // system thread on timeout vs. a wait loop. + return CreateWorkItemForIoTargetOpenDevice(device); + } } - KdPrint(("TailLight: EvtSetBlackTimer end\n")); + return STATUS_SUCCESS; } + NTSTATUS EvtSelfManagedIoInit(WDFDEVICE device) { - // Initialize tail-light to black to have control over HW state - WDF_TIMER_CONFIG timerCfg = {}; - WDF_TIMER_CONFIG_INIT(&timerCfg, EvtSetBlackTimer); - WDF_OBJECT_ATTRIBUTES attribs = {}; - WDF_OBJECT_ATTRIBUTES_INIT(&attribs); - attribs.ParentObject = device; - attribs.ExecutionLevel = WdfExecutionLevelPassive; // required to access HID functions + WDFDRIVER driver = WdfDeviceGetDriver(device); + PDRIVER_CONTEXT driverContext = WdfObjectGet_DRIVER_CONTEXT(driver); + UNREFERENCED_PARAMETER(device); - WDFTIMER timer = nullptr; - NTSTATUS status = WdfTimerCreate(&timerCfg, &attribs, &timer); - if (!NT_SUCCESS(status)) { - KdPrint(("WdfTimerCreate failed 0x%x\n", status)); - return status; - } + //TRACE_FN_ENTRY; - status = WdfTimerStart(timer, 0); // no wait - if (!NT_SUCCESS(status)) { - KdPrint(("WdfTimerStart failed 0x%x\n", status)); - return status; - } + NTSTATUS status = STATUS_SUCCESS; + + status = IoRegisterPlugPlayNotification( + EventCategoryDeviceInterfaceChange, + PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, + (PVOID)&GUID_DEVINTERFACE_HID, + WdfDriverWdmGetDriverObject(driver), + PnpNotifyDeviceInterfaceChange, + (PVOID)device, + &driverContext->pnpDevInterfaceChangedHandle + ); + + //TRACE_FN_EXIT; return status; } @@ -144,7 +181,6 @@ Routine Description: NTSTATUS status = WmiInitialize(device); if (!NT_SUCCESS(status)) { KdPrint(("TailLight: Error initializing WMI 0x%x\n", status)); - return status; } return status; diff --git a/TailLight/device.h b/TailLight/device.h index 78543c2..3e8aaf7 100644 --- a/TailLight/device.h +++ b/TailLight/device.h @@ -1,13 +1,37 @@ #pragma once +#include "debug.h" + +#define MSINTELLIMOUSE_USBINTERFACE5_PREFIX L"\\??\\HID#VID_045E&PID_082A&MI_01&Col05" + +#define BEGIN_WITH(x) { \ + auto &_ = x; +#define END_WITH() } + /** Driver-specific struct for storing instance-specific data. */ typedef struct _DEVICE_CONTEXT { UNICODE_STRING PdoName; + + // Useful for debugging. This way less need to search all the system threads + // for the callers stack after calling IoCallDriver. + PKTHREAD previousThread; + BOOLEAN fSetBlackSuccess; WDFWMIINSTANCE WmiInstance; } DEVICE_CONTEXT; +template inline void NukeWdfHandle(T& handle) { + if (handle) { + WdfObjectDelete(handle); + handle = 0; + } +} + WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT) WDF_DECLARE_CONTEXT_TYPE(TailLightDeviceInformation) EVT_WDF_DEVICE_CONTEXT_CLEANUP EvtDeviceContextCleanup; + +NTSTATUS PnpNotifyDeviceInterfaceChange( + _In_ PVOID NotificationStructure, + _Inout_opt_ PVOID Context); \ No newline at end of file diff --git a/TailLight/driver.h b/TailLight/driver.h index a14e0a9..4ac0bea 100644 --- a/TailLight/driver.h +++ b/TailLight/driver.h @@ -12,9 +12,15 @@ #include "wmi.h" #include "vfeature.h" - /** Memory allocation tag name (for debugging leaks). */ -static constexpr ULONG POOL_TAG = 'ffly'; +static constexpr ULONG POOL_TAG = 'ylff'; +static constexpr ULONG WDF_POOL_TAG = 'wTLT'; // Taillight WPF allocated + +typedef struct _DRIVER_CONTEXT { + PVOID pnpDevInterfaceChangedHandle; +} DRIVER_CONTEXT, * PDRIVER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE(DRIVER_CONTEXT); extern "C" DRIVER_INITIALIZE DriverEntry;