18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * U2F Zero LED and RNG driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2018 Andrej Shadura <andrew@shadura.me> 68c2ecf20Sopenharmony_ci * Loosely based on drivers/hid/hid-led.c 78c2ecf20Sopenharmony_ci * and drivers/usb/misc/chaoskey.c 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or 108c2ecf20Sopenharmony_ci * modify it under the terms of the GNU General Public License as 118c2ecf20Sopenharmony_ci * published by the Free Software Foundation, version 2. 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/hid.h> 158c2ecf20Sopenharmony_ci#include <linux/hidraw.h> 168c2ecf20Sopenharmony_ci#include <linux/hw_random.h> 178c2ecf20Sopenharmony_ci#include <linux/leds.h> 188c2ecf20Sopenharmony_ci#include <linux/module.h> 198c2ecf20Sopenharmony_ci#include <linux/mutex.h> 208c2ecf20Sopenharmony_ci#include <linux/usb.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include "usbhid/usbhid.h" 238c2ecf20Sopenharmony_ci#include "hid-ids.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define DRIVER_SHORT "u2fzero" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define HID_REPORT_SIZE 64 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* We only use broadcast (CID-less) messages */ 308c2ecf20Sopenharmony_ci#define CID_BROADCAST 0xffffffff 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct u2f_hid_msg { 338c2ecf20Sopenharmony_ci u32 cid; 348c2ecf20Sopenharmony_ci union { 358c2ecf20Sopenharmony_ci struct { 368c2ecf20Sopenharmony_ci u8 cmd; 378c2ecf20Sopenharmony_ci u8 bcnth; 388c2ecf20Sopenharmony_ci u8 bcntl; 398c2ecf20Sopenharmony_ci u8 data[HID_REPORT_SIZE - 7]; 408c2ecf20Sopenharmony_ci } init; 418c2ecf20Sopenharmony_ci struct { 428c2ecf20Sopenharmony_ci u8 seq; 438c2ecf20Sopenharmony_ci u8 data[HID_REPORT_SIZE - 5]; 448c2ecf20Sopenharmony_ci } cont; 458c2ecf20Sopenharmony_ci }; 468c2ecf20Sopenharmony_ci} __packed; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistruct u2f_hid_report { 498c2ecf20Sopenharmony_ci u8 report_type; 508c2ecf20Sopenharmony_ci struct u2f_hid_msg msg; 518c2ecf20Sopenharmony_ci} __packed; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define U2F_HID_MSG_LEN(f) (size_t)(((f).init.bcnth << 8) + (f).init.bcntl) 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* Custom extensions to the U2FHID protocol */ 568c2ecf20Sopenharmony_ci#define U2F_CUSTOM_GET_RNG 0x21 578c2ecf20Sopenharmony_ci#define U2F_CUSTOM_WINK 0x24 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistruct u2fzero_device { 608c2ecf20Sopenharmony_ci struct hid_device *hdev; 618c2ecf20Sopenharmony_ci struct urb *urb; /* URB for the RNG data */ 628c2ecf20Sopenharmony_ci struct led_classdev ldev; /* Embedded struct for led */ 638c2ecf20Sopenharmony_ci struct hwrng hwrng; /* Embedded struct for hwrng */ 648c2ecf20Sopenharmony_ci char *led_name; 658c2ecf20Sopenharmony_ci char *rng_name; 668c2ecf20Sopenharmony_ci u8 *buf_out; 678c2ecf20Sopenharmony_ci u8 *buf_in; 688c2ecf20Sopenharmony_ci struct mutex lock; 698c2ecf20Sopenharmony_ci bool present; 708c2ecf20Sopenharmony_ci}; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic int u2fzero_send(struct u2fzero_device *dev, struct u2f_hid_report *req) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci int ret; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci mutex_lock(&dev->lock); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci memcpy(dev->buf_out, req, sizeof(struct u2f_hid_report)); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci ret = hid_hw_output_report(dev->hdev, dev->buf_out, 818c2ecf20Sopenharmony_ci sizeof(struct u2f_hid_msg)); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci mutex_unlock(&dev->lock); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci if (ret < 0) 868c2ecf20Sopenharmony_ci return ret; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return ret == sizeof(struct u2f_hid_msg) ? 0 : -EMSGSIZE; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistruct u2fzero_transfer_context { 928c2ecf20Sopenharmony_ci struct completion done; 938c2ecf20Sopenharmony_ci int status; 948c2ecf20Sopenharmony_ci}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic void u2fzero_read_callback(struct urb *urb) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci struct u2fzero_transfer_context *ctx = urb->context; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci ctx->status = urb->status; 1018c2ecf20Sopenharmony_ci complete(&ctx->done); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int u2fzero_recv(struct u2fzero_device *dev, 1058c2ecf20Sopenharmony_ci struct u2f_hid_report *req, 1068c2ecf20Sopenharmony_ci struct u2f_hid_msg *resp) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci int ret; 1098c2ecf20Sopenharmony_ci struct hid_device *hdev = dev->hdev; 1108c2ecf20Sopenharmony_ci struct u2fzero_transfer_context ctx; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci mutex_lock(&dev->lock); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci memcpy(dev->buf_out, req, sizeof(struct u2f_hid_report)); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci dev->urb->context = &ctx; 1178c2ecf20Sopenharmony_ci init_completion(&ctx.done); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci ret = usb_submit_urb(dev->urb, GFP_NOIO); 1208c2ecf20Sopenharmony_ci if (unlikely(ret)) { 1218c2ecf20Sopenharmony_ci hid_err(hdev, "usb_submit_urb failed: %d", ret); 1228c2ecf20Sopenharmony_ci goto err; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci ret = hid_hw_output_report(dev->hdev, dev->buf_out, 1268c2ecf20Sopenharmony_ci sizeof(struct u2f_hid_msg)); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (ret < 0) { 1298c2ecf20Sopenharmony_ci hid_err(hdev, "hid_hw_output_report failed: %d", ret); 1308c2ecf20Sopenharmony_ci goto err; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci ret = (wait_for_completion_timeout( 1348c2ecf20Sopenharmony_ci &ctx.done, msecs_to_jiffies(USB_CTRL_SET_TIMEOUT))); 1358c2ecf20Sopenharmony_ci if (ret == 0) { 1368c2ecf20Sopenharmony_ci usb_kill_urb(dev->urb); 1378c2ecf20Sopenharmony_ci hid_err(hdev, "urb submission timed out"); 1388c2ecf20Sopenharmony_ci } else { 1398c2ecf20Sopenharmony_ci ret = dev->urb->actual_length; 1408c2ecf20Sopenharmony_ci memcpy(resp, dev->buf_in, ret); 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cierr: 1448c2ecf20Sopenharmony_ci mutex_unlock(&dev->lock); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return ret; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_cistatic int u2fzero_blink(struct led_classdev *ldev) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci struct u2fzero_device *dev = container_of(ldev, 1528c2ecf20Sopenharmony_ci struct u2fzero_device, ldev); 1538c2ecf20Sopenharmony_ci struct u2f_hid_report req = { 1548c2ecf20Sopenharmony_ci .report_type = 0, 1558c2ecf20Sopenharmony_ci .msg.cid = CID_BROADCAST, 1568c2ecf20Sopenharmony_ci .msg.init = { 1578c2ecf20Sopenharmony_ci .cmd = U2F_CUSTOM_WINK, 1588c2ecf20Sopenharmony_ci .bcnth = 0, 1598c2ecf20Sopenharmony_ci .bcntl = 0, 1608c2ecf20Sopenharmony_ci .data = {0}, 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci }; 1638c2ecf20Sopenharmony_ci return u2fzero_send(dev, &req); 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic int u2fzero_brightness_set(struct led_classdev *ldev, 1678c2ecf20Sopenharmony_ci enum led_brightness brightness) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci ldev->brightness = LED_OFF; 1708c2ecf20Sopenharmony_ci if (brightness) 1718c2ecf20Sopenharmony_ci return u2fzero_blink(ldev); 1728c2ecf20Sopenharmony_ci else 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic int u2fzero_rng_read(struct hwrng *rng, void *data, 1778c2ecf20Sopenharmony_ci size_t max, bool wait) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci struct u2fzero_device *dev = container_of(rng, 1808c2ecf20Sopenharmony_ci struct u2fzero_device, hwrng); 1818c2ecf20Sopenharmony_ci struct u2f_hid_report req = { 1828c2ecf20Sopenharmony_ci .report_type = 0, 1838c2ecf20Sopenharmony_ci .msg.cid = CID_BROADCAST, 1848c2ecf20Sopenharmony_ci .msg.init = { 1858c2ecf20Sopenharmony_ci .cmd = U2F_CUSTOM_GET_RNG, 1868c2ecf20Sopenharmony_ci .bcnth = 0, 1878c2ecf20Sopenharmony_ci .bcntl = 0, 1888c2ecf20Sopenharmony_ci .data = {0}, 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci }; 1918c2ecf20Sopenharmony_ci struct u2f_hid_msg resp; 1928c2ecf20Sopenharmony_ci int ret; 1938c2ecf20Sopenharmony_ci size_t actual_length; 1948c2ecf20Sopenharmony_ci /* valid packets must have a correct header */ 1958c2ecf20Sopenharmony_ci int min_length = offsetof(struct u2f_hid_msg, init.data); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (!dev->present) { 1988c2ecf20Sopenharmony_ci hid_dbg(dev->hdev, "device not present"); 1998c2ecf20Sopenharmony_ci return 0; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci ret = u2fzero_recv(dev, &req, &resp); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* ignore errors or packets without data */ 2058c2ecf20Sopenharmony_ci if (ret < min_length) 2068c2ecf20Sopenharmony_ci return 0; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* only take the minimum amount of data it is safe to take */ 2098c2ecf20Sopenharmony_ci actual_length = min3((size_t)ret - min_length, 2108c2ecf20Sopenharmony_ci U2F_HID_MSG_LEN(resp), max); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci memcpy(data, resp.init.data, actual_length); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci return actual_length; 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic int u2fzero_init_led(struct u2fzero_device *dev, 2188c2ecf20Sopenharmony_ci unsigned int minor) 2198c2ecf20Sopenharmony_ci{ 2208c2ecf20Sopenharmony_ci dev->led_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, 2218c2ecf20Sopenharmony_ci "%s%u", DRIVER_SHORT, minor); 2228c2ecf20Sopenharmony_ci if (dev->led_name == NULL) 2238c2ecf20Sopenharmony_ci return -ENOMEM; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci dev->ldev.name = dev->led_name; 2268c2ecf20Sopenharmony_ci dev->ldev.max_brightness = LED_ON; 2278c2ecf20Sopenharmony_ci dev->ldev.flags = LED_HW_PLUGGABLE; 2288c2ecf20Sopenharmony_ci dev->ldev.brightness_set_blocking = u2fzero_brightness_set; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci return devm_led_classdev_register(&dev->hdev->dev, &dev->ldev); 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic int u2fzero_init_hwrng(struct u2fzero_device *dev, 2348c2ecf20Sopenharmony_ci unsigned int minor) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci dev->rng_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, 2378c2ecf20Sopenharmony_ci "%s-rng%u", DRIVER_SHORT, minor); 2388c2ecf20Sopenharmony_ci if (dev->rng_name == NULL) 2398c2ecf20Sopenharmony_ci return -ENOMEM; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci dev->hwrng.name = dev->rng_name; 2428c2ecf20Sopenharmony_ci dev->hwrng.read = u2fzero_rng_read; 2438c2ecf20Sopenharmony_ci dev->hwrng.quality = 1; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci return devm_hwrng_register(&dev->hdev->dev, &dev->hwrng); 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic int u2fzero_fill_in_urb(struct u2fzero_device *dev) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci struct hid_device *hdev = dev->hdev; 2518c2ecf20Sopenharmony_ci struct usb_device *udev; 2528c2ecf20Sopenharmony_ci struct usbhid_device *usbhid = hdev->driver_data; 2538c2ecf20Sopenharmony_ci unsigned int pipe_in; 2548c2ecf20Sopenharmony_ci struct usb_host_endpoint *ep; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci if (dev->hdev->bus != BUS_USB) 2578c2ecf20Sopenharmony_ci return -EINVAL; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci udev = hid_to_usb_dev(hdev); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci if (!usbhid->urbout || !usbhid->urbin) 2628c2ecf20Sopenharmony_ci return -ENODEV; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci ep = usb_pipe_endpoint(udev, usbhid->urbin->pipe); 2658c2ecf20Sopenharmony_ci if (!ep) 2668c2ecf20Sopenharmony_ci return -ENODEV; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci dev->urb = usb_alloc_urb(0, GFP_KERNEL); 2698c2ecf20Sopenharmony_ci if (!dev->urb) 2708c2ecf20Sopenharmony_ci return -ENOMEM; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci pipe_in = (usbhid->urbin->pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci usb_fill_int_urb(dev->urb, 2758c2ecf20Sopenharmony_ci udev, 2768c2ecf20Sopenharmony_ci pipe_in, 2778c2ecf20Sopenharmony_ci dev->buf_in, 2788c2ecf20Sopenharmony_ci HID_REPORT_SIZE, 2798c2ecf20Sopenharmony_ci u2fzero_read_callback, 2808c2ecf20Sopenharmony_ci NULL, 2818c2ecf20Sopenharmony_ci ep->desc.bInterval); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic int u2fzero_probe(struct hid_device *hdev, 2878c2ecf20Sopenharmony_ci const struct hid_device_id *id) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci struct u2fzero_device *dev; 2908c2ecf20Sopenharmony_ci unsigned int minor; 2918c2ecf20Sopenharmony_ci int ret; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci if (!hid_is_usb(hdev)) 2948c2ecf20Sopenharmony_ci return -EINVAL; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); 2978c2ecf20Sopenharmony_ci if (dev == NULL) 2988c2ecf20Sopenharmony_ci return -ENOMEM; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci dev->buf_out = devm_kmalloc(&hdev->dev, 3018c2ecf20Sopenharmony_ci sizeof(struct u2f_hid_report), GFP_KERNEL); 3028c2ecf20Sopenharmony_ci if (dev->buf_out == NULL) 3038c2ecf20Sopenharmony_ci return -ENOMEM; 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci dev->buf_in = devm_kmalloc(&hdev->dev, 3068c2ecf20Sopenharmony_ci sizeof(struct u2f_hid_msg), GFP_KERNEL); 3078c2ecf20Sopenharmony_ci if (dev->buf_in == NULL) 3088c2ecf20Sopenharmony_ci return -ENOMEM; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci ret = hid_parse(hdev); 3118c2ecf20Sopenharmony_ci if (ret) 3128c2ecf20Sopenharmony_ci return ret; 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci dev->hdev = hdev; 3158c2ecf20Sopenharmony_ci hid_set_drvdata(hdev, dev); 3168c2ecf20Sopenharmony_ci mutex_init(&dev->lock); 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 3198c2ecf20Sopenharmony_ci if (ret) 3208c2ecf20Sopenharmony_ci return ret; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci u2fzero_fill_in_urb(dev); 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci dev->present = true; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci minor = ((struct hidraw *) hdev->hidraw)->minor; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci ret = u2fzero_init_led(dev, minor); 3298c2ecf20Sopenharmony_ci if (ret) { 3308c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 3318c2ecf20Sopenharmony_ci return ret; 3328c2ecf20Sopenharmony_ci } 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci hid_info(hdev, "U2F Zero LED initialised\n"); 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci ret = u2fzero_init_hwrng(dev, minor); 3378c2ecf20Sopenharmony_ci if (ret) { 3388c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 3398c2ecf20Sopenharmony_ci return ret; 3408c2ecf20Sopenharmony_ci } 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci hid_info(hdev, "U2F Zero RNG initialised\n"); 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci return 0; 3458c2ecf20Sopenharmony_ci} 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_cistatic void u2fzero_remove(struct hid_device *hdev) 3488c2ecf20Sopenharmony_ci{ 3498c2ecf20Sopenharmony_ci struct u2fzero_device *dev = hid_get_drvdata(hdev); 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci mutex_lock(&dev->lock); 3528c2ecf20Sopenharmony_ci dev->present = false; 3538c2ecf20Sopenharmony_ci mutex_unlock(&dev->lock); 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 3568c2ecf20Sopenharmony_ci usb_poison_urb(dev->urb); 3578c2ecf20Sopenharmony_ci usb_free_urb(dev->urb); 3588c2ecf20Sopenharmony_ci} 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_cistatic const struct hid_device_id u2fzero_table[] = { 3618c2ecf20Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, 3628c2ecf20Sopenharmony_ci USB_DEVICE_ID_U2F_ZERO) }, 3638c2ecf20Sopenharmony_ci { } 3648c2ecf20Sopenharmony_ci}; 3658c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(hid, u2fzero_table); 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_cistatic struct hid_driver u2fzero_driver = { 3688c2ecf20Sopenharmony_ci .name = "hid-" DRIVER_SHORT, 3698c2ecf20Sopenharmony_ci .probe = u2fzero_probe, 3708c2ecf20Sopenharmony_ci .remove = u2fzero_remove, 3718c2ecf20Sopenharmony_ci .id_table = u2fzero_table, 3728c2ecf20Sopenharmony_ci}; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cimodule_hid_driver(u2fzero_driver); 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3778c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andrej Shadura <andrew@shadura.me>"); 3788c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("U2F Zero LED and RNG driver"); 379