diff --git a/FrameworkArgb/Device.c b/FrameworkArgb/Device.c index 9a03d1f..356e992 100644 --- a/FrameworkArgb/Device.c +++ b/FrameworkArgb/Device.c @@ -15,19 +15,15 @@ Module Name: --*/ #include "driver.h" +#include "EcCommunication.h" #include "device.tmh" -#define LAMP_ARRAY_ATTRIBUTES_REPORT_ID 0x01 -#define LAMP_ATTRIBUTES_REQUEST_REPORT_ID 0x02 -#define LAMP_ATTRIBUTES_RESPONSE_REPORT_ID 0x03 -#define LAMP_MULTI_UPDATE_REPORT_ID 0x04 -#define LAMP_RANGE_UPDATE_REPORT_ID 0x05 -#define LAMP_ARRAY_CONTROL_REPORT_ID 0x06 - // // This is the default report descriptor for the virtual Hid device returned // by the mini driver in response to IOCTL_HID_GET_REPORT_DESCRIPTOR. // +// It's taken straight from the HID specification for LampArray devices, no changes needed. +// HID_REPORT_DESCRIPTOR G_DefaultReportDescriptor[] = { 0x05, 0x59, // USAGE_PAGE (LightingAndIllumination) 0x09, 0x01, // USAGE (LampArray) @@ -232,52 +228,301 @@ Return Value: WDFDEVICE device; NTSTATUS status; + TraceInformation("%!FUNC! Entry"); + + // + // Mark ourselves as a filter, which also relinquishes power policy ownership + // + WdfFdoInitSetFilter(DeviceInit); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT); status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device); + if (!NT_SUCCESS(status)) { + TraceError("WdfDeviceCreate failed %!STATUS!", status); + return status; + } + + // + // Get a pointer to the device context structure that we just associated + // with the device object. We define this structure in the device.h + // header file. DeviceGetContext is an inline function generated by + // using the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro in device.h. + // This function will do the type checking and return the device context. + // If you pass a wrong object handle it will return NULL and assert if + // run under framework verifier mode. + // + deviceContext = GetDeviceContext(device); + + // + // Initialize the context. + // + deviceContext->Device = device; + deviceContext->CurrentLampId = 0; + deviceContext->AutonomousMode = TRUE; + // 8 LEDs in a circle + // z is 0 for all LEDs, they're all in the same plane + // Bottom LED + deviceContext->LampPositions[0].x = 40000; + deviceContext->LampPositions[0].y = 0; + deviceContext->LampPositions[1].x = 60000; + deviceContext->LampPositions[1].y = 20000; + // Right LED + deviceContext->LampPositions[2].x = 80000; + deviceContext->LampPositions[2].y = 40000; + deviceContext->LampPositions[3].x = 60000; + deviceContext->LampPositions[3].y = 60000; + // Top LED + deviceContext->LampPositions[4].x = 40000; + deviceContext->LampPositions[4].y = 80000; + deviceContext->LampPositions[5].x = 20000; + deviceContext->LampPositions[5].y = 60000; + // Left LED + deviceContext->LampPositions[6].x = 0; + deviceContext->LampPositions[6].y = 40000; + deviceContext->LampPositions[7].x = 20000; + deviceContext->LampPositions[7].y = 20000; + + hidAttributes = &deviceContext->HidDeviceAttributes; + RtlZeroMemory(hidAttributes, sizeof(HID_DEVICE_ATTRIBUTES)); + hidAttributes->Size = sizeof(HID_DEVICE_ATTRIBUTES); + hidAttributes->VendorID = FWK_ARGB_HID_VID; + hidAttributes->ProductID = FWK_ARGB_HID_PID; + hidAttributes->VersionNumber = FWK_ARGB_HID_VERSION; + + deviceContext->CrosEcHandle = INVALID_HANDLE_VALUE; + status = ConnectToEc(&deviceContext->CrosEcHandle); + if (!NT_SUCCESS(status)) { + TraceError("COMBO %!FUNC! ConnectToEc failed %!STATUS!", status); + return status; + } + + status = QueueCreate(device, + &deviceContext->DefaultQueue); + if (!NT_SUCCESS(status)) { + TraceError("QueueCreate failed %!STATUS!", status); + return status; + } + + status = ManualQueueCreate(device, + &deviceContext->ManualQueue); + if (!NT_SUCCESS(status)) { + TraceError("ManualQueueCreate failed %!STATUS!", status); + return status; + } + + // + // Use default "HID Descriptor" and "HID Report Descriptor" (hardcoded) + // + deviceContext->HidDescriptor = G_DefaultHidDescriptor; + deviceContext->ReportDescriptor = G_DefaultReportDescriptor; + + return status; +} + +NTSTATUS +GetStringId( + _In_ WDFREQUEST Request, + _Out_ ULONG* StringId, + _Out_ ULONG* LanguageId +) +/*++ + +Routine Description: + + Helper routine to decode IOCTL_HID_GET_INDEXED_STRING and IOCTL_HID_GET_STRING. + +Arguments: + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + ULONG inputValue; + +#ifdef _KERNEL_MODE + + WDF_REQUEST_PARAMETERS requestParameters; + + // + // IOCTL_HID_GET_STRING: // METHOD_NEITHER + // IOCTL_HID_GET_INDEXED_STRING: // METHOD_OUT_DIRECT + // + // The string id (or string index) is passed in Parameters.DeviceIoControl. + // Type3InputBuffer. However, Parameters.DeviceIoControl.InputBufferLength + // was not initialized by hidclass.sys, therefore trying to access the + // buffer with WdfRequestRetrieveInputMemory will fail + // + // Another problem with IOCTL_HID_GET_INDEXED_STRING is that METHOD_OUT_DIRECT + // expects the input buffer to be Irp->AssociatedIrp.SystemBuffer instead of + // Type3InputBuffer. That will also fail WdfRequestRetrieveInputMemory. + // + // The solution to the above two problems is to get Type3InputBuffer directly + // + // Also note that instead of the buffer's content, it is the buffer address + // that was used to store the string id (or index) + // + + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + inputValue = PtrToUlong( + requestParameters.Parameters.DeviceIoControl.Type3InputBuffer); + + status = STATUS_SUCCESS; + +#else + + WDFMEMORY inputMemory; + size_t inputBufferLength; + PVOID inputBuffer; + + // + // mshidumdf.sys updates the IRP and passes the string id (or index) through + // the input buffer correctly based on the IOCTL buffer type + // + + status = WdfRequestRetrieveInputMemory(Request, &inputMemory); + if (!NT_SUCCESS(status)) { + TraceError("WdfRequestRetrieveInputMemory failed 0x%x\n", status); + return status; + } + inputBuffer = WdfMemoryGetBuffer(inputMemory, &inputBufferLength); + + // + // make sure buffer is big enough. + // + if (inputBufferLength < sizeof(ULONG)) + { + status = STATUS_INVALID_BUFFER_SIZE; + TraceError("GetStringId: invalid input buffer. size %d, expect %d\n", + (int)inputBufferLength, (int)sizeof(ULONG)); + return status; + } + + inputValue = (*(PULONG)inputBuffer); + +#endif + + // + // The least significant two bytes of the INT value contain the string id. + // + * StringId = (inputValue & 0x0ffff); + + // + // The most significant two bytes of the INT value contain the language + // ID (for example, a value of 1033 indicates English). + // + *LanguageId = (inputValue >> 16); + + return status; +} + + +NTSTATUS +GetIndexedString( + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_GET_INDEXED_STRING + +Arguments: + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + ULONG languageId, stringIndex; + + status = GetStringId(Request, &stringIndex, &languageId); + + // While we don't use the language id, some minidrivers might. + // + UNREFERENCED_PARAMETER(languageId); + if (NT_SUCCESS(status)) { - // - // Get a pointer to the device context structure that we just associated - // with the device object. We define this structure in the device.h - // header file. DeviceGetContext is an inline function generated by - // using the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro in device.h. - // This function will do the type checking and return the device context. - // If you pass a wrong object handle it will return NULL and assert if - // run under framework verifier mode. - // - deviceContext = DeviceGetContext(device); - - // - // Initialize the context. - // - deviceContext->Device = device; - deviceContext->DeviceData = 0; - - hidAttributes = &deviceContext->HidDeviceAttributes; - RtlZeroMemory(hidAttributes, sizeof(HID_DEVICE_ATTRIBUTES)); - hidAttributes->Size = sizeof(HID_DEVICE_ATTRIBUTES); - hidAttributes->VendorID = FWK_ARGB_HID_VID; - hidAttributes->ProductID = FWK_ARGB_HID_PID; - hidAttributes->VersionNumber = FWK_ARGB_HID_VERSION; - - // - // Create a device interface so that applications can find and talk - // to us. - // - status = WdfDeviceCreateDeviceInterface( - device, - &GUID_DEVINTERFACE_FrameworkArgb, - NULL // ReferenceString - ); - - if (NT_SUCCESS(status)) { - // - // Initialize the I/O Package and any Queues - // - status = FrameworkArgbQueueInitialize(device); + + if (stringIndex != FWK_ARGB_DEVICE_STRING_INDEX) + { + status = STATUS_INVALID_PARAMETER; + TraceError("GetString: unkown string index %d\n", stringIndex); + return status; } + + status = RequestCopyFromBuffer(Request, FWK_ARGB_DEVICE_STRING, sizeof(FWK_ARGB_DEVICE_STRING)); + } + return status; +} + + +NTSTATUS +GetString( + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_GET_STRING. + +Arguments: + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + ULONG languageId, stringId; + size_t stringSizeCb; + PWSTR string; + + status = GetStringId(Request, &stringId, &languageId); + + // While we don't use the language id, some minidrivers might. + // + UNREFERENCED_PARAMETER(languageId); + + if (!NT_SUCCESS(status)) { + return status; + } + + switch (stringId) { + case HID_STRING_ID_IMANUFACTURER: + stringSizeCb = sizeof(FWK_ARGB_MANUFACTURER_STRING); + string = FWK_ARGB_MANUFACTURER_STRING; + break; + case HID_STRING_ID_IPRODUCT: + stringSizeCb = sizeof(FWK_ARGB_PRODUCT_STRING); + string = FWK_ARGB_PRODUCT_STRING; + break; + case HID_STRING_ID_ISERIALNUMBER: + stringSizeCb = sizeof(FWK_ARGB_SERIAL_NUMBER_STRING); + string = FWK_ARGB_SERIAL_NUMBER_STRING; + break; + default: + status = STATUS_INVALID_PARAMETER; + TraceError("GetString: unkown string id %d\n", stringId); + return status; } + status = RequestCopyFromBuffer(Request, string, stringSizeCb); return status; } diff --git a/FrameworkArgb/Device.h b/FrameworkArgb/Device.h index 3175a27..188ae69 100644 --- a/FrameworkArgb/Device.h +++ b/FrameworkArgb/Device.h @@ -13,23 +13,27 @@ Module Name: User-mode Driver Framework 2 --*/ - +#pragma once #include "public.h" +#include +#include #include // located in $(DDK_INC_PATH)/wdm +#include "LampArrayStructs.h" +EXTERN_C_START typedef UCHAR HID_REPORT_DESCRIPTOR, * PHID_REPORT_DESCRIPTOR; +DRIVER_INITIALIZE DriverEntry; +EVT_WDF_DRIVER_DEVICE_ADD EvtDeviceAdd; +EVT_WDF_TIMER EvtTimerFunc; -// -// These are the device attributes returned by the mini driver in response -// to IOCTL_HID_GET_DEVICE_ATTRIBUTES. -// -#define FWK_ARGB_HID_VID 0x32AC -#define FWK_ARGB_HID_PID 0x0033 -#define FWK_ARGB_HID_VERSION 0x0101 - -EXTERN_C_START +#define LAMPARRAY_LAMP_COUNT 8 +#define LAMPARRAY_WIDTH 80000 // 80mm +#define LAMPARRAY_HEIGHT 80000 // 80mm +#define LAMPARRAY_DEPTH 20000 // 20mm +#define LAMPARRAY_KIND 0x07 // LampArrayKindChassis +#define LAMPARRAY_UPDATE_INTERVAL 100000 // 10ms // // The device context performs the same job as @@ -41,19 +45,177 @@ typedef struct _DEVICE_CONTEXT WDFQUEUE DefaultQueue; WDFQUEUE ManualQueue; HID_DEVICE_ATTRIBUTES HidDeviceAttributes; - BYTE DeviceData; + HANDLE CrosEcHandle; + UINT16 CurrentLampId; + BOOLEAN AutonomousMode; + Position LampPositions[LAMPARRAY_LAMP_COUNT]; HID_DESCRIPTOR HidDescriptor; PHID_REPORT_DESCRIPTOR ReportDescriptor; BOOLEAN ReadReportDescFromRegistry; - -} DEVICE_CONTEXT, *PDEVICE_CONTEXT; +} DEVICE_CONTEXT, * PDEVICE_CONTEXT; // -// This macro will generate an inline function called DeviceGetContext +// This macro will generate an inline function called GetDeviceContext // which will be used to get a pointer to the device context memory // in a type safe manner. // -WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, DeviceGetContext) +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext) + +typedef struct _QUEUE_CONTEXT +{ + WDFQUEUE Queue; + PDEVICE_CONTEXT DeviceContext; + UCHAR OutputReport; + +} QUEUE_CONTEXT, * PQUEUE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, GetQueueContext); + +NTSTATUS +QueueCreate( + _In_ WDFDEVICE Device, + _Out_ WDFQUEUE* Queue +); + +typedef struct _MANUAL_QUEUE_CONTEXT +{ + WDFQUEUE Queue; + PDEVICE_CONTEXT DeviceContext; + WDFTIMER Timer; + +} MANUAL_QUEUE_CONTEXT, * PMANUAL_QUEUE_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(MANUAL_QUEUE_CONTEXT, GetManualQueueContext); + +NTSTATUS +ManualQueueCreate( + _In_ WDFDEVICE Device, + _Out_ WDFQUEUE* Queue +); + +NTSTATUS +ReadReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request, + _Always_(_Out_) + BOOLEAN* CompleteRequest +); + +NTSTATUS +WriteReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +); + +NTSTATUS +GetFeature( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +); + +NTSTATUS +SetFeature( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +); + +NTSTATUS +GetInputReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +); + +NTSTATUS +SetOutputReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +); + +NTSTATUS +GetString( + _In_ WDFREQUEST Request +); + +NTSTATUS +GetIndexedString( + _In_ WDFREQUEST Request +); + +NTSTATUS +GetStringId( + _In_ WDFREQUEST Request, + _Out_ ULONG* StringId, + _Out_ ULONG* LanguageId +); + +NTSTATUS +RequestCopyFromBuffer( + _In_ WDFREQUEST Request, + _In_ PVOID SourceBuffer, + _When_(NumBytesToCopyFrom == 0, __drv_reportError(NumBytesToCopyFrom cannot be zero)) + _In_ size_t NumBytesToCopyFrom +); + +NTSTATUS +RequestGetHidXferPacket_ToReadFromDevice( + _In_ WDFREQUEST Request, + _Out_ HID_XFER_PACKET* Packet +); + +NTSTATUS +RequestGetHidXferPacket_ToWriteToDevice( + _In_ WDFREQUEST Request, + _Out_ HID_XFER_PACKET* Packet +); + + +// +// Misc definitions +// +#define CONTROL_FEATURE_REPORT_ID 0x01 + +// +// These are the device attributes returned by the mini driver in response +// to IOCTL_HID_GET_DEVICE_ATTRIBUTES. +// +#define FWK_ARGB_HID_VID 0x32AC +#define FWK_ARGB_HID_PID 0x0033 +#define FWK_ARGB_HID_VERSION 0x0101 + +// +// Custom control codes are defined here. They are to be used for sideband +// communication with the hid minidriver. These control codes are sent to +// the hid minidriver using Hid_SetFeature() API to a custom collection +// defined especially to handle such requests. +// +#define FWK_ARGB_CONTROL_CODE_SET_ATTRIBUTES 0x00 +#define FWK_ARGB_CONTROL_CODE_DUMMY1 0x01 +#define FWK_ARGB_CONTROL_CODE_DUMMY2 0x02 + +#define LAMP_ARRAY_ATTRIBUTES_REPORT_ID 0x01 +#define LAMP_ATTRIBUTES_REQUEST_REPORT_ID 0x02 +#define LAMP_ATTRIBUTES_RESPONSE_REPORT_ID 0x03 +#define LAMP_MULTI_UPDATE_REPORT_ID 0x04 +#define LAMP_RANGE_UPDATE_REPORT_ID 0x05 +#define LAMP_ARRAY_CONTROL_REPORT_ID 0x06 +#define CONTROL_COLLECTION_REPORT_ID 0x07 + +#define MAXIMUM_STRING_LENGTH (126 * sizeof(WCHAR)) +#define FWK_ARGB_MANUFACTURER_STRING L"Framework" +#define FWK_ARGB_PRODUCT_STRING L"Desktop UMDF ARGB Driver" +#define FWK_ARGB_SERIAL_NUMBER_STRING L"FRAMPBCP00" +#define FWK_ARGB_DEVICE_STRING L"Desktop UMDF ARGB Device" +#define FWK_ARGB_DEVICE_STRING_INDEX 5 + +// +// SetFeature request requires that the feature report buffer size be exactly +// same as the size of report described in the hid report descriptor ( +// excluding the report ID). Since FWK_ARGB_CONTROL_INFO includes report ID, +// we subtract one from the size. +// +#define FEATURE_REPORT_SIZE_CB ((USHORT)(sizeof(FWK_ARGB_CONTROL_INFO) - 1)) +#define INPUT_REPORT_SIZE_CB ((USHORT)(sizeof(FWK_ARGB_INPUT_REPORT) - 1)) +#define OUTPUT_REPORT_SIZE_CB ((USHORT)(sizeof(FWK_ARGB_OUTPUT_REPORT) - 1)) // // Function to initialize the device and its callbacks diff --git a/FrameworkArgb/Driver.c b/FrameworkArgb/Driver.c index 3551e80..13e98f4 100644 --- a/FrameworkArgb/Driver.c +++ b/FrameworkArgb/Driver.c @@ -152,10 +152,19 @@ Return Value: --*/ { - UNREFERENCED_PARAMETER(DriverObject); + PDEVICE_CONTEXT DeviceContext; TraceInformation("%!FUNC! Entry"); + DeviceContext = GetDeviceContext(DriverObject); + // Close handle to EC driver + if (DeviceContext && DeviceContext->CrosEcHandle && DeviceContext->CrosEcHandle != INVALID_HANDLE_VALUE) { + CloseHandle(DeviceContext->CrosEcHandle); + DeviceContext->CrosEcHandle = INVALID_HANDLE_VALUE; + TraceError("%!FUNC! Failed to close CrosEc Handle"); + } + TraceInformation("%!FUNC! Closed CrosEc Handle"); + // // Stop WPP Tracing // diff --git a/FrameworkArgb/EcCommunication.cpp b/FrameworkArgb/EcCommunication.cpp index 4c103f4..9848e9e 100644 --- a/FrameworkArgb/EcCommunication.cpp +++ b/FrameworkArgb/EcCommunication.cpp @@ -18,15 +18,108 @@ #include "EcCommunication.tmh" +NTSTATUS ConnectToEc( + _Inout_ HANDLE* Handle +) { + NTSTATUS Status = STATUS_SUCCESS; + + *Handle = CreateFileW( + LR"(\\.\GLOBALROOT\Device\CrosEC)", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + + if (*Handle == INVALID_HANDLE_VALUE) { + TraceError("%!FUNC! CreateFileW failed %!STATUS!", Status); + return STATUS_INVALID_HANDLE; + } + + return Status; +} + +int CrosEcSendCommand( + HANDLE Handle, + UINT16 command, + UINT8 version, + LPVOID outdata, + unsigned int outlen, + LPVOID indata, + unsigned int inlen +) +{ + NTSTATUS Status = STATUS_SUCCESS; + DWORD retb{}; + CROSEC_COMMAND cmd{}; + + if (Handle == NULL || Handle == INVALID_HANDLE_VALUE) { + Status = STATUS_INVALID_HANDLE; + TraceError("%!FUNC! Invalid Handle"); + return 0; + } + + if (outlen > CROS_EC_CMD_MAX_REQUEST || inlen > CROS_EC_CMD_MAX_REQUEST) { + TraceError("%!FUNC! outlen %d or inlen %d too large", outlen, inlen); + return 0; + } + if (outlen == 0) { + TraceError("%!FUNC! outlen is 0"); + return 0; + } + if (outdata == nullptr) { + TraceError("%!FUNC! Invalid outdata - NULL"); + return 0; + } + + cmd.command = command; + cmd.version = version; + cmd.result = 0xFF; + cmd.outlen = outlen; + cmd.inlen = CROS_EC_CMD_MAX_REQUEST - 8; // 8 is the header length + + RtlCopyMemory(cmd.data, outdata, outlen); + + Status = DeviceIoControl(Handle, + (DWORD) IOCTL_CROSEC_XCMD, + &cmd, + sizeof(cmd), + &cmd, + sizeof(cmd), + &retb, + nullptr); + if (!NT_SUCCESS(Status)) { + TraceError("%!FUNC! ConnectToEc failed %!STATUS!", Status); + return 0; + } + + if (cmd.result != EC_RES_SUCCESS) { + TraceError("%!FUNC! Host command failed - EC result %d", cmd.result); + return 0; + } + + if (inlen > 0) { + if (indata == nullptr) { + TraceError("%!FUNC! inlen is %d. But indata is NULL", inlen); + return 0; + } + RtlCopyMemory(cmd.data, indata, inlen); + } + + return cmd.inlen; +} + + int CrosEcReadMemU8(HANDLE Handle, unsigned int offset, UINT8* dest) { NTSTATUS Status = STATUS_SUCCESS; DWORD retb{}; CROSEC_READMEM rm{}; - if (Handle == INVALID_HANDLE_VALUE) { + if (Handle == NULL || Handle == INVALID_HANDLE_VALUE) { Status = STATUS_INVALID_HANDLE; - TraceError("COMBO %!FUNC! Invalid Handle"); + TraceError("%!FUNC! Invalid Handle"); return 0; } @@ -41,11 +134,11 @@ int CrosEcReadMemU8(HANDLE Handle, unsigned int offset, UINT8* dest) &retb, nullptr); if (!NT_SUCCESS(Status)) { - TraceError("COMBO %!FUNC! ConnectToEc failed %!STATUS!", Status); + TraceError("%!FUNC! ConnectToEc failed %!STATUS!", Status); return 0; } - TraceInformation("COMBO %!FUNC! Successfully read %d bytes from EC memory at %02x. First one %02x. retb=%d", rm.bytes, rm.offset, rm.buffer[0], retb); + TraceInformation("%!FUNC! Successfully read %d bytes from EC memory at %02x. First one %02x. retb=%d", rm.bytes, rm.offset, rm.buffer[0], retb); *dest = rm.buffer[0]; return rm.bytes; diff --git a/FrameworkArgb/EcCommunication.h b/FrameworkArgb/EcCommunication.h index 77b6def..05d9ebd 100644 --- a/FrameworkArgb/EcCommunication.h +++ b/FrameworkArgb/EcCommunication.h @@ -12,6 +12,8 @@ extern "C" { #endif #include +#include +#include #include "trace.h" /* Command version mask */ @@ -43,12 +45,65 @@ extern "C" { #define CROSEC_CMD_MAX_RESPONSE 0x100 #define CROSEC_MEMMAP_SIZE 0xFF +NTSTATUS ConnectToEc( + _Inout_ HANDLE* Handle +); + +#define EC_CMD_RGBKBD_SET_COLOR 0x013A +#define EC_CMD_RGBKBD 0x013B + +#define EC_RES_SUCCESS 0 +#define EC_INVALID_COMMAND 1 +#define EC_ERROR 2 +#define EC_INVALID_PARAMETER 3 +#define EC_ACCESS_DENIED 4 +#define EC_INVALID_RESPONSE 5 +#define EC_INVALID_VERSION 6 +#define EC_INVALID_CHECKSUM 7 + +#define CROS_EC_CMD_MAX_REQUEST (0x100-8) + +typedef struct _CROSEC_COMMAND { + UINT32 version; + UINT32 command; + UINT32 outlen; + UINT32 inlen; + UINT32 result; + UINT8 data[CROS_EC_CMD_MAX_REQUEST]; +} * PCROSEC_COMMAND, CROSEC_COMMAND; + typedef struct _CROSEC_READMEM { ULONG offset; ULONG bytes; UCHAR buffer[CROSEC_MEMMAP_SIZE]; } * PCROSEC_READMEM, CROSEC_READMEM; + +#include +#define CROS_EC_CMD_MAX_KEY_COUNT 64 +typedef struct { + UINT8 r; + UINT8 g; + UINT8 b; +} Rgb; + +typedef struct { + UINT8 StartKey; + UINT8 Length; + Rgb Colors[CROS_EC_CMD_MAX_KEY_COUNT]; +} EC_REQUEST_RGB_KBD_SET_COLOR; + +#include + +int CrosEcSendCommand( + HANDLE Handle, + UINT16 command, + UINT8 version, + LPVOID outdata, + unsigned int outlen, + LPVOID indata, + unsigned int inlen + ); int CrosEcReadMemU8(HANDLE Handle, unsigned int offset, UINT8* dest); #ifdef __cplusplus diff --git a/FrameworkArgb/FrameworkArgb.inf b/FrameworkArgb/FrameworkArgb.inf index 2ef301d..5299b3c 100644 --- a/FrameworkArgb/FrameworkArgb.inf +++ b/FrameworkArgb/FrameworkArgb.inf @@ -1,6 +1,19 @@ +;/*++ ; -; FrameworkArgb.inf +;Copyright (c) Framework Computer Inc ; +;Module Name: +; +; FrameworkArgb.inf +; +;Abstract: +; INF file for installing HID minidriver (UMDF 2 version) +; +; Installation Notes: +; Using Devcon: Type "devcon install FrameworkArgb.inf root\FrameworkArgb" to install +; +; Important: +; This INF depends on MsHidUmdf.inf, which was introduced in build 22000 [Version] Signature = "$Windows NT$" @@ -11,6 +24,17 @@ CatalogFile = FrameworkArgb.cat DriverVer = ; TODO: set DriverVer in stampinf property pages PnpLockdown = 1 +[DestinationDirs] +UMDriverCopy = 13 + +[SourceDisksNames] +1 = %DiskName%,,, + +[SourceDisksFiles] +FrameworkArgb.dll = 1 + +; ================= Device section ===================== + [Manufacturer] ; This driver package is only installable on Win11+ %ManufacturerName% = Standard,NT$ARCH$.10.0...22000 ; wudfrd.inf introduced in build 22000 @@ -18,26 +42,31 @@ PnpLockdown = 1 [Standard.NT$ARCH$.10.0...22000] %DeviceName% = FrameworkArgb, Root\FrameworkArgb ; TODO: edit hw-id -[SourceDisksFiles] -FrameworkArgb.dll = 1 - -[SourceDisksNames] -1 = %DiskName% ; =================== UMDF Device ================================== [FrameworkArgb.NT] -CopyFiles = UMDriverCopy -Include = wudfrd.inf -Needs = WUDFRD.NT +CopyFiles=UMDriverCopy +Include=MsHidUmdf.inf +Needs=MsHidUmdf.NT +Include=WUDFRD.inf +Needs=WUDFRD_LowerFilter.NT [FrameworkArgb.NT.hw] -Include = wudfrd.inf -Needs = WUDFRD.NT.HW +Include=MsHidUmdf.inf +Needs=MsHidUmdf.NT.hw +Include=WUDFRD.inf +Needs=WUDFRD_LowerFilter.NT.hw [FrameworkArgb.NT.Services] -Include = wudfrd.inf -Needs = WUDFRD.NT.Services +Include=MsHidUmdf.inf +Needs=MsHidUmdf.NT.Services +Include=WUDFRD.inf +Needs=WUDFRD_LowerFilter.NT.Services + +[FrameworkArgb.NT.Filters] +Include=WUDFRD.inf +Needs=WUDFRD_LowerFilter.NT.Filters [FrameworkArgb.NT.Wdf] UmdfService = FrameworkArgb,FrameworkArgb_Install @@ -51,8 +80,7 @@ UmdfFsContextUsePolicy=CanUseFsContext2 UmdfLibraryVersion = $UMDFVERSION$ ServiceBinary = %13%\FrameworkArgb.dll -[DestinationDirs] -UMDriverCopy = 13 +; ================= copy files ===================== [UMDriverCopy] FrameworkArgb.dll @@ -63,3 +91,5 @@ FrameworkArgb.dll ManufacturerName="Framework Computer Inc" DiskName = "FrameworkArgb Installation Disk" DeviceName ="FrameworkArgb Device" +ClassName ="Sample Device" +WudfRdDisplayName ="Windows Driver Foundation - User-mode Driver Framework Reflector" diff --git a/FrameworkArgb/FrameworkArgb.vcxproj b/FrameworkArgb/FrameworkArgb.vcxproj index ecf71ad..2e22b3b 100644 --- a/FrameworkArgb/FrameworkArgb.vcxproj +++ b/FrameworkArgb/FrameworkArgb.vcxproj @@ -26,11 +26,14 @@ + + + @@ -97,15 +100,19 @@ DbgengRemoteDebugger + true DbgengRemoteDebugger + true DbgengRemoteDebugger + true DbgengRemoteDebugger + true diff --git a/FrameworkArgb/FrameworkArgb.vcxproj.filters b/FrameworkArgb/FrameworkArgb.vcxproj.filters index c4c1a91..6e0815b 100644 --- a/FrameworkArgb/FrameworkArgb.vcxproj.filters +++ b/FrameworkArgb/FrameworkArgb.vcxproj.filters @@ -45,6 +45,12 @@ Header Files + + Header Files + + + Header Files + @@ -59,5 +65,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/FrameworkArgb/LampArray.cpp b/FrameworkArgb/LampArray.cpp new file mode 100644 index 0000000..47e1330 --- /dev/null +++ b/FrameworkArgb/LampArray.cpp @@ -0,0 +1,177 @@ +/*++ + +Module Name: + + queue.c + +Abstract: + + This file contains the queue entry points and callbacks. + +Environment: + + User-mode Driver Framework 2 + +--*/ + +#include "driver.h" +#include "LampArray.tmh" +#include "LampArray.h" +#include "EcCommunication.h" + +void CrosEcSetColorRange(HANDLE Handle, UINT16 LampIdStart, UINT16 LampIdEnd, LampColor *Color) +{ + EC_REQUEST_RGB_KBD_SET_COLOR cmd{}; + cmd.StartKey = (UINT8) LampIdStart; + cmd.Length = (UINT8) (LampIdEnd - LampIdStart) + 1; + for (UINT8 i = 0; i < cmd.Length; i++) { + if (Color->intensity == 1) { + cmd.Colors[i].r = Color->red; + cmd.Colors[i].g = Color->green; + cmd.Colors[i].b = Color->blue; + } + // TODO: Handle intensity, but I don't think it's normally used + // cmd.Colors[i].intensity = Color->intensity; + } + if (Color->intensity != 1 && Color->intensity != 1) { + TraceError("%!FUNC! Intensity is %d, not 0 or 1, should handle that", Color->intensity); + } + + CrosEcSendCommand( + Handle, + EC_CMD_RGBKBD_SET_COLOR, + 0, + &cmd, + sizeof(cmd), + &cmd, + sizeof(cmd) + ); +} + +void CrosEcSetColor(HANDLE Handle, UINT16 LampId, LampColor *Color) +{ + CrosEcSetColorRange(Handle, LampId, LampId, Color); +} + +ULONG +GetLampArrayAttributesReport( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext, + _In_ ULONG MaxLen + ) +{ + ULONG Size = sizeof(LampArrayAttributesReport); + if (Size > MaxLen) { + TraceError("%!FUNC!: output buffer too small. Size %d, expect %d\n", + Size, MaxLen); + return 0; + } + + DeviceContext = DeviceContext; + + LampArrayAttributesReport report = { + LAMPARRAY_LAMP_COUNT, + LAMPARRAY_WIDTH, + LAMPARRAY_HEIGHT, + LAMPARRAY_DEPTH, + LAMPARRAY_KIND, + LAMPARRAY_UPDATE_INTERVAL + }; + + RtlCopyMemory(ReportBuffer, &report, Size); + + return Size; +} + +ULONG +GetLampAttributesResponseReport( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext, + _In_ ULONG MaxLen + ) +{ + UINT16 CurrentLampId = DeviceContext->CurrentLampId; + ULONG Size = sizeof(LampAttributesResponseReport); + if (Size > MaxLen) { + TraceError("%!FUNC!: output buffer too small. Size %d, expect %d\n", + Size, MaxLen); + return 0; + } + + LampAttributesResponseReport report = { + CurrentLampId, // LampId + DeviceContext->LampPositions[CurrentLampId], // Lamp position + LAMPARRAY_UPDATE_INTERVAL, // Lamp update interval + LAMP_PURPOSE_ACCENT, // Lamp purpose + 255, // Red level count + 255, // Blue level count + 255, // Green level count + 1, // Intensity + 1, // Is Programmable + 0, // InputBinding + }; + + RtlCopyMemory(ReportBuffer, &report, sizeof(LampAttributesResponseReport)); + DeviceContext->CurrentLampId = CurrentLampId + 1 >= LAMPARRAY_LAMP_COUNT ? CurrentLampId : CurrentLampId + 1; + + return Size; + +} + +void SetLampAttributesId( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext + ) +{ + LampAttributesRequestReport* report = (LampAttributesRequestReport*)ReportBuffer; + DeviceContext->CurrentLampId = report->lampId; +} + +void SetMultipleLamps( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext + ) +{ + LampMultiUpdateReport* report = (LampMultiUpdateReport*)ReportBuffer; + TraceInformation("%!FUNC! LampCount: %d", report->lampCount); + + for (int i = 0; i < report->lampCount; i++) { + TraceInformation("%!FUNC! LampId: %d, Color: #%02X%02X%02X Intensity: %d", + report->lampIds[i], + report->colors[i].red, + report->colors[i].green, + report->colors[i].blue, + report->colors[i].intensity + ); + CrosEcSetColor(DeviceContext->CrosEcHandle, report->lampIds[i], &report->colors[i]); + } +} + +void SetLampRange( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext + ) +{ + LampRangeUpdateReport* report = (LampRangeUpdateReport*)ReportBuffer; + TraceInformation("%!FUNC! LampIdStart: %d, LampIdEnd: %d, Color: #%02X%02X%02X Intensity: %d", + report->lampIdStart, + report->lampIdEnd, + report->color.red, + report->color.green, + report->color.blue, + report->color.intensity + ); + CrosEcSetColorRange(DeviceContext->CrosEcHandle, report->lampIdStart, report->lampIdEnd, &report->color); +} + +void SetAutonomousMode( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext + ) +{ + LampArrayControlReport* report = (LampArrayControlReport*)ReportBuffer; + + TraceInformation("%!FUNC! AutonomousMode: %d", report->autonomousMode); + DeviceContext->AutonomousMode = report->autonomousMode != 0; + // Not doing anything here, unless we want to have a default animation +} diff --git a/FrameworkArgb/LampArray.h b/FrameworkArgb/LampArray.h new file mode 100644 index 0000000..07afcc0 --- /dev/null +++ b/FrameworkArgb/LampArray.h @@ -0,0 +1,60 @@ +/*++ + +Module Name: + + LampArray.h + +Abstract: + + This file contains the queue definitions. + +Environment: + + User-mode Driver Framework 2 + +--*/ +#include "public.h" +#include +#include +#include // located in $(DDK_INC_PATH)/wdm + +#include "Device.h" + +EXTERN_C_START + +ULONG +GetLampArrayAttributesReport( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext, + _In_ ULONG MaxLen +); + +ULONG +GetLampAttributesResponseReport( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext, + _In_ ULONG MaxLen +); + + +void SetLampAttributesId( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext +); + +void SetMultipleLamps( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext +); + +void SetLampRange( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext +); + +void SetAutonomousMode( + _In_ PUCHAR ReportBuffer, + _In_ _Out_ PDEVICE_CONTEXT DeviceContext +); + +EXTERN_C_END diff --git a/FrameworkArgb/LampArrayStructs.h b/FrameworkArgb/LampArrayStructs.h new file mode 100644 index 0000000..973504a --- /dev/null +++ b/FrameworkArgb/LampArrayStructs.h @@ -0,0 +1,97 @@ +/*++ + +Module Name: + + LampArrayStructs.h + +Abstract: + + This file contains the queue definitions. + +Environment: + + User-mode Driver Framework 2 + +--*/ +#pragma once +#include + +EXTERN_C_START + +#include + +typedef struct { + UINT8 red; + UINT8 green; + UINT8 blue; + UINT8 intensity; +} LampColor; + +typedef struct { + UINT32 x; + UINT32 y; + UINT32 z; +} Position; + +typedef struct { + UINT16 lampCount; + + UINT32 width; + UINT32 height; + UINT32 depth; + + UINT32 lampArrayKind; + UINT32 minUpdateInterval; +} LampArrayAttributesReport; + +typedef struct { + UINT16 lampId; +} LampAttributesRequestReport; + +#define LAMP_PURPOSE_CONTROL 0x01 +#define LAMP_PURPOSE_ACCENT 0x02 +#define LAMP_PURPOSE_BRANDING 0x04 +#define LAMP_PURPOSE_STATUS 0x08 +#define LAMP_PURPOSE_ILLUMINATION 0x10 +#define LAMP_PURPOSE_PRESENTATION 0x20 + +typedef struct { + UINT16 lampId; + + Position lampPosition; + + UINT32 updateLatency; + UINT32 lampPurposes; + + UINT8 redLevelCount; + UINT8 greenLevelCount; + UINT8 blueLevelCount; + UINT8 intensityLevelCount; + + UINT8 isProgrammable; + UINT8 inputBinding; +} LampAttributesResponseReport; + +typedef struct { + UINT8 lampCount; + UINT8 flags; + UINT16 lampIds[8]; + + LampColor colors[8]; +} LampMultiUpdateReport; + +typedef struct { + UINT8 flags; + UINT16 lampIdStart; + UINT16 lampIdEnd; + + LampColor color; +} LampRangeUpdateReport; + +typedef struct { + UINT8 autonomousMode; +} LampArrayControlReport; + +#include + +EXTERN_C_END diff --git a/FrameworkArgb/Public.h b/FrameworkArgb/Public.h index ae58592..eea7deb 100644 --- a/FrameworkArgb/Public.h +++ b/FrameworkArgb/Public.h @@ -14,11 +14,3 @@ Module Name: driver and application --*/ - -// -// Define an Interface Guid so that apps can find the device and talk to it. -// - -DEFINE_GUID (GUID_DEVINTERFACE_FrameworkArgb, - 0x1cb4f6fe,0xb974,0x4e4f,0xbf,0xb9,0x43,0x1e,0x71,0xbb,0x01,0xa8); -// {1cb4f6fe-b974-4e4f-bfb9-431e71bb01a8} diff --git a/FrameworkArgb/Queue.c b/FrameworkArgb/Queue.c index b5041e2..4df28bd 100644 --- a/FrameworkArgb/Queue.c +++ b/FrameworkArgb/Queue.c @@ -16,176 +16,1005 @@ Module Name: #include "driver.h" #include "queue.tmh" +#include "LampArray.h" + +#ifdef _KERNEL_MODE +EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL EvtIoDeviceControl; +#else +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControl; +#endif NTSTATUS -FrameworkArgbQueueInitialize( - _In_ WDFDEVICE Device - ) +QueueCreate( + _In_ WDFDEVICE Device, + _Out_ WDFQUEUE* Queue +) /*++ - Routine Description: - The I/O dispatch callbacks for the frameworks device object - are configured in this function. - - A single default I/O Queue is configured for parallel request - processing, and a driver context memory allocation is created - to hold our structure QUEUE_CONTEXT. + This function creates a default, parallel I/O queue to proces IOCTLs + from hidclass.sys. Arguments: Device - Handle to a framework device object. + Queue - Output pointer to a framework I/O queue handle, on success. + Return Value: - VOID + NTSTATUS --*/ { - WDFQUEUE queue; - NTSTATUS status; - WDF_IO_QUEUE_CONFIG queueConfig; + NTSTATUS status; + WDF_IO_QUEUE_CONFIG queueConfig; + WDF_OBJECT_ATTRIBUTES queueAttributes; + WDFQUEUE queue; + PQUEUE_CONTEXT queueContext; + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( + &queueConfig, + WdfIoQueueDispatchParallel); +#ifdef _KERNEL_MODE + queueConfig.EvtIoInternalDeviceControl = EvtIoDeviceControl; +#else // - // Configure a default queue so that requests that are not - // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto - // other queues get dispatched here. + // HIDclass uses INTERNAL_IOCTL which is not supported by UMDF. Therefore + // the hidumdf.sys changes the IOCTL type to DEVICE_CONTROL for next stack + // and sends it down // - WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( - &queueConfig, - WdfIoQueueDispatchParallel - ); + queueConfig.EvtIoDeviceControl = EvtIoDeviceControl; +#endif - queueConfig.EvtIoDeviceControl = FrameworkArgbEvtIoDeviceControl; - queueConfig.EvtIoStop = FrameworkArgbEvtIoStop; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE( + &queueAttributes, + QUEUE_CONTEXT); status = WdfIoQueueCreate( - Device, - &queueConfig, - WDF_NO_OBJECT_ATTRIBUTES, - &queue - ); + Device, + &queueConfig, + &queueAttributes, + &queue); - if(!NT_SUCCESS(status)) { + if (!NT_SUCCESS(status)) { TraceError("WdfIoQueueCreate failed %!STATUS!", status); return status; } + queueContext = GetQueueContext(queue); + queueContext->Queue = queue; + queueContext->DeviceContext = GetDeviceContext(Device); + queueContext->OutputReport = 0; + + *Queue = queue; return status; } VOID -FrameworkArgbEvtIoDeviceControl( - _In_ WDFQUEUE Queue, - _In_ WDFREQUEST Request, - _In_ size_t OutputBufferLength, - _In_ size_t InputBufferLength, - _In_ ULONG IoControlCode - ) +EvtIoDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode +) /*++ - Routine Description: - This event is invoked when the framework receives IRP_MJ_DEVICE_CONTROL request. + This event callback function is called when the driver receives an + + (KMDF) IOCTL_HID_Xxx code when handlng IRP_MJ_INTERNAL_DEVICE_CONTROL + (UMDF) IOCTL_HID_Xxx, IOCTL_UMDF_HID_Xxx when handling IRP_MJ_DEVICE_CONTROL Arguments: - Queue - Handle to the framework queue object that is associated with the - I/O request. + Queue - A handle to the queue object that is associated with the I/O request - Request - Handle to a framework request object. + Request - A handle to a framework request object. - OutputBufferLength - Size of the output buffer in bytes + OutputBufferLength - The length, in bytes, of the request's output buffer, + if an output buffer is available. - InputBufferLength - Size of the input buffer in bytes + InputBufferLength - The length, in bytes, of the request's input buffer, if + an input buffer is available. - IoControlCode - I/O control code. + IoControlCode - The driver or system defined IOCTL associated with the request Return Value: - VOID + NTSTATUS --*/ { - TraceInformation( - "%!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", - Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode); + NTSTATUS status; + BOOLEAN completeRequest = TRUE; + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + PDEVICE_CONTEXT deviceContext = NULL; + PQUEUE_CONTEXT queueContext = GetQueueContext(Queue); + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + + deviceContext = GetDeviceContext(device); + + TraceInformation("%!FUNC! called with IoControlCode=%d", IoControlCode); + + switch (IoControlCode) + { + case IOCTL_HID_GET_DEVICE_DESCRIPTOR: // METHOD_NEITHER + // + // Retrieves the device's HID descriptor. + // + _Analysis_assume_(deviceContext->HidDescriptor.bLength != 0); + status = RequestCopyFromBuffer(Request, + &deviceContext->HidDescriptor, + deviceContext->HidDescriptor.bLength); + break; + + case IOCTL_HID_GET_DEVICE_ATTRIBUTES: // METHOD_NEITHER + // + //Retrieves a device's attributes in a HID_DEVICE_ATTRIBUTES structure. + // + status = RequestCopyFromBuffer(Request, + &queueContext->DeviceContext->HidDeviceAttributes, + sizeof(HID_DEVICE_ATTRIBUTES)); + break; + + case IOCTL_HID_GET_REPORT_DESCRIPTOR: // METHOD_NEITHER + // + //Obtains the report descriptor for the HID device. + // + status = RequestCopyFromBuffer(Request, + deviceContext->ReportDescriptor, + deviceContext->HidDescriptor.DescriptorList[0].wReportLength); + break; + + case IOCTL_HID_READ_REPORT: // METHOD_NEITHER + // + // Returns a report from the device into a class driver-supplied + // buffer. + // + status = ReadReport(queueContext, Request, &completeRequest); + break; + + case IOCTL_HID_WRITE_REPORT: // METHOD_NEITHER + // + // Transmits a class driver-supplied report to the device. + // + status = WriteReport(queueContext, Request); + break; + +#ifdef _KERNEL_MODE + + case IOCTL_HID_GET_FEATURE: // METHOD_OUT_DIRECT + + status = GetFeature(queueContext, Request); + break; + + case IOCTL_HID_SET_FEATURE: // METHOD_IN_DIRECT + + status = SetFeature(queueContext, Request); + break; + + case IOCTL_HID_GET_INPUT_REPORT: // METHOD_OUT_DIRECT + + status = GetInputReport(queueContext, Request); + break; + + case IOCTL_HID_SET_OUTPUT_REPORT: // METHOD_IN_DIRECT + + status = SetOutputReport(queueContext, Request); + break; + +#else // UMDF specific + + // + // HID minidriver IOCTL uses HID_XFER_PACKET which contains an embedded pointer. + // + // typedef struct _HID_XFER_PACKET { + // PUCHAR reportBuffer; + // ULONG reportBufferLen; + // UCHAR reportId; + // } HID_XFER_PACKET, *PHID_XFER_PACKET; + // + // UMDF cannot handle embedded pointers when marshalling buffers between processes. + // Therefore a special driver mshidumdf.sys is introduced to convert such IRPs to + // new IRPs (with new IOCTL name like IOCTL_UMDF_HID_Xxxx) where: + // + // reportBuffer - passed as one buffer inside the IRP + // reportId - passed as a second buffer inside the IRP + // + // The new IRP is then passed to UMDF host and driver for further processing. + // + + case IOCTL_UMDF_HID_GET_FEATURE: // METHOD_NEITHER + + status = GetFeature(queueContext, Request); + break; + + case IOCTL_UMDF_HID_SET_FEATURE: // METHOD_NEITHER + + status = SetFeature(queueContext, Request); + break; + + case IOCTL_UMDF_HID_GET_INPUT_REPORT: // METHOD_NEITHER + + status = GetInputReport(queueContext, Request); + break; + + case IOCTL_UMDF_HID_SET_OUTPUT_REPORT: // METHOD_NEITHER + + status = SetOutputReport(queueContext, Request); + break; + +#endif // _KERNEL_MODE + + case IOCTL_HID_GET_STRING: // METHOD_NEITHER + + status = GetString(Request); + break; + + case IOCTL_HID_GET_INDEXED_STRING: // METHOD_OUT_DIRECT + + status = GetIndexedString(Request); + break; + + case IOCTL_HID_SEND_IDLE_NOTIFICATION_REQUEST: // METHOD_NEITHER + // + // This has the USBSS Idle notification callback. If the lower driver + // can handle it (e.g. USB stack can handle it) then pass it down + // otherwise complete it here as not inplemented. For a virtual + // device, idling is not needed. + // + // Not implemented. fall through... + // + case IOCTL_HID_ACTIVATE_DEVICE: // METHOD_NEITHER + case IOCTL_HID_DEACTIVATE_DEVICE: // METHOD_NEITHER + case IOCTL_GET_PHYSICAL_DESCRIPTOR: // METHOD_OUT_DIRECT + // + // We don't do anything for these IOCTLs but some minidrivers might. + // + // Not implemented. fall through... + // + default: + status = STATUS_NOT_IMPLEMENTED; + break; + } + + // + // Complete the request. Information value has already been set by request + // handlers. + // + if (completeRequest) { + WdfRequestComplete(Request, status); + } + TraceInformation("%!FUNC! exiting with completeRequest=%d", completeRequest); +} + +NTSTATUS +RequestCopyFromBuffer( + _In_ WDFREQUEST Request, + _In_ PVOID SourceBuffer, + _When_(NumBytesToCopyFrom == 0, __drv_reportError(NumBytesToCopyFrom cannot be zero)) + _In_ size_t NumBytesToCopyFrom +) +/*++ + +Routine Description: + + A helper function to copy specified bytes to the request's output memory + +Arguments: - WdfRequestComplete(Request, STATUS_SUCCESS); + Request - A handle to a framework request object. - return; + SourceBuffer - The buffer to copy data from. + + NumBytesToCopyFrom - The length, in bytes, of data to be copied. + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status; + WDFMEMORY memory; + size_t outputBufferLength; + + status = WdfRequestRetrieveOutputMemory(Request, &memory); + if (!NT_SUCCESS(status)) { + TraceError("WdfRequestRetrieveOutputMemory failed 0x%x\n", status); + return status; + } + + WdfMemoryGetBuffer(memory, &outputBufferLength); + if (outputBufferLength < NumBytesToCopyFrom) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceError("RequestCopyFromBuffer: buffer too small. Size %d, expect %d\n", + (int)outputBufferLength, (int)NumBytesToCopyFrom); + return status; + } + + status = WdfMemoryCopyFromBuffer(memory, + 0, + SourceBuffer, + NumBytesToCopyFrom); + if (!NT_SUCCESS(status)) { + TraceError("WdfMemoryCopyFromBuffer failed 0x%x\n", status); + return status; + } + + WdfRequestSetInformation(Request, NumBytesToCopyFrom); + return status; } -VOID -FrameworkArgbEvtIoStop( - _In_ WDFQUEUE Queue, - _In_ WDFREQUEST Request, - _In_ ULONG ActionFlags +// +// HID minidriver IOCTL uses HID_XFER_PACKET which contains an embedded pointer. +// +// typedef struct _HID_XFER_PACKET { +// PUCHAR reportBuffer; +// ULONG reportBufferLen; +// UCHAR reportId; +// } HID_XFER_PACKET, *PHID_XFER_PACKET; +// +// UMDF cannot handle embedded pointers when marshalling buffers between processes. +// Therefore a special driver mshidumdf.sys is introduced to convert such IRPs to +// new IRPs (with new IOCTL name like IOCTL_UMDF_HID_Xxxx) where: +// +// reportBuffer - passed as one buffer inside the IRP +// reportId - passed as a second buffer inside the IRP +// +// The new IRP is then passed to UMDF host and driver for further processing. +// + +NTSTATUS +RequestGetHidXferPacket_ToReadFromDevice( + _In_ WDFREQUEST Request, + _Out_ HID_XFER_PACKET* Packet +) +{ + // + // Driver need to write to the output buffer (so that App can read from it) + // + // Report Buffer: Output Buffer + // Report Id : Input Buffer + // + + NTSTATUS status; + WDFMEMORY inputMemory; + WDFMEMORY outputMemory; + size_t inputBufferLength; + size_t outputBufferLength; + PVOID inputBuffer; + PVOID outputBuffer; + + // + // Get report Id from input buffer + // + status = WdfRequestRetrieveInputMemory(Request, &inputMemory); + if (!NT_SUCCESS(status)) { + TraceError("WdfRequestRetrieveInputMemory failed 0x%x\n", status); + return status; + } + inputBuffer = WdfMemoryGetBuffer(inputMemory, &inputBufferLength); + + if (inputBufferLength < sizeof(UCHAR)) { + status = STATUS_INVALID_BUFFER_SIZE; + TraceError("WdfRequestRetrieveInputMemory: invalid input buffer. size %d, expect %d\n", + (int)inputBufferLength, (int)sizeof(UCHAR)); + return status; + } + + Packet->reportId = *(PUCHAR)inputBuffer; + + // + // Get report buffer from output buffer + // + status = WdfRequestRetrieveOutputMemory(Request, &outputMemory); + if (!NT_SUCCESS(status)) { + TraceError("WdfRequestRetrieveOutputMemory failed 0x%x\n", status); + return status; + } + + outputBuffer = WdfMemoryGetBuffer(outputMemory, &outputBufferLength); + + Packet->reportBuffer = (PUCHAR)outputBuffer; + Packet->reportBufferLen = (ULONG)outputBufferLength; + + return status; +} + +NTSTATUS +RequestGetHidXferPacket_ToWriteToDevice( + _In_ WDFREQUEST Request, + _Out_ HID_XFER_PACKET* Packet +) +{ + // + // Driver need to read from the input buffer (which was written by App) + // + // Report Buffer: Input Buffer + // Report Id : Output Buffer Length + // + // Note that the report id is not stored inside the output buffer, as the + // driver has no read-access right to the output buffer, and trying to read + // from the buffer will cause an access violation error. + // + // The workaround is to store the report id in the OutputBufferLength field, + // to which the driver does have read-access right. + // + + NTSTATUS status; + WDFMEMORY inputMemory; + WDFMEMORY outputMemory; + size_t inputBufferLength; + size_t outputBufferLength; + PVOID inputBuffer; + + // + // Get report Id from output buffer length + // + status = WdfRequestRetrieveOutputMemory(Request, &outputMemory); + if (!NT_SUCCESS(status)) { + TraceError("WdfRequestRetrieveOutputMemory failed 0x%x\n", status); + return status; + } + WdfMemoryGetBuffer(outputMemory, &outputBufferLength); + Packet->reportId = (UCHAR)outputBufferLength; + + // + // Get report buffer from input buffer + // + status = WdfRequestRetrieveInputMemory(Request, &inputMemory); + if (!NT_SUCCESS(status)) { + TraceError("WdfRequestRetrieveInputMemory failed 0x%x\n", status); + return status; + } + inputBuffer = WdfMemoryGetBuffer(inputMemory, &inputBufferLength); + + Packet->reportBuffer = (PUCHAR)inputBuffer; + Packet->reportBufferLen = (ULONG)inputBufferLength; + + return status; +} + +NTSTATUS +ReadReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request, + _Always_(_Out_) + BOOLEAN* CompleteRequest ) /*++ Routine Description: - This event is invoked for a power-managed queue before the device leaves the working state (D0). + Handles IOCTL_HID_READ_REPORT for the HID collection. Normally the request + will be forwarded to a manual queue for further process. In that case, the + caller should not try to complete the request at this time, as the request + will later be retrieved back from the manually queue and completed there. + However, if for some reason the forwarding fails, the caller still need + to complete the request with proper error code immediately. Arguments: - Queue - Handle to the framework queue object that is associated with the - I/O request. + QueueContext - The object context associated with the queue - Request - Handle to a framework request object. + Request - Pointer to Request Packet. - ActionFlags - A bitwise OR of one or more WDF_REQUEST_STOP_ACTION_FLAGS-typed flags - that identify the reason that the callback function is being called - and whether the request is cancelable. + CompleteRequest - A boolean output value, indicating whether the caller + should complete the request or not Return Value: - VOID + NT status code. + +--*/ +{ + NTSTATUS status; + +TraceInformation("%!FUNC! Entry"); + +// +// forward the request to manual queue +// +status = WdfRequestForwardToIoQueue( + Request, + QueueContext->DeviceContext->ManualQueue); +if (!NT_SUCCESS(status)) { + TraceError("WdfRequestForwardToIoQueue failed with 0x%x\n", status); + *CompleteRequest = TRUE; +} +else { + *CompleteRequest = FALSE; +} + +return status; +} + +NTSTATUS +WriteReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_WRITE_REPORT all the collection. + +Arguments: + + QueueContext - The object context associated with the queue + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. --*/ + { - TraceInformation( - "%!FUNC! Queue 0x%p, Request 0x%p ActionFlags %d", - Queue, Request, ActionFlags); + NTSTATUS status; + HID_XFER_PACKET packet; + ULONG reportSize; + + UNREFERENCED_PARAMETER(QueueContext); + TraceInformation("%!FUNC! Entry"); + + status = RequestGetHidXferPacket_ToWriteToDevice( + Request, + &packet); + if (!NT_SUCCESS(status)) { + return status; + } + + TraceError("WriteReport: with ReportID %d and size: %d\n", packet.reportId, packet.reportBufferLen); + status = STATUS_INVALID_PARAMETER; + reportSize = 0; + + // + // set status and information // - // In most cases, the EvtIoStop callback function completes, cancels, or postpones - // further processing of the I/O request. + WdfRequestSetInformation(Request, reportSize); + return status; +} + +HRESULT +GetFeature( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_GET_FEATURE for all the collection. + +Arguments: + + QueueContext - The object context associated with the queue + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + HID_XFER_PACKET packet; + ULONG reportSize; + PUCHAR responseBuffer; + + TraceInformation("%!FUNC! Entry"); + + status = RequestGetHidXferPacket_ToReadFromDevice( + Request, + &packet); + if (!NT_SUCCESS(status)) { + return status; + } + + reportSize = 0; + responseBuffer = packet.reportBuffer + sizeof(packet.reportId); + + switch (packet.reportId) { + case LAMP_ARRAY_ATTRIBUTES_REPORT_ID: + TraceInformation("%!FUNC! LAMP_ARRAY_ATTRIBUTES_REPORT_ID"); + reportSize = GetLampArrayAttributesReport(responseBuffer, QueueContext->DeviceContext, packet.reportBufferLen); + break; + case LAMP_ATTRIBUTES_REQUEST_REPORT_ID: + TraceError("%!FUNC! LAMP_ATTRIBUTES_REQUEST_REPORT_ID - Unsupported"); + return STATUS_INVALID_PARAMETER; + case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID: + TraceInformation("%!FUNC! LAMP_ATTRIBUTES_RESPONSE_REPORT_ID"); + reportSize = GetLampAttributesResponseReport(responseBuffer, QueueContext->DeviceContext, packet.reportBufferLen); + break; + case LAMP_MULTI_UPDATE_REPORT_ID: + TraceError("%!FUNC! LAMP_MULTI_UPDATE_REPORT_ID - Unsupported"); + return STATUS_INVALID_PARAMETER; + case LAMP_RANGE_UPDATE_REPORT_ID: + TraceError("%!FUNC! LAMP_RANGE_UPDATE_REPORT_ID - Unsupported"); + return STATUS_INVALID_PARAMETER; + case LAMP_ARRAY_CONTROL_REPORT_ID: + TraceError("%!FUNC! LAMP_ARRAY_CONTROL_REPORT_ID - Unsupported"); + return STATUS_INVALID_PARAMETER; + case CONTROL_COLLECTION_REPORT_ID: + TraceError("%!FUNC! CONTROL_COLLECTION_REPORT_ID - Unsupported"); + break; + default: + // + // If collection ID is not for control collection then handle + // this request just as you would for a regular collection. + // + TraceError("%!FUNC! invalid report id %d\n", packet.reportId); + return STATUS_INVALID_PARAMETER; + } + // - // Typically, the driver uses the following rules: + // Since output buffer is for write only (no read allowed by UMDF in output + // buffer), any read from output buffer would be reading garbage), so don't + // let app embed custom control code in output buffer. The minidriver can + // support multiple features using separate report ID instead of using + // custom control code. Since this is targeted at report ID 1, we know it + // is a request for getting attributes. // - // - If the driver owns the I/O request, it calls WdfRequestUnmarkCancelable - // (if the request is cancelable) and either calls WdfRequestStopAcknowledge - // with a Requeue value of TRUE, or it calls WdfRequestComplete with a - // completion status value of STATUS_SUCCESS or STATUS_CANCELLED. + // While KMDF does not enforce the rule (disallow read from output buffer), + // it is good practice to not do so. + // Since this device has one report ID, hidclass would pass on the report + // ID in the buffer (it wouldn't if report descriptor did not have any report + // ID). However, since UMDF allows only writes to an output buffer, we can't + // "read" the report ID from "output" buffer. There is no need to read the + // report ID since we get it other way as shown above, however this is + // something to keep in mind. // - // Before it can call these methods safely, the driver must make sure that - // its implementation of EvtIoStop has exclusive access to the request. + // - // In order to do that, the driver must synchronize access to the request - // to prevent other threads from manipulating the request concurrently. - // The synchronization method you choose will depend on your driver's design. + // Report how many bytes were copied // - // For example, if the request is held in a shared context, the EvtIoStop callback - // might acquire an internal driver lock, take the request from the shared context, - // and then release the lock. At this point, the EvtIoStop callback owns the request - // and can safely complete or requeue the request. + if (reportSize == 0) { + TraceError("%!FUNC! empty buffer. Not returning anything"); + return STATUS_INVALID_BUFFER_SIZE; + } + + TraceError("%!FUNC! Sending %d bytes", reportSize); + WdfRequestSetInformation(Request, reportSize); + return status; +} + +NTSTATUS +SetFeature( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_SET_FEATURE for all the collection. + For control collection (custom defined collection) it handles + the user-defined control codes for sideband communication + +Arguments: + + QueueContext - The object context associated with the queue + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + HID_XFER_PACKET packet; + ULONG reportSize; + PUCHAR responseBuffer; + + TraceInformation("%!FUNC! Entry"); + + status = RequestGetHidXferPacket_ToWriteToDevice( + Request, + &packet); + if (!NT_SUCCESS(status)) { + return status; + } + + // No response + reportSize = 0; + responseBuffer = packet.reportBuffer + sizeof(packet.reportId); + + switch (packet.reportId) { + case LAMP_ARRAY_ATTRIBUTES_REPORT_ID: + TraceInformation("%!FUNC! LAMP_ARRAY_ATTRIBUTES_REPORT_ID - Unsupported"); + return STATUS_INVALID_PARAMETER; + case LAMP_ATTRIBUTES_REQUEST_REPORT_ID: + TraceInformation("%!FUNC! LAMP_ATTRIBUTES_REQUEST_REPORT_ID"); + SetLampAttributesId(responseBuffer, QueueContext->DeviceContext); + break; + case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID: + TraceInformation("%!FUNC! LAMP_ATTRIBUTES_RESPONSE_REPORT_ID - Unsupported"); + return STATUS_INVALID_PARAMETER; + case LAMP_MULTI_UPDATE_REPORT_ID: + TraceInformation("%!FUNC! LAMP_MULTI_UPDATE_REPORT_ID"); + SetMultipleLamps(responseBuffer, QueueContext->DeviceContext); + break; + case LAMP_RANGE_UPDATE_REPORT_ID: + TraceInformation("%!FUNC! LAMP_RANGE_UPDATE_REPORT_ID"); + SetLampRange(responseBuffer, QueueContext->DeviceContext); + break; + case LAMP_ARRAY_CONTROL_REPORT_ID: + TraceInformation("%!FUNC! LAMP_ARRAY_CONTROL_REPORT_ID"); + SetAutonomousMode(responseBuffer, QueueContext->DeviceContext); + break; + default: + // + // If collection ID is not for control collection then handle + // this request just as you would for a regular collection. + // + TraceError("%!FUNC! invalid report id %d\n", packet.reportId); + return STATUS_INVALID_PARAMETER; + } + + WdfRequestSetInformation(Request, reportSize); + return status; +} + +NTSTATUS +GetInputReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_GET_INPUT_REPORT for all the collection. + +Arguments: + + QueueContext - The object context associated with the queue + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + HID_XFER_PACKET packet; + ULONG reportSize; + + TraceInformation("%!FUNC! Entry"); + + UNREFERENCED_PARAMETER(QueueContext); + + status = RequestGetHidXferPacket_ToReadFromDevice( + Request, + &packet); + if (!NT_SUCCESS(status)) { + return status; + } + + TraceError("GetInputReport: with ReportID %d and size: %d\n", packet.reportId, packet.reportBufferLen); + status = STATUS_INVALID_PARAMETER; + reportSize = 0; + // - // - If the driver has forwarded the I/O request to an I/O target, it either calls - // WdfRequestCancelSentRequest to attempt to cancel the request, or it postpones - // further processing of the request and calls WdfRequestStopAcknowledge with - // a Requeue value of FALSE. + // Report how many bytes were copied // - // A driver might choose to take no action in EvtIoStop for requests that are - // guaranteed to complete in a small amount of time. + WdfRequestSetInformation(Request, reportSize); + return status; +} + + +NTSTATUS +SetOutputReport( + _In_ PQUEUE_CONTEXT QueueContext, + _In_ WDFREQUEST Request +) +/*++ + +Routine Description: + + Handles IOCTL_HID_SET_OUTPUT_REPORT for all the collection. + +Arguments: + + QueueContext - The object context associated with the queue + + Request - Pointer to Request Packet. + +Return Value: + + NT status code. + +--*/ +{ + NTSTATUS status; + HID_XFER_PACKET packet; + ULONG reportSize; + + TraceInformation("%!FUNC! Entry"); + + UNREFERENCED_PARAMETER(QueueContext); + + status = RequestGetHidXferPacket_ToWriteToDevice( + Request, + &packet); + if (!NT_SUCCESS(status)) { + return status; + } + + TraceError("GetInputReport: with ReportID %d and size: %d\n", packet.reportId, packet.reportBufferLen); + status = STATUS_INVALID_PARAMETER; + reportSize = 0; + // - // In this case, the framework waits until the specified request is complete - // before moving the device (or system) to a lower power state or removing the device. - // Potentially, this inaction can prevent a system from entering its hibernation state - // or another low system power state. In extreme cases, it can cause the system - // to crash with bugcheck code 9F. + // Report how many bytes were copied // + WdfRequestSetInformation(Request, reportSize); + return status; +} + +NTSTATUS +ManualQueueCreate( + _In_ WDFDEVICE Device, + _Out_ WDFQUEUE* Queue +) +/*++ +Routine Description: + + This function creates a manual I/O queue to receive IOCTL_HID_READ_REPORT + forwarded from the device's default queue handler. + + It also creates a periodic timer to check the queue and complete any pending + request with data from the device. Here timer expiring is used to simulate + a hardware event that new data is ready. + + The workflow is like this: + + - Hidclass.sys sends an ioctl to the miniport to read input report. + + - The request reaches the driver's default queue. As data may not be avaiable + yet, the request is forwarded to a second manual queue temporarily. + + - Later when data is ready (as simulated by timer expiring), the driver + checks for any pending request in the manual queue, and then completes it. + + - Hidclass gets notified for the read request completion and return data to + the caller. + + On the other hand, for IOCTL_HID_WRITE_REPORT request, the driver simply + sends the request to the hardware (as simulated by storing the data at + DeviceContext->DeviceData) and completes the request immediately. There is + no need to use another queue for write operation. + +Arguments: + + Device - Handle to a framework device object. + + Queue - Output pointer to a framework I/O queue handle, on success. + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status; + WDF_IO_QUEUE_CONFIG queueConfig; + WDF_OBJECT_ATTRIBUTES queueAttributes; + WDFQUEUE queue; + PMANUAL_QUEUE_CONTEXT queueContext; + WDF_TIMER_CONFIG timerConfig; + WDF_OBJECT_ATTRIBUTES timerAttributes; + ULONG timerPeriodInSeconds = 5; + + WDF_IO_QUEUE_CONFIG_INIT( + &queueConfig, + WdfIoQueueDispatchManual); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE( + &queueAttributes, + MANUAL_QUEUE_CONTEXT); + + status = WdfIoQueueCreate( + Device, + &queueConfig, + &queueAttributes, + &queue); + + if (!NT_SUCCESS(status)) { + TraceError("WdfIoQueueCreate failed 0x%x\n", status); + return status; + } + + queueContext = GetManualQueueContext(queue); + queueContext->Queue = queue; + queueContext->DeviceContext = GetDeviceContext(Device); + + WDF_TIMER_CONFIG_INIT_PERIODIC( + &timerConfig, + EvtTimerFunc, + timerPeriodInSeconds * 1000); + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + timerAttributes.ParentObject = queue; + status = WdfTimerCreate(&timerConfig, + &timerAttributes, + &queueContext->Timer); - return; + if (!NT_SUCCESS(status)) { + TraceError("WdfTimerCreate failed 0x%x\n", status); + return status; + } + + WdfTimerStart(queueContext->Timer, WDF_REL_TIMEOUT_IN_SEC(1)); + + *Queue = queue; + + return status; +} + +void +EvtTimerFunc( + _In_ WDFTIMER Timer +) +/*++ +Routine Description: + + This periodic timer callback routine checks the device's manual queue and + completes any pending request with data from the device. + +Arguments: + + Timer - Handle to a timer object that was obtained from WdfTimerCreate. + +Return Value: + + VOID + +--*/ +{ + NTSTATUS status; + WDFQUEUE queue; + PMANUAL_QUEUE_CONTEXT queueContext; + WDFREQUEST request; + + TraceInformation("%!FUNC! Entry"); + + queue = (WDFQUEUE)WdfTimerGetParentObject(Timer); + queueContext = GetManualQueueContext(queue); + + // + // see if we have a request in manual queue + // + status = WdfIoQueueRetrieveNextRequest( + queueContext->Queue, + &request); + + if (STATUS_NO_MORE_ENTRIES == status) { + // + // No request in the queue, nothing to do + // + TraceInformation("No request in manual queue\n"); + return; + } else if (!NT_SUCCESS(status)) { + TraceError("WdfIoQueueRetrieveNextRequest failed 0x%x\n", status); + return; + } + + TraceInformation("Has request in manual queue\n"); + + WdfRequestComplete(request, status); } diff --git a/FrameworkArgb/Queue.h b/FrameworkArgb/Queue.h index 39e8059..30a63f2 100644 --- a/FrameworkArgb/Queue.h +++ b/FrameworkArgb/Queue.h @@ -16,22 +16,16 @@ Module Name: EXTERN_C_START -// -// This is the context that can be placed per queue -// and would contain per queue information. -// -typedef struct _QUEUE_CONTEXT { - - ULONG PrivateDeviceData; // just a placeholder - -} QUEUE_CONTEXT, *PQUEUE_CONTEXT; - -WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext) - NTSTATUS -FrameworkArgbQueueInitialize( - _In_ WDFDEVICE Device - ); +QueueCreate( + _In_ WDFDEVICE Device, + _Out_ WDFQUEUE* Queue +); +NTSTATUS +ManualQueueCreate( + _In_ WDFDEVICE Device, + _Out_ WDFQUEUE* Queue +); // // Events from the IoQueue object diff --git a/FrameworkArgb/ReadMe.txt b/FrameworkArgb/ReadMe.txt deleted file mode 100644 index cb2bd7e..0000000 --- a/FrameworkArgb/ReadMe.txt +++ /dev/null @@ -1,33 +0,0 @@ -======================================================================== - FrameworkArgb Project Overview -======================================================================== - -This file contains a summary of what you will find in each of the files that make up your project. - -FrameworkArgb.vcxproj - This is the main project file for projects generated using an Application Wizard. - It contains information about the version of the product that generated the file, and - information about the platforms, configurations, and project features selected with the - Application Wizard. - -FrameworkArgb.vcxproj.filters - This is the filters file for VC++ projects generated using an Application Wizard. - It contains information about the association between the files in your project - and the filters. This association is used in the IDE to show grouping of files with - similar extensions under a specific node (for e.g. ".cpp" files are associated with the - "Source Files" filter). - -Public.h - Header file to be shared with applications. - -Driver.c & Driver.h - DriverEntry and WDFDRIVER related functionality and callbacks. - -Device.c & Device.h - WDFDEVICE related functionality and callbacks. - -Queue.c & Queue.h - WDFQUEUE related functionality and callbacks. - -Trace.h - Definitions for WPP tracing. diff --git a/README.md b/README.md index b2ddc16..fa02dd9 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,51 @@ # Framework Windows ARGB Driver -Resources: - -- Documentation - - https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/dynamic-lighting-devices - - https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf - - https://www.usb.org/document-library/hid-usage-tables-16 -- Drivers - - https://github.com/microsoft/Windows-driver-samples/tree/main/hid/vhidmini2 - - Can use this as a base, pretends to be a HID device -- Firmware - - https://github.com/hathach/tinyusb/blob/29ffd57237554b1f2339af543e3789ae04d3b29b/src/class/hid/hid_device.h#L495 - - https://github.com/microsoft/RP2040MacropadHidSample/blob/main/src/usb_descriptors.c#L76 - - https://github.com/microsoft/ArduinoHidForWindows -- Software - - https://github.com/microsoft/Dynamic-Lighting-AutoRGB - - https://github.com/microsoft/Windows-universal-samples/tree/main/Samples/LampArray - -Implementation steps: - -- [x] Make a UMDF driver -- [ ] Make it act as a HID device - - [ ] https://learn.microsoft.com/en-us/windows-hardware/drivers/wdf/creating-umdf-hid-minidrivers - - [x] Set INF directives -- [ ] Adjust HID report descriptor to Lighting and Illumination spec -- [ ] Test with Lighting Settings -- [ ] Hook up reports to CrosEC -- [ ] Sign it with EV Cert -- [ ] Run HLK tests -- [ ] Sign it with WHQL +This driver implements a HID device with support for the ["Lighting And IlluminationPage"](https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf) +for the ARGB connector on the Framework Desktop, that is commonly used with an RGB fan. + +Applications/Drivers that can interface with this type of HID device can control the connected LEDs. +Additionally Windows has a "Dynamic Lighting" API that can be controlled via Windows settings and that third party applications can interact with. + +## Applications + +### Windows Dynamic Lighting + +In Windows Settings => Personalization => Dynamic Lighting you can control the LEDs. + +Applications: + +- Microsoft Samples + - [LampArray sample](https://github.com/microsoft/Windows-universal-samples/tree/main/Samples/LampArray) has multiple different ways for the user to control the LEDs + - [Dynamic-Lighting-AutoRGB](https://github.com/microsoft/Dynamic-Lighting-AutoRGB) sets lighting based on average screen color + +References: + +- [End User Documentation](https://support.microsoft.com/en-us/windows/control-dynamic-lighting-devices-in-windows-8e8f22e3-e820-476c-8f9d-9ffc7b6ffcd2) +- [Developer Documentation](https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/dynamic-lighting-devices) +- Windows UWP API + - [Overview](https://learn.microsoft.com/en-us/windows/uwp/devices-sensors/lighting-dynamic-lamparray) + - [Windows.Devices.Lights](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights?view=winrt-26100) + - [Windows.Devices.Lights.Effects](https://learn.microsoft.com/en-us/uwp/api/windows.devices.lights.effects?view=winrt-26100) + - [Windows.Devices.Enumeration](https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.devicewatcher?view=winrt-26100) +- [Game Dev Documentation](https://learn.microsoft.com/en-us/gaming/gdk/docs/features/common/lighting/gc-lighting-toc) ## Development +### Build + Follow [Microsoft's instructions](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) to install Visual Studio, Windows SDK and WDK. I tried to install the SDK and WDK using winget but it either failed or couldn't get detected by Visual Studio. -### Build - -Use Visual Studio 2022 Community Edition. +Use Visual Studio 2022 Community Edition and build the project. ### Install ``` -sudo pnputil /add-driver .\FrameworkArgb\x64\Debug\FrameworkArgb\FrameworkArgb.inf /install +# Software device right now cp "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.26100.0\x64\devcon.exe" . sudo .\devcon install .\FrameworkArgb\x64\Debug\FrameworkArgb\FrameworkArgb.inf root\FrameworkArgb + +# Soon with ACPI device in updated BIOS +sudo pnputil /add-driver .\FrameworkArgb\x64\Debug\FrameworkArgb\FrameworkArgb.inf /install ```