]>
Commit | Line | Data |
---|---|---|
d7d34918 HZZ |
1 | /* |
2 | * CanoKey QEMU device implementation. | |
3 | * | |
4 | * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org> | |
5 | * Written by Hongren (Zenithal) Zheng <i@zenithal.me> | |
6 | * | |
0e6b20b9 | 7 | * This code is licensed under the GPL v2 or later. |
d7d34918 HZZ |
8 | */ |
9 | ||
10 | #include "qemu/osdep.h" | |
11 | #include <canokey-qemu.h> | |
12 | ||
13 | #include "qemu/module.h" | |
14 | #include "qapi/error.h" | |
15 | #include "hw/usb.h" | |
16 | #include "hw/qdev-properties.h" | |
d37d0e0e | 17 | #include "trace.h" |
d7d34918 HZZ |
18 | #include "desc.h" |
19 | #include "canokey.h" | |
20 | ||
21 | #define CANOKEY_EP_IN(ep) ((ep) & 0x7F) | |
22 | ||
23 | #define CANOKEY_VENDOR_NUM 0x20a0 | |
24 | #define CANOKEY_PRODUCT_NUM 0x42d2 | |
25 | ||
26 | /* | |
27 | * placeholder, canokey-qemu implements its own usb desc | |
28 | * Namely we do not use usb_desc_handle_contorl | |
29 | */ | |
30 | enum { | |
31 | STR_MANUFACTURER = 1, | |
32 | STR_PRODUCT, | |
33 | STR_SERIALNUMBER | |
34 | }; | |
35 | ||
36 | static const USBDescStrings desc_strings = { | |
37 | [STR_MANUFACTURER] = "canokeys.org", | |
38 | [STR_PRODUCT] = "CanoKey QEMU", | |
39 | [STR_SERIALNUMBER] = "0" | |
40 | }; | |
41 | ||
42 | static const USBDescDevice desc_device_canokey = { | |
43 | .bcdUSB = 0x0, | |
44 | .bMaxPacketSize0 = 16, | |
45 | .bNumConfigurations = 0, | |
46 | .confs = NULL, | |
47 | }; | |
48 | ||
49 | static const USBDesc desc_canokey = { | |
50 | .id = { | |
51 | .idVendor = CANOKEY_VENDOR_NUM, | |
52 | .idProduct = CANOKEY_PRODUCT_NUM, | |
53 | .bcdDevice = 0x0100, | |
54 | .iManufacturer = STR_MANUFACTURER, | |
55 | .iProduct = STR_PRODUCT, | |
56 | .iSerialNumber = STR_SERIALNUMBER, | |
57 | }, | |
58 | .full = &desc_device_canokey, | |
d7d34918 HZZ |
59 | .str = desc_strings, |
60 | }; | |
61 | ||
62 | ||
63 | /* | |
64 | * libcanokey-qemu.so side functions | |
65 | * All functions are called from canokey_emu_device_loop | |
66 | */ | |
67 | int canokey_emu_stall_ep(void *base, uint8_t ep) | |
68 | { | |
d37d0e0e | 69 | trace_canokey_emu_stall_ep(ep); |
d7d34918 HZZ |
70 | CanoKeyState *key = base; |
71 | uint8_t ep_in = CANOKEY_EP_IN(ep); /* INTR IN has ep 129 */ | |
72 | key->ep_in_size[ep_in] = 0; | |
73 | key->ep_in_state[ep_in] = CANOKEY_EP_IN_STALL; | |
74 | return 0; | |
75 | } | |
76 | ||
77 | int canokey_emu_set_address(void *base, uint8_t addr) | |
78 | { | |
d37d0e0e | 79 | trace_canokey_emu_set_address(addr); |
d7d34918 HZZ |
80 | CanoKeyState *key = base; |
81 | key->dev.addr = addr; | |
82 | return 0; | |
83 | } | |
84 | ||
85 | int canokey_emu_prepare_receive( | |
86 | void *base, uint8_t ep, uint8_t *pbuf, uint16_t size) | |
87 | { | |
d37d0e0e | 88 | trace_canokey_emu_prepare_receive(ep, size); |
d7d34918 HZZ |
89 | CanoKeyState *key = base; |
90 | key->ep_out[ep] = pbuf; | |
91 | key->ep_out_size[ep] = size; | |
92 | return 0; | |
93 | } | |
94 | ||
95 | int canokey_emu_transmit( | |
96 | void *base, uint8_t ep, const uint8_t *pbuf, uint16_t size) | |
97 | { | |
d37d0e0e | 98 | trace_canokey_emu_transmit(ep, size); |
d7d34918 HZZ |
99 | CanoKeyState *key = base; |
100 | uint8_t ep_in = CANOKEY_EP_IN(ep); /* INTR IN has ep 129 */ | |
101 | memcpy(key->ep_in[ep_in] + key->ep_in_size[ep_in], | |
102 | pbuf, size); | |
103 | key->ep_in_size[ep_in] += size; | |
104 | key->ep_in_state[ep_in] = CANOKEY_EP_IN_READY; | |
10425630 HZZ |
105 | /* |
106 | * wake up controller if we NAKed IN token before | |
107 | * Note: this is a quirk for CanoKey CTAPHID | |
108 | */ | |
109 | if (ep_in == CANOKEY_EMU_EP_CTAPHID) { | |
110 | usb_wakeup(usb_ep_get(&key->dev, USB_TOKEN_IN, ep_in), 0); | |
111 | } | |
d7d34918 HZZ |
112 | /* |
113 | * ready for more data in device loop | |
114 | * | |
115 | * Note: this is a quirk for CanoKey CTAPHID | |
116 | * because it calls multiple emu_transmit in one device_loop | |
117 | * but w/o data_in it would stuck in device_loop | |
ada270cd HZZ |
118 | * This has side effect for CCID since CCID can send ZLP |
119 | * This also has side effect for Control transfer | |
d7d34918 | 120 | */ |
ada270cd | 121 | if (ep_in == CANOKEY_EMU_EP_CTAPHID) { |
d7d34918 HZZ |
122 | canokey_emu_data_in(ep_in); |
123 | } | |
124 | return 0; | |
125 | } | |
126 | ||
127 | uint32_t canokey_emu_get_rx_data_size(void *base, uint8_t ep) | |
128 | { | |
129 | CanoKeyState *key = base; | |
130 | return key->ep_out_size[ep]; | |
131 | } | |
132 | ||
133 | /* | |
134 | * QEMU side functions | |
135 | */ | |
136 | static void canokey_handle_reset(USBDevice *dev) | |
137 | { | |
d37d0e0e | 138 | trace_canokey_handle_reset(); |
d7d34918 HZZ |
139 | CanoKeyState *key = CANOKEY(dev); |
140 | for (int i = 0; i != CANOKEY_EP_NUM; ++i) { | |
141 | key->ep_in_state[i] = CANOKEY_EP_IN_WAIT; | |
142 | key->ep_in_pos[i] = 0; | |
143 | key->ep_in_size[i] = 0; | |
144 | } | |
145 | canokey_emu_reset(); | |
146 | } | |
147 | ||
148 | static void canokey_handle_control(USBDevice *dev, USBPacket *p, | |
149 | int request, int value, int index, int length, uint8_t *data) | |
150 | { | |
d37d0e0e | 151 | trace_canokey_handle_control_setup(request, value, index, length); |
d7d34918 HZZ |
152 | CanoKeyState *key = CANOKEY(dev); |
153 | ||
154 | canokey_emu_setup(request, value, index, length); | |
155 | ||
156 | uint32_t dir_in = request & DeviceRequest; | |
157 | if (!dir_in) { | |
158 | /* OUT */ | |
d37d0e0e | 159 | trace_canokey_handle_control_out(); |
d7d34918 HZZ |
160 | if (key->ep_out[0] != NULL) { |
161 | memcpy(key->ep_out[0], data, length); | |
162 | } | |
163 | canokey_emu_data_out(p->ep->nr, data); | |
164 | } | |
165 | ||
166 | canokey_emu_device_loop(); | |
167 | ||
168 | /* IN */ | |
169 | switch (key->ep_in_state[0]) { | |
170 | case CANOKEY_EP_IN_WAIT: | |
171 | p->status = USB_RET_NAK; | |
172 | break; | |
173 | case CANOKEY_EP_IN_STALL: | |
174 | p->status = USB_RET_STALL; | |
175 | break; | |
176 | case CANOKEY_EP_IN_READY: | |
177 | memcpy(data, key->ep_in[0], key->ep_in_size[0]); | |
178 | p->actual_length = key->ep_in_size[0]; | |
d37d0e0e | 179 | trace_canokey_handle_control_in(p->actual_length); |
d7d34918 HZZ |
180 | /* reset state */ |
181 | key->ep_in_state[0] = CANOKEY_EP_IN_WAIT; | |
182 | key->ep_in_size[0] = 0; | |
183 | key->ep_in_pos[0] = 0; | |
184 | break; | |
185 | } | |
186 | } | |
187 | ||
188 | static void canokey_handle_data(USBDevice *dev, USBPacket *p) | |
189 | { | |
190 | CanoKeyState *key = CANOKEY(dev); | |
191 | ||
192 | uint8_t ep_in = CANOKEY_EP_IN(p->ep->nr); | |
193 | uint8_t ep_out = p->ep->nr; | |
194 | uint32_t in_len; | |
195 | uint32_t out_pos; | |
196 | uint32_t out_len; | |
197 | switch (p->pid) { | |
198 | case USB_TOKEN_OUT: | |
d37d0e0e | 199 | trace_canokey_handle_data_out(ep_out, p->iov.size); |
d7d34918 HZZ |
200 | usb_packet_copy(p, key->ep_out_buffer[ep_out], p->iov.size); |
201 | out_pos = 0; | |
202 | while (out_pos != p->iov.size) { | |
203 | /* | |
204 | * key->ep_out[ep_out] set by prepare_receive | |
205 | * to be a buffer inside libcanokey-qemu.so | |
206 | * key->ep_out_size[ep_out] set by prepare_receive | |
207 | * to be the buffer length | |
208 | */ | |
209 | out_len = MIN(p->iov.size - out_pos, key->ep_out_size[ep_out]); | |
210 | memcpy(key->ep_out[ep_out], | |
211 | key->ep_out_buffer[ep_out] + out_pos, out_len); | |
212 | out_pos += out_len; | |
213 | /* update ep_out_size to actual len */ | |
214 | key->ep_out_size[ep_out] = out_len; | |
215 | canokey_emu_data_out(ep_out, NULL); | |
216 | } | |
10425630 HZZ |
217 | /* |
218 | * Note: this is a quirk for CanoKey CTAPHID | |
219 | * | |
220 | * There is one code path that uses this device loop | |
221 | * INTR IN -> useful data_in and useless device_loop -> NAKed | |
222 | * INTR OUT -> useful device loop -> transmit -> wakeup | |
223 | * (useful thanks to both data_in and data_out having been called) | |
224 | * the next INTR IN -> actual data to guest | |
225 | * | |
226 | * if there is no such device loop, there would be no further | |
227 | * INTR IN, no device loop, no transmit hence no usb_wakeup | |
228 | * then qemu would hang | |
229 | */ | |
230 | if (ep_in == CANOKEY_EMU_EP_CTAPHID) { | |
231 | canokey_emu_device_loop(); /* may call transmit multiple times */ | |
232 | } | |
d7d34918 HZZ |
233 | break; |
234 | case USB_TOKEN_IN: | |
235 | if (key->ep_in_pos[ep_in] == 0) { /* first time IN */ | |
236 | canokey_emu_data_in(ep_in); | |
237 | canokey_emu_device_loop(); /* may call transmit multiple times */ | |
238 | } | |
239 | switch (key->ep_in_state[ep_in]) { | |
240 | case CANOKEY_EP_IN_WAIT: | |
241 | /* NAK for early INTR IN */ | |
242 | p->status = USB_RET_NAK; | |
243 | break; | |
244 | case CANOKEY_EP_IN_STALL: | |
245 | p->status = USB_RET_STALL; | |
246 | break; | |
247 | case CANOKEY_EP_IN_READY: | |
248 | /* submit part of ep_in buffer to USBPacket */ | |
249 | in_len = MIN(key->ep_in_size[ep_in] - key->ep_in_pos[ep_in], | |
250 | p->iov.size); | |
251 | usb_packet_copy(p, | |
252 | key->ep_in[ep_in] + key->ep_in_pos[ep_in], in_len); | |
253 | key->ep_in_pos[ep_in] += in_len; | |
254 | /* reset state if all data submitted */ | |
255 | if (key->ep_in_pos[ep_in] == key->ep_in_size[ep_in]) { | |
256 | key->ep_in_state[ep_in] = CANOKEY_EP_IN_WAIT; | |
257 | key->ep_in_size[ep_in] = 0; | |
258 | key->ep_in_pos[ep_in] = 0; | |
259 | } | |
d37d0e0e | 260 | trace_canokey_handle_data_in(ep_in, in_len); |
d7d34918 HZZ |
261 | break; |
262 | } | |
263 | break; | |
264 | default: | |
265 | p->status = USB_RET_STALL; | |
266 | break; | |
267 | } | |
268 | } | |
269 | ||
270 | static void canokey_realize(USBDevice *base, Error **errp) | |
271 | { | |
d37d0e0e | 272 | trace_canokey_realize(); |
d7d34918 HZZ |
273 | CanoKeyState *key = CANOKEY(base); |
274 | ||
275 | if (key->file == NULL) { | |
276 | error_setg(errp, "You must provide file=/path/to/canokey-file"); | |
277 | return; | |
278 | } | |
279 | ||
280 | usb_desc_init(base); | |
281 | ||
282 | for (int i = 0; i != CANOKEY_EP_NUM; ++i) { | |
283 | key->ep_in_state[i] = CANOKEY_EP_IN_WAIT; | |
284 | key->ep_in_size[i] = 0; | |
285 | key->ep_in_pos[i] = 0; | |
286 | } | |
287 | ||
288 | if (canokey_emu_init(key, key->file)) { | |
289 | error_setg(errp, "canokey can not create or read %s", key->file); | |
290 | return; | |
291 | } | |
292 | } | |
293 | ||
294 | static void canokey_unrealize(USBDevice *base) | |
295 | { | |
d37d0e0e | 296 | trace_canokey_unrealize(); |
d7d34918 HZZ |
297 | } |
298 | ||
299 | static Property canokey_properties[] = { | |
300 | DEFINE_PROP_STRING("file", CanoKeyState, file), | |
301 | DEFINE_PROP_END_OF_LIST(), | |
302 | }; | |
303 | ||
304 | static void canokey_class_init(ObjectClass *klass, void *data) | |
305 | { | |
306 | DeviceClass *dc = DEVICE_CLASS(klass); | |
307 | USBDeviceClass *uc = USB_DEVICE_CLASS(klass); | |
308 | ||
309 | uc->product_desc = "CanoKey QEMU"; | |
310 | uc->usb_desc = &desc_canokey; | |
311 | uc->handle_reset = canokey_handle_reset; | |
312 | uc->handle_control = canokey_handle_control; | |
313 | uc->handle_data = canokey_handle_data; | |
314 | uc->handle_attach = usb_desc_attach; | |
315 | uc->realize = canokey_realize; | |
316 | uc->unrealize = canokey_unrealize; | |
317 | dc->desc = "CanoKey QEMU"; | |
318 | device_class_set_props(dc, canokey_properties); | |
319 | set_bit(DEVICE_CATEGORY_MISC, dc->categories); | |
320 | } | |
321 | ||
322 | static const TypeInfo canokey_info = { | |
323 | .name = TYPE_CANOKEY, | |
324 | .parent = TYPE_USB_DEVICE, | |
325 | .instance_size = sizeof(CanoKeyState), | |
326 | .class_init = canokey_class_init | |
327 | }; | |
328 | ||
329 | static void canokey_register_types(void) | |
330 | { | |
331 | type_register_static(&canokey_info); | |
332 | } | |
333 | ||
334 | type_init(canokey_register_types) |