diff options
Diffstat (limited to 'src/hid_linux.c')
-rw-r--r-- | src/hid_linux.c | 308 |
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 | 22 | struct hid_linux { |
23 | int fd; | ||
24 | size_t report_in_len; | ||
25 | size_t report_out_len; | ||
26 | }; | ||
21 | 27 | ||
22 | static int | 28 | static int |
23 | get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) | 29 | get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) |
@@ -63,11 +69,8 @@ static int | |||
63 | get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page, | 69 | get_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 | ||
99 | static int | 102 | static int |
100 | get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) | 103 | get_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 | |||
139 | static int | ||
140 | get_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); |
125 | fail: | ||
126 | if (fd != -1) | ||
127 | close(fd); | ||
128 | |||
129 | return (ok); | ||
130 | } | 158 | } |
131 | 159 | ||
132 | static bool | 160 | static bool |
133 | is_fido(const char *path) | 161 | is_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 | ||
149 | static int | 186 | static int |
150 | parse_uevent(struct udev_device *dev, int16_t *vendor_id, int16_t *product_id) | 187 | parse_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 | ||
218 | static char * | ||
219 | get_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 | |||
233 | static char * | ||
234 | get_usb_attr(struct udev_device *dev, const char *attr) | ||
235 | { | ||
236 | return (get_parent_attr(dev, "usb", "usb_device", attr)); | ||
237 | } | ||
238 | |||
182 | static int | 239 | static int |
183 | copy_info(fido_dev_info_t *di, struct udev *udev, | 240 | copy_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: | |||
291 | void * | 348 | void * |
292 | fido_hid_open(const char *path) | 349 | fido_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 | ||
305 | void | 373 | void |
306 | fido_hid_close(void *handle) | 374 | fido_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 | |||
382 | static int | ||
383 | timespec_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 | |||
401 | static int | ||
402 | waitfd(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 | ||
314 | int | 447 | int |
315 | fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) | 448 | fido_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 | ||
333 | int | 471 | int |
334 | fido_hid_write(void *handle, const unsigned char *buf, size_t len) | 472 | fido_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 | |||
490 | size_t | ||
491 | fido_hid_report_in_len(void *handle) | ||
492 | { | ||
493 | struct hid_linux *ctx = handle; | ||
494 | |||
495 | return (ctx->report_in_len); | ||
496 | } | ||
497 | |||
498 | size_t | ||
499 | fido_hid_report_out_len(void *handle) | ||
500 | { | ||
501 | struct hid_linux *ctx = handle; | ||
502 | |||
503 | return (ctx->report_out_len); | ||
350 | } | 504 | } |