blob: 9ef4c3259958e67536eca5849322740e9928721b [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
122 m_attnReportQueue = new unsigned char[m_inputReportSize * HID_REPORT_QUEUE_MAX_SIZE]();
Andrew Dugganfa10fc82014-07-07 17:36:33 -0700123 if (!m_attnReportQueue) {
124 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;
224 int rc;
225
226 if (!m_deviceOpen)
227 return -1;
228
229 if (m_bytesPerReadRequest)
230 bytesPerRequest = m_bytesPerReadRequest;
231 else
232 bytesPerRequest = len;
233
234 for (totalBytesRead = 0; totalBytesRead < len; totalBytesRead += bytesReadPerRequest) {
235 count = 0;
236 if ((len - totalBytesRead) < bytesPerRequest)
237 bytesToRequest = len % bytesPerRequest;
238 else
239 bytesToRequest = bytesPerRequest;
240
241 m_outputReport[HID_RMI4_REPORT_ID] = RMI_READ_ADDR_REPORT_ID;
242 m_outputReport[1] = 0; /* old 1 byte read count */
243 m_outputReport[HID_RMI4_READ_OUTPUT_ADDR] = addr & 0xFF;
244 m_outputReport[HID_RMI4_READ_OUTPUT_ADDR + 1] = (addr >> 8) & 0xFF;
245 m_outputReport[HID_RMI4_READ_OUTPUT_COUNT] = bytesToRequest & 0xFF;
246 m_outputReport[HID_RMI4_READ_OUTPUT_COUNT + 1] = (bytesToRequest >> 8) & 0xFF;
247
248 m_dataBytesRead = 0;
249
250 for (bytesWritten = 0; bytesWritten < m_outputReportSize; bytesWritten += count) {
251 m_bCancel = false;
252 count = write(m_fd, m_outputReport + bytesWritten,
253 m_outputReportSize - bytesWritten);
254 if (count < 0) {
255 if (errno == EINTR && m_deviceOpen && !m_bCancel)
256 continue;
257 else
258 return count;
259 }
260 break;
261 }
262
263 bytesReadPerRequest = 0;
264 while (bytesReadPerRequest < bytesToRequest) {
265 rc = GetReport(RMI_READ_DATA_REPORT_ID);
266 if (rc > 0) {
267 bytesInDataReport = m_readData[HID_RMI4_READ_INPUT_COUNT];
268 memcpy(buf + bytesReadPerRequest, &m_readData[HID_RMI4_READ_INPUT_DATA],
269 bytesInDataReport);
270 bytesReadPerRequest += bytesInDataReport;
271 m_dataBytesRead = 0;
272 }
273 }
274 addr += bytesPerRequest;
275 }
276
277 return totalBytesRead;
278}
279
280int HIDDevice::Write(unsigned short addr, const unsigned char *buf, unsigned short len)
281{
Andrew Duggan64d6e592014-07-01 13:02:10 -0700282 ssize_t count;
Andrew Duggan4e811252014-04-03 15:17:57 -0700283
284 if (!m_deviceOpen)
285 return -1;
286
287 m_outputReport[HID_RMI4_REPORT_ID] = RMI_WRITE_REPORT_ID;
288 m_outputReport[HID_RMI4_WRITE_OUTPUT_COUNT] = len;
289 m_outputReport[HID_RMI4_WRITE_OUTPUT_ADDR] = addr & 0xFF;
290 m_outputReport[HID_RMI4_WRITE_OUTPUT_ADDR + 1] = (addr >> 8) & 0xFF;
291 memcpy(&m_outputReport[HID_RMI4_WRITE_OUTPUT_DATA], buf, len);
292
293 for (;;) {
294 m_bCancel = false;
295 count = write(m_fd, m_outputReport, m_outputReportSize);
296 if (count < 0) {
297 if (errno == EINTR && m_deviceOpen && !m_bCancel)
298 continue;
299 else
300 return count;
301 }
302 return count;
303 }
304}
305
306int HIDDevice::SetMode(int mode)
307{
308 int rc;
309 char buf[2];
310
311 if (!m_deviceOpen)
312 return -1;
313
314 buf[0] = 0xF;
315 buf[1] = mode;
316 rc = ioctl(m_fd, HIDIOCSFEATURE(2), buf);
317 if (rc < 0) {
318 perror("HIDIOCSFEATURE");
319 return rc;
320 }
321
322 return 0;
323}
324
325void HIDDevice::Close()
326{
327 if (!m_deviceOpen)
328 return;
329
330 SetMode(HID_RMI4_MODE_MOUSE);
331 m_deviceOpen = false;
332 close(m_fd);
333 m_fd = -1;
334
335 delete[] m_inputReport;
336 m_inputReport = NULL;
337 delete[] m_outputReport;
338 m_outputReport = NULL;
339 delete[] m_readData;
340 m_readData = NULL;
341 delete[] m_attnReportQueue;
342 m_attnReportQueue = NULL;
343}
344
345int HIDDevice::WaitForAttention(struct timeval * timeout, int *sources)
346{
347 return GetAttentionReport(timeout, sources, NULL, NULL);
348}
349
Andrew Duggane9a5cd02014-04-29 13:34:42 -0700350int HIDDevice::GetAttentionReport(struct timeval * timeout, int *sources, unsigned char *buf, unsigned int *len)
Andrew Duggan4e811252014-04-03 15:17:57 -0700351{
352 int rc;
353 int interrupt_sources;
354 const unsigned char * queue_report;
355 int bytes = m_inputReportSize;
356
Andrew Duggane9a5cd02014-04-29 13:34:42 -0700357 if (len && m_inputReportSize < *len) {
Andrew Duggan4e811252014-04-03 15:17:57 -0700358 bytes = *len;
359 *len = m_inputReportSize;
360 }
361
362 rc = GetReport(RMI_ATTN_REPORT_ID, timeout);
363 if (rc > 0) {
364 queue_report = m_attnReportQueue
365 + m_inputReportSize * m_tailIdx;
366 interrupt_sources = queue_report[HID_RMI4_ATTN_INTERUPT_SOURCES];
367 if (buf)
368 memcpy(buf, queue_report, bytes);
369 m_tailIdx = (m_tailIdx + 1) & (HID_REPORT_QUEUE_MAX_SIZE - 1);
370 --m_attnQueueCount;
371 if (sources)
372 *sources = interrupt_sources;
373 return rc;
374 }
375
376 return rc;
377}
378
379int HIDDevice::GetReport(int reportid, struct timeval * timeout)
380{
Andrew Duggan64d6e592014-07-01 13:02:10 -0700381 ssize_t count = 0;
Andrew Duggan4e811252014-04-03 15:17:57 -0700382 unsigned char *queue_report;
383 fd_set fds;
384 int rc;
385 int report_count = 0;
386
387 if (!m_deviceOpen)
388 return -1;
389
390 for (;;) {
391 FD_ZERO(&fds);
392 FD_SET(m_fd, &fds);
393
394 rc = select(m_fd + 1, &fds, NULL, NULL, timeout);
395 if (rc == 0) {
396 return -ETIMEDOUT;
397 } else if (rc < 0) {
398 if (errno == EINTR && m_deviceOpen && !m_bCancel)
399 continue;
400 else
401 return rc;
402 } else if (rc > 0 && FD_ISSET(m_fd, &fds)) {
403 size_t offset = 0;
404 for (;;) {
405 m_bCancel = false;
406 count = read(m_fd, m_inputReport + offset, m_inputReportSize - offset);
407 if (count < 0) {
408 if (errno == EINTR && m_deviceOpen && !m_bCancel)
409 continue;
410 else
411 return count;
412 }
413 offset += count;
414 if (offset == m_inputReportSize)
415 break;
416 }
417 }
418
419 if (m_inputReport[HID_RMI4_REPORT_ID] == RMI_ATTN_REPORT_ID) {
420 queue_report = m_attnReportQueue
421 + m_inputReportSize * m_headIdx;
422 memcpy(queue_report, m_inputReport, count);
423 m_headIdx = (m_headIdx + 1) & (HID_REPORT_QUEUE_MAX_SIZE - 1);
424 ++m_attnQueueCount;
425 } else if (m_inputReport[HID_RMI4_REPORT_ID] == RMI_READ_DATA_REPORT_ID) {
426 memcpy(m_readData, m_inputReport, count);
427 m_dataBytesRead = count;
428 }
429 ++report_count;
430
431 if (m_inputReport[HID_RMI4_REPORT_ID] == reportid)
432 break;
433 }
434 return report_count;
435}
436
437void HIDDevice::PrintReport(const unsigned char *report)
438{
439 int i;
440 int len = 0;
441 const unsigned char * data;
442 int addr = 0;
443
444 switch (report[HID_RMI4_REPORT_ID]) {
445 case RMI_WRITE_REPORT_ID:
446 len = report[HID_RMI4_WRITE_OUTPUT_COUNT];
447 data = &report[HID_RMI4_WRITE_OUTPUT_DATA];
448 addr = (report[HID_RMI4_WRITE_OUTPUT_ADDR] & 0xFF)
449 | ((report[HID_RMI4_WRITE_OUTPUT_ADDR + 1] & 0xFF) << 8);
450 fprintf(stdout, "Write Report:\n");
451 fprintf(stdout, "Address = 0x%02X\n", addr);
452 fprintf(stdout, "Length = 0x%02X\n", len);
453 break;
454 case RMI_READ_ADDR_REPORT_ID:
455 addr = (report[HID_RMI4_READ_OUTPUT_ADDR] & 0xFF)
456 | ((report[HID_RMI4_READ_OUTPUT_ADDR + 1] & 0xFF) << 8);
457 len = (report[HID_RMI4_READ_OUTPUT_COUNT] & 0xFF)
458 | ((report[HID_RMI4_READ_OUTPUT_COUNT + 1] & 0xFF) << 8);
459 fprintf(stdout, "Read Request (Output Report):\n");
460 fprintf(stdout, "Address = 0x%02X\n", addr);
461 fprintf(stdout, "Length = 0x%02X\n", len);
462 return;
463 break;
464 case RMI_READ_DATA_REPORT_ID:
465 len = report[HID_RMI4_READ_INPUT_COUNT];
466 data = &report[HID_RMI4_READ_INPUT_DATA];
467 fprintf(stdout, "Read Data Report:\n");
468 fprintf(stdout, "Length = 0x%02X\n", len);
469 break;
470 case RMI_ATTN_REPORT_ID:
471 fprintf(stdout, "Attention Report:\n");
472 len = 28;
473 data = &report[HID_RMI4_ATTN_DATA];
474 fprintf(stdout, "Interrupt Sources: 0x%02X\n",
475 report[HID_RMI4_ATTN_INTERUPT_SOURCES]);
476 break;
477 default:
478 fprintf(stderr, "Unknown Report: ID 0x%02x\n", report[HID_RMI4_REPORT_ID]);
479 return;
480 }
481
482 fprintf(stdout, "Data:\n");
483 for (i = 0; i < len; ++i) {
484 fprintf(stdout, "0x%02X ", data[i]);
485 if (i % 8 == 7) {
486 fprintf(stdout, "\n");
487 }
488 }
489 fprintf(stdout, "\n\n");
Andrew Duggan64d6e592014-07-01 13:02:10 -0700490}