blob: f756660ee7832966cbd3dae7b776b22c5888e9fb [file] [log] [blame]
Andrew Duggan052556f2014-04-16 11:32:30 -07001/*
2 * Copyright (C) 2014 Andrew Duggan
3 * Copyright (C) 2014 Synaptics Inc
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
Andrew Duggan4e811252014-04-03 15:17:57 -070018#include <stdio.h>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <fcntl.h>
22#include <errno.h>
23#include <string.h>
24#include <unistd.h>
25#include <sys/ioctl.h>
26#include <sys/select.h>
27
28#include <linux/types.h>
29#include <linux/input.h>
30#include <linux/hidraw.h>
31#include <signal.h>
32#include <stdlib.h>
33
34#include "hiddevice.h"
35
36#define RMI_WRITE_REPORT_ID 0x9 // Output Report
37#define RMI_READ_ADDR_REPORT_ID 0xa // Output Report
38#define RMI_READ_DATA_REPORT_ID 0xb // Input Report
39#define RMI_ATTN_REPORT_ID 0xc // Input Report
40#define RMI_SET_RMI_MODE_REPORT_ID 0xf // Feature Report
41
42enum rmi_hid_mode_type {
43 HID_RMI4_MODE_MOUSE = 0,
44 HID_RMI4_MODE_ATTN_REPORTS = 1,
45 HID_RMI4_MODE_NO_PACKED_ATTN_REPORTS = 2,
46};
47
Andrew Duggan8b774392014-06-18 13:11:49 -070048enum hid_report_type {
49 HID_REPORT_TYPE_UNKNOWN = 0x0,
50 HID_REPORT_TYPE_INPUT = 0x81,
51 HID_REPORT_TYPE_OUTPUT = 0x91,
52 HID_REPORT_TYPE_FEATURE = 0xb1,
53};
54
Andrew Duggan4e811252014-04-03 15:17:57 -070055#define HID_RMI4_REPORT_ID 0
56#define HID_RMI4_READ_INPUT_COUNT 1
57#define HID_RMI4_READ_INPUT_DATA 2
58#define HID_RMI4_READ_OUTPUT_ADDR 2
59#define HID_RMI4_READ_OUTPUT_COUNT 4
60#define HID_RMI4_WRITE_OUTPUT_COUNT 1
61#define HID_RMI4_WRITE_OUTPUT_ADDR 2
62#define HID_RMI4_WRITE_OUTPUT_DATA 4
63#define HID_RMI4_FEATURE_MODE 1
64#define HID_RMI4_ATTN_INTERUPT_SOURCES 1
65#define HID_RMI4_ATTN_DATA 2
66
Andrew Dugganfa10fc82014-07-07 17:36:33 -070067#define SYNAPTICS_VENDOR_ID 0x06cb
68
Andrew Duggan4e811252014-04-03 15:17:57 -070069int HIDDevice::Open(const char * filename)
70{
71 int rc;
72 int desc_size;
73
Andrew Duggan72163582014-04-03 16:25:12 -070074 if (!filename)
75 return -EINVAL;
76
Andrew Duggan4e811252014-04-03 15:17:57 -070077 m_fd = open(filename, O_RDWR);
78 if (m_fd < 0)
79 return -1;
80
81 memset(&m_rptDesc, 0, sizeof(m_rptDesc));
82 memset(&m_info, 0, sizeof(m_info));
83
84 rc = ioctl(m_fd, HIDIOCGRDESCSIZE, &desc_size);
85 if (rc < 0)
86 return rc;
87
88 m_rptDesc.size = desc_size;
89 rc = ioctl(m_fd, HIDIOCGRDESC, &m_rptDesc);
90 if (rc < 0)
91 return rc;
92
93 rc = ioctl(m_fd, HIDIOCGRAWINFO, &m_info);
94 if (rc < 0)
95 return rc;
96
Andrew Dugganfa10fc82014-07-07 17:36:33 -070097 if (m_info.vendor != SYNAPTICS_VENDOR_ID) {
98 errno = -ENODEV;
99 return -1;
100 }
101
Andrew Duggan8b774392014-06-18 13:11:49 -0700102 ParseReportSizes();
Andrew Duggan4e811252014-04-03 15:17:57 -0700103
104 m_inputReport = new unsigned char[m_inputReportSize]();
Andrew Dugganfa10fc82014-07-07 17:36:33 -0700105 if (!m_inputReport) {
106 errno = -ENOMEM;
107 return -1;
108 }
Andrew Duggan4e811252014-04-03 15:17:57 -0700109
110 m_outputReport = new unsigned char[m_outputReportSize]();
Andrew Dugganfa10fc82014-07-07 17:36:33 -0700111 if (!m_outputReport) {
112 errno = -ENOMEM;
113 return -1;
114 }
Andrew Duggan4e811252014-04-03 15:17:57 -0700115
116 m_readData = new unsigned char[m_inputReportSize]();
Andrew Dugganfa10fc82014-07-07 17:36:33 -0700117 if (!m_readData) {
118 errno = -ENOMEM;
119 return -1;
120 }
Andrew Duggan4e811252014-04-03 15:17:57 -0700121
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800122 m_attnData = new unsigned char[m_inputReportSize]();
123 if (!m_attnData) {
Andrew Dugganfa10fc82014-07-07 17:36:33 -0700124 errno = -ENOMEM;
125 return -1;
126 }
Andrew Duggan4e811252014-04-03 15:17:57 -0700127
128 m_deviceOpen = true;
129
130 rc = SetMode(HID_RMI4_MODE_ATTN_REPORTS);
131 if (rc)
132 return -1;
133
134 return 0;
135}
136
Andrew Duggan8b774392014-06-18 13:11:49 -0700137void HIDDevice::ParseReportSizes()
138{
139 bool isVendorSpecific = false;
140 bool isReport = false;
141 int totalReportSize = 0;
142 int reportSize = 0;
143 int reportCount = 0;
144 enum hid_report_type hidReportType = HID_REPORT_TYPE_UNKNOWN;
145
146 for (unsigned int i = 0; i < m_rptDesc.size; ++i) {
147 if (isVendorSpecific) {
148 if (m_rptDesc.value[i] == 0x85 || m_rptDesc.value[i] == 0xc0) {
149 if (isReport) {
150 // finish up data on the previous report
151 totalReportSize = (reportSize * reportCount) >> 3;
152
153 switch (hidReportType) {
154 case HID_REPORT_TYPE_INPUT:
155 m_inputReportSize = totalReportSize + 1;
156 break;
157 case HID_REPORT_TYPE_OUTPUT:
158 m_outputReportSize = totalReportSize + 1;
159 break;
160 case HID_REPORT_TYPE_FEATURE:
161 m_featureReportSize = totalReportSize + 1;
162 break;
163 case HID_REPORT_TYPE_UNKNOWN:
164 default:
165 break;
166 }
167 }
168
169 // reset values for the new report
170 totalReportSize = 0;
171 reportSize = 0;
172 reportCount = 0;
173 hidReportType = HID_REPORT_TYPE_UNKNOWN;
174
175 if (m_rptDesc.value[i] == 0x85)
176 isReport = true;
177 else
178 isReport = false;
179
180 if (m_rptDesc.value[i] == 0xc0)
181 isVendorSpecific = false;
182 }
183
184 if (isReport) {
185 if (m_rptDesc.value[i] == 0x75) {
186 reportSize = m_rptDesc.value[++i];
187 continue;
188 }
189
190 if (m_rptDesc.value[i] == 0x95) {
191 reportCount = m_rptDesc.value[++i];
192 continue;
193 }
194
195 if (m_rptDesc.value[i] == HID_REPORT_TYPE_INPUT)
196 hidReportType = HID_REPORT_TYPE_INPUT;
197
198 if (m_rptDesc.value[i] == HID_REPORT_TYPE_OUTPUT)
199 hidReportType = HID_REPORT_TYPE_OUTPUT;
200
201 if (m_rptDesc.value[i] == HID_REPORT_TYPE_FEATURE) {
202 hidReportType = HID_REPORT_TYPE_FEATURE;
203 }
204 }
205 }
206
207 if (m_rptDesc.value[i] == 0x06 && m_rptDesc.value[i + 1] == 0x00
208 && m_rptDesc.value[i + 2] == 0xFF) {
209 isVendorSpecific = true;
210 i += 2;
211 }
212 }
213}
214
Andrew Duggan4e811252014-04-03 15:17:57 -0700215int HIDDevice::Read(unsigned short addr, unsigned char *buf, unsigned short len)
216{
Andrew Duggan64d6e592014-07-01 13:02:10 -0700217 ssize_t count;
Andrew Duggan4e811252014-04-03 15:17:57 -0700218 size_t bytesReadPerRequest;
219 size_t bytesInDataReport;
220 size_t totalBytesRead;
221 size_t bytesPerRequest;
222 size_t bytesWritten;
223 size_t bytesToRequest;
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800224 int reportId;
Andrew Duggan4e811252014-04-03 15:17:57 -0700225 int rc;
226
227 if (!m_deviceOpen)
228 return -1;
229
230 if (m_bytesPerReadRequest)
231 bytesPerRequest = m_bytesPerReadRequest;
232 else
233 bytesPerRequest = len;
234
235 for (totalBytesRead = 0; totalBytesRead < len; totalBytesRead += bytesReadPerRequest) {
236 count = 0;
237 if ((len - totalBytesRead) < bytesPerRequest)
238 bytesToRequest = len % bytesPerRequest;
239 else
240 bytesToRequest = bytesPerRequest;
241
242 m_outputReport[HID_RMI4_REPORT_ID] = RMI_READ_ADDR_REPORT_ID;
243 m_outputReport[1] = 0; /* old 1 byte read count */
244 m_outputReport[HID_RMI4_READ_OUTPUT_ADDR] = addr & 0xFF;
245 m_outputReport[HID_RMI4_READ_OUTPUT_ADDR + 1] = (addr >> 8) & 0xFF;
246 m_outputReport[HID_RMI4_READ_OUTPUT_COUNT] = bytesToRequest & 0xFF;
247 m_outputReport[HID_RMI4_READ_OUTPUT_COUNT + 1] = (bytesToRequest >> 8) & 0xFF;
248
249 m_dataBytesRead = 0;
250
251 for (bytesWritten = 0; bytesWritten < m_outputReportSize; bytesWritten += count) {
252 m_bCancel = false;
253 count = write(m_fd, m_outputReport + bytesWritten,
254 m_outputReportSize - bytesWritten);
255 if (count < 0) {
256 if (errno == EINTR && m_deviceOpen && !m_bCancel)
257 continue;
258 else
259 return count;
260 }
261 break;
262 }
263
264 bytesReadPerRequest = 0;
265 while (bytesReadPerRequest < bytesToRequest) {
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800266 rc = GetReport(&reportId);
267 if (rc > 0 && reportId == RMI_READ_DATA_REPORT_ID) {
Andrew Duggan4e811252014-04-03 15:17:57 -0700268 bytesInDataReport = m_readData[HID_RMI4_READ_INPUT_COUNT];
269 memcpy(buf + bytesReadPerRequest, &m_readData[HID_RMI4_READ_INPUT_DATA],
270 bytesInDataReport);
271 bytesReadPerRequest += bytesInDataReport;
272 m_dataBytesRead = 0;
273 }
274 }
275 addr += bytesPerRequest;
276 }
277
278 return totalBytesRead;
279}
280
281int HIDDevice::Write(unsigned short addr, const unsigned char *buf, unsigned short len)
282{
Andrew Duggan64d6e592014-07-01 13:02:10 -0700283 ssize_t count;
Andrew Duggan4e811252014-04-03 15:17:57 -0700284
285 if (!m_deviceOpen)
286 return -1;
287
288 m_outputReport[HID_RMI4_REPORT_ID] = RMI_WRITE_REPORT_ID;
289 m_outputReport[HID_RMI4_WRITE_OUTPUT_COUNT] = len;
290 m_outputReport[HID_RMI4_WRITE_OUTPUT_ADDR] = addr & 0xFF;
291 m_outputReport[HID_RMI4_WRITE_OUTPUT_ADDR + 1] = (addr >> 8) & 0xFF;
292 memcpy(&m_outputReport[HID_RMI4_WRITE_OUTPUT_DATA], buf, len);
293
294 for (;;) {
295 m_bCancel = false;
296 count = write(m_fd, m_outputReport, m_outputReportSize);
297 if (count < 0) {
298 if (errno == EINTR && m_deviceOpen && !m_bCancel)
299 continue;
300 else
301 return count;
302 }
303 return count;
304 }
305}
306
307int HIDDevice::SetMode(int mode)
308{
309 int rc;
310 char buf[2];
311
312 if (!m_deviceOpen)
313 return -1;
314
315 buf[0] = 0xF;
316 buf[1] = mode;
317 rc = ioctl(m_fd, HIDIOCSFEATURE(2), buf);
318 if (rc < 0) {
319 perror("HIDIOCSFEATURE");
320 return rc;
321 }
322
323 return 0;
324}
325
326void HIDDevice::Close()
327{
328 if (!m_deviceOpen)
329 return;
330
331 SetMode(HID_RMI4_MODE_MOUSE);
332 m_deviceOpen = false;
333 close(m_fd);
334 m_fd = -1;
335
336 delete[] m_inputReport;
337 m_inputReport = NULL;
338 delete[] m_outputReport;
339 m_outputReport = NULL;
340 delete[] m_readData;
341 m_readData = NULL;
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800342 delete[] m_attnData;
343 m_attnData = NULL;
Andrew Duggan4e811252014-04-03 15:17:57 -0700344}
345
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800346int HIDDevice::WaitForAttention(struct timeval * timeout, unsigned int source_mask)
Andrew Duggan4e811252014-04-03 15:17:57 -0700347{
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800348 return GetAttentionReport(timeout, source_mask, NULL, NULL);
Andrew Duggan4e811252014-04-03 15:17:57 -0700349}
350
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800351int HIDDevice::GetAttentionReport(struct timeval * timeout, unsigned int source_mask,
352 unsigned char *buf, unsigned int *len)
Andrew Duggan4e811252014-04-03 15:17:57 -0700353{
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800354 int rc = 0;
Andrew Duggan4e811252014-04-03 15:17:57 -0700355 int bytes = m_inputReportSize;
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800356 int reportId;
Andrew Duggan4e811252014-04-03 15:17:57 -0700357
Andrew Duggane9a5cd02014-04-29 13:34:42 -0700358 if (len && m_inputReportSize < *len) {
Andrew Duggan4e811252014-04-03 15:17:57 -0700359 bytes = *len;
360 *len = m_inputReportSize;
361 }
362
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800363 // Assume the Linux implementation of select with timeout set to the
364 // time remaining.
365 while (!timeout || (timeout->tv_sec != 0 || timeout->tv_usec != 0)) {
366 rc = GetReport(&reportId, timeout);
367 if (rc > 0) {
368 if (reportId == RMI_ATTN_REPORT_ID) {
369 if (buf)
370 memcpy(buf, m_attnData, bytes);
371 if (source_mask & m_attnData[HID_RMI4_ATTN_INTERUPT_SOURCES])
372 return rc;
373 }
374 } else {
375 return rc;
376 }
Andrew Duggan4e811252014-04-03 15:17:57 -0700377 }
378
379 return rc;
380}
381
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800382int HIDDevice::GetReport(int *reportId, struct timeval * timeout)
Andrew Duggan4e811252014-04-03 15:17:57 -0700383{
Andrew Duggan64d6e592014-07-01 13:02:10 -0700384 ssize_t count = 0;
Andrew Duggan4e811252014-04-03 15:17:57 -0700385 fd_set fds;
386 int rc;
Andrew Duggan4e811252014-04-03 15:17:57 -0700387
388 if (!m_deviceOpen)
389 return -1;
390
391 for (;;) {
392 FD_ZERO(&fds);
393 FD_SET(m_fd, &fds);
394
395 rc = select(m_fd + 1, &fds, NULL, NULL, timeout);
396 if (rc == 0) {
397 return -ETIMEDOUT;
398 } else if (rc < 0) {
399 if (errno == EINTR && m_deviceOpen && !m_bCancel)
400 continue;
401 else
402 return rc;
403 } else if (rc > 0 && FD_ISSET(m_fd, &fds)) {
404 size_t offset = 0;
405 for (;;) {
406 m_bCancel = false;
407 count = read(m_fd, m_inputReport + offset, m_inputReportSize - offset);
408 if (count < 0) {
409 if (errno == EINTR && m_deviceOpen && !m_bCancel)
410 continue;
411 else
412 return count;
413 }
414 offset += count;
415 if (offset == m_inputReportSize)
416 break;
417 }
418 }
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800419 break;
Andrew Duggan4e811252014-04-03 15:17:57 -0700420 }
Andrew Dugganf73fdc72014-11-09 11:02:22 -0800421
422 if (reportId)
423 *reportId = m_inputReport[HID_RMI4_REPORT_ID];
424
425 if (m_inputReport[HID_RMI4_REPORT_ID] == RMI_ATTN_REPORT_ID) {
426 memcpy(m_attnData, m_inputReport, count);
427 } else if (m_inputReport[HID_RMI4_REPORT_ID] == RMI_READ_DATA_REPORT_ID) {
428 memcpy(m_readData, m_inputReport, count);
429 m_dataBytesRead = count;
430 }
431 return 1;
Andrew Duggan4e811252014-04-03 15:17:57 -0700432}
433
434void HIDDevice::PrintReport(const unsigned char *report)
435{
436 int i;
437 int len = 0;
438 const unsigned char * data;
439 int addr = 0;
440
441 switch (report[HID_RMI4_REPORT_ID]) {
442 case RMI_WRITE_REPORT_ID:
443 len = report[HID_RMI4_WRITE_OUTPUT_COUNT];
444 data = &report[HID_RMI4_WRITE_OUTPUT_DATA];
445 addr = (report[HID_RMI4_WRITE_OUTPUT_ADDR] & 0xFF)
446 | ((report[HID_RMI4_WRITE_OUTPUT_ADDR + 1] & 0xFF) << 8);
447 fprintf(stdout, "Write Report:\n");
448 fprintf(stdout, "Address = 0x%02X\n", addr);
449 fprintf(stdout, "Length = 0x%02X\n", len);
450 break;
451 case RMI_READ_ADDR_REPORT_ID:
452 addr = (report[HID_RMI4_READ_OUTPUT_ADDR] & 0xFF)
453 | ((report[HID_RMI4_READ_OUTPUT_ADDR + 1] & 0xFF) << 8);
454 len = (report[HID_RMI4_READ_OUTPUT_COUNT] & 0xFF)
455 | ((report[HID_RMI4_READ_OUTPUT_COUNT + 1] & 0xFF) << 8);
456 fprintf(stdout, "Read Request (Output Report):\n");
457 fprintf(stdout, "Address = 0x%02X\n", addr);
458 fprintf(stdout, "Length = 0x%02X\n", len);
459 return;
460 break;
461 case RMI_READ_DATA_REPORT_ID:
462 len = report[HID_RMI4_READ_INPUT_COUNT];
463 data = &report[HID_RMI4_READ_INPUT_DATA];
464 fprintf(stdout, "Read Data Report:\n");
465 fprintf(stdout, "Length = 0x%02X\n", len);
466 break;
467 case RMI_ATTN_REPORT_ID:
468 fprintf(stdout, "Attention Report:\n");
469 len = 28;
470 data = &report[HID_RMI4_ATTN_DATA];
471 fprintf(stdout, "Interrupt Sources: 0x%02X\n",
472 report[HID_RMI4_ATTN_INTERUPT_SOURCES]);
473 break;
474 default:
475 fprintf(stderr, "Unknown Report: ID 0x%02x\n", report[HID_RMI4_REPORT_ID]);
476 return;
477 }
478
479 fprintf(stdout, "Data:\n");
480 for (i = 0; i < len; ++i) {
481 fprintf(stdout, "0x%02X ", data[i]);
482 if (i % 8 == 7) {
483 fprintf(stdout, "\n");
484 }
485 }
486 fprintf(stdout, "\n\n");
Andrew Duggan64d6e592014-07-01 13:02:10 -0700487}