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
u2fzero_send(struct u2fzero_device * dev,struct u2f_hid_report * req)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
u2fzero_read_callback(struct urb * urb)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
u2fzero_recv(struct u2fzero_device * dev,struct u2f_hid_report * req,struct u2f_hid_msg * resp)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
u2fzero_blink(struct led_classdev * ldev)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
u2fzero_brightness_set(struct led_classdev * ldev,enum led_brightness brightness)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
u2fzero_rng_read(struct hwrng * rng,void * data,size_t max,bool wait)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
u2fzero_init_led(struct u2fzero_device * dev,unsigned int minor)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
u2fzero_init_hwrng(struct u2fzero_device * dev,unsigned int minor)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
26542337b9dSAndrej Shadura return devm_hwrng_register(&dev->hdev->dev, &dev->hwrng);
26642337b9dSAndrej Shadura }
26742337b9dSAndrej Shadura
u2fzero_fill_in_urb(struct u2fzero_device * dev)26842337b9dSAndrej Shadura static int u2fzero_fill_in_urb(struct u2fzero_device *dev)
26942337b9dSAndrej Shadura {
27042337b9dSAndrej Shadura struct hid_device *hdev = dev->hdev;
27142337b9dSAndrej Shadura struct usb_device *udev;
27242337b9dSAndrej Shadura struct usbhid_device *usbhid = hdev->driver_data;
27342337b9dSAndrej Shadura unsigned int pipe_in;
27442337b9dSAndrej Shadura struct usb_host_endpoint *ep;
27542337b9dSAndrej Shadura
27642337b9dSAndrej Shadura if (dev->hdev->bus != BUS_USB)
27742337b9dSAndrej Shadura return -EINVAL;
27842337b9dSAndrej Shadura
27942337b9dSAndrej Shadura udev = hid_to_usb_dev(hdev);
28042337b9dSAndrej Shadura
28142337b9dSAndrej Shadura if (!usbhid->urbout || !usbhid->urbin)
28242337b9dSAndrej Shadura return -ENODEV;
28342337b9dSAndrej Shadura
28442337b9dSAndrej Shadura ep = usb_pipe_endpoint(udev, usbhid->urbin->pipe);
28542337b9dSAndrej Shadura if (!ep)
28642337b9dSAndrej Shadura return -ENODEV;
28742337b9dSAndrej Shadura
28842337b9dSAndrej Shadura dev->urb = usb_alloc_urb(0, GFP_KERNEL);
28942337b9dSAndrej Shadura if (!dev->urb)
29042337b9dSAndrej Shadura return -ENOMEM;
29142337b9dSAndrej Shadura
29242337b9dSAndrej Shadura pipe_in = (usbhid->urbin->pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30);
29342337b9dSAndrej Shadura
29442337b9dSAndrej Shadura usb_fill_int_urb(dev->urb,
29542337b9dSAndrej Shadura udev,
29642337b9dSAndrej Shadura pipe_in,
29742337b9dSAndrej Shadura dev->buf_in,
29842337b9dSAndrej Shadura HID_REPORT_SIZE,
29942337b9dSAndrej Shadura u2fzero_read_callback,
30042337b9dSAndrej Shadura NULL,
30142337b9dSAndrej Shadura ep->desc.bInterval);
30242337b9dSAndrej Shadura
30342337b9dSAndrej Shadura return 0;
30442337b9dSAndrej Shadura }
30542337b9dSAndrej Shadura
u2fzero_probe(struct hid_device * hdev,const struct hid_device_id * id)30642337b9dSAndrej Shadura static int u2fzero_probe(struct hid_device *hdev,
30742337b9dSAndrej Shadura const struct hid_device_id *id)
30842337b9dSAndrej Shadura {
30942337b9dSAndrej Shadura struct u2fzero_device *dev;
31042337b9dSAndrej Shadura unsigned int minor;
31142337b9dSAndrej Shadura int ret;
31242337b9dSAndrej Shadura
313*f83baa0cSGreg Kroah-Hartman if (!hid_is_usb(hdev))
31459579a8dSJiri Kosina return -EINVAL;
31559579a8dSJiri Kosina
31642337b9dSAndrej Shadura dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
31742337b9dSAndrej Shadura if (dev == NULL)
31842337b9dSAndrej Shadura return -ENOMEM;
31942337b9dSAndrej Shadura
3206748031aSAndrej Shadura dev->hw_revision = id->driver_data;
3216748031aSAndrej Shadura
32242337b9dSAndrej Shadura dev->buf_out = devm_kmalloc(&hdev->dev,
32342337b9dSAndrej Shadura sizeof(struct u2f_hid_report), GFP_KERNEL);
32442337b9dSAndrej Shadura if (dev->buf_out == NULL)
32542337b9dSAndrej Shadura return -ENOMEM;
32642337b9dSAndrej Shadura
32742337b9dSAndrej Shadura dev->buf_in = devm_kmalloc(&hdev->dev,
32842337b9dSAndrej Shadura sizeof(struct u2f_hid_msg), GFP_KERNEL);
32942337b9dSAndrej Shadura if (dev->buf_in == NULL)
33042337b9dSAndrej Shadura return -ENOMEM;
33142337b9dSAndrej Shadura
33242337b9dSAndrej Shadura ret = hid_parse(hdev);
33342337b9dSAndrej Shadura if (ret)
33442337b9dSAndrej Shadura return ret;
33542337b9dSAndrej Shadura
33642337b9dSAndrej Shadura dev->hdev = hdev;
33742337b9dSAndrej Shadura hid_set_drvdata(hdev, dev);
33842337b9dSAndrej Shadura mutex_init(&dev->lock);
33942337b9dSAndrej Shadura
34042337b9dSAndrej Shadura ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
34142337b9dSAndrej Shadura if (ret)
34242337b9dSAndrej Shadura return ret;
34342337b9dSAndrej Shadura
34442337b9dSAndrej Shadura u2fzero_fill_in_urb(dev);
34542337b9dSAndrej Shadura
34642337b9dSAndrej Shadura dev->present = true;
34742337b9dSAndrej Shadura
34842337b9dSAndrej Shadura minor = ((struct hidraw *) hdev->hidraw)->minor;
34942337b9dSAndrej Shadura
35042337b9dSAndrej Shadura ret = u2fzero_init_led(dev, minor);
35142337b9dSAndrej Shadura if (ret) {
35242337b9dSAndrej Shadura hid_hw_stop(hdev);
35342337b9dSAndrej Shadura return ret;
35442337b9dSAndrej Shadura }
35542337b9dSAndrej Shadura
3566748031aSAndrej Shadura hid_info(hdev, "%s LED initialised\n", hw_configs[dev->hw_revision].name);
35742337b9dSAndrej Shadura
35842337b9dSAndrej Shadura ret = u2fzero_init_hwrng(dev, minor);
35942337b9dSAndrej Shadura if (ret) {
36042337b9dSAndrej Shadura hid_hw_stop(hdev);
36142337b9dSAndrej Shadura return ret;
36242337b9dSAndrej Shadura }
36342337b9dSAndrej Shadura
3646748031aSAndrej Shadura hid_info(hdev, "%s RNG initialised\n", hw_configs[dev->hw_revision].name);
36542337b9dSAndrej Shadura
36642337b9dSAndrej Shadura return 0;
36742337b9dSAndrej Shadura }
36842337b9dSAndrej Shadura
u2fzero_remove(struct hid_device * hdev)36942337b9dSAndrej Shadura static void u2fzero_remove(struct hid_device *hdev)
37042337b9dSAndrej Shadura {
37142337b9dSAndrej Shadura struct u2fzero_device *dev = hid_get_drvdata(hdev);
37242337b9dSAndrej Shadura
37342337b9dSAndrej Shadura mutex_lock(&dev->lock);
37442337b9dSAndrej Shadura dev->present = false;
37542337b9dSAndrej Shadura mutex_unlock(&dev->lock);
37642337b9dSAndrej Shadura
37742337b9dSAndrej Shadura hid_hw_stop(hdev);
37842337b9dSAndrej Shadura usb_poison_urb(dev->urb);
37942337b9dSAndrej Shadura usb_free_urb(dev->urb);
38042337b9dSAndrej Shadura }
38142337b9dSAndrej Shadura
38242337b9dSAndrej Shadura static const struct hid_device_id u2fzero_table[] = {
38342337b9dSAndrej Shadura { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL,
3846748031aSAndrej Shadura USB_DEVICE_ID_U2F_ZERO),
3856748031aSAndrej Shadura .driver_data = HW_U2FZERO },
3866748031aSAndrej Shadura { HID_USB_DEVICE(USB_VENDOR_ID_CLAY_LOGIC,
3876748031aSAndrej Shadura USB_DEVICE_ID_NITROKEY_U2F),
3886748031aSAndrej Shadura .driver_data = HW_NITROKEY_U2F },
38942337b9dSAndrej Shadura { }
39042337b9dSAndrej Shadura };
39142337b9dSAndrej Shadura MODULE_DEVICE_TABLE(hid, u2fzero_table);
39242337b9dSAndrej Shadura
39342337b9dSAndrej Shadura static struct hid_driver u2fzero_driver = {
39442337b9dSAndrej Shadura .name = "hid-" DRIVER_SHORT,
39542337b9dSAndrej Shadura .probe = u2fzero_probe,
39642337b9dSAndrej Shadura .remove = u2fzero_remove,
39742337b9dSAndrej Shadura .id_table = u2fzero_table,
39842337b9dSAndrej Shadura };
39942337b9dSAndrej Shadura
40042337b9dSAndrej Shadura module_hid_driver(u2fzero_driver);
40142337b9dSAndrej Shadura
40242337b9dSAndrej Shadura MODULE_LICENSE("GPL");
40342337b9dSAndrej Shadura MODULE_AUTHOR("Andrej Shadura <andrew@shadura.me>");
40442337b9dSAndrej Shadura MODULE_DESCRIPTION("U2F Zero LED and RNG driver");
405