Commit | Line | Data |
---|---|---|
42337b9d AS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * U2F Zero LED and RNG driver | |
4 | * | |
5 | * Copyright 2018 Andrej Shadura <andrew@shadura.me> | |
6 | * Loosely based on drivers/hid/hid-led.c | |
7 | * and drivers/usb/misc/chaoskey.c | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU General Public License as | |
11 | * published by the Free Software Foundation, version 2. | |
12 | */ | |
13 | ||
14 | #include <linux/hid.h> | |
15 | #include <linux/hidraw.h> | |
16 | #include <linux/hw_random.h> | |
17 | #include <linux/leds.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/mutex.h> | |
20 | #include <linux/usb.h> | |
21 | ||
22 | #include "usbhid/usbhid.h" | |
23 | #include "hid-ids.h" | |
24 | ||
25 | #define DRIVER_SHORT "u2fzero" | |
26 | ||
27 | #define HID_REPORT_SIZE 64 | |
28 | ||
29 | /* We only use broadcast (CID-less) messages */ | |
30 | #define CID_BROADCAST 0xffffffff | |
31 | ||
32 | struct u2f_hid_msg { | |
33 | u32 cid; | |
34 | union { | |
35 | struct { | |
36 | u8 cmd; | |
37 | u8 bcnth; | |
38 | u8 bcntl; | |
39 | u8 data[HID_REPORT_SIZE - 7]; | |
40 | } init; | |
41 | struct { | |
42 | u8 seq; | |
43 | u8 data[HID_REPORT_SIZE - 5]; | |
44 | } cont; | |
45 | }; | |
46 | } __packed; | |
47 | ||
48 | struct u2f_hid_report { | |
49 | u8 report_type; | |
50 | struct u2f_hid_msg msg; | |
51 | } __packed; | |
52 | ||
53 | #define U2F_HID_MSG_LEN(f) (size_t)(((f).init.bcnth << 8) + (f).init.bcntl) | |
54 | ||
55 | /* Custom extensions to the U2FHID protocol */ | |
56 | #define U2F_CUSTOM_GET_RNG 0x21 | |
57 | #define U2F_CUSTOM_WINK 0x24 | |
58 | ||
59 | struct u2fzero_device { | |
60 | struct hid_device *hdev; | |
61 | struct urb *urb; /* URB for the RNG data */ | |
62 | struct led_classdev ldev; /* Embedded struct for led */ | |
63 | struct hwrng hwrng; /* Embedded struct for hwrng */ | |
64 | char *led_name; | |
65 | char *rng_name; | |
66 | u8 *buf_out; | |
67 | u8 *buf_in; | |
68 | struct mutex lock; | |
69 | bool present; | |
70 | }; | |
71 | ||
72 | static int u2fzero_send(struct u2fzero_device *dev, struct u2f_hid_report *req) | |
73 | { | |
74 | int ret; | |
75 | ||
76 | mutex_lock(&dev->lock); | |
77 | ||
78 | memcpy(dev->buf_out, req, sizeof(struct u2f_hid_report)); | |
79 | ||
80 | ret = hid_hw_output_report(dev->hdev, dev->buf_out, | |
81 | sizeof(struct u2f_hid_msg)); | |
82 | ||
83 | mutex_unlock(&dev->lock); | |
84 | ||
85 | if (ret < 0) | |
86 | return ret; | |
87 | ||
88 | return ret == sizeof(struct u2f_hid_msg) ? 0 : -EMSGSIZE; | |
89 | } | |
90 | ||
91 | struct u2fzero_transfer_context { | |
92 | struct completion done; | |
93 | int status; | |
94 | }; | |
95 | ||
96 | static void u2fzero_read_callback(struct urb *urb) | |
97 | { | |
98 | struct u2fzero_transfer_context *ctx = urb->context; | |
99 | ||
100 | ctx->status = urb->status; | |
101 | complete(&ctx->done); | |
102 | } | |
103 | ||
104 | static int u2fzero_recv(struct u2fzero_device *dev, | |
105 | struct u2f_hid_report *req, | |
106 | struct u2f_hid_msg *resp) | |
107 | { | |
108 | int ret; | |
109 | struct hid_device *hdev = dev->hdev; | |
110 | struct u2fzero_transfer_context ctx; | |
111 | ||
112 | mutex_lock(&dev->lock); | |
113 | ||
114 | memcpy(dev->buf_out, req, sizeof(struct u2f_hid_report)); | |
115 | ||
116 | dev->urb->context = &ctx; | |
117 | init_completion(&ctx.done); | |
118 | ||
119 | ret = usb_submit_urb(dev->urb, GFP_NOIO); | |
120 | if (unlikely(ret)) { | |
121 | hid_err(hdev, "usb_submit_urb failed: %d", ret); | |
122 | goto err; | |
123 | } | |
124 | ||
125 | ret = hid_hw_output_report(dev->hdev, dev->buf_out, | |
126 | sizeof(struct u2f_hid_msg)); | |
127 | ||
128 | if (ret < 0) { | |
129 | hid_err(hdev, "hid_hw_output_report failed: %d", ret); | |
130 | goto err; | |
131 | } | |
132 | ||
133 | ret = (wait_for_completion_timeout( | |
134 | &ctx.done, msecs_to_jiffies(USB_CTRL_SET_TIMEOUT))); | |
135 | if (ret < 0) { | |
136 | usb_kill_urb(dev->urb); | |
137 | hid_err(hdev, "urb submission timed out"); | |
138 | } else { | |
139 | ret = dev->urb->actual_length; | |
140 | memcpy(resp, dev->buf_in, ret); | |
141 | } | |
142 | ||
143 | err: | |
144 | mutex_unlock(&dev->lock); | |
145 | ||
146 | return ret; | |
147 | } | |
148 | ||
149 | static int u2fzero_blink(struct led_classdev *ldev) | |
150 | { | |
151 | struct u2fzero_device *dev = container_of(ldev, | |
152 | struct u2fzero_device, ldev); | |
153 | struct u2f_hid_report req = { | |
154 | .report_type = 0, | |
155 | .msg.cid = CID_BROADCAST, | |
156 | .msg.init = { | |
157 | .cmd = U2F_CUSTOM_WINK, | |
158 | .bcnth = 0, | |
159 | .bcntl = 0, | |
160 | .data = {0}, | |
161 | } | |
162 | }; | |
163 | return u2fzero_send(dev, &req); | |
164 | } | |
165 | ||
166 | static int u2fzero_brightness_set(struct led_classdev *ldev, | |
167 | enum led_brightness brightness) | |
168 | { | |
169 | ldev->brightness = LED_OFF; | |
170 | if (brightness) | |
171 | return u2fzero_blink(ldev); | |
172 | else | |
173 | return 0; | |
174 | } | |
175 | ||
176 | static int u2fzero_rng_read(struct hwrng *rng, void *data, | |
177 | size_t max, bool wait) | |
178 | { | |
179 | struct u2fzero_device *dev = container_of(rng, | |
180 | struct u2fzero_device, hwrng); | |
181 | struct u2f_hid_report req = { | |
182 | .report_type = 0, | |
183 | .msg.cid = CID_BROADCAST, | |
184 | .msg.init = { | |
185 | .cmd = U2F_CUSTOM_GET_RNG, | |
186 | .bcnth = 0, | |
187 | .bcntl = 0, | |
188 | .data = {0}, | |
189 | } | |
190 | }; | |
191 | struct u2f_hid_msg resp; | |
192 | int ret; | |
193 | size_t actual_length; | |
194 | ||
195 | if (!dev->present) { | |
196 | hid_dbg(dev->hdev, "device not present"); | |
197 | return 0; | |
198 | } | |
199 | ||
200 | ret = u2fzero_recv(dev, &req, &resp); | |
201 | if (ret < 0) | |
202 | return 0; | |
203 | ||
204 | /* only take the minimum amount of data it is safe to take */ | |
205 | actual_length = min3((size_t)ret - offsetof(struct u2f_hid_msg, | |
206 | init.data), U2F_HID_MSG_LEN(resp), max); | |
207 | ||
208 | memcpy(data, resp.init.data, actual_length); | |
209 | ||
210 | return actual_length; | |
211 | } | |
212 | ||
213 | static int u2fzero_init_led(struct u2fzero_device *dev, | |
214 | unsigned int minor) | |
215 | { | |
216 | dev->led_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, | |
217 | "%s%u", DRIVER_SHORT, minor); | |
218 | if (dev->led_name == NULL) | |
219 | return -ENOMEM; | |
220 | ||
221 | dev->ldev.name = dev->led_name; | |
222 | dev->ldev.max_brightness = LED_ON; | |
223 | dev->ldev.flags = LED_HW_PLUGGABLE; | |
224 | dev->ldev.brightness_set_blocking = u2fzero_brightness_set; | |
225 | ||
226 | return devm_led_classdev_register(&dev->hdev->dev, &dev->ldev); | |
227 | } | |
228 | ||
229 | static int u2fzero_init_hwrng(struct u2fzero_device *dev, | |
230 | unsigned int minor) | |
231 | { | |
232 | dev->rng_name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, | |
233 | "%s-rng%u", DRIVER_SHORT, minor); | |
234 | if (dev->rng_name == NULL) | |
235 | return -ENOMEM; | |
236 | ||
237 | dev->hwrng.name = dev->rng_name; | |
238 | dev->hwrng.read = u2fzero_rng_read; | |
239 | dev->hwrng.quality = 1; | |
240 | ||
241 | return devm_hwrng_register(&dev->hdev->dev, &dev->hwrng); | |
242 | } | |
243 | ||
244 | static int u2fzero_fill_in_urb(struct u2fzero_device *dev) | |
245 | { | |
246 | struct hid_device *hdev = dev->hdev; | |
247 | struct usb_device *udev; | |
248 | struct usbhid_device *usbhid = hdev->driver_data; | |
249 | unsigned int pipe_in; | |
250 | struct usb_host_endpoint *ep; | |
251 | ||
252 | if (dev->hdev->bus != BUS_USB) | |
253 | return -EINVAL; | |
254 | ||
255 | udev = hid_to_usb_dev(hdev); | |
256 | ||
257 | if (!usbhid->urbout || !usbhid->urbin) | |
258 | return -ENODEV; | |
259 | ||
260 | ep = usb_pipe_endpoint(udev, usbhid->urbin->pipe); | |
261 | if (!ep) | |
262 | return -ENODEV; | |
263 | ||
264 | dev->urb = usb_alloc_urb(0, GFP_KERNEL); | |
265 | if (!dev->urb) | |
266 | return -ENOMEM; | |
267 | ||
268 | pipe_in = (usbhid->urbin->pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); | |
269 | ||
270 | usb_fill_int_urb(dev->urb, | |
271 | udev, | |
272 | pipe_in, | |
273 | dev->buf_in, | |
274 | HID_REPORT_SIZE, | |
275 | u2fzero_read_callback, | |
276 | NULL, | |
277 | ep->desc.bInterval); | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | static int u2fzero_probe(struct hid_device *hdev, | |
283 | const struct hid_device_id *id) | |
284 | { | |
285 | struct u2fzero_device *dev; | |
286 | unsigned int minor; | |
287 | int ret; | |
288 | ||
59579a8d JK |
289 | if (!hid_is_using_ll_driver(hdev, &usb_hid_driver)) |
290 | return -EINVAL; | |
291 | ||
42337b9d AS |
292 | dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); |
293 | if (dev == NULL) | |
294 | return -ENOMEM; | |
295 | ||
296 | dev->buf_out = devm_kmalloc(&hdev->dev, | |
297 | sizeof(struct u2f_hid_report), GFP_KERNEL); | |
298 | if (dev->buf_out == NULL) | |
299 | return -ENOMEM; | |
300 | ||
301 | dev->buf_in = devm_kmalloc(&hdev->dev, | |
302 | sizeof(struct u2f_hid_msg), GFP_KERNEL); | |
303 | if (dev->buf_in == NULL) | |
304 | return -ENOMEM; | |
305 | ||
306 | ret = hid_parse(hdev); | |
307 | if (ret) | |
308 | return ret; | |
309 | ||
310 | dev->hdev = hdev; | |
311 | hid_set_drvdata(hdev, dev); | |
312 | mutex_init(&dev->lock); | |
313 | ||
314 | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); | |
315 | if (ret) | |
316 | return ret; | |
317 | ||
318 | u2fzero_fill_in_urb(dev); | |
319 | ||
320 | dev->present = true; | |
321 | ||
322 | minor = ((struct hidraw *) hdev->hidraw)->minor; | |
323 | ||
324 | ret = u2fzero_init_led(dev, minor); | |
325 | if (ret) { | |
326 | hid_hw_stop(hdev); | |
327 | return ret; | |
328 | } | |
329 | ||
330 | hid_info(hdev, "U2F Zero LED initialised\n"); | |
331 | ||
332 | ret = u2fzero_init_hwrng(dev, minor); | |
333 | if (ret) { | |
334 | hid_hw_stop(hdev); | |
335 | return ret; | |
336 | } | |
337 | ||
338 | hid_info(hdev, "U2F Zero RNG initialised\n"); | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
343 | static void u2fzero_remove(struct hid_device *hdev) | |
344 | { | |
345 | struct u2fzero_device *dev = hid_get_drvdata(hdev); | |
346 | ||
347 | mutex_lock(&dev->lock); | |
348 | dev->present = false; | |
349 | mutex_unlock(&dev->lock); | |
350 | ||
351 | hid_hw_stop(hdev); | |
352 | usb_poison_urb(dev->urb); | |
353 | usb_free_urb(dev->urb); | |
354 | } | |
355 | ||
356 | static const struct hid_device_id u2fzero_table[] = { | |
357 | { HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, | |
358 | USB_DEVICE_ID_U2F_ZERO) }, | |
359 | { } | |
360 | }; | |
361 | MODULE_DEVICE_TABLE(hid, u2fzero_table); | |
362 | ||
363 | static struct hid_driver u2fzero_driver = { | |
364 | .name = "hid-" DRIVER_SHORT, | |
365 | .probe = u2fzero_probe, | |
366 | .remove = u2fzero_remove, | |
367 | .id_table = u2fzero_table, | |
368 | }; | |
369 | ||
370 | module_hid_driver(u2fzero_driver); | |
371 | ||
372 | MODULE_LICENSE("GPL"); | |
373 | MODULE_AUTHOR("Andrej Shadura <andrew@shadura.me>"); | |
374 | MODULE_DESCRIPTION("U2F Zero LED and RNG driver"); |