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 <[email protected]>
This commit is contained in:
Lt-Henry
2026-01-02 22:42:26 +00:00
committed by waddlesplash
parent 5c1e12f601
commit 8323a6694e
2 changed files with 169 additions and 14 deletions
@@ -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 <new>
// 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/[email protected]/
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;
}
@@ -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,