qemu/hw/usb/canokey.c
<<
>>
Prefs
   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 *
   7 * This code is licensed under the GPL v2 or later.
   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 "trace.h"
  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 */
  30enum {
  31    STR_MANUFACTURER = 1,
  32    STR_PRODUCT,
  33    STR_SERIALNUMBER
  34};
  35
  36static const USBDescStrings desc_strings = {
  37    [STR_MANUFACTURER]     = "canokeys.org",
  38    [STR_PRODUCT]          = "CanoKey QEMU",
  39    [STR_SERIALNUMBER]     = "0"
  40};
  41
  42static const USBDescDevice desc_device_canokey = {
  43    .bcdUSB                        = 0x0,
  44    .bMaxPacketSize0               = 16,
  45    .bNumConfigurations            = 0,
  46    .confs = NULL,
  47};
  48
  49static 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,
  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 */
  67int canokey_emu_stall_ep(void *base, uint8_t ep)
  68{
  69    trace_canokey_emu_stall_ep(ep);
  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
  77int canokey_emu_set_address(void *base, uint8_t addr)
  78{
  79    trace_canokey_emu_set_address(addr);
  80    CanoKeyState *key = base;
  81    key->dev.addr = addr;
  82    return 0;
  83}
  84
  85int canokey_emu_prepare_receive(
  86        void *base, uint8_t ep, uint8_t *pbuf, uint16_t size)
  87{
  88    trace_canokey_emu_prepare_receive(ep, size);
  89    CanoKeyState *key = base;
  90    key->ep_out[ep] = pbuf;
  91    key->ep_out_size[ep] = size;
  92    return 0;
  93}
  94
  95int canokey_emu_transmit(
  96        void *base, uint8_t ep, const uint8_t *pbuf, uint16_t size)
  97{
  98    trace_canokey_emu_transmit(ep, size);
  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;
 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    }
 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
 118     * This has side effect for CCID since CCID can send ZLP
 119     * This also has side effect for Control transfer
 120     */
 121    if (ep_in == CANOKEY_EMU_EP_CTAPHID) {
 122        canokey_emu_data_in(ep_in);
 123    }
 124    return 0;
 125}
 126
 127uint32_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 */
 136static void canokey_handle_reset(USBDevice *dev)
 137{
 138    trace_canokey_handle_reset();
 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
 148static void canokey_handle_control(USBDevice *dev, USBPacket *p,
 149               int request, int value, int index, int length, uint8_t *data)
 150{
 151    trace_canokey_handle_control_setup(request, value, index, length);
 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 */
 159        trace_canokey_handle_control_out();
 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];
 179        trace_canokey_handle_control_in(p->actual_length);
 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
 188static 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:
 199        trace_canokey_handle_data_out(ep_out, p->iov.size);
 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        }
 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        }
 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            }
 260            trace_canokey_handle_data_in(ep_in, in_len);
 261            break;
 262        }
 263        break;
 264    default:
 265        p->status = USB_RET_STALL;
 266        break;
 267    }
 268}
 269
 270static void canokey_realize(USBDevice *base, Error **errp)
 271{
 272    trace_canokey_realize();
 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
 294static void canokey_unrealize(USBDevice *base)
 295{
 296    trace_canokey_unrealize();
 297}
 298
 299static Property canokey_properties[] = {
 300    DEFINE_PROP_STRING("file", CanoKeyState, file),
 301    DEFINE_PROP_END_OF_LIST(),
 302};
 303
 304static 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
 322static 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
 329static void canokey_register_types(void)
 330{
 331    type_register_static(&canokey_info);
 332}
 333
 334type_init(canokey_register_types)
 335