142337b9dSAndrej Shadura // SPDX-License-Identifier: GPL-2.0 242337b9dSAndrej Shadura /* 342337b9dSAndrej Shadura * U2F Zero LED and RNG driver 442337b9dSAndrej Shadura * 542337b9dSAndrej Shadura * Copyright 2018 Andrej Shadura <andrew@shadura.me> 642337b9dSAndrej Shadura * Loosely based on drivers/hid/hid-led.c 742337b9dSAndrej Shadura * and drivers/usb/misc/chaoskey.c 842337b9dSAndrej Shadura * 942337b9dSAndrej Shadura * This program is free software; you can redistribute it and/or 1042337b9dSAndrej Shadura * modify it under the terms of the GNU General Public License as 1142337b9dSAndrej Shadura * published by the Free Software Foundation, version 2. 1242337b9dSAndrej Shadura */ 1342337b9dSAndrej Shadura 1442337b9dSAndrej Shadura #include <linux/hid.h> 1542337b9dSAndrej Shadura #include <linux/hidraw.h> 1642337b9dSAndrej Shadura #include <linux/hw_random.h> 1742337b9dSAndrej Shadura #include <linux/leds.h> 1842337b9dSAndrej Shadura #include <linux/module.h> 1942337b9dSAndrej Shadura #include <linux/mutex.h> 2042337b9dSAndrej Shadura #include <linux/usb.h> 2142337b9dSAndrej Shadura 2242337b9dSAndrej Shadura #include "usbhid/usbhid.h" 2342337b9dSAndrej Shadura #include "hid-ids.h" 2442337b9dSAndrej Shadura 2542337b9dSAndrej Shadura #define DRIVER_SHORT "u2fzero" 2642337b9dSAndrej Shadura 2742337b9dSAndrej Shadura #define HID_REPORT_SIZE 64 2842337b9dSAndrej Shadura 296748031aSAndrej Shadura enum hw_revision { 306748031aSAndrej Shadura HW_U2FZERO, 316748031aSAndrej Shadura HW_NITROKEY_U2F, 326748031aSAndrej Shadura }; 336748031aSAndrej Shadura 346748031aSAndrej Shadura struct hw_revision_config { 356748031aSAndrej Shadura u8 rng_cmd; 366748031aSAndrej Shadura u8 wink_cmd; 376748031aSAndrej Shadura const char *name; 386748031aSAndrej Shadura }; 396748031aSAndrej Shadura 406748031aSAndrej Shadura static const struct hw_revision_config hw_configs[] = { 416748031aSAndrej Shadura [HW_U2FZERO] = { 426748031aSAndrej Shadura .rng_cmd = 0x21, 436748031aSAndrej Shadura .wink_cmd = 0x24, 446748031aSAndrej Shadura .name = "U2F Zero", 456748031aSAndrej Shadura }, 466748031aSAndrej Shadura [HW_NITROKEY_U2F] = { 476748031aSAndrej Shadura .rng_cmd = 0xc0, 486748031aSAndrej Shadura .wink_cmd = 0xc2, 496748031aSAndrej Shadura .name = "NitroKey U2F", 506748031aSAndrej Shadura }, 516748031aSAndrej Shadura }; 526748031aSAndrej Shadura 5342337b9dSAndrej Shadura /* We only use broadcast (CID-less) messages */ 5442337b9dSAndrej Shadura #define CID_BROADCAST 0xffffffff 5542337b9dSAndrej Shadura 5642337b9dSAndrej Shadura struct u2f_hid_msg { 5742337b9dSAndrej Shadura u32 cid; 5842337b9dSAndrej Shadura union { 5942337b9dSAndrej Shadura struct { 6042337b9dSAndrej Shadura u8 cmd; 6142337b9dSAndrej Shadura u8 bcnth; 6242337b9dSAndrej Shadura u8 bcntl; 6342337b9dSAndrej Shadura u8 data[HID_REPORT_SIZE - 7]; 6442337b9dSAndrej Shadura } init; 6542337b9dSAndrej Shadura struct { 6642337b9dSAndrej Shadura u8 seq; 6742337b9dSAndrej Shadura u8 data[HID_REPORT_SIZE - 5]; 6842337b9dSAndrej Shadura } cont; 6942337b9dSAndrej Shadura }; 7042337b9dSAndrej Shadura } __packed; 7142337b9dSAndrej Shadura 7242337b9dSAndrej Shadura struct u2f_hid_report { 7342337b9dSAndrej Shadura u8 report_type; 7442337b9dSAndrej Shadura struct u2f_hid_msg msg; 7542337b9dSAndrej Shadura } __packed; 7642337b9dSAndrej Shadura 7742337b9dSAndrej Shadura #define U2F_HID_MSG_LEN(f) (size_t)(((f).init.bcnth << 8) + (f).init.bcntl) 7842337b9dSAndrej Shadura 7942337b9dSAndrej Shadura struct u2fzero_device { 8042337b9dSAndrej Shadura struct hid_device *hdev; 8142337b9dSAndrej Shadura struct urb *urb; /* URB for the RNG data */ 8242337b9dSAndrej Shadura struct led_classdev ldev; /* Embedded struct for led */ 8342337b9dSAndrej Shadura struct hwrng hwrng; /* Embedded struct for hwrng */ 8442337b9dSAndrej Shadura char *led_name; 8542337b9dSAndrej Shadura char *rng_name; 8642337b9dSAndrej Shadura u8 *buf_out; 8742337b9dSAndrej Shadura u8 *buf_in; 8842337b9dSAndrej Shadura struct mutex lock; 8942337b9dSAndrej Shadura bool present; 906748031aSAndrej Shadura kernel_ulong_t hw_revision; 9142337b9dSAndrej Shadura }; 9242337b9dSAndrej Shadura 9342337b9dSAndrej Shadura static int u2fzero_send(struct u2fzero_device *dev, struct u2f_hid_report *req) 9442337b9dSAndrej Shadura { 9542337b9dSAndrej Shadura int ret; 9642337b9dSAndrej Shadura 9742337b9dSAndrej Shadura mutex_lock(&dev->lock); 9842337b9dSAndrej Shadura 9942337b9dSAndrej Shadura memcpy(dev->buf_out, req, sizeof(struct u2f_hid_report)); 10042337b9dSAndrej Shadura 10142337b9dSAndrej Shadura ret = hid_hw_output_report(dev->hdev, dev->buf_out, 10242337b9dSAndrej Shadura sizeof(struct u2f_hid_msg)); 10342337b9dSAndrej Shadura 10442337b9dSAndrej Shadura mutex_unlock(&dev->lock); 10542337b9dSAndrej Shadura 10642337b9dSAndrej Shadura if (ret < 0) 10742337b9dSAndrej Shadura return ret; 10842337b9dSAndrej Shadura 10942337b9dSAndrej Shadura return ret == sizeof(struct u2f_hid_msg) ? 0 : -EMSGSIZE; 11042337b9dSAndrej Shadura } 11142337b9dSAndrej Shadura 11242337b9dSAndrej Shadura struct u2fzero_transfer_context { 11342337b9dSAndrej Shadura struct completion done; 11442337b9dSAndrej Shadura int status; 11542337b9dSAndrej Shadura }; 11642337b9dSAndrej Shadura 11742337b9dSAndrej Shadura static void u2fzero_read_callback(struct urb *urb) 11842337b9dSAndrej Shadura { 11942337b9dSAndrej Shadura struct u2fzero_transfer_context *ctx = urb->context; 12042337b9dSAndrej Shadura 12142337b9dSAndrej Shadura ctx->status = urb->status; 12242337b9dSAndrej Shadura complete(&ctx->done); 12342337b9dSAndrej Shadura } 12442337b9dSAndrej Shadura 12542337b9dSAndrej Shadura static int u2fzero_recv(struct u2fzero_device *dev, 12642337b9dSAndrej Shadura struct u2f_hid_report *req, 12742337b9dSAndrej Shadura struct u2f_hid_msg *resp) 12842337b9dSAndrej Shadura { 12942337b9dSAndrej Shadura int ret; 13042337b9dSAndrej Shadura struct hid_device *hdev = dev->hdev; 13142337b9dSAndrej Shadura struct u2fzero_transfer_context ctx; 13242337b9dSAndrej Shadura 13342337b9dSAndrej Shadura mutex_lock(&dev->lock); 13442337b9dSAndrej Shadura 13542337b9dSAndrej Shadura memcpy(dev->buf_out, req, sizeof(struct u2f_hid_report)); 13642337b9dSAndrej Shadura 13742337b9dSAndrej Shadura dev->urb->context = &ctx; 13842337b9dSAndrej Shadura init_completion(&ctx.done); 13942337b9dSAndrej Shadura 14042337b9dSAndrej Shadura ret = usb_submit_urb(dev->urb, GFP_NOIO); 14142337b9dSAndrej Shadura if (unlikely(ret)) { 14242337b9dSAndrej Shadura hid_err(hdev, "usb_submit_urb failed: %d", ret); 14342337b9dSAndrej Shadura goto err; 14442337b9dSAndrej Shadura } 14542337b9dSAndrej Shadura 14642337b9dSAndrej Shadura ret = hid_hw_output_report(dev->hdev, dev->buf_out, 14742337b9dSAndrej Shadura sizeof(struct u2f_hid_msg)); 14842337b9dSAndrej Shadura 14942337b9dSAndrej Shadura if (ret < 0) { 15042337b9dSAndrej Shadura hid_err(hdev, "hid_hw_output_report failed: %d", ret); 15142337b9dSAndrej Shadura goto err; 15242337b9dSAndrej Shadura } 15342337b9dSAndrej Shadura 15442337b9dSAndrej Shadura ret = (wait_for_completion_timeout( 15542337b9dSAndrej Shadura &ctx.done, msecs_to_jiffies(USB_CTRL_SET_TIMEOUT))); 15643775e62SAndrej Shadura if (ret == 0) { 15742337b9dSAndrej Shadura usb_kill_urb(dev->urb); 15842337b9dSAndrej Shadura hid_err(hdev, "urb submission timed out"); 15942337b9dSAndrej Shadura } else { 16042337b9dSAndrej Shadura ret = dev->urb->actual_length; 16142337b9dSAndrej Shadura memcpy(resp, dev->buf_in, ret); 16242337b9dSAndrej Shadura } 16342337b9dSAndrej Shadura 16442337b9dSAndrej Shadura err: 16542337b9dSAndrej Shadura mutex_unlock(&dev->lock); 16642337b9dSAndrej Shadura 16742337b9dSAndrej Shadura return ret; 16842337b9dSAndrej Shadura } 16942337b9dSAndrej Shadura 17042337b9dSAndrej Shadura static int u2fzero_blink(struct led_classdev *ldev) 17142337b9dSAndrej Shadura { 17242337b9dSAndrej Shadura struct u2fzero_device *dev = container_of(ldev, 17342337b9dSAndrej Shadura struct u2fzero_device, ldev); 17442337b9dSAndrej Shadura struct u2f_hid_report req = { 17542337b9dSAndrej Shadura .report_type = 0, 17642337b9dSAndrej Shadura .msg.cid = CID_BROADCAST, 17742337b9dSAndrej Shadura .msg.init = { 1786748031aSAndrej Shadura .cmd = hw_configs[dev->hw_revision].wink_cmd, 17942337b9dSAndrej Shadura .bcnth = 0, 18042337b9dSAndrej Shadura .bcntl = 0, 18142337b9dSAndrej Shadura .data = {0}, 18242337b9dSAndrej Shadura } 18342337b9dSAndrej Shadura }; 18442337b9dSAndrej Shadura return u2fzero_send(dev, &req); 18542337b9dSAndrej Shadura } 18642337b9dSAndrej Shadura 18742337b9dSAndrej Shadura static int u2fzero_brightness_set(struct led_classdev *ldev, 18842337b9dSAndrej Shadura enum led_brightness brightness) 18942337b9dSAndrej Shadura { 19042337b9dSAndrej Shadura ldev->brightness = LED_OFF; 19142337b9dSAndrej Shadura if (brightness) 19242337b9dSAndrej Shadura return u2fzero_blink(ldev); 19342337b9dSAndrej Shadura else 19442337b9dSAndrej Shadura return 0; 19542337b9dSAndrej Shadura } 19642337b9dSAndrej Shadura 19742337b9dSAndrej Shadura static int u2fzero_rng_read(struct hwrng *rng, void *data, 19842337b9dSAndrej Shadura size_t max, bool wait) 19942337b9dSAndrej Shadura { 20042337b9dSAndrej Shadura struct u2fzero_device *dev = container_of(rng, 20142337b9dSAndrej Shadura struct u2fzero_device, hwrng); 20242337b9dSAndrej Shadura struct u2f_hid_report req = { 20342337b9dSAndrej Shadura .report_type = 0, 20442337b9dSAndrej Shadura .msg.cid = CID_BROADCAST, 20542337b9dSAndrej Shadura .msg.init = { 2066748031aSAndrej Shadura .cmd = hw_configs[dev->hw_revision].rng_cmd, 20742337b9dSAndrej Shadura .bcnth = 0, 20842337b9dSAndrej Shadura .bcntl = 0, 20942337b9dSAndrej Shadura .data = {0}, 21042337b9dSAndrej Shadura } 21142337b9dSAndrej Shadura }; 21242337b9dSAndrej Shadura struct u2f_hid_msg resp; 21342337b9dSAndrej Shadura int ret; 21442337b9dSAndrej Shadura size_t actual_length; 215b7abf78bSAndrej Shadura /* valid packets must have a correct header */ 216b7abf78bSAndrej Shadura int min_length = offsetof(struct u2f_hid_msg, init.data); 21742337b9dSAndrej Shadura 21842337b9dSAndrej Shadura if (!dev->present) { 21942337b9dSAndrej Shadura hid_dbg(dev->hdev, "device not present"); 22042337b9dSAndrej Shadura return 0; 22142337b9dSAndrej Shadura } 22242337b9dSAndrej Shadura 22342337b9dSAndrej Shadura ret = u2fzero_recv(dev, &req, &resp); 22422d65765SAndrej Shadura 22522d65765SAndrej Shadura /* ignore errors or packets without data */ 226b7abf78bSAndrej Shadura if (ret < min_length) 22742337b9dSAndrej Shadura return 0; 22842337b9dSAndrej Shadura 22942337b9dSAndrej Shadura /* only take the minimum amount of data it is safe to take */ 230b7abf78bSAndrej Shadura actual_length = min3((size_t)ret - min_length, 231b7abf78bSAndrej Shadura U2F_HID_MSG_LEN(resp), max); 23242337b9dSAndrej Shadura 23342337b9dSAndrej Shadura memcpy(data, resp.init.data, actual_length); 23442337b9dSAndrej Shadura 23542337b9dSAndrej Shadura return actual_length; 23642337b9dSAndrej Shadura } 23742337b9dSAndrej Shadura 23842337b9dSAndrej Shadura static int u2fzero_init_led(struct u2fzero_device *dev, 23942337b9dSAndrej Shadura unsigned int minor) 24042337b9dSAndrej Shadura { 24142337b9dSAndrej Shadura dev->led_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, 24242337b9dSAndrej Shadura "%s%u", DRIVER_SHORT, minor); 24342337b9dSAndrej Shadura if (dev->led_name == NULL) 24442337b9dSAndrej Shadura return -ENOMEM; 24542337b9dSAndrej Shadura 24642337b9dSAndrej Shadura dev->ldev.name = dev->led_name; 24742337b9dSAndrej Shadura dev->ldev.max_brightness = LED_ON; 24842337b9dSAndrej Shadura dev->ldev.flags = LED_HW_PLUGGABLE; 24942337b9dSAndrej Shadura dev->ldev.brightness_set_blocking = u2fzero_brightness_set; 25042337b9dSAndrej Shadura 25142337b9dSAndrej Shadura return devm_led_classdev_register(&dev->hdev->dev, &dev->ldev); 25242337b9dSAndrej Shadura } 25342337b9dSAndrej Shadura 25442337b9dSAndrej Shadura static int u2fzero_init_hwrng(struct u2fzero_device *dev, 25542337b9dSAndrej Shadura unsigned int minor) 25642337b9dSAndrej Shadura { 25742337b9dSAndrej Shadura dev->rng_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, 25842337b9dSAndrej Shadura "%s-rng%u", DRIVER_SHORT, minor); 25942337b9dSAndrej Shadura if (dev->rng_name == NULL) 26042337b9dSAndrej Shadura return -ENOMEM; 26142337b9dSAndrej Shadura 26242337b9dSAndrej Shadura dev->hwrng.name = dev->rng_name; 26342337b9dSAndrej Shadura dev->hwrng.read = u2fzero_rng_read; 26442337b9dSAndrej Shadura dev->hwrng.quality = 1; 26542337b9dSAndrej Shadura 26642337b9dSAndrej Shadura return devm_hwrng_register(&dev->hdev->dev, &dev->hwrng); 26742337b9dSAndrej Shadura } 26842337b9dSAndrej Shadura 26942337b9dSAndrej Shadura static int u2fzero_fill_in_urb(struct u2fzero_device *dev) 27042337b9dSAndrej Shadura { 27142337b9dSAndrej Shadura struct hid_device *hdev = dev->hdev; 27242337b9dSAndrej Shadura struct usb_device *udev; 27342337b9dSAndrej Shadura struct usbhid_device *usbhid = hdev->driver_data; 27442337b9dSAndrej Shadura unsigned int pipe_in; 27542337b9dSAndrej Shadura struct usb_host_endpoint *ep; 27642337b9dSAndrej Shadura 27742337b9dSAndrej Shadura if (dev->hdev->bus != BUS_USB) 27842337b9dSAndrej Shadura return -EINVAL; 27942337b9dSAndrej Shadura 28042337b9dSAndrej Shadura udev = hid_to_usb_dev(hdev); 28142337b9dSAndrej Shadura 28242337b9dSAndrej Shadura if (!usbhid->urbout || !usbhid->urbin) 28342337b9dSAndrej Shadura return -ENODEV; 28442337b9dSAndrej Shadura 28542337b9dSAndrej Shadura ep = usb_pipe_endpoint(udev, usbhid->urbin->pipe); 28642337b9dSAndrej Shadura if (!ep) 28742337b9dSAndrej Shadura return -ENODEV; 28842337b9dSAndrej Shadura 28942337b9dSAndrej Shadura dev->urb = usb_alloc_urb(0, GFP_KERNEL); 29042337b9dSAndrej Shadura if (!dev->urb) 29142337b9dSAndrej Shadura return -ENOMEM; 29242337b9dSAndrej Shadura 29342337b9dSAndrej Shadura pipe_in = (usbhid->urbin->pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); 29442337b9dSAndrej Shadura 29542337b9dSAndrej Shadura usb_fill_int_urb(dev->urb, 29642337b9dSAndrej Shadura udev, 29742337b9dSAndrej Shadura pipe_in, 29842337b9dSAndrej Shadura dev->buf_in, 29942337b9dSAndrej Shadura HID_REPORT_SIZE, 30042337b9dSAndrej Shadura u2fzero_read_callback, 30142337b9dSAndrej Shadura NULL, 30242337b9dSAndrej Shadura ep->desc.bInterval); 30342337b9dSAndrej Shadura 30442337b9dSAndrej Shadura return 0; 30542337b9dSAndrej Shadura } 30642337b9dSAndrej Shadura 30742337b9dSAndrej Shadura static int u2fzero_probe(struct hid_device *hdev, 30842337b9dSAndrej Shadura const struct hid_device_id *id) 30942337b9dSAndrej Shadura { 31042337b9dSAndrej Shadura struct u2fzero_device *dev; 31142337b9dSAndrej Shadura unsigned int minor; 31242337b9dSAndrej Shadura int ret; 31342337b9dSAndrej Shadura 314*f83baa0cSGreg Kroah-Hartman if (!hid_is_usb(hdev)) 31559579a8dSJiri Kosina return -EINVAL; 31659579a8dSJiri Kosina 31742337b9dSAndrej Shadura dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); 31842337b9dSAndrej Shadura if (dev == NULL) 31942337b9dSAndrej Shadura return -ENOMEM; 32042337b9dSAndrej Shadura 3216748031aSAndrej Shadura dev->hw_revision = id->driver_data; 3226748031aSAndrej Shadura 32342337b9dSAndrej Shadura dev->buf_out = devm_kmalloc(&hdev->dev, 32442337b9dSAndrej Shadura sizeof(struct u2f_hid_report), GFP_KERNEL); 32542337b9dSAndrej Shadura if (dev->buf_out == NULL) 32642337b9dSAndrej Shadura return -ENOMEM; 32742337b9dSAndrej Shadura 32842337b9dSAndrej Shadura dev->buf_in = devm_kmalloc(&hdev->dev, 32942337b9dSAndrej Shadura sizeof(struct u2f_hid_msg), GFP_KERNEL); 33042337b9dSAndrej Shadura if (dev->buf_in == NULL) 33142337b9dSAndrej Shadura return -ENOMEM; 33242337b9dSAndrej Shadura 33342337b9dSAndrej Shadura ret = hid_parse(hdev); 33442337b9dSAndrej Shadura if (ret) 33542337b9dSAndrej Shadura return ret; 33642337b9dSAndrej Shadura 33742337b9dSAndrej Shadura dev->hdev = hdev; 33842337b9dSAndrej Shadura hid_set_drvdata(hdev, dev); 33942337b9dSAndrej Shadura mutex_init(&dev->lock); 34042337b9dSAndrej Shadura 34142337b9dSAndrej Shadura ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 34242337b9dSAndrej Shadura if (ret) 34342337b9dSAndrej Shadura return ret; 34442337b9dSAndrej Shadura 34542337b9dSAndrej Shadura u2fzero_fill_in_urb(dev); 34642337b9dSAndrej Shadura 34742337b9dSAndrej Shadura dev->present = true; 34842337b9dSAndrej Shadura 34942337b9dSAndrej Shadura minor = ((struct hidraw *) hdev->hidraw)->minor; 35042337b9dSAndrej Shadura 35142337b9dSAndrej Shadura ret = u2fzero_init_led(dev, minor); 35242337b9dSAndrej Shadura if (ret) { 35342337b9dSAndrej Shadura hid_hw_stop(hdev); 35442337b9dSAndrej Shadura return ret; 35542337b9dSAndrej Shadura } 35642337b9dSAndrej Shadura 3576748031aSAndrej Shadura hid_info(hdev, "%s LED initialised\n", hw_configs[dev->hw_revision].name); 35842337b9dSAndrej Shadura 35942337b9dSAndrej Shadura ret = u2fzero_init_hwrng(dev, minor); 36042337b9dSAndrej Shadura if (ret) { 36142337b9dSAndrej Shadura hid_hw_stop(hdev); 36242337b9dSAndrej Shadura return ret; 36342337b9dSAndrej Shadura } 36442337b9dSAndrej Shadura 3656748031aSAndrej Shadura hid_info(hdev, "%s RNG initialised\n", hw_configs[dev->hw_revision].name); 36642337b9dSAndrej Shadura 36742337b9dSAndrej Shadura return 0; 36842337b9dSAndrej Shadura } 36942337b9dSAndrej Shadura 37042337b9dSAndrej Shadura static void u2fzero_remove(struct hid_device *hdev) 37142337b9dSAndrej Shadura { 37242337b9dSAndrej Shadura struct u2fzero_device *dev = hid_get_drvdata(hdev); 37342337b9dSAndrej Shadura 37442337b9dSAndrej Shadura mutex_lock(&dev->lock); 37542337b9dSAndrej Shadura dev->present = false; 37642337b9dSAndrej Shadura mutex_unlock(&dev->lock); 37742337b9dSAndrej Shadura 37842337b9dSAndrej Shadura hid_hw_stop(hdev); 37942337b9dSAndrej Shadura usb_poison_urb(dev->urb); 38042337b9dSAndrej Shadura usb_free_urb(dev->urb); 38142337b9dSAndrej Shadura } 38242337b9dSAndrej Shadura 38342337b9dSAndrej Shadura static const struct hid_device_id u2fzero_table[] = { 38442337b9dSAndrej Shadura { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, 3856748031aSAndrej Shadura USB_DEVICE_ID_U2F_ZERO), 3866748031aSAndrej Shadura .driver_data = HW_U2FZERO }, 3876748031aSAndrej Shadura { HID_USB_DEVICE(USB_VENDOR_ID_CLAY_LOGIC, 3886748031aSAndrej Shadura USB_DEVICE_ID_NITROKEY_U2F), 3896748031aSAndrej Shadura .driver_data = HW_NITROKEY_U2F }, 39042337b9dSAndrej Shadura { } 39142337b9dSAndrej Shadura }; 39242337b9dSAndrej Shadura MODULE_DEVICE_TABLE(hid, u2fzero_table); 39342337b9dSAndrej Shadura 39442337b9dSAndrej Shadura static struct hid_driver u2fzero_driver = { 39542337b9dSAndrej Shadura .name = "hid-" DRIVER_SHORT, 39642337b9dSAndrej Shadura .probe = u2fzero_probe, 39742337b9dSAndrej Shadura .remove = u2fzero_remove, 39842337b9dSAndrej Shadura .id_table = u2fzero_table, 39942337b9dSAndrej Shadura }; 40042337b9dSAndrej Shadura 40142337b9dSAndrej Shadura module_hid_driver(u2fzero_driver); 40242337b9dSAndrej Shadura 40342337b9dSAndrej Shadura MODULE_LICENSE("GPL"); 40442337b9dSAndrej Shadura MODULE_AUTHOR("Andrej Shadura <andrew@shadura.me>"); 40542337b9dSAndrej Shadura MODULE_DESCRIPTION("U2F Zero LED and RNG driver"); 406