qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

canokey.c (9930B)


      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 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 "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  */
     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,
     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     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 
     77 int 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 
     85 int 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 
     95 int 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 
    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 {
    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 
    148 static 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 
    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:
    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 
    270 static 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 
    294 static void canokey_unrealize(USBDevice *base)
    295 {
    296     trace_canokey_unrealize();
    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)