summaryrefslogtreecommitdiff
path: root/src/hid_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_linux.c')
-rw-r--r--src/hid_linux.c308
1 files changed, 231 insertions, 77 deletions
diff --git a/src/hid_linux.c b/src/hid_linux.c
index 99c5afb..9788012 100644
--- a/src/hid_linux.c
+++ b/src/hid_linux.c
@@ -8,16 +8,22 @@
8 8
9#include <sys/ioctl.h> 9#include <sys/ioctl.h>
10#include <linux/hidraw.h> 10#include <linux/hidraw.h>
11#include <linux/input.h>
11 12
13#include <errno.h>
12#include <fcntl.h> 14#include <fcntl.h>
13#include <libudev.h> 15#include <libudev.h>
16#include <poll.h>
14#include <string.h> 17#include <string.h>
15#include <unistd.h> 18#include <unistd.h>
16#include <errno.h>
17 19
18#include "fido.h" 20#include "fido.h"
19 21
20#define REPORT_LEN 65 22struct hid_linux {
23 int fd;
24 size_t report_in_len;
25 size_t report_out_len;
26};
21 27
22static int 28static int
23get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) 29get_key_len(uint8_t tag, uint8_t *key, size_t *key_len)
@@ -63,11 +69,8 @@ static int
63get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page, 69get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page,
64 uint32_t *usage) 70 uint32_t *usage)
65{ 71{
66 const uint8_t *ptr; 72 const uint8_t *ptr = hrd->value;
67 size_t len; 73 size_t len = hrd->size;
68
69 ptr = hrd->value;
70 len = hrd->size;
71 74
72 while (len > 0) { 75 while (len > 0) {
73 const uint8_t tag = ptr[0]; 76 const uint8_t tag = ptr[0];
@@ -97,80 +100,113 @@ get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page,
97} 100}
98 101
99static int 102static int
100get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) 103get_report_sizes(const struct hidraw_report_descriptor *hrd,
104 size_t *report_in_len, size_t *report_out_len)
101{ 105{
102 int s = -1; 106 const uint8_t *ptr = hrd->value;
103 int fd; 107 size_t len = hrd->size;
104 int ok = -1; 108 uint32_t report_size = 0;
105 109
106 if ((fd = open(path, O_RDONLY)) < 0) { 110 while (len > 0) {
107 fido_log_debug("%s: open", __func__); 111 const uint8_t tag = ptr[0];
108 return (-1); 112 ptr++;
113 len--;
114
115 uint8_t key;
116 size_t key_len;
117 uint32_t key_val;
118
119 if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
120 get_key_val(ptr, key_len, &key_val) < 0) {
121 return (-1);
122 }
123
124 if (key == 0x94) {
125 report_size = key_val;
126 } else if (key == 0x80) {
127 *report_in_len = (size_t)report_size;
128 } else if (key == 0x90) {
129 *report_out_len = (size_t)report_size;
130 }
131
132 ptr += key_len;
133 len -= key_len;
109 } 134 }
110 135
136 return (0);
137}
138
139static int
140get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
141{
142 int s = -1;
143
111 if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 || 144 if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 ||
112 (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { 145 (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
113 fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__); 146 fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__);
114 goto fail; 147 return (-1);
115 } 148 }
116 149
117 hrd->size = s; 150 hrd->size = (unsigned)s;
118 151
119 if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) { 152 if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) {
120 fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__); 153 fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__);
121 goto fail; 154 return (-1);
122 } 155 }
123 156
124 ok = 0; 157 return (0);
125fail:
126 if (fd != -1)
127 close(fd);
128
129 return (ok);
130} 158}
131 159
132static bool 160static bool
133is_fido(const char *path) 161is_fido(const char *path)
134{ 162{
163 int fd;
135 uint32_t usage = 0; 164 uint32_t usage = 0;
136 uint32_t usage_page = 0; 165 uint32_t usage_page = 0;
137 struct hidraw_report_descriptor hrd; 166 struct hidraw_report_descriptor hrd;
138 167
139 memset(&hrd, 0, sizeof(hrd)); 168 memset(&hrd, 0, sizeof(hrd));
140 169
141 if (get_report_descriptor(path, &hrd) < 0 || 170 if ((fd = open(path, O_RDONLY)) == -1) {
171 fido_log_debug("%s: open", __func__);
172 return (false);
173 }
174
175 if (get_report_descriptor(fd, &hrd) < 0 ||
142 get_usage_info(&hrd, &usage_page, &usage) < 0) { 176 get_usage_info(&hrd, &usage_page, &usage) < 0) {
177 close(fd);
143 return (false); 178 return (false);
144 } 179 }
145 180
181 close(fd);
182
146 return (usage_page == 0xf1d0); 183 return (usage_page == 0xf1d0);
147} 184}
148 185
149static int 186static int
150parse_uevent(struct udev_device *dev, int16_t *vendor_id, int16_t *product_id) 187parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
188 int16_t *product_id)
151{ 189{
152 const char *uevent;
153 char *cp; 190 char *cp;
154 char *p; 191 char *p;
155 char *s; 192 char *s;
156 int ok = -1; 193 int ok = -1;
157 short unsigned int x; 194 short unsigned int x;
158 short unsigned int y; 195 short unsigned int y;
159 196 short unsigned int z;
160 if ((uevent = udev_device_get_sysattr_value(dev, "uevent")) == NULL)
161 return (-1);
162 197
163 if ((s = cp = strdup(uevent)) == NULL) 198 if ((s = cp = strdup(uevent)) == NULL)
164 return (-1); 199 return (-1);
165 200
166 for ((p = strsep(&cp, "\n")); p && *p != '\0'; (p = strsep(&cp, "\n"))) { 201 while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
167 if (strncmp(p, "HID_ID=", 7) == 0) { 202 if (strncmp(p, "HID_ID=", 7) == 0) {
168 if (sscanf(p + 7, "%*x:%hx:%hx", &x, &y) == 2) { 203 if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
169 *vendor_id = (int16_t)x; 204 *bus = (int)x;
170 *product_id = (int16_t)y; 205 *vendor_id = (int16_t)y;
206 *product_id = (int16_t)z;
171 ok = 0; 207 ok = 0;
208 break;
172 } 209 }
173 break;
174 } 210 }
175 } 211 }
176 212
@@ -179,17 +215,36 @@ parse_uevent(struct udev_device *dev, int16_t *vendor_id, int16_t *product_id)
179 return (ok); 215 return (ok);
180} 216}
181 217
218static char *
219get_parent_attr(struct udev_device *dev, const char *subsystem,
220 const char *devtype, const char *attr)
221{
222 struct udev_device *parent;
223 const char *value;
224
225 if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
226 subsystem, devtype)) == NULL || (value =
227 udev_device_get_sysattr_value(parent, attr)) == NULL)
228 return (NULL);
229
230 return (strdup(value));
231}
232
233static char *
234get_usb_attr(struct udev_device *dev, const char *attr)
235{
236 return (get_parent_attr(dev, "usb", "usb_device", attr));
237}
238
182static int 239static int
183copy_info(fido_dev_info_t *di, struct udev *udev, 240copy_info(fido_dev_info_t *di, struct udev *udev,
184 struct udev_list_entry *udev_entry) 241 struct udev_list_entry *udev_entry)
185{ 242{
186 const char *name; 243 const char *name;
187 const char *path; 244 const char *path;
188 const char *manufacturer; 245 char *uevent = NULL;
189 const char *product;
190 struct udev_device *dev = NULL; 246 struct udev_device *dev = NULL;
191 struct udev_device *hid_parent; 247 int bus = 0;
192 struct udev_device *usb_parent;
193 int ok = -1; 248 int ok = -1;
194 249
195 memset(di, 0, sizeof(*di)); 250 memset(di, 0, sizeof(*di));
@@ -200,28 +255,24 @@ copy_info(fido_dev_info_t *di, struct udev *udev,
200 is_fido(path) == 0) 255 is_fido(path) == 0)
201 goto fail; 256 goto fail;
202 257
203 if ((hid_parent = udev_device_get_parent_with_subsystem_devtype(dev, 258 if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
204 "hid", NULL)) == NULL) 259 parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
205 goto fail; 260 fido_log_debug("%s: uevent", __func__);
206
207 if ((usb_parent = udev_device_get_parent_with_subsystem_devtype(dev,
208 "usb", "usb_device")) == NULL)
209 goto fail; 261 goto fail;
262 }
210 263
211 if (parse_uevent(hid_parent, &di->vendor_id, &di->product_id) < 0 || 264#ifndef FIDO_HID_ANY
212 (manufacturer = udev_device_get_sysattr_value(usb_parent, 265 if (bus != BUS_USB) {
213 "manufacturer")) == NULL || 266 fido_log_debug("%s: bus", __func__);
214 (product = udev_device_get_sysattr_value(usb_parent,
215 "product")) == NULL)
216 goto fail; 267 goto fail;
268 }
269#endif
217 270
218 di->path = strdup(path); 271 di->path = strdup(path);
219 di->manufacturer = strdup(manufacturer); 272 di->manufacturer = get_usb_attr(dev, "manufacturer");
220 di->product = strdup(product); 273 di->product = get_usb_attr(dev, "product");
221 274
222 if (di->path == NULL || 275 if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
223 di->manufacturer == NULL ||
224 di->product == NULL)
225 goto fail; 276 goto fail;
226 277
227 ok = 0; 278 ok = 0;
@@ -229,6 +280,8 @@ fail:
229 if (dev != NULL) 280 if (dev != NULL)
230 udev_device_unref(dev); 281 udev_device_unref(dev);
231 282
283 free(uevent);
284
232 if (ok < 0) { 285 if (ok < 0) {
233 free(di->path); 286 free(di->path);
234 free(di->manufacturer); 287 free(di->manufacturer);
@@ -261,9 +314,13 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
261 goto fail; 314 goto fail;
262 315
263 if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 || 316 if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
264 udev_enumerate_scan_devices(udev_enum) < 0 || 317 udev_enumerate_scan_devices(udev_enum) < 0)
265 (udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) 318 goto fail;
319
320 if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
321 r = FIDO_OK; /* zero hidraw devices */
266 goto fail; 322 goto fail;
323 }
267 324
268 udev_list_entry_foreach(udev_entry, udev_list) { 325 udev_list_entry_foreach(udev_entry, udev_list) {
269 if (copy_info(&devlist[*olen], udev, udev_entry) == 0) { 326 if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
@@ -291,60 +348,157 @@ fail:
291void * 348void *
292fido_hid_open(const char *path) 349fido_hid_open(const char *path)
293{ 350{
294 int *fd; 351 struct hid_linux *ctx;
352 struct hidraw_report_descriptor hrd;
353
354 if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
355 return (NULL);
295 356
296 if ((fd = malloc(sizeof(*fd))) == NULL || 357 if ((ctx->fd = open(path, O_RDWR)) < 0) {
297 (*fd = open(path, O_RDWR)) < 0) { 358 free(ctx);
298 free(fd);
299 return (NULL); 359 return (NULL);
300 } 360 }
301 361
302 return (fd); 362 if (get_report_descriptor(ctx->fd, &hrd) < 0 || get_report_sizes(&hrd,
363 &ctx->report_in_len, &ctx->report_out_len) < 0 ||
364 ctx->report_in_len == 0 || ctx->report_out_len == 0) {
365 fido_log_debug("%s: using default report sizes", __func__);
366 ctx->report_in_len = CTAP_MAX_REPORT_LEN;
367 ctx->report_out_len = CTAP_MAX_REPORT_LEN;
368 }
369
370 return (ctx);
303} 371}
304 372
305void 373void
306fido_hid_close(void *handle) 374fido_hid_close(void *handle)
307{ 375{
308 int *fd = handle; 376 struct hid_linux *ctx = handle;
377
378 close(ctx->fd);
379 free(ctx);
380}
381
382static int
383timespec_to_ms(const struct timespec *ts, int upper_bound)
384{
385 int64_t x;
386 int64_t y;
387
388 if (ts->tv_sec < 0 || ts->tv_sec > INT64_MAX / 1000LL ||
389 ts->tv_nsec < 0 || ts->tv_nsec / 1000000LL > INT64_MAX)
390 return (upper_bound);
391
392 x = ts->tv_sec * 1000LL;
393 y = ts->tv_nsec / 1000000LL;
394
395 if (INT64_MAX - x < y || x + y > upper_bound)
396 return (upper_bound);
397
398 return (int)(x + y);
399}
400
401static int
402waitfd(int fd, int ms)
403{
404 struct timespec ts_start;
405 struct timespec ts_now;
406 struct timespec ts_delta;
407 struct pollfd pfd;
408 int ms_remain;
409 int r;
410
411 if (ms < 0)
412 return (0);
413
414 memset(&pfd, 0, sizeof(pfd));
415 pfd.events = POLLIN;
416 pfd.fd = fd;
417
418 if (clock_gettime(CLOCK_MONOTONIC, &ts_start) != 0) {
419 fido_log_debug("%s: clock_gettime: %s", __func__,
420 strerror(errno));
421 return (-1);
422 }
423
424 for (ms_remain = ms; ms_remain > 0;) {
425 if ((r = poll(&pfd, 1, ms_remain)) > 0)
426 return (0);
427 else if (r == 0)
428 break;
429 else if (errno != EINTR) {
430 fido_log_debug("%s: poll: %s", __func__,
431 strerror(errno));
432 return (-1);
433 }
434 /* poll interrupted - subtract time already waited */
435 if (clock_gettime(CLOCK_MONOTONIC, &ts_now) != 0) {
436 fido_log_debug("%s: clock_gettime: %s", __func__,
437 strerror(errno));
438 return (-1);
439 }
440 timespecsub(&ts_now, &ts_start, &ts_delta);
441 ms_remain = ms - timespec_to_ms(&ts_delta, ms);
442 }
309 443
310 close(*fd); 444 return (-1);
311 free(fd);
312} 445}
313 446
314int 447int
315fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) 448fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
316{ 449{
317 int *fd = handle; 450 struct hid_linux *ctx = handle;
318 ssize_t r; 451 ssize_t r;
319 452
320 (void)ms; /* XXX */ 453 if (len != ctx->report_in_len) {
454 fido_log_debug("%s: len %zu", __func__, len);
455 return (-1);
456 }
321 457
322 if (len != REPORT_LEN - 1) { 458 if (waitfd(ctx->fd, ms) < 0) {
323 fido_log_debug("%s: invalid len", __func__); 459 fido_log_debug("%s: fd not ready", __func__);
324 return (-1); 460 return (-1);
325 } 461 }
326 462
327 if ((r = read(*fd, buf, len)) < 0 || r != REPORT_LEN - 1) 463 if ((r = read(ctx->fd, buf, len)) < 0 || (size_t)r != len) {
464 fido_log_debug("%s: read", __func__);
328 return (-1); 465 return (-1);
466 }
329 467
330 return (REPORT_LEN - 1); 468 return ((int)r);
331} 469}
332 470
333int 471int
334fido_hid_write(void *handle, const unsigned char *buf, size_t len) 472fido_hid_write(void *handle, const unsigned char *buf, size_t len)
335{ 473{
336 int *fd = handle; 474 struct hid_linux *ctx = handle;
337 ssize_t r; 475 ssize_t r;
338 476
339 if (len != REPORT_LEN) { 477 if (len != ctx->report_out_len + 1) {
340 fido_log_debug("%s: invalid len", __func__); 478 fido_log_debug("%s: len %zu", __func__, len);
341 return (-1); 479 return (-1);
342 } 480 }
343 481
344 if ((r = write(*fd, buf, len)) < 0 || r != REPORT_LEN) { 482 if ((r = write(ctx->fd, buf, len)) < 0 || (size_t)r != len) {
345 fido_log_debug("%s: write", __func__); 483 fido_log_debug("%s: write", __func__);
346 return (-1); 484 return (-1);
347 } 485 }
348 486
349 return (REPORT_LEN); 487 return ((int)r);
488}
489
490size_t
491fido_hid_report_in_len(void *handle)
492{
493 struct hid_linux *ctx = handle;
494
495 return (ctx->report_in_len);
496}
497
498size_t
499fido_hid_report_out_len(void *handle)
500{
501 struct hid_linux *ctx = handle;
502
503 return (ctx->report_out_len);
350} 504}