Skip to content

Commit d7d3491

Browse files
ZenithalHourlyRatekraxel
authored andcommitted
hw/usb: Add CanoKey Implementation
This commit added a new emulated device called CanoKey to QEMU. CanoKey implements platform independent features in canokey-core https://github.com/canokeys/canokey-core, and leaves the USB implementation to the platform. In this commit the USB part was implemented in QEMU using QEMU's USB APIs, therefore the emulated CanoKey can communicate with the guest OS using USB. Signed-off-by: Hongren (Zenithal) Zheng <[email protected]> Message-Id: <YoY6Mgph6f6Hc/zI@Sun> Signed-off-by: Gerd Hoffmann <[email protected]>
1 parent 2910abd commit d7d3491

File tree

2 files changed

+369
-0
lines changed

2 files changed

+369
-0
lines changed

hw/usb/canokey.c

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
/*
2+
* CanoKey QEMU device implementation.
3+
*
4+
* Copyright (c) 2021-2022 Canokeys.org <[email protected]>
5+
* Written by Hongren (Zenithal) Zheng <[email protected]>
6+
*
7+
* This code is licensed under the Apache-2.0.
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"
17+
#include "desc.h"
18+
#include "canokey.h"
19+
20+
#define CANOKEY_EP_IN(ep) ((ep) & 0x7F)
21+
22+
#define CANOKEY_VENDOR_NUM 0x20a0
23+
#define CANOKEY_PRODUCT_NUM 0x42d2
24+
25+
/*
26+
* placeholder, canokey-qemu implements its own usb desc
27+
* Namely we do not use usb_desc_handle_contorl
28+
*/
29+
enum {
30+
STR_MANUFACTURER = 1,
31+
STR_PRODUCT,
32+
STR_SERIALNUMBER
33+
};
34+
35+
static const USBDescStrings desc_strings = {
36+
[STR_MANUFACTURER] = "canokeys.org",
37+
[STR_PRODUCT] = "CanoKey QEMU",
38+
[STR_SERIALNUMBER] = "0"
39+
};
40+
41+
static const USBDescDevice desc_device_canokey = {
42+
.bcdUSB = 0x0,
43+
.bMaxPacketSize0 = 16,
44+
.bNumConfigurations = 0,
45+
.confs = NULL,
46+
};
47+
48+
static const USBDesc desc_canokey = {
49+
.id = {
50+
.idVendor = CANOKEY_VENDOR_NUM,
51+
.idProduct = CANOKEY_PRODUCT_NUM,
52+
.bcdDevice = 0x0100,
53+
.iManufacturer = STR_MANUFACTURER,
54+
.iProduct = STR_PRODUCT,
55+
.iSerialNumber = STR_SERIALNUMBER,
56+
},
57+
.full = &desc_device_canokey,
58+
.high = &desc_device_canokey,
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+
{
69+
CanoKeyState *key = base;
70+
uint8_t ep_in = CANOKEY_EP_IN(ep); /* INTR IN has ep 129 */
71+
key->ep_in_size[ep_in] = 0;
72+
key->ep_in_state[ep_in] = CANOKEY_EP_IN_STALL;
73+
return 0;
74+
}
75+
76+
int canokey_emu_set_address(void *base, uint8_t addr)
77+
{
78+
CanoKeyState *key = base;
79+
key->dev.addr = addr;
80+
return 0;
81+
}
82+
83+
int canokey_emu_prepare_receive(
84+
void *base, uint8_t ep, uint8_t *pbuf, uint16_t size)
85+
{
86+
CanoKeyState *key = base;
87+
key->ep_out[ep] = pbuf;
88+
key->ep_out_size[ep] = size;
89+
return 0;
90+
}
91+
92+
int canokey_emu_transmit(
93+
void *base, uint8_t ep, const uint8_t *pbuf, uint16_t size)
94+
{
95+
CanoKeyState *key = base;
96+
uint8_t ep_in = CANOKEY_EP_IN(ep); /* INTR IN has ep 129 */
97+
memcpy(key->ep_in[ep_in] + key->ep_in_size[ep_in],
98+
pbuf, size);
99+
key->ep_in_size[ep_in] += size;
100+
key->ep_in_state[ep_in] = CANOKEY_EP_IN_READY;
101+
/*
102+
* ready for more data in device loop
103+
*
104+
* Note: this is a quirk for CanoKey CTAPHID
105+
* because it calls multiple emu_transmit in one device_loop
106+
* but w/o data_in it would stuck in device_loop
107+
* This has no side effect for CCID as it is strictly
108+
* OUT then IN transfer
109+
* However it has side effect for Control transfer
110+
*/
111+
if (ep_in != 0) {
112+
canokey_emu_data_in(ep_in);
113+
}
114+
return 0;
115+
}
116+
117+
uint32_t canokey_emu_get_rx_data_size(void *base, uint8_t ep)
118+
{
119+
CanoKeyState *key = base;
120+
return key->ep_out_size[ep];
121+
}
122+
123+
/*
124+
* QEMU side functions
125+
*/
126+
static void canokey_handle_reset(USBDevice *dev)
127+
{
128+
CanoKeyState *key = CANOKEY(dev);
129+
for (int i = 0; i != CANOKEY_EP_NUM; ++i) {
130+
key->ep_in_state[i] = CANOKEY_EP_IN_WAIT;
131+
key->ep_in_pos[i] = 0;
132+
key->ep_in_size[i] = 0;
133+
}
134+
canokey_emu_reset();
135+
}
136+
137+
static void canokey_handle_control(USBDevice *dev, USBPacket *p,
138+
int request, int value, int index, int length, uint8_t *data)
139+
{
140+
CanoKeyState *key = CANOKEY(dev);
141+
142+
canokey_emu_setup(request, value, index, length);
143+
144+
uint32_t dir_in = request & DeviceRequest;
145+
if (!dir_in) {
146+
/* OUT */
147+
if (key->ep_out[0] != NULL) {
148+
memcpy(key->ep_out[0], data, length);
149+
}
150+
canokey_emu_data_out(p->ep->nr, data);
151+
}
152+
153+
canokey_emu_device_loop();
154+
155+
/* IN */
156+
switch (key->ep_in_state[0]) {
157+
case CANOKEY_EP_IN_WAIT:
158+
p->status = USB_RET_NAK;
159+
break;
160+
case CANOKEY_EP_IN_STALL:
161+
p->status = USB_RET_STALL;
162+
break;
163+
case CANOKEY_EP_IN_READY:
164+
memcpy(data, key->ep_in[0], key->ep_in_size[0]);
165+
p->actual_length = key->ep_in_size[0];
166+
/* reset state */
167+
key->ep_in_state[0] = CANOKEY_EP_IN_WAIT;
168+
key->ep_in_size[0] = 0;
169+
key->ep_in_pos[0] = 0;
170+
break;
171+
}
172+
}
173+
174+
static void canokey_handle_data(USBDevice *dev, USBPacket *p)
175+
{
176+
CanoKeyState *key = CANOKEY(dev);
177+
178+
uint8_t ep_in = CANOKEY_EP_IN(p->ep->nr);
179+
uint8_t ep_out = p->ep->nr;
180+
uint32_t in_len;
181+
uint32_t out_pos;
182+
uint32_t out_len;
183+
switch (p->pid) {
184+
case USB_TOKEN_OUT:
185+
usb_packet_copy(p, key->ep_out_buffer[ep_out], p->iov.size);
186+
out_pos = 0;
187+
while (out_pos != p->iov.size) {
188+
/*
189+
* key->ep_out[ep_out] set by prepare_receive
190+
* to be a buffer inside libcanokey-qemu.so
191+
* key->ep_out_size[ep_out] set by prepare_receive
192+
* to be the buffer length
193+
*/
194+
out_len = MIN(p->iov.size - out_pos, key->ep_out_size[ep_out]);
195+
memcpy(key->ep_out[ep_out],
196+
key->ep_out_buffer[ep_out] + out_pos, out_len);
197+
out_pos += out_len;
198+
/* update ep_out_size to actual len */
199+
key->ep_out_size[ep_out] = out_len;
200+
canokey_emu_data_out(ep_out, NULL);
201+
}
202+
break;
203+
case USB_TOKEN_IN:
204+
if (key->ep_in_pos[ep_in] == 0) { /* first time IN */
205+
canokey_emu_data_in(ep_in);
206+
canokey_emu_device_loop(); /* may call transmit multiple times */
207+
}
208+
switch (key->ep_in_state[ep_in]) {
209+
case CANOKEY_EP_IN_WAIT:
210+
/* NAK for early INTR IN */
211+
p->status = USB_RET_NAK;
212+
break;
213+
case CANOKEY_EP_IN_STALL:
214+
p->status = USB_RET_STALL;
215+
break;
216+
case CANOKEY_EP_IN_READY:
217+
/* submit part of ep_in buffer to USBPacket */
218+
in_len = MIN(key->ep_in_size[ep_in] - key->ep_in_pos[ep_in],
219+
p->iov.size);
220+
usb_packet_copy(p,
221+
key->ep_in[ep_in] + key->ep_in_pos[ep_in], in_len);
222+
key->ep_in_pos[ep_in] += in_len;
223+
/* reset state if all data submitted */
224+
if (key->ep_in_pos[ep_in] == key->ep_in_size[ep_in]) {
225+
key->ep_in_state[ep_in] = CANOKEY_EP_IN_WAIT;
226+
key->ep_in_size[ep_in] = 0;
227+
key->ep_in_pos[ep_in] = 0;
228+
}
229+
break;
230+
}
231+
break;
232+
default:
233+
p->status = USB_RET_STALL;
234+
break;
235+
}
236+
}
237+
238+
static void canokey_realize(USBDevice *base, Error **errp)
239+
{
240+
CanoKeyState *key = CANOKEY(base);
241+
242+
if (key->file == NULL) {
243+
error_setg(errp, "You must provide file=/path/to/canokey-file");
244+
return;
245+
}
246+
247+
usb_desc_init(base);
248+
249+
for (int i = 0; i != CANOKEY_EP_NUM; ++i) {
250+
key->ep_in_state[i] = CANOKEY_EP_IN_WAIT;
251+
key->ep_in_size[i] = 0;
252+
key->ep_in_pos[i] = 0;
253+
}
254+
255+
if (canokey_emu_init(key, key->file)) {
256+
error_setg(errp, "canokey can not create or read %s", key->file);
257+
return;
258+
}
259+
}
260+
261+
static void canokey_unrealize(USBDevice *base)
262+
{
263+
}
264+
265+
static Property canokey_properties[] = {
266+
DEFINE_PROP_STRING("file", CanoKeyState, file),
267+
DEFINE_PROP_END_OF_LIST(),
268+
};
269+
270+
static void canokey_class_init(ObjectClass *klass, void *data)
271+
{
272+
DeviceClass *dc = DEVICE_CLASS(klass);
273+
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
274+
275+
uc->product_desc = "CanoKey QEMU";
276+
uc->usb_desc = &desc_canokey;
277+
uc->handle_reset = canokey_handle_reset;
278+
uc->handle_control = canokey_handle_control;
279+
uc->handle_data = canokey_handle_data;
280+
uc->handle_attach = usb_desc_attach;
281+
uc->realize = canokey_realize;
282+
uc->unrealize = canokey_unrealize;
283+
dc->desc = "CanoKey QEMU";
284+
device_class_set_props(dc, canokey_properties);
285+
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
286+
}
287+
288+
static const TypeInfo canokey_info = {
289+
.name = TYPE_CANOKEY,
290+
.parent = TYPE_USB_DEVICE,
291+
.instance_size = sizeof(CanoKeyState),
292+
.class_init = canokey_class_init
293+
};
294+
295+
static void canokey_register_types(void)
296+
{
297+
type_register_static(&canokey_info);
298+
}
299+
300+
type_init(canokey_register_types)

hw/usb/canokey.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* CanoKey QEMU device header.
3+
*
4+
* Copyright (c) 2021-2022 Canokeys.org <[email protected]>
5+
* Written by Hongren (Zenithal) Zheng <[email protected]>
6+
*
7+
* This code is licensed under the Apache-2.0.
8+
*/
9+
10+
#ifndef CANOKEY_H
11+
#define CANOKEY_H
12+
13+
#include "hw/qdev-core.h"
14+
15+
#define TYPE_CANOKEY "canokey"
16+
#define CANOKEY(obj) \
17+
OBJECT_CHECK(CanoKeyState, (obj), TYPE_CANOKEY)
18+
19+
/*
20+
* State of Canokey (i.e. hw/canokey.c)
21+
*/
22+
23+
/* CTRL INTR BULK */
24+
#define CANOKEY_EP_NUM 3
25+
/* BULK/INTR IN can be up to 1352 bytes, e.g. get key info */
26+
#define CANOKEY_EP_IN_BUFFER_SIZE 2048
27+
/* BULK OUT can be up to 270 bytes, e.g. PIV import cert */
28+
#define CANOKEY_EP_OUT_BUFFER_SIZE 512
29+
30+
typedef enum {
31+
CANOKEY_EP_IN_WAIT,
32+
CANOKEY_EP_IN_READY,
33+
CANOKEY_EP_IN_STALL
34+
} CanoKeyEPState;
35+
36+
typedef struct CanoKeyState {
37+
USBDevice dev;
38+
39+
/* IN packets from canokey device loop */
40+
uint8_t ep_in[CANOKEY_EP_NUM][CANOKEY_EP_IN_BUFFER_SIZE];
41+
/*
42+
* See canokey_emu_transmit
43+
*
44+
* For large INTR IN, receive multiple data from canokey device loop
45+
* in this case ep_in_size would increase with every call
46+
*/
47+
uint32_t ep_in_size[CANOKEY_EP_NUM];
48+
/*
49+
* Used in canokey_handle_data
50+
* for IN larger than p->iov.size, we would do multiple handle_data()
51+
*
52+
* The difference between ep_in_pos and ep_in_size:
53+
* We first increase ep_in_size to fill ep_in buffer in device_loop,
54+
* then use ep_in_pos to submit data from ep_in buffer in handle_data
55+
*/
56+
uint32_t ep_in_pos[CANOKEY_EP_NUM];
57+
CanoKeyEPState ep_in_state[CANOKEY_EP_NUM];
58+
59+
/* OUT pointer to canokey recv buffer */
60+
uint8_t *ep_out[CANOKEY_EP_NUM];
61+
uint32_t ep_out_size[CANOKEY_EP_NUM];
62+
/* For large BULK OUT, multiple write to ep_out is needed */
63+
uint8_t ep_out_buffer[CANOKEY_EP_NUM][CANOKEY_EP_OUT_BUFFER_SIZE];
64+
65+
/* Properties */
66+
char *file; /* canokey-file */
67+
} CanoKeyState;
68+
69+
#endif /* CANOKEY_H */

0 commit comments

Comments
 (0)