1001 lines
24 KiB
C++
Executable File
1001 lines
24 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2014 Andrew Duggan
|
|
* Copyright (C) 2014 Synaptics Inc
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/input.h>
|
|
#include <linux/hidraw.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <sys/inotify.h>
|
|
|
|
#include "hiddevice.h"
|
|
|
|
#define RMI_WRITE_REPORT_ID 0x9 // Output Report
|
|
#define RMI_READ_ADDR_REPORT_ID 0xa // Output Report
|
|
#define RMI_READ_DATA_REPORT_ID 0xb // Input Report
|
|
#define RMI_ATTN_REPORT_ID 0xc // Input Report
|
|
#define RMI_SET_RMI_MODE_REPORT_ID 0xf // Feature Report
|
|
|
|
enum hid_report_type {
|
|
HID_REPORT_TYPE_UNKNOWN = 0x0,
|
|
HID_REPORT_TYPE_INPUT = 0x81,
|
|
HID_REPORT_TYPE_OUTPUT = 0x91,
|
|
HID_REPORT_TYPE_FEATURE = 0xb1,
|
|
};
|
|
|
|
#define HID_RMI4_REPORT_ID 0
|
|
#define HID_RMI4_READ_INPUT_COUNT 1
|
|
#define HID_RMI4_READ_INPUT_DATA 2
|
|
#define HID_RMI4_READ_OUTPUT_ADDR 2
|
|
#define HID_RMI4_READ_OUTPUT_COUNT 4
|
|
#define HID_RMI4_WRITE_OUTPUT_COUNT 1
|
|
#define HID_RMI4_WRITE_OUTPUT_ADDR 2
|
|
#define HID_RMI4_WRITE_OUTPUT_DATA 4
|
|
#define HID_RMI4_FEATURE_MODE 1
|
|
#define HID_RMI4_ATTN_INTERUPT_SOURCES 1
|
|
#define HID_RMI4_ATTN_DATA 2
|
|
|
|
#define SYNAPTICS_VENDOR_ID 0x06cb
|
|
|
|
int HIDDevice::Open(const char * filename)
|
|
{
|
|
int rc;
|
|
int desc_size;
|
|
std::string hidDeviceName;
|
|
std::string hidDriverName;
|
|
|
|
if (!filename)
|
|
return -EINVAL;
|
|
|
|
m_fd = open(filename, O_RDWR);
|
|
if (m_fd < 0)
|
|
return -1;
|
|
|
|
memset(&m_rptDesc, 0, sizeof(m_rptDesc));
|
|
memset(&m_info, 0, sizeof(m_info));
|
|
|
|
rc = ioctl(m_fd, HIDIOCGRDESCSIZE, &desc_size);
|
|
if (rc < 0)
|
|
goto error;
|
|
|
|
m_rptDesc.size = desc_size;
|
|
rc = ioctl(m_fd, HIDIOCGRDESC, &m_rptDesc);
|
|
if (rc < 0)
|
|
goto error;
|
|
|
|
rc = ioctl(m_fd, HIDIOCGRAWINFO, &m_info);
|
|
if (rc < 0)
|
|
goto error;
|
|
|
|
if (m_info.vendor != SYNAPTICS_VENDOR_ID) {
|
|
errno = -ENODEV;
|
|
rc = -1;
|
|
goto error;
|
|
}
|
|
|
|
ParseReportDescriptor();
|
|
|
|
m_inputReport = new unsigned char[m_inputReportSize]();
|
|
if (!m_inputReport) {
|
|
errno = -ENOMEM;
|
|
rc = -1;
|
|
goto error;
|
|
}
|
|
|
|
m_outputReport = new unsigned char[m_outputReportSize]();
|
|
if (!m_outputReport) {
|
|
errno = -ENOMEM;
|
|
rc = -1;
|
|
goto error;
|
|
}
|
|
|
|
m_readData = new unsigned char[m_inputReportSize]();
|
|
if (!m_readData) {
|
|
errno = -ENOMEM;
|
|
rc = -1;
|
|
goto error;
|
|
}
|
|
|
|
m_attnData = new unsigned char[m_inputReportSize]();
|
|
if (!m_attnData) {
|
|
errno = -ENOMEM;
|
|
rc = -1;
|
|
goto error;
|
|
}
|
|
|
|
m_deviceOpen = true;
|
|
|
|
// Determine which mode the device is currently running in based on the current HID driver
|
|
// hid-rmi indicated RMI Mode 1 all others would be Mode 0
|
|
if (LookupHidDeviceName(m_info.bustype, m_info.vendor, m_info.product, hidDeviceName)) {
|
|
if (LookupHidDriverName(hidDeviceName, hidDriverName)) {
|
|
if (hidDriverName == "hid-rmi")
|
|
m_initialMode = HID_RMI4_MODE_ATTN_REPORTS;
|
|
}
|
|
}
|
|
|
|
if (m_initialMode != m_mode) {
|
|
rc = SetMode(m_mode);
|
|
if (rc) {
|
|
rc = -1;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
Close();
|
|
return rc;
|
|
}
|
|
|
|
void HIDDevice::ParseReportDescriptor()
|
|
{
|
|
bool isVendorSpecific = false;
|
|
bool isReport = false;
|
|
int totalReportSize = 0;
|
|
int reportSize = 0;
|
|
int reportCount = 0;
|
|
enum hid_report_type hidReportType = HID_REPORT_TYPE_UNKNOWN;
|
|
bool inCollection = false;
|
|
|
|
for (unsigned int i = 0; i < m_rptDesc.size; ++i) {
|
|
if (m_rptDesc.value[i] == 0xc0) {
|
|
inCollection = false;
|
|
isVendorSpecific = false;
|
|
isReport = false;
|
|
continue;
|
|
}
|
|
|
|
if (isVendorSpecific) {
|
|
if (m_rptDesc.value[i] == 0x85) {
|
|
if (isReport) {
|
|
// finish up data on the previous report
|
|
totalReportSize = (reportSize * reportCount) >> 3;
|
|
|
|
switch (hidReportType) {
|
|
case HID_REPORT_TYPE_INPUT:
|
|
m_inputReportSize = totalReportSize + 1;
|
|
break;
|
|
case HID_REPORT_TYPE_OUTPUT:
|
|
m_outputReportSize = totalReportSize + 1;
|
|
break;
|
|
case HID_REPORT_TYPE_FEATURE:
|
|
m_featureReportSize = totalReportSize + 1;
|
|
break;
|
|
case HID_REPORT_TYPE_UNKNOWN:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// reset values for the new report
|
|
totalReportSize = 0;
|
|
reportSize = 0;
|
|
reportCount = 0;
|
|
hidReportType = HID_REPORT_TYPE_UNKNOWN;
|
|
|
|
isReport = true;
|
|
}
|
|
|
|
if (isReport) {
|
|
if (m_rptDesc.value[i] == 0x75) {
|
|
if (i + 1 >= m_rptDesc.size)
|
|
return;
|
|
reportSize = m_rptDesc.value[++i];
|
|
continue;
|
|
}
|
|
|
|
if (m_rptDesc.value[i] == 0x95) {
|
|
if (i + 1 >= m_rptDesc.size)
|
|
return;
|
|
reportCount = m_rptDesc.value[++i];
|
|
continue;
|
|
}
|
|
|
|
if (m_rptDesc.value[i] == HID_REPORT_TYPE_INPUT)
|
|
hidReportType = HID_REPORT_TYPE_INPUT;
|
|
|
|
if (m_rptDesc.value[i] == HID_REPORT_TYPE_OUTPUT)
|
|
hidReportType = HID_REPORT_TYPE_OUTPUT;
|
|
|
|
if (m_rptDesc.value[i] == HID_REPORT_TYPE_FEATURE) {
|
|
hidReportType = HID_REPORT_TYPE_FEATURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!inCollection) {
|
|
switch (m_rptDesc.value[i]) {
|
|
case 0x00:
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0x04:
|
|
inCollection = true;
|
|
break;
|
|
case 0x05:
|
|
inCollection = true;
|
|
|
|
if (i + 3 >= m_rptDesc.size)
|
|
break;
|
|
|
|
// touchscreens with active pen have a Generic Mouse collection
|
|
// so stop searching if we have already found the touchscreen digitizer
|
|
// usage.
|
|
if (m_deviceType == RMI_DEVICE_TYPE_TOUCHSCREEN)
|
|
break;
|
|
|
|
if (m_rptDesc.value[i + 1] == 0x01) {
|
|
if (m_rptDesc.value[i + 2] == 0x09 && m_rptDesc.value[i + 3] == 0x02)
|
|
m_deviceType = RMI_DEVICE_TYPE_TOUCHPAD;
|
|
} else if (m_rptDesc.value[i + 1] == 0x0d) {
|
|
if (m_rptDesc.value[i + 2] == 0x09 && m_rptDesc.value[i + 3] == 0x04)
|
|
m_deviceType = RMI_DEVICE_TYPE_TOUCHSCREEN;
|
|
// for Precision Touch Pad
|
|
else if (m_rptDesc.value[i + 2] == 0x09 && m_rptDesc.value[i + 3] == 0x05)
|
|
m_deviceType = RMI_DEVICE_TYPE_TOUCHPAD;
|
|
}
|
|
i += 3;
|
|
break;
|
|
case 0x06:
|
|
inCollection = true;
|
|
if (i + 2 >= m_rptDesc.size)
|
|
break;
|
|
|
|
if (m_rptDesc.value[i + 1] == 0x00 && m_rptDesc.value[i + 2] == 0xFF)
|
|
isVendorSpecific = true;
|
|
i += 2;
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int HIDDevice::Read(unsigned short addr, unsigned char *buf, unsigned short len)
|
|
{
|
|
ssize_t count;
|
|
size_t bytesReadPerRequest;
|
|
size_t bytesInDataReport;
|
|
size_t totalBytesRead;
|
|
size_t bytesPerRequest;
|
|
size_t bytesWritten;
|
|
size_t bytesToRequest;
|
|
int reportId;
|
|
int rc;
|
|
|
|
if (!m_deviceOpen)
|
|
return -1;
|
|
|
|
if (m_bytesPerReadRequest)
|
|
bytesPerRequest = m_bytesPerReadRequest;
|
|
else
|
|
bytesPerRequest = len;
|
|
|
|
for (totalBytesRead = 0; totalBytesRead < len; totalBytesRead += bytesReadPerRequest) {
|
|
count = 0;
|
|
if ((len - totalBytesRead) < bytesPerRequest)
|
|
bytesToRequest = len % bytesPerRequest;
|
|
else
|
|
bytesToRequest = bytesPerRequest;
|
|
|
|
if (m_outputReportSize < HID_RMI4_READ_OUTPUT_COUNT + 2) {
|
|
return -1;
|
|
}
|
|
m_outputReport[HID_RMI4_REPORT_ID] = RMI_READ_ADDR_REPORT_ID;
|
|
m_outputReport[1] = 0; /* old 1 byte read count */
|
|
m_outputReport[HID_RMI4_READ_OUTPUT_ADDR] = addr & 0xFF;
|
|
m_outputReport[HID_RMI4_READ_OUTPUT_ADDR + 1] = (addr >> 8) & 0xFF;
|
|
m_outputReport[HID_RMI4_READ_OUTPUT_COUNT] = bytesToRequest & 0xFF;
|
|
m_outputReport[HID_RMI4_READ_OUTPUT_COUNT + 1] = (bytesToRequest >> 8) & 0xFF;
|
|
|
|
m_dataBytesRead = 0;
|
|
|
|
for (bytesWritten = 0; bytesWritten < m_outputReportSize; bytesWritten += count) {
|
|
m_bCancel = false;
|
|
count = write(m_fd, m_outputReport + bytesWritten,
|
|
m_outputReportSize - bytesWritten);
|
|
if (count < 0) {
|
|
if (errno == EINTR && m_deviceOpen && !m_bCancel)
|
|
continue;
|
|
else
|
|
return count;
|
|
}
|
|
break;
|
|
}
|
|
|
|
bytesReadPerRequest = 0;
|
|
while (bytesReadPerRequest < bytesToRequest) {
|
|
rc = GetReport(&reportId);
|
|
if (rc > 0 && reportId == RMI_READ_DATA_REPORT_ID) {
|
|
if (static_cast<ssize_t>(m_inputReportSize) <
|
|
std::max(HID_RMI4_READ_INPUT_COUNT,
|
|
HID_RMI4_READ_INPUT_DATA)){
|
|
return -1;
|
|
}
|
|
bytesInDataReport = m_readData[HID_RMI4_READ_INPUT_COUNT];
|
|
if (bytesInDataReport > bytesToRequest
|
|
|| bytesReadPerRequest + bytesInDataReport > len){
|
|
return -1;
|
|
}
|
|
memcpy(buf + bytesReadPerRequest, &m_readData[HID_RMI4_READ_INPUT_DATA],
|
|
bytesInDataReport);
|
|
bytesReadPerRequest += bytesInDataReport;
|
|
m_dataBytesRead = 0;
|
|
}
|
|
}
|
|
addr += bytesPerRequest;
|
|
}
|
|
|
|
return totalBytesRead;
|
|
}
|
|
|
|
int HIDDevice::Write(unsigned short addr, const unsigned char *buf, unsigned short len)
|
|
{
|
|
ssize_t count;
|
|
|
|
if (!m_deviceOpen)
|
|
return -1;
|
|
|
|
if (static_cast<ssize_t>(m_outputReportSize) <
|
|
HID_RMI4_WRITE_OUTPUT_DATA + len)
|
|
return -1;
|
|
m_outputReport[HID_RMI4_REPORT_ID] = RMI_WRITE_REPORT_ID;
|
|
m_outputReport[HID_RMI4_WRITE_OUTPUT_COUNT] = len;
|
|
m_outputReport[HID_RMI4_WRITE_OUTPUT_ADDR] = addr & 0xFF;
|
|
m_outputReport[HID_RMI4_WRITE_OUTPUT_ADDR + 1] = (addr >> 8) & 0xFF;
|
|
memcpy(&m_outputReport[HID_RMI4_WRITE_OUTPUT_DATA], buf, len);
|
|
|
|
for (;;) {
|
|
m_bCancel = false;
|
|
count = write(m_fd, m_outputReport, m_outputReportSize);
|
|
if (count < 0) {
|
|
if (errno == EINTR && m_deviceOpen && !m_bCancel)
|
|
continue;
|
|
else
|
|
return count;
|
|
}
|
|
return len;
|
|
}
|
|
}
|
|
|
|
int HIDDevice::SetMode(int mode)
|
|
{
|
|
int rc;
|
|
char buf[2];
|
|
|
|
if (!m_deviceOpen)
|
|
return -1;
|
|
|
|
buf[0] = 0xF;
|
|
buf[1] = mode;
|
|
rc = ioctl(m_fd, HIDIOCSFEATURE(2), buf);
|
|
if (rc < 0) {
|
|
perror("HIDIOCSFEATURE");
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void HIDDevice::Close()
|
|
{
|
|
RMIDevice::Close();
|
|
|
|
if (!m_deviceOpen)
|
|
return;
|
|
|
|
if (m_initialMode != m_mode)
|
|
SetMode(m_initialMode);
|
|
|
|
m_deviceOpen = false;
|
|
close(m_fd);
|
|
m_fd = -1;
|
|
|
|
delete[] m_inputReport;
|
|
m_inputReport = NULL;
|
|
delete[] m_outputReport;
|
|
m_outputReport = NULL;
|
|
delete[] m_readData;
|
|
m_readData = NULL;
|
|
delete[] m_attnData;
|
|
m_attnData = NULL;
|
|
}
|
|
|
|
int HIDDevice::WaitForAttention(struct timeval * timeout, unsigned int source_mask)
|
|
{
|
|
return GetAttentionReport(timeout, source_mask, NULL, NULL);
|
|
}
|
|
|
|
int HIDDevice::GetAttentionReport(struct timeval * timeout, unsigned int source_mask,
|
|
unsigned char *buf, unsigned int *len)
|
|
{
|
|
int rc = 0;
|
|
int reportId;
|
|
|
|
// Assume the Linux implementation of select with timeout set to the
|
|
// time remaining.
|
|
while (!timeout || (timeout->tv_sec != 0 || timeout->tv_usec != 0)) {
|
|
rc = GetReport(&reportId, timeout);
|
|
if (rc > 0) {
|
|
if (reportId == RMI_ATTN_REPORT_ID) {
|
|
// If a valid buffer is passed in then copy the data from
|
|
// the attention report into it. If the buffer is
|
|
// too small simply set *len to 0 to indicate nothing
|
|
// was copied. Some callers won't care about the contents
|
|
// of the report so failing to copy the data should not return
|
|
// an error.
|
|
if (buf && len) {
|
|
if (*len >= m_inputReportSize) {
|
|
*len = m_inputReportSize;
|
|
memcpy(buf, m_attnData, *len);
|
|
} else {
|
|
*len = 0;
|
|
}
|
|
}
|
|
|
|
if (m_inputReportSize < HID_RMI4_ATTN_INTERUPT_SOURCES + 1)
|
|
return -1;
|
|
|
|
if (source_mask & m_attnData[HID_RMI4_ATTN_INTERUPT_SOURCES])
|
|
return rc;
|
|
}
|
|
} else {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int HIDDevice::GetReport(int *reportId, struct timeval * timeout)
|
|
{
|
|
ssize_t count = 0;
|
|
fd_set fds;
|
|
int rc;
|
|
|
|
if (!m_deviceOpen)
|
|
return -1;
|
|
|
|
if (m_inputReportSize < HID_RMI4_REPORT_ID + 1)
|
|
return -1;
|
|
|
|
for (;;) {
|
|
FD_ZERO(&fds);
|
|
FD_SET(m_fd, &fds);
|
|
|
|
rc = select(m_fd + 1, &fds, NULL, NULL, timeout);
|
|
if (rc == 0) {
|
|
return -ETIMEDOUT;
|
|
} else if (rc < 0) {
|
|
if (errno == EINTR && m_deviceOpen && !m_bCancel)
|
|
continue;
|
|
else
|
|
return rc;
|
|
} else if (rc > 0 && FD_ISSET(m_fd, &fds)) {
|
|
size_t offset = 0;
|
|
for (;;) {
|
|
m_bCancel = false;
|
|
count = read(m_fd, m_inputReport + offset, m_inputReportSize - offset);
|
|
if (count < 0) {
|
|
if (errno == EINTR && m_deviceOpen && !m_bCancel)
|
|
continue;
|
|
else
|
|
return count;
|
|
}
|
|
offset += count;
|
|
if (offset == m_inputReportSize)
|
|
break;
|
|
}
|
|
count = offset;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (reportId)
|
|
*reportId = m_inputReport[HID_RMI4_REPORT_ID];
|
|
|
|
if (m_inputReport[HID_RMI4_REPORT_ID] == RMI_ATTN_REPORT_ID) {
|
|
if (static_cast<ssize_t>(m_inputReportSize) < count)
|
|
return -1;
|
|
memcpy(m_attnData, m_inputReport, count);
|
|
} else if (m_inputReport[HID_RMI4_REPORT_ID] == RMI_READ_DATA_REPORT_ID) {
|
|
if (static_cast<ssize_t>(m_inputReportSize) < count)
|
|
return -1;
|
|
memcpy(m_readData, m_inputReport, count);
|
|
m_dataBytesRead = count;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void HIDDevice::PrintReport(const unsigned char *report)
|
|
{
|
|
int i;
|
|
int len = 0;
|
|
const unsigned char * data;
|
|
int addr = 0;
|
|
|
|
switch (report[HID_RMI4_REPORT_ID]) {
|
|
case RMI_WRITE_REPORT_ID:
|
|
len = report[HID_RMI4_WRITE_OUTPUT_COUNT];
|
|
data = &report[HID_RMI4_WRITE_OUTPUT_DATA];
|
|
addr = (report[HID_RMI4_WRITE_OUTPUT_ADDR] & 0xFF)
|
|
| ((report[HID_RMI4_WRITE_OUTPUT_ADDR + 1] & 0xFF) << 8);
|
|
fprintf(stdout, "Write Report:\n");
|
|
fprintf(stdout, "Address = 0x%02X\n", addr);
|
|
fprintf(stdout, "Length = 0x%02X\n", len);
|
|
break;
|
|
case RMI_READ_ADDR_REPORT_ID:
|
|
addr = (report[HID_RMI4_READ_OUTPUT_ADDR] & 0xFF)
|
|
| ((report[HID_RMI4_READ_OUTPUT_ADDR + 1] & 0xFF) << 8);
|
|
len = (report[HID_RMI4_READ_OUTPUT_COUNT] & 0xFF)
|
|
| ((report[HID_RMI4_READ_OUTPUT_COUNT + 1] & 0xFF) << 8);
|
|
fprintf(stdout, "Read Request (Output Report):\n");
|
|
fprintf(stdout, "Address = 0x%02X\n", addr);
|
|
fprintf(stdout, "Length = 0x%02X\n", len);
|
|
return;
|
|
break;
|
|
case RMI_READ_DATA_REPORT_ID:
|
|
len = report[HID_RMI4_READ_INPUT_COUNT];
|
|
data = &report[HID_RMI4_READ_INPUT_DATA];
|
|
fprintf(stdout, "Read Data Report:\n");
|
|
fprintf(stdout, "Length = 0x%02X\n", len);
|
|
break;
|
|
case RMI_ATTN_REPORT_ID:
|
|
fprintf(stdout, "Attention Report:\n");
|
|
len = 28;
|
|
data = &report[HID_RMI4_ATTN_DATA];
|
|
fprintf(stdout, "Interrupt Sources: 0x%02X\n",
|
|
report[HID_RMI4_ATTN_INTERUPT_SOURCES]);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown Report: ID 0x%02x\n", report[HID_RMI4_REPORT_ID]);
|
|
return;
|
|
}
|
|
|
|
fprintf(stdout, "Data:\n");
|
|
for (i = 0; i < len; ++i) {
|
|
fprintf(stdout, "0x%02X ", data[i]);
|
|
if (i % 8 == 7) {
|
|
fprintf(stdout, "\n");
|
|
}
|
|
}
|
|
fprintf(stdout, "\n\n");
|
|
}
|
|
|
|
// Print protocol specific device information
|
|
void HIDDevice::PrintDeviceInfo()
|
|
{
|
|
enum RMIDeviceType deviceType = GetDeviceType();
|
|
|
|
fprintf(stdout, "HID device info:\nBus: %s Vendor: 0x%04x Product: 0x%04x\n",
|
|
m_info.bustype == BUS_I2C ? "I2C" : "USB", m_info.vendor, m_info.product);
|
|
fprintf(stdout, "Report sizes: input: %ld output: %ld\n", (unsigned long)m_inputReportSize,
|
|
(unsigned long)m_outputReportSize);
|
|
if (deviceType)
|
|
fprintf(stdout, "device type: %s\n", deviceType == RMI_DEVICE_TYPE_TOUCHSCREEN ?
|
|
"touchscreen" : "touchpad");
|
|
}
|
|
|
|
bool WriteDeviceNameToFile(const char * file, const char * str)
|
|
{
|
|
int fd;
|
|
ssize_t size;
|
|
|
|
fd = open(file, O_WRONLY);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
for (;;) {
|
|
size = write(fd, str, strlen(str));
|
|
if (size < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return close(fd) == 0 && size == static_cast<ssize_t>(strlen(str));
|
|
}
|
|
static const char * const absval[6] = { "Value", "Min ", "Max ", "Fuzz ", "Flat ", "Resolution "};
|
|
#define KEY_MAX 0x2ff
|
|
#define EV_MAX 0x1f
|
|
#define BITS_PER_LONG (sizeof(long) * 8)
|
|
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
|
|
#define OFF(x) ((x)%BITS_PER_LONG)
|
|
#define BIT(x) (1UL<<OFF(x))
|
|
#define LONG(x) ((x)/BITS_PER_LONG)
|
|
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
|
|
#define DEV_INPUT_EVENT "/dev/input"
|
|
#define EVENT_DEV_NAME "event"
|
|
/**
|
|
* Filter for the AutoDevProbe scandir on /dev/input.
|
|
*
|
|
* @param dir The current directory entry provided by scandir.
|
|
*
|
|
* @return Non-zero if the given directory entry starts with "event", or zero
|
|
* otherwise.
|
|
*/
|
|
static int is_event_device(const struct dirent *dir) {
|
|
return strncmp(EVENT_DEV_NAME, dir->d_name, 5) == 0;
|
|
}
|
|
|
|
bool HIDDevice::CheckABSEvent()
|
|
{
|
|
int fd=-1;
|
|
unsigned int type;
|
|
int abs[6] = {0};
|
|
int k;
|
|
struct dirent **namelist;
|
|
int i, ndev, devnum, match;
|
|
char *filename;
|
|
int max_device = 0;
|
|
char input_event_name[PATH_MAX];
|
|
unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
|
|
|
|
|
|
#ifdef __BIONIC__
|
|
// Android's libc doesn't have the GNU versionsort extension.
|
|
ndev = scandir(DEV_INPUT_EVENT, &namelist, is_event_device, alphasort);
|
|
#else
|
|
ndev = scandir(DEV_INPUT_EVENT, &namelist, is_event_device, versionsort);
|
|
#endif
|
|
if (ndev <= 0)
|
|
return false;
|
|
for (i = 0; i < ndev; i++)
|
|
{
|
|
char fname[64];
|
|
int fd = -1;
|
|
char name[256] = "???";
|
|
|
|
snprintf(fname, sizeof(fname),
|
|
"%s/%s", DEV_INPUT_EVENT, namelist[i]->d_name);
|
|
fd = open(fname, O_RDONLY);
|
|
if (fd < 0)
|
|
continue;
|
|
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
|
|
//fprintf(stderr, "%s: %s\n", fname, name);
|
|
close(fd);
|
|
|
|
if(strstr(name, m_transportDeviceName.c_str()+4))
|
|
{
|
|
snprintf(input_event_name, sizeof(fname), "%s", fname);
|
|
}
|
|
free(namelist[i]);
|
|
}
|
|
|
|
if ((fd = open(input_event_name, O_RDONLY)) < 0) {
|
|
if (errno == EACCES && getuid() != 0)
|
|
fprintf(stderr, "No access right \n");
|
|
}
|
|
memset(bit, 0, sizeof(bit));
|
|
ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
|
|
for (type = 0; type < EV_MAX; type++) {
|
|
if (test_bit(type, bit[0]) && type == EV_ABS) {
|
|
ioctl(fd, EVIOCGBIT(type, KEY_MAX), bit[type]);
|
|
if (test_bit(ABS_X, bit[type])) {
|
|
ioctl(fd, EVIOCGABS(ABS_X), abs);
|
|
if(abs[2] == 0) //maximum
|
|
{
|
|
Sleep(1000);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
void HIDDevice::RebindDriver()
|
|
{
|
|
int bus = m_info.bustype;
|
|
int vendor = m_info.vendor;
|
|
int product = m_info.product;
|
|
std::string hidDeviceName;
|
|
std::string bindFile;
|
|
std::string unbindFile;
|
|
std::string hidrawFile;
|
|
int notifyFd;
|
|
int wd;
|
|
int rc;
|
|
Close();
|
|
|
|
notifyFd = inotify_init();
|
|
if (notifyFd < 0) {
|
|
fprintf(stderr, "Failed to initialize inotify\n");
|
|
return;
|
|
}
|
|
|
|
wd = inotify_add_watch(notifyFd, "/dev", IN_CREATE);
|
|
if (wd < 0) {
|
|
fprintf(stderr, "Failed to add watcher for /dev\n");
|
|
return;
|
|
}
|
|
|
|
if (m_transportDeviceName == "") {
|
|
if (!LookupHidDeviceName(bus, vendor, product, hidDeviceName)) {
|
|
fprintf(stderr, "Failed to find HID device name for the specified device: bus (0x%x) vendor: (0x%x) product: (0x%x)\n",
|
|
bus, vendor, product);
|
|
return;
|
|
}
|
|
|
|
if (!FindTransportDevice(bus, hidDeviceName, m_transportDeviceName, m_driverPath)) {
|
|
fprintf(stderr, "Failed to find the transport device / driver for %s\n", hidDeviceName.c_str());
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
bindFile = m_driverPath + "bind";
|
|
unbindFile = m_driverPath + "unbind";
|
|
|
|
Sleep(500);
|
|
if (!WriteDeviceNameToFile(unbindFile.c_str(), m_transportDeviceName.c_str())) {
|
|
fprintf(stderr, "Failed to unbind HID device %s: %s\n",
|
|
m_transportDeviceName.c_str(), strerror(errno));
|
|
return;
|
|
}
|
|
Sleep(500);
|
|
if (!WriteDeviceNameToFile(bindFile.c_str(), m_transportDeviceName.c_str())) {
|
|
fprintf(stderr, "Failed to bind HID device %s: %s\n",
|
|
m_transportDeviceName.c_str(), strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (WaitForHidRawDevice(notifyFd, hidrawFile)) {
|
|
rc = Open(hidrawFile.c_str());
|
|
if (rc)
|
|
fprintf(stderr, "Failed to open device (%s) during rebind: %d: errno: %s (%d)\n",
|
|
hidrawFile.c_str(), rc, strerror(errno), errno);
|
|
}
|
|
}
|
|
|
|
bool HIDDevice::FindTransportDevice(uint32_t bus, std::string & hidDeviceName,
|
|
std::string & transportDeviceName, std::string & driverPath)
|
|
{
|
|
std::string devicePrefix = "/sys/bus/";
|
|
std::string devicePath;
|
|
struct dirent * devicesDirEntry;
|
|
DIR * devicesDir;
|
|
struct dirent * devDirEntry;
|
|
DIR * devDir;
|
|
bool deviceFound = false;
|
|
ssize_t sz;
|
|
|
|
if (bus == BUS_I2C) {
|
|
devicePrefix += "i2c/";
|
|
// From new patch released on 2020/11, i2c_hid would be renamed as i2c_hid_acpi,
|
|
// and also need backward compatible.
|
|
std::string driverPathTemp = devicePrefix + "drivers/i2c_hid/";
|
|
DIR *driverPathtest = opendir(driverPathTemp.c_str());
|
|
if(!driverPathtest) {
|
|
driverPath = devicePrefix + "drivers/i2c_hid_acpi/";
|
|
} else {
|
|
driverPath = devicePrefix + "drivers/i2c_hid/";
|
|
}
|
|
} else {
|
|
devicePrefix += "usb/";
|
|
driverPath = devicePrefix + "drivers/usbhid/";
|
|
}
|
|
devicePath = devicePrefix + "devices/";
|
|
|
|
devicesDir = opendir(devicePath.c_str());
|
|
if (!devicesDir)
|
|
return false;
|
|
|
|
while((devicesDirEntry = readdir(devicesDir)) != NULL) {
|
|
if (devicesDirEntry->d_type != DT_LNK)
|
|
continue;
|
|
|
|
char buf[PATH_MAX];
|
|
|
|
sz = readlinkat(dirfd(devicesDir), devicesDirEntry->d_name, buf, PATH_MAX);
|
|
if (sz < 0)
|
|
continue;
|
|
|
|
buf[sz] = 0;
|
|
|
|
std::string fullLinkPath = devicePath + buf;
|
|
devDir = opendir(fullLinkPath.c_str());
|
|
if (!devDir) {
|
|
fprintf(stdout, "opendir failed\n");
|
|
continue;
|
|
}
|
|
|
|
while ((devDirEntry = readdir(devDir)) != NULL) {
|
|
if (!strcmp(devDirEntry->d_name, hidDeviceName.c_str())) {
|
|
transportDeviceName = devicesDirEntry->d_name;
|
|
deviceFound = true;
|
|
break;
|
|
}
|
|
}
|
|
closedir(devDir);
|
|
|
|
if (deviceFound)
|
|
break;
|
|
}
|
|
closedir(devicesDir);
|
|
|
|
return deviceFound;
|
|
}
|
|
|
|
bool HIDDevice::LookupHidDeviceName(uint32_t bus, int16_t vendorId, int16_t productId, std::string & deviceName)
|
|
{
|
|
bool ret = false;
|
|
struct dirent * devDirEntry;
|
|
DIR * devDir;
|
|
char devicePrefix[15];
|
|
|
|
snprintf(devicePrefix, 15, "%04X:%04X:%04X", bus, (vendorId & 0xFFFF), (productId & 0xFFFF));
|
|
|
|
devDir = opendir("/sys/bus/hid/devices");
|
|
if (!devDir)
|
|
return false;
|
|
|
|
while ((devDirEntry = readdir(devDir)) != NULL) {
|
|
if (!strncmp(devDirEntry->d_name, devicePrefix, 14)) {
|
|
deviceName = devDirEntry->d_name;
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
closedir(devDir);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool HIDDevice::LookupHidDriverName(std::string &deviceName, std::string &driverName)
|
|
{
|
|
bool ret = false;
|
|
ssize_t sz;
|
|
char link[PATH_MAX];
|
|
std::string driverLink = "/sys/bus/hid/devices/" + deviceName + "/driver";
|
|
|
|
sz = readlink(driverLink.c_str(), link, PATH_MAX);
|
|
if (sz == -1)
|
|
return ret;
|
|
|
|
link[sz] = 0;
|
|
|
|
driverName = std::string(StripPath(link, PATH_MAX));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HIDDevice::WaitForHidRawDevice(int notifyFd, std::string & hidrawFile)
|
|
{
|
|
struct timeval timeout;
|
|
fd_set fds;
|
|
int rc;
|
|
ssize_t eventBytesRead;
|
|
int eventBytesAvailable;
|
|
size_t sz;
|
|
char link[PATH_MAX];
|
|
std::string transportDeviceName;
|
|
std::string driverPath;
|
|
std::string hidDeviceName;
|
|
int offset = 0;
|
|
|
|
for (;;) {
|
|
FD_ZERO(&fds);
|
|
FD_SET(notifyFd, &fds);
|
|
|
|
timeout.tv_sec = 20;
|
|
timeout.tv_usec = 0;
|
|
|
|
rc = select(notifyFd + 1, &fds, NULL, NULL, &timeout);
|
|
if (rc < 0) {
|
|
if (errno == -EINTR)
|
|
continue;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (FD_ISSET(notifyFd, &fds)) {
|
|
struct inotify_event * event;
|
|
|
|
rc = ioctl(notifyFd, FIONREAD, &eventBytesAvailable);
|
|
if (rc < 0) {
|
|
continue;
|
|
}
|
|
|
|
char buf[eventBytesAvailable];
|
|
|
|
eventBytesRead = read(notifyFd, buf, eventBytesAvailable);
|
|
if (eventBytesRead < 0) {
|
|
continue;
|
|
}
|
|
|
|
while (offset < eventBytesRead) {
|
|
event = (struct inotify_event *)&buf[offset];
|
|
|
|
if (!strncmp(event->name, "hidraw", 6)) {
|
|
std::string classPath = std::string("/sys/class/hidraw/")
|
|
+ event->name + "/device";
|
|
sz = readlink(classPath.c_str(), link, PATH_MAX);
|
|
link[sz] = 0;
|
|
|
|
hidDeviceName = std::string(link).substr(9, 19);
|
|
|
|
if (!FindTransportDevice(m_info.bustype, hidDeviceName, transportDeviceName, driverPath)) {
|
|
fprintf(stderr, "Failed to find the transport device / driver for %s\n", hidDeviceName.c_str());
|
|
continue;
|
|
}
|
|
|
|
if (transportDeviceName == m_transportDeviceName) {
|
|
hidrawFile = std::string("/dev/") + event->name;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
offset += sizeof(struct inotify_event) + event->len;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HIDDevice::FindDevice(enum RMIDeviceType type)
|
|
{
|
|
DIR * devDir;
|
|
struct dirent * devDirEntry;
|
|
char deviceFile[PATH_MAX];
|
|
bool found = false;
|
|
int rc;
|
|
devDir = opendir("/dev");
|
|
if (!devDir)
|
|
return -1;
|
|
|
|
while ((devDirEntry = readdir(devDir)) != NULL) {
|
|
if (strstr(devDirEntry->d_name, "hidraw")) {
|
|
snprintf(deviceFile, PATH_MAX, "/dev/%s", devDirEntry->d_name);
|
|
fprintf(stdout, "Got device : /dev/%s\n", devDirEntry->d_name);
|
|
rc = Open(deviceFile);
|
|
if (rc != 0) {
|
|
continue;
|
|
} else if (type != RMI_DEVICE_TYPE_ANY && GetDeviceType() != type) {
|
|
Close();
|
|
continue;
|
|
} else {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
closedir(devDir);
|
|
|
|
return found;
|
|
}
|