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:
commit
8e6c70b9d4
|
@ -2427,6 +2427,14 @@ F: hw/intc/s390_flic*.c
|
||||||
F: include/hw/s390x/s390_flic.h
|
F: include/hw/s390x/s390_flic.h
|
||||||
L: qemu-s390x@nongnu.org
|
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
|
Subsystems
|
||||||
----------
|
----------
|
||||||
Overall Audio backends
|
Overall Audio backends
|
||||||
|
|
|
@ -92,3 +92,4 @@ Emulated Devices
|
||||||
devices/vhost-user.rst
|
devices/vhost-user.rst
|
||||||
devices/virtio-pmem.rst
|
devices/virtio-pmem.rst
|
||||||
devices/vhost-user-rng.rst
|
devices/vhost-user-rng.rst
|
||||||
|
devices/canokey.rst
|
||||||
|
|
|
@ -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/>`_
|
|
@ -199,6 +199,10 @@ option or the ``device_add`` monitor command. Available devices are:
|
||||||
``u2f-{emulated,passthru}``
|
``u2f-{emulated,passthru}``
|
||||||
Universal Second Factor device
|
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
|
Physical port addressing
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -69,16 +69,17 @@ static void virtio_gpu_notify_event(VirtIOGPUBase *g, uint32_t event_type)
|
||||||
virtio_notify_config(&g->parent_obj);
|
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;
|
VirtIOGPUBase *g = opaque;
|
||||||
|
|
||||||
if (idx >= g->conf.max_outputs) {
|
if (idx >= g->conf.max_outputs) {
|
||||||
return -1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g->req_state[idx].x = info->xoff;
|
g->req_state[idx].x = info->xoff;
|
||||||
g->req_state[idx].y = info->yoff;
|
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].width = info->width;
|
||||||
g->req_state[idx].height = info->height;
|
g->req_state[idx].height = info->height;
|
||||||
g->req_state[idx].width_mm = info->width_mm;
|
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 */
|
/* send event to guest */
|
||||||
virtio_gpu_notify_event(g, VIRTIO_GPU_EVENT_DISPLAY);
|
virtio_gpu_notify_event(g, VIRTIO_GPU_EVENT_DISPLAY);
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
|
@ -217,6 +217,7 @@ virtio_gpu_generate_edid(VirtIOGPU *g, int scanout,
|
||||||
.height_mm = b->req_state[scanout].height_mm,
|
.height_mm = b->req_state[scanout].height_mm,
|
||||||
.prefx = b->req_state[scanout].width,
|
.prefx = b->req_state[scanout].width,
|
||||||
.prefy = b->req_state[scanout].height,
|
.prefy = b->req_state[scanout].height,
|
||||||
|
.refresh_rate = b->req_state[scanout].refresh_rate,
|
||||||
};
|
};
|
||||||
|
|
||||||
edid->size = cpu_to_le32(sizeof(edid->edid));
|
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++) {
|
for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
|
||||||
scanout = &g->parent_obj.scanout[i];
|
scanout = &g->parent_obj.scanout[i];
|
||||||
if (scanout->resource_id == res->resource_id &&
|
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)) {
|
console_has_gl(scanout->con)) {
|
||||||
dpy_gl_update(scanout->con, 0, 0, scanout->width,
|
dpy_gl_update(scanout->con, 0, 0, scanout->width,
|
||||||
scanout->height);
|
scanout->height);
|
||||||
|
|
|
@ -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;
|
VirtIOVGABase *vvga = opaque;
|
||||||
VirtIOGPUBase *g = vvga->vgpu;
|
VirtIOGPUBase *g = vvga->vgpu;
|
||||||
|
|
||||||
if (g->hw_ops->ui_info) {
|
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)
|
static void virtio_vga_base_gl_block(void *opaque, bool block)
|
||||||
|
|
|
@ -777,16 +777,24 @@ static void xenfb_update(void *opaque)
|
||||||
xenfb->up_fullscreen = 0;
|
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;
|
struct XenFB *xenfb = opaque;
|
||||||
|
uint32_t refresh_rate;
|
||||||
|
|
||||||
if (xenfb->feature_update) {
|
if (xenfb->feature_update) {
|
||||||
#ifdef XENFB_TYPE_REFRESH_PERIOD
|
#ifdef XENFB_TYPE_REFRESH_PERIOD
|
||||||
if (xenfb_queue_full(xenfb)) {
|
if (xenfb_queue_full(xenfb)) {
|
||||||
return;
|
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
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -983,5 +991,5 @@ struct XenDevOps xen_framebuffer_ops = {
|
||||||
static const GraphicHwOps xenfb_ops = {
|
static const GraphicHwOps xenfb_ops = {
|
||||||
.invalidate = xenfb_invalidate,
|
.invalidate = xenfb_invalidate,
|
||||||
.gfx_update = xenfb_update,
|
.gfx_update = xenfb_update,
|
||||||
.update_interval = xenfb_update_interval,
|
.ui_info = xenfb_ui_info,
|
||||||
};
|
};
|
||||||
|
|
|
@ -119,6 +119,11 @@ config USB_U2F
|
||||||
default y
|
default y
|
||||||
depends on USB
|
depends on USB
|
||||||
|
|
||||||
|
config USB_CANOKEY
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
depends on USB
|
||||||
|
|
||||||
config IMX_USBPHY
|
config IMX_USBPHY
|
||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
|
|
|
@ -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)
|
|
@ -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 */
|
|
@ -2011,7 +2011,10 @@ static int ehci_state_writeback(EHCIQueue *q)
|
||||||
ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd);
|
ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd);
|
||||||
qtd = (uint32_t *) &q->qh.next_qtd;
|
qtd = (uint32_t *) &q->qh.next_qtd;
|
||||||
addr = NLPTR_GET(p->qtdaddr);
|
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);
|
ehci_free_packet(p);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -63,6 +63,11 @@ if u2f.found()
|
||||||
softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: [u2f, files('u2f-emulated.c')])
|
softmmu_ss.add(when: 'CONFIG_USB_U2F', if_true: [u2f, files('u2f-emulated.c')])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# CanoKey
|
||||||
|
if canokey.found()
|
||||||
|
softmmu_ss.add(when: 'CONFIG_USB_CANOKEY', if_true: [canokey, files('canokey.c')])
|
||||||
|
endif
|
||||||
|
|
||||||
# usb redirect
|
# usb redirect
|
||||||
if usbredir.found()
|
if usbredir.found()
|
||||||
usbredir_ss = ss.source_set()
|
usbredir_ss = ss.source_set()
|
||||||
|
|
|
@ -1280,7 +1280,8 @@ static void usbredir_create_parser(USBRedirDevice *dev)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (runstate_check(RUN_STATE_INMIGRATE)) {
|
if (runstate_check(RUN_STATE_INMIGRATE) ||
|
||||||
|
runstate_check(RUN_STATE_PRELAUNCH)) {
|
||||||
flags |= usbredirparser_fl_no_hello;
|
flags |= usbredirparser_fl_no_hello;
|
||||||
}
|
}
|
||||||
usbredirparser_init(dev->parser, VERSION, caps, USB_REDIR_CAPS_SIZE,
|
usbredirparser_init(dev->parser, VERSION, caps, USB_REDIR_CAPS_SIZE,
|
||||||
|
|
|
@ -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_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_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"
|
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)
|
||||||
|
|
|
@ -106,14 +106,14 @@ err:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vfio_display_edid_ui_info(void *opaque, uint32_t idx,
|
static void vfio_display_edid_ui_info(void *opaque, uint32_t idx,
|
||||||
QemuUIInfo *info)
|
QemuUIInfo *info)
|
||||||
{
|
{
|
||||||
VFIOPCIDevice *vdev = opaque;
|
VFIOPCIDevice *vdev = opaque;
|
||||||
VFIODisplay *dpy = vdev->dpy;
|
VFIODisplay *dpy = vdev->dpy;
|
||||||
|
|
||||||
if (!dpy->edid_regs) {
|
if (!dpy->edid_regs) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->width && info->height) {
|
if (info->width && info->height) {
|
||||||
|
@ -121,8 +121,6 @@ static int vfio_display_edid_ui_info(void *opaque, uint32_t idx,
|
||||||
} else {
|
} else {
|
||||||
vfio_display_edid_update(vdev, false, 0, 0);
|
vfio_display_edid_update(vdev, false, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void vfio_display_edid_init(VFIOPCIDevice *vdev)
|
static void vfio_display_edid_init(VFIOPCIDevice *vdev)
|
||||||
|
|
|
@ -80,6 +80,7 @@ struct virtio_gpu_scanout {
|
||||||
struct virtio_gpu_requested_state {
|
struct virtio_gpu_requested_state {
|
||||||
uint16_t width_mm, height_mm;
|
uint16_t width_mm, height_mm;
|
||||||
uint32_t width, height;
|
uint32_t width, height;
|
||||||
|
uint32_t refresh_rate;
|
||||||
int x, y;
|
int x, y;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,7 @@ typedef struct QemuUIInfo {
|
||||||
int yoff;
|
int yoff;
|
||||||
uint32_t width;
|
uint32_t width;
|
||||||
uint32_t height;
|
uint32_t height;
|
||||||
|
uint32_t refresh_rate;
|
||||||
} QemuUIInfo;
|
} QemuUIInfo;
|
||||||
|
|
||||||
/* cursor data format is 32bit RGBA */
|
/* cursor data format is 32bit RGBA */
|
||||||
|
@ -431,8 +432,7 @@ typedef struct GraphicHwOps {
|
||||||
void (*gfx_update)(void *opaque);
|
void (*gfx_update)(void *opaque);
|
||||||
bool gfx_update_async; /* if true, calls graphic_hw_update_done() */
|
bool gfx_update_async; /* if true, calls graphic_hw_update_done() */
|
||||||
void (*text_update)(void *opaque, console_ch_t *text);
|
void (*text_update)(void *opaque, console_ch_t *text);
|
||||||
void (*update_interval)(void *opaque, uint64_t interval);
|
void (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
|
||||||
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
|
|
||||||
void (*gl_block)(void *opaque, bool block);
|
void (*gl_block)(void *opaque, bool block);
|
||||||
} GraphicHwOps;
|
} GraphicHwOps;
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ extern bool gtk_use_gl_area;
|
||||||
|
|
||||||
/* ui/gtk.c */
|
/* ui/gtk.c */
|
||||||
void gd_update_windowsize(VirtualConsole *vc);
|
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);
|
void gd_hw_gl_flushed(void *vc);
|
||||||
|
|
||||||
/* ui/gtk-egl.c */
|
/* ui/gtk-egl.c */
|
||||||
|
|
|
@ -1408,6 +1408,12 @@ if have_system
|
||||||
method: 'pkg-config',
|
method: 'pkg-config',
|
||||||
kwargs: static_kwargs)
|
kwargs: static_kwargs)
|
||||||
endif
|
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
|
usbredir = not_found
|
||||||
if not get_option('usb_redir').auto() or have_system
|
if not get_option('usb_redir').auto() or have_system
|
||||||
usbredir = dependency('libusbredirparser-0.5', required: get_option('usb_redir'),
|
usbredir = dependency('libusbredirparser-0.5', required: get_option('usb_redir'),
|
||||||
|
|
|
@ -189,6 +189,8 @@ option('spice_protocol', type : 'feature', value : 'auto',
|
||||||
description: 'Spice protocol support')
|
description: 'Spice protocol support')
|
||||||
option('u2f', type : 'feature', value : 'auto',
|
option('u2f', type : 'feature', value : 'auto',
|
||||||
description: 'U2F emulation support')
|
description: 'U2F emulation support')
|
||||||
|
option('canokey', type : 'feature', value : 'auto',
|
||||||
|
description: 'CanoKey support')
|
||||||
option('usb_redir', type : 'feature', value : 'auto',
|
option('usb_redir', type : 'feature', value : 'auto',
|
||||||
description: 'libusbredir support')
|
description: 'libusbredir support')
|
||||||
option('l2tpv3', type : 'feature', value : 'auto',
|
option('l2tpv3', type : 'feature', value : 'auto',
|
||||||
|
|
|
@ -73,6 +73,7 @@ meson_options_help() {
|
||||||
printf "%s\n" ' bpf eBPF support'
|
printf "%s\n" ' bpf eBPF support'
|
||||||
printf "%s\n" ' brlapi brlapi character device driver'
|
printf "%s\n" ' brlapi brlapi character device driver'
|
||||||
printf "%s\n" ' bzip2 bzip2 support for DMG images'
|
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" ' cap-ng cap_ng support'
|
||||||
printf "%s\n" ' capstone Whether and how to find the capstone library'
|
printf "%s\n" ' capstone Whether and how to find the capstone library'
|
||||||
printf "%s\n" ' cloop cloop image format support'
|
printf "%s\n" ' cloop cloop image format support'
|
||||||
|
@ -204,6 +205,8 @@ _meson_option_parse() {
|
||||||
--disable-brlapi) printf "%s" -Dbrlapi=disabled ;;
|
--disable-brlapi) printf "%s" -Dbrlapi=disabled ;;
|
||||||
--enable-bzip2) printf "%s" -Dbzip2=enabled ;;
|
--enable-bzip2) printf "%s" -Dbzip2=enabled ;;
|
||||||
--disable-bzip2) printf "%s" -Dbzip2=disabled ;;
|
--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 ;;
|
--enable-cap-ng) printf "%s" -Dcap_ng=enabled ;;
|
||||||
--disable-cap-ng) printf "%s" -Dcap_ng=disabled ;;
|
--disable-cap-ng) printf "%s" -Dcap_ng=disabled ;;
|
||||||
--enable-capstone) printf "%s" -Dcapstone=enabled ;;
|
--enable-capstone) printf "%s" -Dcapstone=enabled ;;
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "ui/kbd-state.h"
|
#include "ui/kbd-state.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "sysemu/runstate.h"
|
#include "sysemu/runstate.h"
|
||||||
|
#include "sysemu/runstate-action.h"
|
||||||
#include "sysemu/cpu-throttle.h"
|
#include "sysemu/cpu-throttle.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
#include "qapi/qapi-commands-block.h"
|
#include "qapi/qapi-commands-block.h"
|
||||||
|
@ -1290,7 +1291,10 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
|
||||||
{
|
{
|
||||||
COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
|
COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
|
||||||
|
|
||||||
|
with_iothread_lock(^{
|
||||||
|
shutdown_action = SHUTDOWN_ACTION_POWEROFF;
|
||||||
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
|
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sleep here, because returning will cause OSX to kill us
|
* Sleep here, because returning will cause OSX to kill us
|
||||||
|
|
|
@ -160,7 +160,6 @@ static void gui_update(void *opaque)
|
||||||
uint64_t dcl_interval;
|
uint64_t dcl_interval;
|
||||||
DisplayState *ds = opaque;
|
DisplayState *ds = opaque;
|
||||||
DisplayChangeListener *dcl;
|
DisplayChangeListener *dcl;
|
||||||
QemuConsole *con;
|
|
||||||
|
|
||||||
ds->refreshing = true;
|
ds->refreshing = true;
|
||||||
dpy_refresh(ds);
|
dpy_refresh(ds);
|
||||||
|
@ -175,11 +174,6 @@ static void gui_update(void *opaque)
|
||||||
}
|
}
|
||||||
if (ds->update_interval != interval) {
|
if (ds->update_interval != interval) {
|
||||||
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);
|
trace_console_refresh(interval);
|
||||||
}
|
}
|
||||||
ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
||||||
|
|
|
@ -140,8 +140,8 @@ void gd_egl_refresh(DisplayChangeListener *dcl)
|
||||||
{
|
{
|
||||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||||
|
|
||||||
vc->gfx.dcl.update_interval = gd_monitor_update_interval(
|
gd_update_monitor_refresh_rate(
|
||||||
vc->window ? vc->window : vc->gfx.drawing_area);
|
vc, vc->window ? vc->window : vc->gfx.drawing_area);
|
||||||
|
|
||||||
if (!vc->gfx.esurface) {
|
if (!vc->gfx.esurface) {
|
||||||
gd_egl_init(vc);
|
gd_egl_init(vc);
|
||||||
|
|
|
@ -121,8 +121,7 @@ void gd_gl_area_refresh(DisplayChangeListener *dcl)
|
||||||
{
|
{
|
||||||
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
|
||||||
|
|
||||||
vc->gfx.dcl.update_interval = gd_monitor_update_interval(
|
gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : vc->gfx.drawing_area);
|
||||||
vc->window ? vc->window : vc->gfx.drawing_area);
|
|
||||||
|
|
||||||
if (!vc->gfx.gls) {
|
if (!vc->gfx.gls) {
|
||||||
if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
|
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,
|
QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
|
||||||
QEMUGLParams *params)
|
QEMUGLParams *params)
|
||||||
{
|
{
|
||||||
|
@ -177,8 +193,8 @@ QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
|
||||||
GdkWindow *window;
|
GdkWindow *window;
|
||||||
GdkGLContext *ctx;
|
GdkGLContext *ctx;
|
||||||
GError *err = NULL;
|
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);
|
window = gtk_widget_get_window(vc->gfx.drawing_area);
|
||||||
ctx = gdk_window_create_gl_context(window, &err);
|
ctx = gdk_window_create_gl_context(window, &err);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -196,12 +212,30 @@ QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
|
||||||
g_clear_object(&ctx);
|
g_clear_object(&ctx);
|
||||||
return NULL;
|
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;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext 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,
|
void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
|
||||||
|
|
43
ui/gtk.c
43
ui/gtk.c
|
@ -710,11 +710,20 @@ static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
|
||||||
return TRUE;
|
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;
|
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.width = width;
|
||||||
info.height = height;
|
info.height = height;
|
||||||
dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
|
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;
|
VirtualConsole *vc = (void *)opaque;
|
||||||
|
|
||||||
gd_set_ui_info(vc, width, height);
|
gd_set_ui_size(vc, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
#ifdef GDK_VERSION_3_22
|
#ifdef GDK_VERSION_3_22
|
||||||
GdkWindow *win = gtk_widget_get_window(widget);
|
GdkWindow *win = gtk_widget_get_window(widget);
|
||||||
|
int refresh_rate;
|
||||||
|
|
||||||
if (win) {
|
if (win) {
|
||||||
GdkDisplay *dpy = gtk_widget_get_display(widget);
|
GdkDisplay *dpy = gtk_widget_get_display(widget);
|
||||||
GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
|
GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
|
||||||
int refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */
|
refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */
|
||||||
|
} else {
|
||||||
|
refresh_rate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gd_set_ui_refresh_rate(vc, refresh_rate);
|
||||||
|
|
||||||
if (refresh_rate) {
|
|
||||||
/* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
|
/* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
|
||||||
return MIN(1000 * 1000 / refresh_rate,
|
vc->gfx.dcl.update_interval = refresh_rate ?
|
||||||
GUI_REFRESH_INTERVAL_DEFAULT);
|
MIN(1000 * 1000 / refresh_rate, GUI_REFRESH_INTERVAL_DEFAULT) :
|
||||||
}
|
GUI_REFRESH_INTERVAL_DEFAULT;
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
|
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;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
vc->gfx.dcl.update_interval =
|
gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : s->window);
|
||||||
gd_monitor_update_interval(vc->window ? vc->window : s->window);
|
|
||||||
|
|
||||||
fbw = surface_width(vc->gfx.ds);
|
fbw = surface_width(vc->gfx.ds);
|
||||||
fbh = surface_height(vc->gfx.ds);
|
fbh = surface_height(vc->gfx.ds);
|
||||||
|
@ -1691,7 +1698,7 @@ static gboolean gd_configure(GtkWidget *widget,
|
||||||
{
|
{
|
||||||
VirtualConsole *vc = opaque;
|
VirtualConsole *vc = opaque;
|
||||||
|
|
||||||
gd_set_ui_info(vc, cfg->width, cfg->height);
|
gd_set_ui_size(vc, cfg->width, cfg->height);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_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_ungrab(const char *tab, const char *device) "tab=%s, dev=%s"
|
||||||
gd_keymap_windowing(const char *name) "backend=%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-sasl.c
|
||||||
# vnc-auth-vencrypt.c
|
# vnc-auth-vencrypt.c
|
||||||
|
|
Loading…
Reference in New Issue