qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 1/6] hw/usb: Add CanoKey Implementation


From: Hongren (Zenithal) Zheng
Subject: [PATCH 1/6] hw/usb: Add CanoKey Implementation
Date: Fri, 24 Dec 2021 01:15:17 +0800

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 | 378 +++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb/canokey.h |  60 ++++++++
 2 files changed, 438 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..f71e5f0438
--- /dev/null
+++ b/hw/usb/canokey.c
@@ -0,0 +1,378 @@
+/*
+ * CanoKey QEMU device implementation.
+ *
+ * Copyright (c) Canokeys.org <contact@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 DEBUG_CANOKEY_DATA 1 */
+/* #define DEBUG_CANOKEY 1 */
+
+#ifdef DEBUG_CANOKEY_DATA
+#define DPRINTF_DATA(data, len, fmt, ...) \
+do { \
+    printf("canokey: payload: " fmt " ", ## __VA_ARGS__); \
+    for (int i = 0; i != (len); ++i) { \
+        printf("%02X ", (data)[i]); \
+    } \
+    printf("\n"); \
+} while (0)
+#else
+#define DPRINTF_DATA(data, len, fmt, ...) do {} while (0)
+#endif
+
+#ifdef DEBUG_CANOKEY
+#define DPRINTF(fmt, ...) \
+do { printf("canokey: " fmt "\n", ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "canokey: error: " fmt "\n", ## __VA_ARGS__); \
+    exit(1); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "canokey: error: " fmt "\n", ## __VA_ARGS__); } while (0)
+#endif
+
+#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)
+{
+    DPRINTF("stall ep %d", 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)
+{
+    DPRINTF("set addr %d", 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)
+{
+    DPRINTF("prepare receive on ep %d size %d", ep, 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)
+{
+    DPRINTF("transmit ep %d size %d", ep, size);
+    DPRINTF_DATA(pbuf, size, "transmit");
+    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;
+    DPRINTF("get rx data size ep %d size %d", ep, key->ep_out_size[ep]);
+    return key->ep_out_size[ep];
+}
+
+static void *canokey_thread(void *arg)
+{
+    DPRINTF("thread");
+    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) {
+            DPRINTF("stop thread");
+            key->stop_thread = false;
+            break;
+        }
+
+        canokey_emu_device_loop();
+    }
+    return NULL;
+}
+
+static void canokey_handle_reset(USBDevice *dev)
+{
+    DPRINTF("reset");
+    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)
+{
+    DPRINTF("contorl SETUP: %04X %04X %04X %04X",
+            request, value, index, length);
+    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 */
+        DPRINTF("control OUT size %d", length);
+        DPRINTF_DATA(data, length, "control 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]);
+
+    DPRINTF("control IN: %d", p->actual_length);
+    DPRINTF_DATA(data, p->actual_length, "control 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:
+        DPRINTF("data OUT, ep %d", ep_out);
+        qemu_mutex_lock(&key->key_mutex);
+        if (p->iov.size > key->ep_out_size[ep_out]) {
+            DPRINTF("data OUT, ep %d overflow! income %d, buffer size %d",
+                    ep_out, p->iov.size, key->ep_out_size[ep_out]);
+            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;
+        DPRINTF_DATA(key->ep_out[ep_out], p->iov.size, "data OUT payload");
+        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;
+            }
+            DPRINTF("data IN, ep %d", ep_in);
+            DPRINTF_DATA(key->ep_in[ep_in], key->ep_in_size[ep_in],
+                    "data IN payload");
+            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)
+{
+    DPRINTF("realize");
+    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)
+{
+    DPRINTF("unrealize");
+    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..caa93a8f3a
--- /dev/null
+++ b/hw/usb/canokey.h
@@ -0,0 +1,60 @@
+/*
+ * CanoKey QEMU device header.
+ *
+ * Copyright (c) Canokeys.org <contact@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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]