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 29*6748031aSAndrej Shadura enum hw_revision { 30*6748031aSAndrej Shadura HW_U2FZERO, 31*6748031aSAndrej Shadura HW_NITROKEY_U2F, 32*6748031aSAndrej Shadura }; 33*6748031aSAndrej Shadura 34*6748031aSAndrej Shadura struct hw_revision_config { 35*6748031aSAndrej Shadura u8 rng_cmd; 36*6748031aSAndrej Shadura u8 wink_cmd; 37*6748031aSAndrej Shadura const char *name; 38*6748031aSAndrej Shadura }; 39*6748031aSAndrej Shadura 40*6748031aSAndrej Shadura static const struct hw_revision_config hw_configs[] = { 41*6748031aSAndrej Shadura [HW_U2FZERO] = { 42*6748031aSAndrej Shadura .rng_cmd = 0x21, 43*6748031aSAndrej Shadura .wink_cmd = 0x24, 44*6748031aSAndrej Shadura .name = "U2F Zero", 45*6748031aSAndrej Shadura }, 46*6748031aSAndrej Shadura [HW_NITROKEY_U2F] = { 47*6748031aSAndrej Shadura .rng_cmd = 0xc0, 48*6748031aSAndrej Shadura .wink_cmd = 0xc2, 49*6748031aSAndrej Shadura .name = "NitroKey U2F", 50*6748031aSAndrej Shadura }, 51*6748031aSAndrej Shadura }; 52*6748031aSAndrej 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; 90*6748031aSAndrej 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))); 15642337b9dSAndrej 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 = { 178*6748031aSAndrej 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 = { 206*6748031aSAndrej 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; 21542337b9dSAndrej Shadura 21642337b9dSAndrej Shadura if (!dev->present) { 21742337b9dSAndrej Shadura hid_dbg(dev->hdev, "device not present"); 21842337b9dSAndrej Shadura return 0; 21942337b9dSAndrej Shadura } 22042337b9dSAndrej Shadura 22142337b9dSAndrej Shadura ret = u2fzero_recv(dev, &req, &resp); 22222d65765SAndrej Shadura 22322d65765SAndrej Shadura /* ignore errors or packets without data */ 22422d65765SAndrej Shadura if (ret < offsetof(struct u2f_hid_msg, init.data)) 22542337b9dSAndrej Shadura return 0; 22642337b9dSAndrej Shadura 22742337b9dSAndrej Shadura /* only take the minimum amount of data it is safe to take */ 22842337b9dSAndrej Shadura actual_length = min3((size_t)ret - offsetof(struct u2f_hid_msg, 22942337b9dSAndrej Shadura init.data), U2F_HID_MSG_LEN(resp), max); 23042337b9dSAndrej Shadura 23142337b9dSAndrej Shadura memcpy(data, resp.init.data, actual_length); 23242337b9dSAndrej Shadura 23342337b9dSAndrej Shadura return actual_length; 23442337b9dSAndrej Shadura } 23542337b9dSAndrej Shadura 23642337b9dSAndrej Shadura static int u2fzero_init_led(struct u2fzero_device *dev, 23742337b9dSAndrej Shadura unsigned int minor) 23842337b9dSAndrej Shadura { 23942337b9dSAndrej Shadura dev->led_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, 24042337b9dSAndrej Shadura "%s%u", DRIVER_SHORT, minor); 24142337b9dSAndrej Shadura if (dev->led_name == NULL) 24242337b9dSAndrej Shadura return -ENOMEM; 24342337b9dSAndrej Shadura 24442337b9dSAndrej Shadura dev->ldev.name = dev->led_name; 24542337b9dSAndrej Shadura dev->ldev.max_brightness = LED_ON; 24642337b9dSAndrej Shadura dev->ldev.flags = LED_HW_PLUGGABLE; 24742337b9dSAndrej Shadura dev->ldev.brightness_set_blocking = u2fzero_brightness_set; 24842337b9dSAndrej Shadura 24942337b9dSAndrej Shadura return devm_led_classdev_register(&dev->hdev->dev, &dev->ldev); 25042337b9dSAndrej Shadura } 25142337b9dSAndrej Shadura 25242337b9dSAndrej Shadura static int u2fzero_init_hwrng(struct u2fzero_device *dev, 25342337b9dSAndrej Shadura unsigned int minor) 25442337b9dSAndrej Shadura { 25542337b9dSAndrej Shadura dev->rng_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, 25642337b9dSAndrej Shadura "%s-rng%u", DRIVER_SHORT, minor); 25742337b9dSAndrej Shadura if (dev->rng_name == NULL) 25842337b9dSAndrej Shadura return -ENOMEM; 25942337b9dSAndrej Shadura 26042337b9dSAndrej Shadura dev->hwrng.name = dev->rng_name; 26142337b9dSAndrej Shadura dev->hwrng.read = u2fzero_rng_read; 26242337b9dSAndrej Shadura dev->hwrng.quality = 1; 26342337b9dSAndrej Shadura 26442337b9dSAndrej Shadura return devm_hwrng_register(&dev->hdev->dev, &dev->hwrng); 26542337b9dSAndrej Shadura } 26642337b9dSAndrej Shadura 26742337b9dSAndrej Shadura static int u2fzero_fill_in_urb(struct u2fzero_device *dev) 26842337b9dSAndrej Shadura { 26942337b9dSAndrej Shadura struct hid_device *hdev = dev->hdev; 27042337b9dSAndrej Shadura struct usb_device *udev; 27142337b9dSAndrej Shadura struct usbhid_device *usbhid = hdev->driver_data; 27242337b9dSAndrej Shadura unsigned int pipe_in; 27342337b9dSAndrej Shadura struct usb_host_endpoint *ep; 27442337b9dSAndrej Shadura 27542337b9dSAndrej Shadura if (dev->hdev->bus != BUS_USB) 27642337b9dSAndrej Shadura return -EINVAL; 27742337b9dSAndrej Shadura 27842337b9dSAndrej Shadura udev = hid_to_usb_dev(hdev); 27942337b9dSAndrej Shadura 28042337b9dSAndrej Shadura if (!usbhid->urbout || !usbhid->urbin) 28142337b9dSAndrej Shadura return -ENODEV; 28242337b9dSAndrej Shadura 28342337b9dSAndrej Shadura ep = usb_pipe_endpoint(udev, usbhid->urbin->pipe); 28442337b9dSAndrej Shadura if (!ep) 28542337b9dSAndrej Shadura return -ENODEV; 28642337b9dSAndrej Shadura 28742337b9dSAndrej Shadura dev->urb = usb_alloc_urb(0, GFP_KERNEL); 28842337b9dSAndrej Shadura if (!dev->urb) 28942337b9dSAndrej Shadura return -ENOMEM; 29042337b9dSAndrej Shadura 29142337b9dSAndrej Shadura pipe_in = (usbhid->urbin->pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); 29242337b9dSAndrej Shadura 29342337b9dSAndrej Shadura usb_fill_int_urb(dev->urb, 29442337b9dSAndrej Shadura udev, 29542337b9dSAndrej Shadura pipe_in, 29642337b9dSAndrej Shadura dev->buf_in, 29742337b9dSAndrej Shadura HID_REPORT_SIZE, 29842337b9dSAndrej Shadura u2fzero_read_callback, 29942337b9dSAndrej Shadura NULL, 30042337b9dSAndrej Shadura ep->desc.bInterval); 30142337b9dSAndrej Shadura 30242337b9dSAndrej Shadura return 0; 30342337b9dSAndrej Shadura } 30442337b9dSAndrej Shadura 30542337b9dSAndrej Shadura static int u2fzero_probe(struct hid_device *hdev, 30642337b9dSAndrej Shadura const struct hid_device_id *id) 30742337b9dSAndrej Shadura { 30842337b9dSAndrej Shadura struct u2fzero_device *dev; 30942337b9dSAndrej Shadura unsigned int minor; 31042337b9dSAndrej Shadura int ret; 31142337b9dSAndrej Shadura 31259579a8dSJiri Kosina if (!hid_is_using_ll_driver(hdev, &usb_hid_driver)) 31359579a8dSJiri Kosina return -EINVAL; 31459579a8dSJiri Kosina 31542337b9dSAndrej Shadura dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); 31642337b9dSAndrej Shadura if (dev == NULL) 31742337b9dSAndrej Shadura return -ENOMEM; 31842337b9dSAndrej Shadura 319*6748031aSAndrej Shadura dev->hw_revision = id->driver_data; 320*6748031aSAndrej Shadura 32142337b9dSAndrej Shadura dev->buf_out = devm_kmalloc(&hdev->dev, 32242337b9dSAndrej Shadura sizeof(struct u2f_hid_report), GFP_KERNEL); 32342337b9dSAndrej Shadura if (dev->buf_out == NULL) 32442337b9dSAndrej Shadura return -ENOMEM; 32542337b9dSAndrej Shadura 32642337b9dSAndrej Shadura dev->buf_in = devm_kmalloc(&hdev->dev, 32742337b9dSAndrej Shadura sizeof(struct u2f_hid_msg), GFP_KERNEL); 32842337b9dSAndrej Shadura if (dev->buf_in == NULL) 32942337b9dSAndrej Shadura return -ENOMEM; 33042337b9dSAndrej Shadura 33142337b9dSAndrej Shadura ret = hid_parse(hdev); 33242337b9dSAndrej Shadura if (ret) 33342337b9dSAndrej Shadura return ret; 33442337b9dSAndrej Shadura 33542337b9dSAndrej Shadura dev->hdev = hdev; 33642337b9dSAndrej Shadura hid_set_drvdata(hdev, dev); 33742337b9dSAndrej Shadura mutex_init(&dev->lock); 33842337b9dSAndrej Shadura 33942337b9dSAndrej Shadura ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 34042337b9dSAndrej Shadura if (ret) 34142337b9dSAndrej Shadura return ret; 34242337b9dSAndrej Shadura 34342337b9dSAndrej Shadura u2fzero_fill_in_urb(dev); 34442337b9dSAndrej Shadura 34542337b9dSAndrej Shadura dev->present = true; 34642337b9dSAndrej Shadura 34742337b9dSAndrej Shadura minor = ((struct hidraw *) hdev->hidraw)->minor; 34842337b9dSAndrej Shadura 34942337b9dSAndrej Shadura ret = u2fzero_init_led(dev, minor); 35042337b9dSAndrej Shadura if (ret) { 35142337b9dSAndrej Shadura hid_hw_stop(hdev); 35242337b9dSAndrej Shadura return ret; 35342337b9dSAndrej Shadura } 35442337b9dSAndrej Shadura 355*6748031aSAndrej Shadura hid_info(hdev, "%s LED initialised\n", hw_configs[dev->hw_revision].name); 35642337b9dSAndrej Shadura 35742337b9dSAndrej Shadura ret = u2fzero_init_hwrng(dev, minor); 35842337b9dSAndrej Shadura if (ret) { 35942337b9dSAndrej Shadura hid_hw_stop(hdev); 36042337b9dSAndrej Shadura return ret; 36142337b9dSAndrej Shadura } 36242337b9dSAndrej Shadura 363*6748031aSAndrej Shadura hid_info(hdev, "%s RNG initialised\n", hw_configs[dev->hw_revision].name); 36442337b9dSAndrej Shadura 36542337b9dSAndrej Shadura return 0; 36642337b9dSAndrej Shadura } 36742337b9dSAndrej Shadura 36842337b9dSAndrej Shadura static void u2fzero_remove(struct hid_device *hdev) 36942337b9dSAndrej Shadura { 37042337b9dSAndrej Shadura struct u2fzero_device *dev = hid_get_drvdata(hdev); 37142337b9dSAndrej Shadura 37242337b9dSAndrej Shadura mutex_lock(&dev->lock); 37342337b9dSAndrej Shadura dev->present = false; 37442337b9dSAndrej Shadura mutex_unlock(&dev->lock); 37542337b9dSAndrej Shadura 37642337b9dSAndrej Shadura hid_hw_stop(hdev); 37742337b9dSAndrej Shadura usb_poison_urb(dev->urb); 37842337b9dSAndrej Shadura usb_free_urb(dev->urb); 37942337b9dSAndrej Shadura } 38042337b9dSAndrej Shadura 38142337b9dSAndrej Shadura static const struct hid_device_id u2fzero_table[] = { 38242337b9dSAndrej Shadura { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, 383*6748031aSAndrej Shadura USB_DEVICE_ID_U2F_ZERO), 384*6748031aSAndrej Shadura .driver_data = HW_U2FZERO }, 385*6748031aSAndrej Shadura { HID_USB_DEVICE(USB_VENDOR_ID_CLAY_LOGIC, 386*6748031aSAndrej Shadura USB_DEVICE_ID_NITROKEY_U2F), 387*6748031aSAndrej Shadura .driver_data = HW_NITROKEY_U2F }, 38842337b9dSAndrej Shadura { } 38942337b9dSAndrej Shadura }; 39042337b9dSAndrej Shadura MODULE_DEVICE_TABLE(hid, u2fzero_table); 39142337b9dSAndrej Shadura 39242337b9dSAndrej Shadura static struct hid_driver u2fzero_driver = { 39342337b9dSAndrej Shadura .name = "hid-" DRIVER_SHORT, 39442337b9dSAndrej Shadura .probe = u2fzero_probe, 39542337b9dSAndrej Shadura .remove = u2fzero_remove, 39642337b9dSAndrej Shadura .id_table = u2fzero_table, 39742337b9dSAndrej Shadura }; 39842337b9dSAndrej Shadura 39942337b9dSAndrej Shadura module_hid_driver(u2fzero_driver); 40042337b9dSAndrej Shadura 40142337b9dSAndrej Shadura MODULE_LICENSE("GPL"); 40242337b9dSAndrej Shadura MODULE_AUTHOR("Andrej Shadura <andrew@shadura.me>"); 40342337b9dSAndrej Shadura MODULE_DESCRIPTION("U2F Zero LED and RNG driver"); 404