From 8323a6694e83ef6e7538eda26b32966a424cb161 Mon Sep 17 00:00:00 2001 From: Lt-Henry Date: Thu, 6 Feb 2025 22:41:11 +0100 Subject: [PATCH] i2c_hid: some fixes and improvements * input reports are no longer requested, devices send them when available and we only have to wait for interrupt to come. This is written on Microsoft i2c hid reference document. In fact, as some devices may have more than one input report (ie: trackpad and fallback mouse), we were requesting (and receiving) an input report for each of them. Worse than that, our driver was missassigning reports to handlers. * fix: i2c report type did not match specification * implemented send feature report * The driver sets the device to fallback mode (mouse emulation). This is a short/mid term solution, because right now we can't handle multi-touch trackpads properly. On some laptops this feature is there but ignores request. * The driver fetches a windows 8 blob (if available). It looks like some devices refuses to work if this blob (I think is a certificate) is not fetched. * Should we reset the device at Open()? * Some style fixes * Added some comments and links where things become ugly * Some refactor is still missing Change-Id: Ibb73e0d9dfa7184d35ed8f2adf377c277c53ce18 Reviewed-on: https://review.haiku-os.org/c/haiku/+/8942 Reviewed-by: Adrien Destugues --- .../drivers/input/i2c_hid/HIDDevice.cpp | 178 ++++++++++++++++-- .../kernel/drivers/input/i2c_hid/HIDDevice.h | 5 +- 2 files changed, 169 insertions(+), 14 deletions(-) diff --git a/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.cpp b/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.cpp index 42c91a2176..eb0d7575b9 100644 --- a/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.cpp +++ b/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.cpp @@ -11,6 +11,7 @@ #include "Driver.h" #include "HIDDevice.h" #include "HIDReport.h" +#include "HIDReportItem.h" #include "HIDWriter.h" #include "ProtocolHandler.h" @@ -23,6 +24,10 @@ #include +// As specified in https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchscreen-required-hid-top-level-collections +#define HID_USAGE_MICROSOFT_THQA_CERTIFICATE 0xC5 + + HIDDevice::HIDDevice(uint16 descriptorAddress, i2c_device_interface* i2c, i2c_device i2cCookie) : fStatus(B_NO_INIT), @@ -39,6 +44,8 @@ HIDDevice::HIDDevice(uint16 descriptorAddress, i2c_device_interface* i2c, fI2C(i2c), fI2CCookie(i2cCookie) { + _Reset(); + // fetch HID descriptor fStatus = _FetchBuffer((uint8*)&fDescriptorAddress, sizeof(fDescriptorAddress), &fDescriptor, sizeof(fDescriptor)); @@ -91,6 +98,7 @@ HIDDevice::HIDDevice(uint16 descriptorAddress, i2c_device_interface* i2c, return; } +// enable for debugging hid reports #if 0 for (uint32 i = 0; i < fParser.CountReports(HID_REPORT_TYPE_ANY); i++) fParser.ReportAt(HID_REPORT_TYPE_ANY, i)->PrintToStream(); @@ -106,13 +114,76 @@ HIDDevice::HIDDevice(uint16 descriptorAddress, i2c_device_interface* i2c, // (as done in HIDReportItem) without the need for an additional boundary // check. We don't increase the transfer buffer size though as to not expose // this implementation detail onto the device when scheduling transfers. - fTransferBuffer = (uint8 *)malloc(fTransferBufferSize + 3); + fTransferBuffer = (uint8 *)malloc(fDescriptor.wMaxInputLength + 3); if (fTransferBuffer == NULL) { TRACE_ALWAYS("failed to allocate transfer buffer\n"); fStatus = B_NO_MEMORY; return; } + for (uint32 i = 0; i < fParser.CountReports(HID_REPORT_TYPE_FEATURE); i++) { + HIDReport *report = fParser.ReportAt(HID_REPORT_TYPE_FEATURE, i); + + // try to toggle trackpad into a mouse emulated mode + // we may remove this once we are capable to handle multitouch events + // some trackpads ignore this feature, however + HIDReportItem *deviceMode = report->FindItem(B_HID_USAGE_PAGE_DIGITIZER, + B_HID_UID_DIG_DEVICE_MODE); + + if (deviceMode) { + status_t result = MaybeScheduleTransfer(report); + + if (result != B_OK) + continue; + TRACE_ALWAYS("Found a trackpad mode configuration\n"); + + if (deviceMode->Extract() == B_OK) { + uint32 value = deviceMode->Data(); + TRACE_ALWAYS("Current device mode:%d\n", value); + report->DoneProcessing(); + deviceMode->SetData(0); + result = report->SendReport(); + + if (result != B_OK) + TRACE_ALWAYS("Failed to set trackpad mode\n"); + } + } + + // we do nothing with this value other than debugging + // perhaps we can get rid of this in a future patch + HIDReportItem *latencyMode = report->FindItem(B_HID_USAGE_PAGE_DIGITIZER, + B_HID_UID_DIG_LATENCY_MODE); + + if (latencyMode) { + status_t result = MaybeScheduleTransfer(report); + if (result != B_OK) + continue; + + if (latencyMode->Extract() == B_OK) { + uint32 value = latencyMode->Data(); + TRACE_ALWAYS("Current latency mode:%d\n", value); + report->DoneProcessing(); + } + } + + // Some trackpads expects this blob to be fetched before running + // https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchpad-windows-precision-touchpad-collection#device-certification-status-feature-report + // https://patchwork.kernel.org/project/linux-input/patch/1457344958-9987-1-git-send-email-benjamin.tissoires@redhat.com/ + HIDReportItem *win8Blob = report->FindItem(B_HID_USAGE_PAGE_MICROSOFT, + HID_USAGE_MICROSOFT_THQA_CERTIFICATE); + + if (win8Blob != NULL) { + + status_t result = MaybeScheduleTransfer(report); + + if (result != B_OK) + continue; + + report->DoneProcessing(); + TRACE_ALWAYS("Fetched a Win8 trackpad blob\n"); + } + } + ProtocolHandler::AddHandlers(*this, fProtocolHandlerList, fProtocolHandlerCount); fStatus = B_OK; @@ -136,7 +207,15 @@ status_t HIDDevice::Open(ProtocolHandler *handler, uint32 flags) { atomic_add(&fOpenCount, 1); + +#if 0 + // Supposedly the host should reset the device when connecting + // to it, but it seems Open() is already too late for that + // (we already fetched the feature report). For now, keep + // the device as it was initialized by the BIOS until we + // decide of a proper place to do this reset. _Reset(); +#endif return B_OK; } @@ -170,20 +249,51 @@ HIDDevice::MaybeScheduleTransfer(HIDReport *report) return B_OK; } - snooze_until(fTransferLastschedule, B_SYSTEM_TIMEBASE); - fTransferLastschedule = system_time() + 10000; + status_t status = _FetchBuffer((uint8*)&fDescriptor.wInputRegister, + sizeof(fDescriptor.wInputRegister), fTransferBuffer, fDescriptor.wMaxInputLength); + if (status != B_OK) { + atomic_set(&fTransferScheduled, 0); + ERROR("failed to fetch HID report\n"); + return status; + } + + uint16 actualLength = fTransferBuffer[0] | (fTransferBuffer[1] << 8); + + if (actualLength <= 2 || actualLength == 0xffff) + actualLength = 0; + else + actualLength -= 2; + + atomic_set(&fTransferScheduled, 0); + + fParser.SetReport(status, + (uint8*)((addr_t)fTransferBuffer + 2), actualLength); + + return B_OK; - TRACE("scheduling interrupt transfer of %lu bytes\n", - report->ReportSize()); - return _FetchReport(report->Type(), report->ID(), report->ReportSize()); } status_t HIDDevice::SendReport(HIDReport *report) { - // TODO - return B_OK; + uint8 reportType = 0; + + switch (report->Type()) { + case HID_REPORT_TYPE_INPUT: + reportType = 1; + break; + + case HID_REPORT_TYPE_OUTPUT: + reportType = 2; + break; + + case HID_REPORT_TYPE_FEATURE: + reportType = 3; + break; + } + + return _WriteReport(reportType, report->ID(), report->CurrentReport(), report->ReportSize()); } @@ -202,7 +312,9 @@ HIDDevice::ProtocolHandlerAt(uint32 index) const return NULL; } - +// current implementation polls input buffer, maybe in the future +// we can move to something more asynchronous as we already do in usb hid +#if 0 void HIDDevice::_UnstallCallback(void *cookie, status_t status, void *data, size_t actualLength) @@ -226,6 +338,7 @@ HIDDevice::_TransferCallback(void *cookie, status_t status, void *data, atomic_set(&device->fTransferScheduled, 0); device->fParser.SetReport(status, device->fTransferBuffer, actualLength); } +#endif status_t @@ -236,7 +349,7 @@ HIDDevice::_Reset() if (status != B_OK) return status; - snooze(1000); + snooze(10000); uint8 cmd[] = { (uint8)(fDescriptor.wCommandRegister & 0xff), @@ -251,7 +364,7 @@ HIDDevice::_Reset() return status; } - snooze(1000); + snooze(10000); return B_OK; } @@ -271,6 +384,45 @@ HIDDevice::_SetPower(uint8 power) } +status_t +HIDDevice::_WriteReport(uint8 type, uint8 id, void *data, size_t reportSize) +{ + uint8 reportId = id > 15 ? 15 : id; + size_t cmdLength = 6; + uint8 cmd[] = { + (uint8)(fDescriptor.wCommandRegister & 0xff), + (uint8)(fDescriptor.wCommandRegister >> 8), + (uint8)(reportId | (type << 4)), + I2C_HID_CMD_SET_REPORT, + 0, 0, 0, + }; + + int dataOffset = 4; + int reportIdLength = 1; + if (reportId == 15) { + cmd[dataOffset++] = id; + cmdLength++; + reportIdLength++; + } + + cmd[dataOffset++] = fDescriptor.wDataRegister & 0xff; + cmd[dataOffset++] = fDescriptor.wDataRegister >> 8; + + size_t bufferLength = reportSize + 1 + 2; + + fTransferBuffer[0] = bufferLength & 0x00ff; + fTransferBuffer[1] = (bufferLength & 0xff00) >> 8; + fTransferBuffer[2] = id; + + memcpy(fTransferBuffer + 3, data, reportSize); + + status_t status = _ExecCommand(I2C_OP_WRITE_STOP, cmd, cmdLength, + fTransferBuffer, bufferLength); + + return status; +} + + status_t HIDDevice::_FetchReport(uint8 type, uint8 id, size_t reportSize) { @@ -312,6 +464,7 @@ HIDDevice::_FetchReport(uint8 type, uint8 id, size_t reportSize) actualLength -= 2; atomic_set(&fTransferScheduled, 0); + fParser.SetReport(status, (uint8*)((addr_t)fTransferBuffer + 2), actualLength); return B_OK; @@ -334,8 +487,7 @@ HIDDevice::_ExecCommand(i2c_op op, uint8* cmd, size_t cmdLength, void* buffer, status_t status = fI2C->acquire_bus(fI2CCookie); if (status != B_OK) return status; - status = fI2C->exec_command(fI2CCookie, I2C_OP_READ_STOP, cmd, cmdLength, - buffer, bufferLength); + status = fI2C->exec_command(fI2CCookie, op, cmd, cmdLength, buffer, bufferLength); fI2C->release_bus(fI2CCookie); return status; } diff --git a/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.h b/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.h index 2394b79782..d19f930f51 100644 --- a/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.h +++ b/src/add-ons/kernel/drivers/input/i2c_hid/HIDDevice.h @@ -40,15 +40,18 @@ public: ProtocolHandler * ProtocolHandlerAt(uint32 index) const; private: +#if 0 static void _TransferCallback(void *cookie, status_t status, void *data, size_t actualLength); static void _UnstallCallback(void *cookie, status_t status, void *data, size_t actualLength); - +#endif status_t _Reset(); status_t _SetPower(uint8 power); + status_t _WriteReport(uint8 type, uint8 id, void *data, + size_t reportSize); status_t _FetchBuffer(uint8* cmd, size_t cmdLength, void* buffer, size_t bufferLength); status_t _FetchReport(uint8 type, uint8 id,