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 <i...@zenithal.me> --- hw/usb/canokey.c | 326 +++++++++++++++++++++++++++++++++++++++++++++++ hw/usb/canokey.h | 60 +++++++++ 2 files changed, 386 insertions(+) create mode 100644 hw/usb/canokey.c create mode 100644 hw/usb/canokey.h diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c new file mode 100644 index 0000000000..6bd4dedfd2 --- /dev/null +++ b/hw/usb/canokey.c @@ -0,0 +1,326 @@ +/* + * CanoKey QEMU device implementation. + * + * Copyright (c) 2021-2022 Canokeys.org <cont...@canokeys.org> + * Written by Hongren (Zenithal) Zheng <i...@zenithal.me> + * + * This code is licensed under the Apache-2.0. + */ + +#include "qemu/osdep.h" +#include <canokey-qemu.h> + +#include "qemu/module.h" +#include "qemu/thread.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "hw/usb.h" +#include "hw/qdev-properties.h" +#include "desc.h" +#include "canokey.h" + +#define CANOKEY_EP_IN(ep) ((ep) & 0x7F) + +#define CANOKEY_VENDOR_NUM 0x20a0 +#define CANOKEY_PRODUCT_NUM 0x42d2 + +/* + * placeholder, canokey-qemu implements its own usb desc + * Namely we do not use usb_desc_handle_contorl + */ +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "canokeys.org", + [STR_PRODUCT] = "CanoKey QEMU", + [STR_SERIALNUMBER] = "0" +}; + +static const USBDescDevice desc_device_canokey = { + .bcdUSB = 0x0, + .bMaxPacketSize0 = 16, + .bNumConfigurations = 0, + .confs = NULL, +}; + +static const USBDesc desc_canokey = { + .id = { + .idVendor = CANOKEY_VENDOR_NUM, + .idProduct = CANOKEY_PRODUCT_NUM, + .bcdDevice = 0x0100, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_canokey, + .str = desc_strings, +}; + + +/* Implement canokey-qemu functions */ +int canokey_emu_stall_ep(void *base, uint8_t ep) +{ + CanoKeyState *key = base; + uint8_t ep_in = CANOKEY_EP_IN(ep); /* INTR IN has ep 129 */ + qemu_mutex_lock(&key->ep_in_mutex[ep_in]); + key->ep_in_size[ep_in] = 0; + key->ep_in_status[ep_in] = CANOKEY_EP_IN_STALL; + qemu_mutex_unlock(&key->ep_in_mutex[ep_in]); + return 0; +} + +int canokey_emu_set_address(void *base, uint8_t addr) +{ + CanoKeyState *key = base; + key->dev.addr = addr; + return 0; +} + +int canokey_emu_prepare_receive( + void *base, uint8_t ep, uint8_t *pbuf, uint16_t size) +{ + CanoKeyState *key = base; + /* + * No mutex here because it is usually called by + * canokey_emu_data_out (qemu thread), which already has mutex + */ + key->ep_out[ep] = pbuf; + key->ep_out_size[ep] = size; + return 0; +} + +int canokey_emu_transmit( + void *base, uint8_t ep, const uint8_t *pbuf, uint16_t size) +{ + CanoKeyState *key = base; + uint8_t ep_in = CANOKEY_EP_IN(ep); /* INTR IN has ep 129 */ + qemu_mutex_lock(&key->ep_in_mutex[ep_in]); + memcpy(key->ep_in[ep_in], pbuf, size); + key->ep_in_size[ep_in] = size; + key->ep_in_status[ep_in] = CANOKEY_EP_IN_READY; + qemu_mutex_unlock(&key->ep_in_mutex[ep_in]); + return 0; +} + +uint32_t canokey_emu_get_rx_data_size(void *base, uint8_t ep) +{ + CanoKeyState *key = base; + return key->ep_out_size[ep]; +} + +static void *canokey_thread(void *arg) +{ + CanoKeyState *key = arg; + + while (true) { + /* Wait signal */ + qemu_mutex_lock(&key->key_mutex); + qemu_cond_wait(&key->key_cond, &key->key_mutex); + qemu_mutex_unlock(&key->key_mutex); + + /* Exit thread check */ + if (key->stop_thread) { + key->stop_thread = false; + break; + } + + canokey_emu_device_loop(); + } + return NULL; +} + +static void canokey_handle_reset(USBDevice *dev) +{ + CanoKeyState *key = CANOKEY(dev); + for (int i = 0; i != CANOKEY_EP_NUM; ++i) { + key->ep_in_status[i] = CANOKEY_EP_IN_WAIT; + key->ep_in_pos[i] = 0; + } + canokey_emu_reset(); +} + +static void canokey_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + CanoKeyState *key = CANOKEY(dev); + + canokey_emu_setup(request, value, index, length); + qemu_cond_signal(&key->key_cond); + + uint8_t ep_in = CANOKEY_EP_IN(p->ep->nr); + uint32_t dir_in = request & DeviceRequest; + if (!dir_in) { + /* OUT */ + qemu_mutex_lock(&key->key_mutex); + if (key->ep_out[0] != NULL) { + memcpy(key->ep_out[0], data, length); + } + canokey_emu_data_out(p->ep->nr, data); + qemu_cond_signal(&key->key_cond); + qemu_mutex_unlock(&key->key_mutex); + } + + /* IN */ + qemu_mutex_lock(&key->ep_in_mutex[ep_in]); + if (key->ep_in_status[ep_in] == CANOKEY_EP_IN_WAIT) { + p->status = USB_RET_NAK; + qemu_mutex_unlock(&key->ep_in_mutex[ep_in]); + return; + } + if (key->ep_in_status[ep_in] == CANOKEY_EP_IN_STALL) { + p->status = USB_RET_STALL; + } + key->ep_in_status[ep_in] = CANOKEY_EP_IN_WAIT; + memcpy(data, key->ep_in[ep_in], key->ep_in_size[ep_in]); + p->actual_length = key->ep_in_size[ep_in]; + + qemu_mutex_unlock(&key->ep_in_mutex[ep_in]); +} + +static void canokey_handle_data(USBDevice *dev, USBPacket *p) +{ + CanoKeyState *key = CANOKEY(dev); + + uint8_t ep_in = CANOKEY_EP_IN(p->ep->nr); + uint8_t ep_out = p->ep->nr; + uint32_t in_len; + switch (p->pid) { + case USB_TOKEN_OUT: + qemu_mutex_lock(&key->key_mutex); + if (p->iov.size > key->ep_out_size[ep_out]) { + /* unlikely we will reach here, but check still needed */ + p->status = USB_RET_NAK; + qemu_mutex_unlock(&key->key_mutex); + break; + } + usb_packet_copy(p, key->ep_out[ep_out], p->iov.size); + key->ep_out_size[ep_out] = p->iov.size; + canokey_emu_data_out(ep_out, NULL); + qemu_cond_signal(&key->key_cond); + qemu_mutex_unlock(&key->key_mutex); + break; + case USB_TOKEN_IN: + qemu_mutex_lock(&key->ep_in_mutex[ep_in]); + if (key->ep_in_pos[ep_in] == 0) { + canokey_emu_data_in(ep_in); + qemu_cond_signal(&key->key_cond); + if (key->ep_in_status[ep_in] == CANOKEY_EP_IN_WAIT) { + p->status = USB_RET_NAK; + qemu_mutex_unlock(&key->ep_in_mutex[ep_in]); + break; + } + if (key->ep_in_status[ep_in] == CANOKEY_EP_IN_STALL) { + p->status = USB_RET_STALL; + } + key->ep_in_status[ep_in] = CANOKEY_EP_IN_WAIT; + + in_len = MIN(key->ep_in_size[ep_in], p->iov.size); + usb_packet_copy(p, key->ep_in[ep_in], in_len); + if (in_len < key->ep_in_size[ep_in]) { + key->ep_in_pos[ep_in] = in_len; + } + } else { + in_len = MIN(key->ep_in_size[ep_in] - key->ep_in_pos[ep_in], + p->iov.size); + usb_packet_copy(p, + key->ep_in[ep_in] + key->ep_in_pos[ep_in], in_len); + key->ep_in_pos[ep_in] += in_len; + if (key->ep_in_pos[ep_in] == key->ep_in_size[ep_in]) { + key->ep_in_pos[ep_in] = 0; + } + } + qemu_mutex_unlock(&key->ep_in_mutex[ep_in]); + break; + default: + p->status = USB_RET_STALL; + break; + } +} + +static void canokey_realize(USBDevice *base, Error **errp) +{ + CanoKeyState *key = CANOKEY(base); + + if (key->file == NULL) { + error_setg(errp, "You must provide file=/path/to/canokey-file"); + return; + } + + usb_desc_init(base); + + /* Synchronization */ + qemu_cond_init(&key->key_cond); + qemu_mutex_init(&key->key_mutex); + for (int i = 0; i != CANOKEY_EP_NUM; ++i) { + qemu_mutex_init(&key->ep_in_mutex[i]); + key->ep_in_status[i] = CANOKEY_EP_IN_WAIT; + key->ep_in_pos[i] = 0; + } + + if (canokey_emu_init(key, key->file)) { + error_setg(errp, "canokey can not create or read %s", key->file); + return; + } + + /* Thread */ + key->stop_thread = false; + qemu_thread_create(&key->key_thread, "canokey", canokey_thread, + key, QEMU_THREAD_JOINABLE); +} + +static void canokey_unrealize(USBDevice *base) +{ + CanoKeyState *key = CANOKEY(base); + + /* Thread */ + key->stop_thread = true; + qemu_cond_signal(&key->key_cond); + qemu_thread_join(&key->key_thread); + + /* Synchronization */ + qemu_cond_destroy(&key->key_cond); + qemu_mutex_destroy(&key->key_mutex); + for (int i = 0; i != CANOKEY_EP_NUM; ++i) { + qemu_mutex_destroy(&key->ep_in_mutex[i]); + } +} + +static Property canokey_properties[] = { + DEFINE_PROP_STRING("file", CanoKeyState, file), + DEFINE_PROP_END_OF_LIST(), +}; + +static void canokey_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "CanoKey QEMU"; + uc->usb_desc = &desc_canokey; + uc->handle_reset = canokey_handle_reset; + uc->handle_control = canokey_handle_control; + uc->handle_data = canokey_handle_data; + uc->handle_attach = usb_desc_attach; + uc->realize = canokey_realize; + uc->unrealize = canokey_unrealize; + dc->desc = "CanoKey QEMU"; + device_class_set_props(dc, canokey_properties); +} + +static const TypeInfo canokey_info = { + .name = TYPE_CANOKEY, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(CanoKeyState), + .class_init = canokey_class_init +}; + +static void canokey_register_types(void) +{ + type_register_static(&canokey_info); +} + +type_init(canokey_register_types) diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h new file mode 100644 index 0000000000..d17648fff6 --- /dev/null +++ b/hw/usb/canokey.h @@ -0,0 +1,60 @@ +/* + * CanoKey QEMU device header. + * + * Copyright (c) 2021-2022 Canokeys.org <cont...@canokeys.org> + * Written by Hongren (Zenithal) Zheng <i...@zenithal.me> + * + * This code is licensed under the Apache-2.0. + */ + +#ifndef CANOKEY_H +#define CANOKEY_H + +#include "hw/qdev-core.h" + +#define TYPE_CANOKEY "canokey" +#define CANOKEY(obj) \ + OBJECT_CHECK(CanoKeyState, (obj), TYPE_CANOKEY) + +/* + * State of the Canokey (i.e. hw/canokey.c) + */ + +/* CTRL INTR BULK */ +#define CANOKEY_EP_NUM 3 +/* BULK IN CAN BE UP TO 500 bytes */ +#define CANOKEY_EP_IN_BUFFER_SIZE 1024 + +typedef enum { + CANOKEY_EP_IN_WAIT, + CANOKEY_EP_IN_READY, + CANOKEY_EP_IN_STALL +} CanoKeyEPStatus; + +typedef struct CanoKeyState { + USBDevice dev; + uint8_t idle; + + /* IN packets from canokey device loop */ + uint8_t ep_in[CANOKEY_EP_NUM][CANOKEY_EP_IN_BUFFER_SIZE]; + /* for IN larger than p->iov.size, we would do multiple handle_data() */ + uint32_t ep_in_pos[CANOKEY_EP_NUM]; + uint32_t ep_in_size[CANOKEY_EP_NUM]; + CanoKeyEPStatus ep_in_status[CANOKEY_EP_NUM]; + QemuMutex ep_in_mutex[CANOKEY_EP_NUM]; + + /* OUT pointer to canokey recv buffer */ + uint8_t *ep_out[CANOKEY_EP_NUM]; + uint32_t ep_out_size[CANOKEY_EP_NUM]; + + /* Properties */ + char *file; /* canokey-file */ + + /* Emulation thread and sync */ + QemuCond key_cond; + QemuMutex key_mutex; + QemuThread key_thread; + bool stop_thread; +} CanoKeyState; + +#endif /* CANOKEY_H */ -- 2.34.0