| /* |
| * 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; |
| } |