diff mbox series

[PULL,04/15] hw/usb: Add CanoKey Implementation

Message ID 20220614121610.508356-5-kraxel@redhat.com
State New
Headers show
Series [PULL,01/15] ui/gtk-gl-area: implement GL context destruction | expand

Commit Message

Gerd Hoffmann June 14, 2022, 12:15 p.m. UTC
From: "Hongren (Zenithal) Zheng" <i@zenithal.me>

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>
Message-Id: <YoY6Mgph6f6Hc/zI@Sun>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 hw/usb/canokey.h |  69 +++++++++++
 hw/usb/canokey.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 369 insertions(+)
 create mode 100644 hw/usb/canokey.h
 create mode 100644 hw/usb/canokey.c

Comments

Daniel P. Berrangé April 27, 2023, 10:11 a.m. UTC | #1
On Tue, Jun 14, 2022 at 02:15:59PM +0200, Gerd Hoffmann wrote:
> From: "Hongren (Zenithal) Zheng" <i@zenithal.me>
> 
> 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>
> Message-Id: <YoY6Mgph6f6Hc/zI@Sun>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  hw/usb/canokey.h |  69 +++++++++++
>  hw/usb/canokey.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 369 insertions(+)
>  create mode 100644 hw/usb/canokey.h
>  create mode 100644 hw/usb/canokey.c
> 
> diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h
> new file mode 100644
> index 000000000000..24cf30420346
> --- /dev/null
> +++ b/hw/usb/canokey.h
> @@ -0,0 +1,69 @@
> +/*
> + * CanoKey QEMU device header.
> + *
> + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> + *
> + * This code is licensed under the Apache-2.0.
> + */

> diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c
> new file mode 100644
> index 000000000000..6cb8b7cdb089
> --- /dev/null
> +++ b/hw/usb/canokey.c
> @@ -0,0 +1,300 @@
> +/*
> + * CanoKey QEMU device implementation.
> + *
> + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> + *
> + * This code is licensed under the Apache-2.0.
> + */

In the process of auditing licensing in QEMU I found this patch
adding code that is Apache-2.0 licensed, and as such I don't
think we should have ever merged the patch as is.

QEMU as a combined work is GPLv2-only.

There is disagreement between the Apache foundation and FSF on this
topic[1], but FSF considered Apache 2.0 to be incompatible with the
GPL-v2. Fedora licensing follows the same view of Apache being GPLv2
incompatible.

More generally I think it is a little dubious to write new devices
while claiming a license that's different from normal QEMU code
license. I expect there is inevitably a degree of cut+paste from
existing QEMU code to handle the device boilerplate code which
would be sufficient to expect a GPLv2-or-later license to apply.

The two added files in this commit are the only occurrence of
Apache licensing in QEMU that I see.

Hongren, IIUC from the attribution above, you wrote the code but
Canokeys.org claims copyright. Could you report whether Canokeys.org
will agree to change the licensing on these files to QEMU's normal
GPLv2-or-later licensing.

With regards,
Daniel

[1] https://www.apache.org/licenses/GPL-compatibility.html
Hongren (Zenithal) Zheng April 30, 2023, 1:26 p.m. UTC | #2
On Thu, Apr 27, 2023 at 11:11:42AM +0100, Daniel P. Berrangé wrote:
> On Tue, Jun 14, 2022 at 02:15:59PM +0200, Gerd Hoffmann wrote:
> > From: "Hongren (Zenithal) Zheng" <i@zenithal.me>
> > 
> > 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>
> > Message-Id: <YoY6Mgph6f6Hc/zI@Sun>
> > Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> > ---
> >  hw/usb/canokey.h |  69 +++++++++++
> >  hw/usb/canokey.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 369 insertions(+)
> >  create mode 100644 hw/usb/canokey.h
> >  create mode 100644 hw/usb/canokey.c
> > 
> > diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h
> > new file mode 100644
> > index 000000000000..24cf30420346
> > --- /dev/null
> > +++ b/hw/usb/canokey.h
> > @@ -0,0 +1,69 @@
> > +/*
> > + * CanoKey QEMU device header.
> > + *
> > + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> > + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> > + *
> > + * This code is licensed under the Apache-2.0.
> > + */
> 
> > diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c
> > new file mode 100644
> > index 000000000000..6cb8b7cdb089
> > --- /dev/null
> > +++ b/hw/usb/canokey.c
> > @@ -0,0 +1,300 @@
> > +/*
> > + * CanoKey QEMU device implementation.
> > + *
> > + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> > + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> > + *
> > + * This code is licensed under the Apache-2.0.
> > + */
> 
> In the process of auditing licensing in QEMU I found this patch
> adding code that is Apache-2.0 licensed, and as such I don't
> think we should have ever merged the patch as is.
> 
> QEMU as a combined work is GPLv2-only.
> 
> There is disagreement between the Apache foundation and FSF on this
> topic[1], but FSF considered Apache 2.0 to be incompatible with the
> GPL-v2. Fedora licensing follows the same view of Apache being GPLv2
> incompatible.
> 
> More generally I think it is a little dubious to write new devices
> while claiming a license that's different from normal QEMU code
> license. I expect there is inevitably a degree of cut+paste from
> existing QEMU code to handle the device boilerplate code which
> would be sufficient to expect a GPLv2-or-later license to apply.
> 
> The two added files in this commit are the only occurrence of
> Apache licensing in QEMU that I see.
> 
> Hongren, IIUC from the attribution above, you wrote the code but
> Canokeys.org claims copyright. Could you report whether Canokeys.org
> will agree to change the licensing on these files to QEMU's normal
> GPLv2-or-later licensing.

I have discussed it internally with canokeys.org and they agreed
to re-license it under GPLv2+

I will send a patch modifying the license.

In the meantime, canokey.c was also modified by
MkfsSion <mkfssion@mkfssion.com>

I've Cc'ed MkfsSion for their attitude on this.

> 
> With regards,
> Daniel
> 
> [1] https://www.apache.org/licenses/GPL-compatibility.html
> -- 
> |: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
> |: https://libvirt.org         -o-            https://fstop138.berrange.com :|
> |: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|
>
MkfsSion May 1, 2023, 12:39 p.m. UTC | #3
On Sun, Apr 30, 2023 at 09:26:50PM +0800, Hongren (Zenithal) Zheng wrote:
> On Thu, Apr 27, 2023 at 11:11:42AM +0100, Daniel P. Berrangé wrote:
> > On Tue, Jun 14, 2022 at 02:15:59PM +0200, Gerd Hoffmann wrote:
> > > From: "Hongren (Zenithal) Zheng" <i@zenithal.me>
> > > 
> > > 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>
> > > Message-Id: <YoY6Mgph6f6Hc/zI@Sun>
> > > Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> > > ---
> > >  hw/usb/canokey.h |  69 +++++++++++
> > >  hw/usb/canokey.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++
> > >  2 files changed, 369 insertions(+)
> > >  create mode 100644 hw/usb/canokey.h
> > >  create mode 100644 hw/usb/canokey.c
> > > 
> > > diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h
> > > new file mode 100644
> > > index 000000000000..24cf30420346
> > > --- /dev/null
> > > +++ b/hw/usb/canokey.h
> > > @@ -0,0 +1,69 @@
> > > +/*
> > > + * CanoKey QEMU device header.
> > > + *
> > > + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> > > + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> > > + *
> > > + * This code is licensed under the Apache-2.0.
> > > + */
> > 
> > > diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c
> > > new file mode 100644
> > > index 000000000000..6cb8b7cdb089
> > > --- /dev/null
> > > +++ b/hw/usb/canokey.c
> > > @@ -0,0 +1,300 @@
> > > +/*
> > > + * CanoKey QEMU device implementation.
> > > + *
> > > + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> > > + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> > > + *
> > > + * This code is licensed under the Apache-2.0.
> > > + */
> > 
> > In the process of auditing licensing in QEMU I found this patch
> > adding code that is Apache-2.0 licensed, and as such I don't
> > think we should have ever merged the patch as is.
> > 
> > QEMU as a combined work is GPLv2-only.
> > 
> > There is disagreement between the Apache foundation and FSF on this
> > topic[1], but FSF considered Apache 2.0 to be incompatible with the
> > GPL-v2. Fedora licensing follows the same view of Apache being GPLv2
> > incompatible.
> > 
> > More generally I think it is a little dubious to write new devices
> > while claiming a license that's different from normal QEMU code
> > license. I expect there is inevitably a degree of cut+paste from
> > existing QEMU code to handle the device boilerplate code which
> > would be sufficient to expect a GPLv2-or-later license to apply.
> > 
> > The two added files in this commit are the only occurrence of
> > Apache licensing in QEMU that I see.
> > 
> > Hongren, IIUC from the attribution above, you wrote the code but
> > Canokeys.org claims copyright. Could you report whether Canokeys.org
> > will agree to change the licensing on these files to QEMU's normal
> > GPLv2-or-later licensing.
> 
> I have discussed it internally with canokeys.org and they agreed
> to re-license it under GPLv2+
> 
> I will send a patch modifying the license.
> 
> In the meantime, canokey.c was also modified by
> MkfsSion <mkfssion@mkfssion.com>
> 
> I've Cc'ed MkfsSion for their attitude on this.
Acked-by: YuanYang Meng <mkfssion@mkfssion.com>
> 
> > 
> > With regards,
> > Daniel
> > 
> > [1] https://www.apache.org/licenses/GPL-compatibility.html
> > -- 
> > |: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
> > |: https://libvirt.org         -o-            https://fstop138.berrange.com :|
> > |: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|
> >
Daniel P. Berrangé May 2, 2023, 8:25 a.m. UTC | #4
On Mon, May 01, 2023 at 08:39:42PM +0800, MkfsSion wrote:
> On Sun, Apr 30, 2023 at 09:26:50PM +0800, Hongren (Zenithal) Zheng wrote:
> > On Thu, Apr 27, 2023 at 11:11:42AM +0100, Daniel P. Berrangé wrote:
> > > On Tue, Jun 14, 2022 at 02:15:59PM +0200, Gerd Hoffmann wrote:
> > > > From: "Hongren (Zenithal) Zheng" <i@zenithal.me>
> > > > 
> > > > 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>
> > > > Message-Id: <YoY6Mgph6f6Hc/zI@Sun>
> > > > Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> > > > ---
> > > >  hw/usb/canokey.h |  69 +++++++++++
> > > >  hw/usb/canokey.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++
> > > >  2 files changed, 369 insertions(+)
> > > >  create mode 100644 hw/usb/canokey.h
> > > >  create mode 100644 hw/usb/canokey.c
> > > > 
> > > > diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h
> > > > new file mode 100644
> > > > index 000000000000..24cf30420346
> > > > --- /dev/null
> > > > +++ b/hw/usb/canokey.h
> > > > @@ -0,0 +1,69 @@
> > > > +/*
> > > > + * CanoKey QEMU device header.
> > > > + *
> > > > + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> > > > + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> > > > + *
> > > > + * This code is licensed under the Apache-2.0.
> > > > + */
> > > 
> > > > diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c
> > > > new file mode 100644
> > > > index 000000000000..6cb8b7cdb089
> > > > --- /dev/null
> > > > +++ b/hw/usb/canokey.c
> > > > @@ -0,0 +1,300 @@
> > > > +/*
> > > > + * CanoKey QEMU device implementation.
> > > > + *
> > > > + * Copyright (c) 2021-2022 Canokeys.org <contact@canokeys.org>
> > > > + * Written by Hongren (Zenithal) Zheng <i@zenithal.me>
> > > > + *
> > > > + * This code is licensed under the Apache-2.0.
> > > > + */
> > > 
> > > In the process of auditing licensing in QEMU I found this patch
> > > adding code that is Apache-2.0 licensed, and as such I don't
> > > think we should have ever merged the patch as is.
> > > 
> > > QEMU as a combined work is GPLv2-only.
> > > 
> > > There is disagreement between the Apache foundation and FSF on this
> > > topic[1], but FSF considered Apache 2.0 to be incompatible with the
> > > GPL-v2. Fedora licensing follows the same view of Apache being GPLv2
> > > incompatible.
> > > 
> > > More generally I think it is a little dubious to write new devices
> > > while claiming a license that's different from normal QEMU code
> > > license. I expect there is inevitably a degree of cut+paste from
> > > existing QEMU code to handle the device boilerplate code which
> > > would be sufficient to expect a GPLv2-or-later license to apply.
> > > 
> > > The two added files in this commit are the only occurrence of
> > > Apache licensing in QEMU that I see.
> > > 
> > > Hongren, IIUC from the attribution above, you wrote the code but
> > > Canokeys.org claims copyright. Could you report whether Canokeys.org
> > > will agree to change the licensing on these files to QEMU's normal
> > > GPLv2-or-later licensing.
> > 
> > I have discussed it internally with canokeys.org and they agreed
> > to re-license it under GPLv2+
> > 
> > I will send a patch modifying the license.
> > 
> > In the meantime, canokey.c was also modified by
> > MkfsSion <mkfssion@mkfssion.com>
> > 
> > I've Cc'ed MkfsSion for their attitude on this.
> Acked-by: YuanYang Meng <mkfssion@mkfssion.com>

That's great, thanks to all for the quick response.

With regards,
Daniel
diff mbox series

Patch

diff --git a/hw/usb/canokey.h b/hw/usb/canokey.h
new file mode 100644
index 000000000000..24cf30420346
--- /dev/null
+++ b/hw/usb/canokey.h
@@ -0,0 +1,69 @@ 
+/*
+ * CanoKey QEMU device header.
+ *
+ * Copyright (c) 2021-2022 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 Canokey (i.e. hw/canokey.c)
+ */
+
+/* CTRL INTR BULK */
+#define CANOKEY_EP_NUM 3
+/* BULK/INTR IN can be up to 1352 bytes, e.g. get key info */
+#define CANOKEY_EP_IN_BUFFER_SIZE 2048
+/* BULK OUT can be up to 270 bytes, e.g. PIV import cert */
+#define CANOKEY_EP_OUT_BUFFER_SIZE 512
+
+typedef enum {
+    CANOKEY_EP_IN_WAIT,
+    CANOKEY_EP_IN_READY,
+    CANOKEY_EP_IN_STALL
+} CanoKeyEPState;
+
+typedef struct CanoKeyState {
+    USBDevice dev;
+
+    /* IN packets from canokey device loop */
+    uint8_t ep_in[CANOKEY_EP_NUM][CANOKEY_EP_IN_BUFFER_SIZE];
+    /*
+     * See canokey_emu_transmit
+     *
+     * For large INTR IN, receive multiple data from canokey device loop
+     * in this case ep_in_size would increase with every call
+     */
+    uint32_t ep_in_size[CANOKEY_EP_NUM];
+    /*
+     * Used in canokey_handle_data
+     * for IN larger than p->iov.size, we would do multiple handle_data()
+     *
+     * The difference between ep_in_pos and ep_in_size:
+     * We first increase ep_in_size to fill ep_in buffer in device_loop,
+     * then use ep_in_pos to submit data from ep_in buffer in handle_data
+     */
+    uint32_t ep_in_pos[CANOKEY_EP_NUM];
+    CanoKeyEPState ep_in_state[CANOKEY_EP_NUM];
+
+    /* OUT pointer to canokey recv buffer */
+    uint8_t *ep_out[CANOKEY_EP_NUM];
+    uint32_t ep_out_size[CANOKEY_EP_NUM];
+    /* For large BULK OUT, multiple write to ep_out is needed */
+    uint8_t ep_out_buffer[CANOKEY_EP_NUM][CANOKEY_EP_OUT_BUFFER_SIZE];
+
+    /* Properties */
+    char *file; /* canokey-file */
+} CanoKeyState;
+
+#endif /* CANOKEY_H */
diff --git a/hw/usb/canokey.c b/hw/usb/canokey.c
new file mode 100644
index 000000000000..6cb8b7cdb089
--- /dev/null
+++ b/hw/usb/canokey.c
@@ -0,0 +1,300 @@ 
+/*
+ * CanoKey QEMU device implementation.
+ *
+ * Copyright (c) 2021-2022 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 "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,
+    .high = &desc_device_canokey,
+    .str  = desc_strings,
+};
+
+
+/*
+ * libcanokey-qemu.so side functions
+ * All functions are called from canokey_emu_device_loop
+ */
+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 */
+    key->ep_in_size[ep_in] = 0;
+    key->ep_in_state[ep_in] = CANOKEY_EP_IN_STALL;
+    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;
+    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 */
+    memcpy(key->ep_in[ep_in] + key->ep_in_size[ep_in],
+            pbuf, size);
+    key->ep_in_size[ep_in] += size;
+    key->ep_in_state[ep_in] = CANOKEY_EP_IN_READY;
+    /*
+     * ready for more data in device loop
+     *
+     * Note: this is a quirk for CanoKey CTAPHID
+     * because it calls multiple emu_transmit in one device_loop
+     * but w/o data_in it would stuck in device_loop
+     * This has no side effect for CCID as it is strictly
+     * OUT then IN transfer
+     * However it has side effect for Control transfer
+     */
+    if (ep_in != 0) {
+        canokey_emu_data_in(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];
+}
+
+/*
+ * QEMU side functions
+ */
+static void canokey_handle_reset(USBDevice *dev)
+{
+    CanoKeyState *key = CANOKEY(dev);
+    for (int i = 0; i != CANOKEY_EP_NUM; ++i) {
+        key->ep_in_state[i] = CANOKEY_EP_IN_WAIT;
+        key->ep_in_pos[i] = 0;
+        key->ep_in_size[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);
+
+    uint32_t dir_in = request & DeviceRequest;
+    if (!dir_in) {
+        /* OUT */
+        if (key->ep_out[0] != NULL) {
+            memcpy(key->ep_out[0], data, length);
+        }
+        canokey_emu_data_out(p->ep->nr, data);
+    }
+
+    canokey_emu_device_loop();
+
+    /* IN */
+    switch (key->ep_in_state[0]) {
+    case CANOKEY_EP_IN_WAIT:
+        p->status = USB_RET_NAK;
+        break;
+    case CANOKEY_EP_IN_STALL:
+        p->status = USB_RET_STALL;
+        break;
+    case CANOKEY_EP_IN_READY:
+        memcpy(data, key->ep_in[0], key->ep_in_size[0]);
+        p->actual_length = key->ep_in_size[0];
+        /* reset state */
+        key->ep_in_state[0] = CANOKEY_EP_IN_WAIT;
+        key->ep_in_size[0] = 0;
+        key->ep_in_pos[0] = 0;
+        break;
+    }
+}
+
+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;
+    uint32_t out_pos;
+    uint32_t out_len;
+    switch (p->pid) {
+    case USB_TOKEN_OUT:
+        usb_packet_copy(p, key->ep_out_buffer[ep_out], p->iov.size);
+        out_pos = 0;
+        while (out_pos != p->iov.size) {
+            /*
+             * key->ep_out[ep_out] set by prepare_receive
+             * to be a buffer inside libcanokey-qemu.so
+             * key->ep_out_size[ep_out] set by prepare_receive
+             * to be the buffer length
+             */
+            out_len = MIN(p->iov.size - out_pos, key->ep_out_size[ep_out]);
+            memcpy(key->ep_out[ep_out],
+                    key->ep_out_buffer[ep_out] + out_pos, out_len);
+            out_pos += out_len;
+            /* update ep_out_size to actual len */
+            key->ep_out_size[ep_out] = out_len;
+            canokey_emu_data_out(ep_out, NULL);
+        }
+        break;
+    case USB_TOKEN_IN:
+        if (key->ep_in_pos[ep_in] == 0) { /* first time IN */
+            canokey_emu_data_in(ep_in);
+            canokey_emu_device_loop(); /* may call transmit multiple times */
+        }
+        switch (key->ep_in_state[ep_in]) {
+        case CANOKEY_EP_IN_WAIT:
+            /* NAK for early INTR IN */
+            p->status = USB_RET_NAK;
+            break;
+        case CANOKEY_EP_IN_STALL:
+            p->status = USB_RET_STALL;
+            break;
+        case CANOKEY_EP_IN_READY:
+            /* submit part of ep_in buffer to USBPacket */
+            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;
+            /* reset state if all data submitted */
+            if (key->ep_in_pos[ep_in] == key->ep_in_size[ep_in]) {
+                key->ep_in_state[ep_in] = CANOKEY_EP_IN_WAIT;
+                key->ep_in_size[ep_in] = 0;
+                key->ep_in_pos[ep_in] = 0;
+            }
+            break;
+        }
+        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);
+
+    for (int i = 0; i != CANOKEY_EP_NUM; ++i) {
+        key->ep_in_state[i] = CANOKEY_EP_IN_WAIT;
+        key->ep_in_size[i] = 0;
+        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;
+    }
+}
+
+static void canokey_unrealize(USBDevice *base)
+{
+}
+
+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);
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+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)