usb: add CanoKey device, fixes for ehci + redir

ui: fixes for gtk and cocoa, rework refresh rate
 virtio-gpu: scanout flush fix
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmKoe/8ACgkQTLbY7tPo
 cTgZqw/9HD5dMjP74jwrf14dSCR6FD8PfSZU43YBZtMKMtYIzSgrG0NGmreDIhmr
 ZM+G0By+J8vFaSqDukX31077DnptyrxsANOg3zc28SfOCrI7I/mNVymd9hl+Ydpd
 A7h0DpHxs1mkpTVxGoXZoJRGXUE41rctbFVjG3CGynSG9K2vFQRsJz0jG723dg5Y
 uv+Di1WkhqNkyKNsTEGbz9LNqtdtGzvQm3COBpKoTsl4X3EXIE68Qh7i3cMTSNIw
 KKPARW3oiCOy3Fc4kQW9nSxkkHMS6NPL1uyQ52j7pXYxRdxRaREFQ9Gxst3ie9bS
 mbqSuzS2+1v0w37bq9wE0PiCkmwWnu2KWiWWkAIYlmmZTgHvgxCvPcJaeItmap27
 dsAuPUGBbhhrmUwfMgJXp/wRvoZQc2l9w9+eUklsbI+VTbr6i+r/OoLRmnDJr+K/
 yNscMU1LzoigK0NDdP+PnFl3k8pux0Awtotgfyd+UGTSW8a5L6UFAWIxcUcd0Jjv
 24jAEEc1S1ciDxJDWYn4+17KJARG7no2PRXsGXCUNaWduGEk8wPK+i6Xk82U36o7
 7j0N16RFNv1YSUaUJHgtmAMRJIQMCiB42VaYxlDfzKupvq2RgRWaWBD/HozgLhXn
 DjEX+JRAnaOYnn1NURzTNDwnhQethJRXI1ntI1U8IFLYT4baSCY=
 =L5PO
 -----END PGP SIGNATURE-----

Merge tag 'kraxel-20220614-pull-request' of git://git.kraxel.org/qemu into staging

usb: add CanoKey device, fixes for ehci + redir
ui: fixes for gtk and cocoa, rework refresh rate
virtio-gpu: scanout flush fix

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmKoe/8ACgkQTLbY7tPo
# cTgZqw/9HD5dMjP74jwrf14dSCR6FD8PfSZU43YBZtMKMtYIzSgrG0NGmreDIhmr
# ZM+G0By+J8vFaSqDukX31077DnptyrxsANOg3zc28SfOCrI7I/mNVymd9hl+Ydpd
# A7h0DpHxs1mkpTVxGoXZoJRGXUE41rctbFVjG3CGynSG9K2vFQRsJz0jG723dg5Y
# uv+Di1WkhqNkyKNsTEGbz9LNqtdtGzvQm3COBpKoTsl4X3EXIE68Qh7i3cMTSNIw
# KKPARW3oiCOy3Fc4kQW9nSxkkHMS6NPL1uyQ52j7pXYxRdxRaREFQ9Gxst3ie9bS
# mbqSuzS2+1v0w37bq9wE0PiCkmwWnu2KWiWWkAIYlmmZTgHvgxCvPcJaeItmap27
# dsAuPUGBbhhrmUwfMgJXp/wRvoZQc2l9w9+eUklsbI+VTbr6i+r/OoLRmnDJr+K/
# yNscMU1LzoigK0NDdP+PnFl3k8pux0Awtotgfyd+UGTSW8a5L6UFAWIxcUcd0Jjv
# 24jAEEc1S1ciDxJDWYn4+17KJARG7no2PRXsGXCUNaWduGEk8wPK+i6Xk82U36o7
# 7j0N16RFNv1YSUaUJHgtmAMRJIQMCiB42VaYxlDfzKupvq2RgRWaWBD/HozgLhXn
# DjEX+JRAnaOYnn1NURzTNDwnhQethJRXI1ntI1U8IFLYT4baSCY=
# =L5PO
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 14 Jun 2022 05:15:59 AM PDT
# gpg:                using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [undefined]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [undefined]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [undefined]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* tag 'kraxel-20220614-pull-request' of git://git.kraxel.org/qemu:
  virtio-gpu: Respect UI refresh rate for EDID
  ui: Deliver refresh rate via QemuUIInfo
  ui/console: Do not return a value with ui_info
  virtio-gpu: update done only on the scanout associated with rect
  usbredir: avoid queuing hello packet on snapshot restore
  hw/usb/hcd-ehci: fix writeback order
  MAINTAINERS: add myself as CanoKey maintainer
  docs/system/devices/usb: Add CanoKey to USB devices examples
  docs: Add CanoKey documentation
  meson: Add CanoKey
  hw/usb/canokey: Add trace events
  hw/usb: Add CanoKey Implementation
  ui/cocoa: Fix poweroff request code
  ui/gtk-gl-area: create the requested GL context version
  ui/gtk-gl-area: implement GL context destruction

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2022-06-14 06:21:46 -07:00
commit 8e6c70b9d4
28 changed files with 707 additions and 51 deletions

View File

@ -2427,6 +2427,14 @@ F: hw/intc/s390_flic*.c
F: include/hw/s390x/s390_flic.h
L: qemu-s390x@nongnu.org
CanoKey
M: Hongren (Zenithal) Zheng <i@zenithal.me>
S: Maintained
R: Canokeys.org <contact@canokeys.org>
F: hw/usb/canokey.c
F: hw/usb/canokey.h
F: docs/system/devices/canokey.rst
Subsystems
----------
Overall Audio backends

View File

@ -92,3 +92,4 @@ Emulated Devices
devices/vhost-user.rst
devices/virtio-pmem.rst
devices/vhost-user-rng.rst
devices/canokey.rst

View File

@ -0,0 +1,168 @@
.. _canokey:
CanoKey QEMU
------------
CanoKey [1]_ is an open-source secure key with supports of
* U2F / FIDO2 with Ed25519 and HMAC-secret
* OpenPGP Card V3.4 with RSA4096, Ed25519 and more [2]_
* PIV (NIST SP 800-73-4)
* HOTP / TOTP
* NDEF
All these platform-independent features are in canokey-core [3]_.
For different platforms, CanoKey has different implementations,
including both hardware implementions and virtual cards:
* CanoKey STM32 [4]_
* CanoKey Pigeon [5]_
* (virt-card) CanoKey USB/IP
* (virt-card) CanoKey FunctionFS
In QEMU, yet another CanoKey virt-card is implemented.
CanoKey QEMU exposes itself as a USB device to the guest OS.
With the same software configuration as a hardware key,
the guest OS can use all the functionalities of a secure key as if
there was actually an hardware key plugged in.
CanoKey QEMU provides much convenience for debuging:
* libcanokey-qemu supports debuging output thus developers can
inspect what happens inside a secure key
* CanoKey QEMU supports trace event thus event
* QEMU USB stack supports pcap thus USB packet between the guest
and key can be captured and analysed
Then for developers:
* For developers on software with secure key support (e.g. FIDO2, OpenPGP),
they can see what happens inside the secure key
* For secure key developers, USB packets between guest OS and CanoKey
can be easily captured and analysed
Also since this is a virtual card, it can be easily used in CI for testing
on code coping with secure key.
Building
========
libcanokey-qemu is required to use CanoKey QEMU.
.. code-block:: shell
git clone https://github.com/canokeys/canokey-qemu
mkdir canokey-qemu/build
pushd canokey-qemu/build
If you want to install libcanokey-qemu in a different place,
add ``-DCMAKE_INSTALL_PREFIX=/path/to/your/place`` to cmake below.
.. code-block:: shell
cmake ..
make
make install # may need sudo
popd
Then configuring and building:
.. code-block:: shell
# depending on your env, lib/pkgconfig can be lib64/pkgconfig
export PKG_CONFIG_PATH=/path/to/your/place/lib/pkgconfig:$PKG_CONFIG_PATH
./configure --enable-canokey && make
Using CanoKey QEMU
==================
CanoKey QEMU stores all its data on a file of the host specified by the argument
when invoking qemu.
.. parsed-literal::
|qemu_system| -usb -device canokey,file=$HOME/.canokey-file
Note: you should keep this file carefully as it may contain your private key!
The first time when the file is used, it is created and initialized by CanoKey,
afterwards CanoKey QEMU would just read this file.
After the guest OS boots, you can check that there is a USB device.
For example, If the guest OS is an Linux machine. You may invoke lsusb
and find CanoKey QEMU there:
.. code-block:: shell
$ lsusb
Bus 001 Device 002: ID 20a0:42d4 Clay Logic CanoKey QEMU
You may setup the key as guided in [6]_. The console for the key is at [7]_.
Debuging
========
CanoKey QEMU consists of two parts, ``libcanokey-qemu.so`` and ``canokey.c``,
the latter of which resides in QEMU. The former provides core functionality
of a secure key while the latter provides platform-dependent functions:
USB packet handling.
If you want to trace what happens inside the secure key, when compiling
libcanokey-qemu, you should add ``-DQEMU_DEBUG_OUTPUT=ON`` in cmake command
line:
.. code-block:: shell
cmake .. -DQEMU_DEBUG_OUTPUT=ON
If you want to trace events happened in canokey.c, use
.. parsed-literal::
|qemu_system| --trace "canokey_*" \\
-usb -device canokey,file=$HOME/.canokey-file
If you want to capture USB packets between the guest and the host, you can:
.. parsed-literal::
|qemu_system| -usb -device canokey,file=$HOME/.canokey-file,pcap=key.pcap
Limitations
===========
Currently libcanokey-qemu.so has dozens of global variables as it was originally
designed for embedded systems. Thus one qemu instance can not have
multiple CanoKey QEMU running, namely you can not
.. parsed-literal::
|qemu_system| -usb -device canokey,file=$HOME/.canokey-file \\
-device canokey,file=$HOME/.canokey-file2
Also, there is no lock on canokey-file, thus two CanoKey QEMU instance
can not read one canokey-file at the same time.
Another limitation is that this device is not compatible with ``qemu-xhci``,
in that this device would hang when there are FIDO2 packets (traffic on
interrupt endpoints). If you do not use FIDO2 then it works as intended,
but for full functionality you should use old uhci/ehci bus and attach canokey
to it, for example
.. parsed-literal::
|qemu_system| -device piix3-usb-uhci,id=uhci -device canokey,bus=uhci.0
References
==========
.. [1] `<https://canokeys.org>`_
.. [2] `<https://docs.canokeys.org/userguide/openpgp/#supported-algorithm>`_
.. [3] `<https://github.com/canokeys/canokey-core>`_
.. [4] `<https://github.com/canokeys/canokey-stm32>`_
.. [5] `<https://github.com/canokeys/canokey-pigeon>`_
.. [6] `<https://docs.canokeys.org/>`_
.. [7] `<https://console.canokeys.org/>`_

View File

@ -199,6 +199,10 @@ option or the ``device_add`` monitor command. Available devices are:
``u2f-{emulated,passthru}``
Universal Second Factor device
``canokey``
An Open-source Secure Key implementing FIDO2, OpenPGP, PIV and more.
For more information, see :ref:`canokey`.
Physical port addressing
^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -69,16 +69,17 @@ static void virtio_gpu_notify_event(VirtIOGPUBase *g, uint32_t event_type)
virtio_notify_config(&g->parent_obj);
}
static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
static void virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
{
VirtIOGPUBase *g = opaque;
if (idx >= g->conf.max_outputs) {
return -1;
return;
}
g->req_state[idx].x = info->xoff;
g->req_state[idx].y = info->yoff;
g->req_state[idx].refresh_rate = info->refresh_rate;
g->req_state[idx].width = info->width;
g->req_state[idx].height = info->height;
g->req_state[idx].width_mm = info->width_mm;
@ -92,7 +93,7 @@ static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
/* send event to guest */
virtio_gpu_notify_event(g, VIRTIO_GPU_EVENT_DISPLAY);
return 0;
return;
}
static void

View File

@ -217,6 +217,7 @@ virtio_gpu_generate_edid(VirtIOGPU *g, int scanout,
.height_mm = b->req_state[scanout].height_mm,
.prefx = b->req_state[scanout].width,
.prefy = b->req_state[scanout].height,
.refresh_rate = b->req_state[scanout].refresh_rate,
};
edid->size = cpu_to_le32(sizeof(edid->edid));
@ -514,6 +515,9 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g,
for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
scanout = &g->parent_obj.scanout[i];
if (scanout->resource_id == res->resource_id &&
rf.r.x >= scanout->x && rf.r.y >= scanout->y &&
rf.r.x + rf.r.width <= scanout->x + scanout->width &&
rf.r.y + rf.r.height <= scanout->y + scanout->height &&
console_has_gl(scanout->con)) {
dpy_gl_update(scanout->con, 0, 0, scanout->width,
scanout->height);

View File

@ -47,15 +47,14 @@ static void virtio_vga_base_text_update(void *opaque, console_ch_t *chardata)
}
}
static int virtio_vga_base_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
static void virtio_vga_base_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
{
VirtIOVGABase *vvga = opaque;
VirtIOGPUBase *g = vvga->vgpu;
if (g->hw_ops->ui_info) {
return g->hw_ops->ui_info(g, idx, info);
g->hw_ops->ui_info(g, idx, info);
}
return -1;
}
static void virtio_vga_base_gl_block(void *opaque, bool block)

View File

@ -777,16 +777,24 @@ static void xenfb_update(void *opaque)
xenfb->up_fullscreen = 0;
}
static void xenfb_update_interval(void *opaque, uint64_t interval)
static void xenfb_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
{
struct XenFB *xenfb = opaque;
uint32_t refresh_rate;
if (xenfb->feature_update) {
#ifdef XENFB_TYPE_REFRESH_PERIOD
if (xenfb_queue_full(xenfb)) {
return;
}
xenfb_send_refresh_period(xenfb, interval);
refresh_rate = info->refresh_rate;
if (!refresh_rate) {
refresh_rate = 75;
}
/* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
xenfb_send_refresh_period(xenfb, 1000 * 1000 / refresh_rate);
#endif
}
}
@ -983,5 +991,5 @@ struct XenDevOps xen_framebuffer_ops = {
static const GraphicHwOps xenfb_ops = {
.invalidate = xenfb_invalidate,
.gfx_update = xenfb_update,
.update_interval = xenfb_update_interval,
.ui_info = xenfb_ui_info,
};

View File

@ -119,6 +119,11 @@ config USB_U2F
default y
depends on USB
config USB_CANOKEY
bool
default y
depends on USB
config IMX_USBPHY
bool
default y

313
hw/usb/canokey.c Normal file
View File

@ -0,0 +1,313 @@
/*
* 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 "trace.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)
{
trace_canokey_emu_stall_ep(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)
{
trace_canokey_emu_set_address(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)
{
trace_canokey_emu_prepare_receive(ep, 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)
{
trace_canokey_emu_transmit(ep, 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)
{
trace_canokey_handle_reset();
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)
{
trace_canokey_handle_control_setup(request, value, index, length);
CanoKeyState *key = CANOKEY(dev);
canokey_emu_setup(request, value, index, length);
uint32_t dir_in = request & DeviceRequest;
if (!dir_in) {
/* OUT */
trace_canokey_handle_control_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];
trace_canokey_handle_control_in(p->actual_length);
/* 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:
trace_canokey_handle_data_out(ep_out, p->iov.size);
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;
}
trace_canokey_handle_data_in(ep_in, in_len);
break;
}
break;
default:
p->status = USB_RET_STALL;
break;
}
}
static void canokey_realize(USBDevice *base, Error **errp)
{
trace_canokey_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);
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)
{
trace_canokey_unrealize();
}
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)

69
hw/usb/canokey.h Normal file
View File

@ -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 */

View File

@ -2011,7 +2011,10 @@ static int ehci_state_writeback(EHCIQueue *q)
ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd);
qtd = (uint32_t *) &q->qh.next_qtd;
addr = NLPTR_GET(p->qtdaddr);
put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 2);
/* First write back the offset */
put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qtd + 3, 1);
/* Then write back the token, clearing the 'active' bit */
put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 1);
ehci_free_packet(p);
/*

View File

@ -63,6 +63,11 @@ if u2f.found()
softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: [u2f, files('u2f-emulated.c')])
endif
# CanoKey
if canokey.found()
softmmu_ss.add(when: 'CONFIG_USB_CANOKEY', if_true: [canokey, files('canokey.c')])
endif
# usb redirect
if usbredir.found()
usbredir_ss = ss.source_set()

View File

@ -1280,7 +1280,8 @@ static void usbredir_create_parser(USBRedirDevice *dev)
}
#endif
if (runstate_check(RUN_STATE_INMIGRATE)) {
if (runstate_check(RUN_STATE_INMIGRATE) ||
runstate_check(RUN_STATE_PRELAUNCH)) {
flags |= usbredirparser_fl_no_hello;
}
usbredirparser_init(dev->parser, VERSION, caps, USB_REDIR_CAPS_SIZE,

View File

@ -345,3 +345,19 @@ usb_serial_set_baud(int bus, int addr, int baud) "dev %d:%u baud rate %d"
usb_serial_set_data(int bus, int addr, int parity, int data, int stop) "dev %d:%u parity %c, data bits %d, stop bits %d"
usb_serial_set_flow_control(int bus, int addr, int index) "dev %d:%u flow control %d"
usb_serial_set_xonxoff(int bus, int addr, uint8_t xon, uint8_t xoff) "dev %d:%u xon 0x%x xoff 0x%x"
# canokey.c
canokey_emu_stall_ep(uint8_t ep) "ep %d"
canokey_emu_set_address(uint8_t addr) "addr %d"
canokey_emu_prepare_receive(uint8_t ep, uint16_t size) "ep %d size %d"
canokey_emu_transmit(uint8_t ep, uint16_t size) "ep %d size %d"
canokey_thread_start(void)
canokey_thread_stop(void)
canokey_handle_reset(void)
canokey_handle_control_setup(int request, int value, int index, int length) "request 0x%04X value 0x%04X index 0x%04X length 0x%04X"
canokey_handle_control_out(void)
canokey_handle_control_in(int actual_len) "len %d"
canokey_handle_data_out(uint8_t ep_out, uint32_t out_len) "ep %d len %d"
canokey_handle_data_in(uint8_t ep_in, uint32_t in_len) "ep %d len %d"
canokey_realize(void)
canokey_unrealize(void)

View File

@ -106,14 +106,14 @@ err:
return;
}
static int vfio_display_edid_ui_info(void *opaque, uint32_t idx,
QemuUIInfo *info)
static void vfio_display_edid_ui_info(void *opaque, uint32_t idx,
QemuUIInfo *info)
{
VFIOPCIDevice *vdev = opaque;
VFIODisplay *dpy = vdev->dpy;
if (!dpy->edid_regs) {
return 0;
return;
}
if (info->width && info->height) {
@ -121,8 +121,6 @@ static int vfio_display_edid_ui_info(void *opaque, uint32_t idx,
} else {
vfio_display_edid_update(vdev, false, 0, 0);
}
return 0;
}
static void vfio_display_edid_init(VFIOPCIDevice *vdev)

View File

@ -80,6 +80,7 @@ struct virtio_gpu_scanout {
struct virtio_gpu_requested_state {
uint16_t width_mm, height_mm;
uint32_t width, height;
uint32_t refresh_rate;
int x, y;
};

View File

@ -139,6 +139,7 @@ typedef struct QemuUIInfo {
int yoff;
uint32_t width;
uint32_t height;
uint32_t refresh_rate;
} QemuUIInfo;
/* cursor data format is 32bit RGBA */
@ -431,8 +432,7 @@ typedef struct GraphicHwOps {
void (*gfx_update)(void *opaque);
bool gfx_update_async; /* if true, calls graphic_hw_update_done() */
void (*text_update)(void *opaque, console_ch_t *text);
void (*update_interval)(void *opaque, uint64_t interval);
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
void (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
void (*gl_block)(void *opaque, bool block);
} GraphicHwOps;

View File

@ -155,7 +155,7 @@ extern bool gtk_use_gl_area;
/* ui/gtk.c */
void gd_update_windowsize(VirtualConsole *vc);
int gd_monitor_update_interval(GtkWidget *widget);
void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget);
void gd_hw_gl_flushed(void *vc);
/* ui/gtk-egl.c */

View File

@ -1408,6 +1408,12 @@ if have_system
method: 'pkg-config',
kwargs: static_kwargs)
endif
canokey = not_found
if have_system
canokey = dependency('canokey-qemu', required: get_option('canokey'),
method: 'pkg-config',
kwargs: static_kwargs)
endif
usbredir = not_found
if not get_option('usb_redir').auto() or have_system
usbredir = dependency('libusbredirparser-0.5', required: get_option('usb_redir'),

View File

@ -189,6 +189,8 @@ option('spice_protocol', type : 'feature', value : 'auto',
description: 'Spice protocol support')
option('u2f', type : 'feature', value : 'auto',
description: 'U2F emulation support')
option('canokey', type : 'feature', value : 'auto',
description: 'CanoKey support')
option('usb_redir', type : 'feature', value : 'auto',
description: 'libusbredir support')
option('l2tpv3', type : 'feature', value : 'auto',

View File

@ -73,6 +73,7 @@ meson_options_help() {
printf "%s\n" ' bpf eBPF support'
printf "%s\n" ' brlapi brlapi character device driver'
printf "%s\n" ' bzip2 bzip2 support for DMG images'
printf "%s\n" ' canokey CanoKey support'
printf "%s\n" ' cap-ng cap_ng support'
printf "%s\n" ' capstone Whether and how to find the capstone library'
printf "%s\n" ' cloop cloop image format support'
@ -204,6 +205,8 @@ _meson_option_parse() {
--disable-brlapi) printf "%s" -Dbrlapi=disabled ;;
--enable-bzip2) printf "%s" -Dbzip2=enabled ;;
--disable-bzip2) printf "%s" -Dbzip2=disabled ;;
--enable-canokey) printf "%s" -Dcanokey=enabled ;;
--disable-canokey) printf "%s" -Dcanokey=disabled ;;
--enable-cap-ng) printf "%s" -Dcap_ng=enabled ;;
--disable-cap-ng) printf "%s" -Dcap_ng=disabled ;;
--enable-capstone) printf "%s" -Dcapstone=enabled ;;

View File

@ -35,6 +35,7 @@
#include "ui/kbd-state.h"
#include "sysemu/sysemu.h"
#include "sysemu/runstate.h"
#include "sysemu/runstate-action.h"
#include "sysemu/cpu-throttle.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-block.h"
@ -1290,7 +1291,10 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
{
COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
with_iothread_lock(^{
shutdown_action = SHUTDOWN_ACTION_POWEROFF;
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
});
/*
* Sleep here, because returning will cause OSX to kill us

View File

@ -160,7 +160,6 @@ static void gui_update(void *opaque)
uint64_t dcl_interval;
DisplayState *ds = opaque;
DisplayChangeListener *dcl;
QemuConsole *con;
ds->refreshing = true;
dpy_refresh(ds);
@ -175,11 +174,6 @@ static void gui_update(void *opaque)
}
if (ds->update_interval != interval) {
ds->update_interval = interval;
QTAILQ_FOREACH(con, &consoles, next) {
if (con->hw_ops->update_interval) {
con->hw_ops->update_interval(con->hw, interval);
}
}
trace_console_refresh(interval);
}
ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);

View File

@ -140,8 +140,8 @@ void gd_egl_refresh(DisplayChangeListener *dcl)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
vc->gfx.dcl.update_interval = gd_monitor_update_interval(
vc->window ? vc->window : vc->gfx.drawing_area);
gd_update_monitor_refresh_rate(
vc, vc->window ? vc->window : vc->gfx.drawing_area);
if (!vc->gfx.esurface) {
gd_egl_init(vc);

View File

@ -121,8 +121,7 @@ void gd_gl_area_refresh(DisplayChangeListener *dcl)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
vc->gfx.dcl.update_interval = gd_monitor_update_interval(
vc->window ? vc->window : vc->gfx.drawing_area);
gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : vc->gfx.drawing_area);
if (!vc->gfx.gls) {
if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
@ -170,6 +169,23 @@ void gd_gl_area_switch(DisplayChangeListener *dcl,
}
}
static int gd_cmp_gl_context_version(int major, int minor, QEMUGLParams *params)
{
if (major > params->major_ver) {
return 1;
}
if (major < params->major_ver) {
return -1;
}
if (minor > params->minor_ver) {
return 1;
}
if (minor < params->minor_ver) {
return -1;
}
return 0;
}
QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
@ -177,8 +193,8 @@ QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
GdkWindow *window;
GdkGLContext *ctx;
GError *err = NULL;
int major, minor;
gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
window = gtk_widget_get_window(vc->gfx.drawing_area);
ctx = gdk_window_create_gl_context(window, &err);
if (err) {
@ -196,12 +212,30 @@ QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
g_clear_object(&ctx);
return NULL;
}
gdk_gl_context_make_current(ctx);
gdk_gl_context_get_version(ctx, &major, &minor);
gdk_gl_context_clear_current();
gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
if (gd_cmp_gl_context_version(major, minor, params) == -1) {
/* created ctx version < requested version */
g_clear_object(&ctx);
}
trace_gd_gl_area_create_context(ctx, params->major_ver, params->minor_ver);
return ctx;
}
void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
/* FIXME */
GdkGLContext *current_ctx = gdk_gl_context_get_current();
trace_gd_gl_area_destroy_context(ctx, current_ctx);
if (ctx == current_ctx) {
gdk_gl_context_clear_current();
}
g_clear_object(&ctx);
}
void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,

View File

@ -710,11 +710,20 @@ static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
return TRUE;
}
static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
static void gd_set_ui_refresh_rate(VirtualConsole *vc, int refresh_rate)
{
QemuUIInfo info;
memset(&info, 0, sizeof(info));
info = *dpy_get_ui_info(vc->gfx.dcl.con);
info.refresh_rate = refresh_rate;
dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
}
static void gd_set_ui_size(VirtualConsole *vc, gint width, gint height)
{
QemuUIInfo info;
info = *dpy_get_ui_info(vc->gfx.dcl.con);
info.width = width;
info.height = height;
dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
@ -738,33 +747,32 @@ static void gd_resize_event(GtkGLArea *area,
{
VirtualConsole *vc = (void *)opaque;
gd_set_ui_info(vc, width, height);
gd_set_ui_size(vc, width, height);
}
#endif
/*
* If available, return the update interval of the monitor in ms,
* else return 0 (the default update interval).
*/
int gd_monitor_update_interval(GtkWidget *widget)
void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget)
{
#ifdef GDK_VERSION_3_22
GdkWindow *win = gtk_widget_get_window(widget);
int refresh_rate;
if (win) {
GdkDisplay *dpy = gtk_widget_get_display(widget);
GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
int refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */
if (refresh_rate) {
/* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
return MIN(1000 * 1000 / refresh_rate,
GUI_REFRESH_INTERVAL_DEFAULT);
}
refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */
} else {
refresh_rate = 0;
}
gd_set_ui_refresh_rate(vc, refresh_rate);
/* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
vc->gfx.dcl.update_interval = refresh_rate ?
MIN(1000 * 1000 / refresh_rate, GUI_REFRESH_INTERVAL_DEFAULT) :
GUI_REFRESH_INTERVAL_DEFAULT;
#endif
return 0;
}
static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
@ -801,8 +809,7 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
return FALSE;
}
vc->gfx.dcl.update_interval =
gd_monitor_update_interval(vc->window ? vc->window : s->window);
gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : s->window);
fbw = surface_width(vc->gfx.ds);
fbh = surface_height(vc->gfx.ds);
@ -1691,7 +1698,7 @@ static gboolean gd_configure(GtkWidget *widget,
{
VirtualConsole *vc = opaque;
gd_set_ui_info(vc, cfg->width, cfg->height);
gd_set_ui_size(vc, cfg->width, cfg->height);
return FALSE;
}

View File

@ -26,6 +26,8 @@ gd_key_event(const char *tab, int gdk_keycode, int qkeycode, const char *action)
gd_grab(const char *tab, const char *device, const char *reason) "tab=%s, dev=%s, reason=%s"
gd_ungrab(const char *tab, const char *device) "tab=%s, dev=%s"
gd_keymap_windowing(const char *name) "backend=%s"
gd_gl_area_create_context(void *ctx, int major, int minor) "ctx=%p, major=%d, minor=%d"
gd_gl_area_destroy_context(void *ctx, void *current_ctx) "ctx=%p, current_ctx=%p"
# vnc-auth-sasl.c
# vnc-auth-vencrypt.c