/* * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com. * Copyright 2008-2011, Michael Lotz * Distributed under the terms of the MIT license. */ //! Driver for I2C Human Interface Devices. #include "Driver.h" #include "HIDDevice.h" #include "HIDReport.h" #include "HIDReportItem.h" #include "HIDWriter.h" #include "ProtocolHandler.h" #include #include #include #include #include #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), fTransferLastschedule(0), fTransferScheduled(0), fTransferBufferSize(0), fTransferBuffer(NULL), fOpenCount(0), fRemoved(false), fParser(this), fProtocolHandlerCount(0), fProtocolHandlerList(NULL), fDescriptorAddress(descriptorAddress), fI2C(i2c), fI2CCookie(i2cCookie) { _Reset(); // fetch HID descriptor fStatus = _FetchBuffer((uint8*)&fDescriptorAddress, sizeof(fDescriptorAddress), &fDescriptor, sizeof(fDescriptor)); if (fStatus != B_OK) { ERROR("failed to fetch HID descriptor\n"); return; } // fetch HID Report descriptor HIDWriter descriptorWriter; uint16 descriptorLength = fDescriptor.wReportDescLength; fReportDescriptor = (uint8 *)malloc(descriptorLength); if (fReportDescriptor == NULL) { ERROR("failed to allocate buffer for report descriptor\n"); fStatus = B_NO_MEMORY; return; } uint16 reportDescRegister = fDescriptor.wReportDescRegister; fStatus = _FetchBuffer((uint8*)&reportDescRegister, sizeof(reportDescRegister), fReportDescriptor, descriptorLength); if (fStatus != B_OK) { ERROR("failed tot get report descriptor\n"); free(fReportDescriptor); return; } #if 1 // save report descriptor for troubleshooting char outputFile[128]; sprintf(outputFile, "/tmp/i2c_hid_report_descriptor_%04x_%04x.bin", fDescriptor.wVendorID, fDescriptor.wProductID); int fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { write(fd, fReportDescriptor, descriptorLength); close(fd); } #endif status_t result = fParser.ParseReportDescriptor(fReportDescriptor, descriptorLength); free(fReportDescriptor); if (result != B_OK) { ERROR("parsing the report descriptor failed\n"); fStatus = result; 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(); #endif fTransferBufferSize = fParser.MaxReportSize(); if (fTransferBufferSize == 0) { TRACE_ALWAYS("report claims a report size of 0\n"); return; } // We pad the allocation size so that we can always read 32 bits at a time // (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(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; } HIDDevice::~HIDDevice() { ProtocolHandler *handler = fProtocolHandlerList; while (handler != NULL) { ProtocolHandler *next = handler->NextHandler(); delete handler; handler = next; } free(fTransferBuffer); } 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; } status_t HIDDevice::Close(ProtocolHandler *handler) { atomic_add(&fOpenCount, -1); _SetPower(I2C_HID_POWER_OFF); return B_OK; } void HIDDevice::Removed() { fRemoved = true; } status_t HIDDevice::MaybeScheduleTransfer(HIDReport *report) { if (fRemoved) return ENODEV; if (atomic_get_and_set(&fTransferScheduled, 1) != 0) { // someone else already caused a transfer to be scheduled return B_OK; } 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; } status_t HIDDevice::SendReport(HIDReport *report) { 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()); } ProtocolHandler * HIDDevice::ProtocolHandlerAt(uint32 index) const { ProtocolHandler *handler = fProtocolHandlerList; while (handler != NULL) { if (index == 0) return handler; handler = handler->NextHandler(); index--; } 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) { HIDDevice *device = (HIDDevice *)cookie; if (status != B_OK) { TRACE_ALWAYS("Unable to unstall device: %s\n", strerror(status)); } // Now report the original failure, since we're ready to retry _TransferCallback(cookie, B_ERROR, device->fTransferBuffer, 0); } void HIDDevice::_TransferCallback(void *cookie, status_t status, void *data, size_t actualLength) { HIDDevice *device = (HIDDevice *)cookie; atomic_set(&device->fTransferScheduled, 0); device->fParser.SetReport(status, device->fTransferBuffer, actualLength); } #endif status_t HIDDevice::_Reset() { CALLED(); status_t status = _SetPower(I2C_HID_POWER_ON); if (status != B_OK) return status; snooze(10000); uint8 cmd[] = { (uint8)(fDescriptor.wCommandRegister & 0xff), (uint8)(fDescriptor.wCommandRegister >> 8), 0, I2C_HID_CMD_RESET, }; status = _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0); if (status != B_OK) { _SetPower(I2C_HID_POWER_OFF); return status; } snooze(10000); return B_OK; } status_t HIDDevice::_SetPower(uint8 power) { CALLED(); uint8 cmd[] = { (uint8)(fDescriptor.wCommandRegister & 0xff), (uint8)(fDescriptor.wCommandRegister >> 8), power, I2C_HID_CMD_SET_POWER }; return _ExecCommand(I2C_OP_WRITE_STOP, cmd, sizeof(cmd), NULL, 0); } 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) { 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_GET_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 + reportIdLength + 2; status_t status = _FetchBuffer(cmd, cmdLength, fTransferBuffer, bufferLength); if (status != B_OK) { atomic_set(&fTransferScheduled, 0); return status; } uint16 actualLength = fTransferBuffer[0] | (fTransferBuffer[1] << 8); TRACE("_FetchReport %" B_PRIuSIZE " %" B_PRIu16 "\n", reportSize, actualLength); if (actualLength <= 2 || actualLength == 0xffff || bufferLength == 0) actualLength = 0; else actualLength -= 2; atomic_set(&fTransferScheduled, 0); fParser.SetReport(status, (uint8*)((addr_t)fTransferBuffer + 2), actualLength); return B_OK; } status_t HIDDevice::_FetchBuffer(uint8* cmd, size_t cmdLength, void* buffer, size_t bufferLength) { return _ExecCommand(I2C_OP_READ_STOP, cmd, cmdLength, buffer, bufferLength); } status_t HIDDevice::_ExecCommand(i2c_op op, uint8* cmd, size_t cmdLength, void* buffer, size_t bufferLength) { status_t status = fI2C->acquire_bus(fI2CCookie); if (status != B_OK) return status; status = fI2C->exec_command(fI2CCookie, op, cmd, cmdLength, buffer, bufferLength); fI2C->release_bus(fI2CCookie); return status; }