aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS16
-rw-r--r--audio/audio.c1
-rw-r--r--audio/audio_int.h7
-rw-r--r--audio/audio_template.h2
-rw-r--r--audio/dbusaudio.c654
-rw-r--r--audio/meson.build6
-rw-r--r--audio/trace-events5
-rw-r--r--backends/dbus-vmstate1.xml52
-rw-r--r--block/nbd.c45
-rw-r--r--chardev/char-socket.c72
-rw-r--r--common-user/host/loongarch64/safe-syscall.inc.S90
-rwxr-xr-xconfigure6
-rw-r--r--docs/conf.py8
-rw-r--r--docs/interop/dbus-display.rst31
-rw-r--r--docs/interop/dbus-vmstate.rst52
-rw-r--r--docs/interop/dbus.rst2
-rw-r--r--docs/interop/index.rst1
-rw-r--r--docs/sphinx/dbusdoc.py166
-rw-r--r--docs/sphinx/dbusdomain.py406
-rw-r--r--docs/sphinx/dbusparser.py373
-rw-r--r--docs/sphinx/fakedbusdoc.py25
-rw-r--r--hw/display/qxl.c7
-rw-r--r--hw/display/vhost-user-gpu.c2
-rw-r--r--hw/display/virtio-gpu-base.c5
-rw-r--r--hw/display/virtio-gpu-virgl.c3
-rw-r--r--hw/display/virtio-vga.c11
-rw-r--r--hw/net/virtio-net.c7
-rw-r--r--include/chardev/char-socket.h86
-rw-r--r--include/elf.h2
-rw-r--r--include/qemu/cutils.h5
-rw-r--r--include/qemu/dbus.h24
-rw-r--r--include/qemu/option.h2
-rw-r--r--include/ui/clipboard.h55
-rw-r--r--include/ui/console.h70
-rw-r--r--include/ui/dbus-display.h17
-rw-r--r--include/ui/dbus-module.h11
-rw-r--r--include/ui/egl-context.h6
-rw-r--r--include/ui/gtk.h11
-rw-r--r--include/ui/sdl2.h7
-rw-r--r--include/ui/spice-display.h5
-rw-r--r--linux-user/host/loongarch64/host-signal.h87
-rw-r--r--meson.build24
-rw-r--r--meson_options.txt2
-rw-r--r--migration/migration.c4
-rw-r--r--monitor/qmp-cmds.c13
-rw-r--r--nbd/client-connection.c57
-rw-r--r--qapi/audio.json3
-rw-r--r--qapi/block-core.json9
-rw-r--r--qapi/char.json27
-rw-r--r--qapi/misc.json4
-rw-r--r--qapi/ui.json34
-rw-r--r--qemu-options.hx20
-rw-r--r--scripts/meson-buildoptions.sh3
-rwxr-xr-xscripts/modinfo-collect.py3
-rw-r--r--tcg/loongarch64/tcg-insn-defs.c.inc979
-rw-r--r--tcg/loongarch64/tcg-target-con-set.h31
-rw-r--r--tcg/loongarch64/tcg-target-con-str.h28
-rw-r--r--tcg/loongarch64/tcg-target.c.inc1677
-rw-r--r--tcg/loongarch64/tcg-target.h180
-rwxr-xr-xtests/qemu-iotests/check4
-rw-r--r--tests/qemu-iotests/iotests.py37
-rw-r--r--tests/qemu-iotests/testrunner.py86
-rwxr-xr-xtests/qemu-iotests/tests/nbd-reconnect-on-open71
-rw-r--r--tests/qemu-iotests/tests/nbd-reconnect-on-open.out11
-rw-r--r--tests/qtest/boot-order-test.c5
-rw-r--r--tests/qtest/boot-serial-test.c10
-rw-r--r--tests/qtest/cdrom-test.c60
-rw-r--r--tests/qtest/dbus-display-test.c257
-rw-r--r--tests/qtest/dbus-vmstate1.xml12
-rw-r--r--tests/qtest/endianness-test.c5
-rw-r--r--tests/qtest/libqos/libqtest.h18
-rw-r--r--tests/qtest/libqtest.c63
-rw-r--r--tests/qtest/meson.build50
-rw-r--r--tests/qtest/test-filter-mirror.c10
-rw-r--r--tests/qtest/test-filter-redirector.c20
-rw-r--r--tests/qtest/test-netfilter.c8
-rw-r--r--tests/qtest/virtio-net-failover.c8
-rw-r--r--ui/clipboard.c34
-rw-r--r--ui/cocoa.m22
-rw-r--r--ui/console.c305
-rw-r--r--ui/dbus-chardev.c296
-rw-r--r--ui/dbus-clipboard.c457
-rw-r--r--ui/dbus-console.c497
-rw-r--r--ui/dbus-display1.xml761
-rw-r--r--ui/dbus-error.c48
-rw-r--r--ui/dbus-listener.c486
-rw-r--r--ui/dbus-module.c35
-rw-r--r--ui/dbus.c482
-rw-r--r--ui/dbus.h144
-rw-r--r--ui/egl-context.c6
-rw-r--r--ui/egl-headless.c20
-rw-r--r--ui/gtk-clipboard.c23
-rw-r--r--ui/gtk-egl.c12
-rw-r--r--ui/gtk-gl-area.c10
-rw-r--r--ui/gtk.c28
-rw-r--r--ui/meson.build28
-rw-r--r--ui/sdl2-gl.c12
-rw-r--r--ui/sdl2.c16
-rw-r--r--ui/spice-core.c50
-rw-r--r--ui/spice-display.c27
-rw-r--r--ui/trace-events15
-rw-r--r--ui/util.c75
-rw-r--r--ui/vdagent.c94
-rw-r--r--ui/vnc-clipboard.c23
-rw-r--r--ui/vnc.c4
105 files changed, 9771 insertions, 515 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 1de6ce6e44..5456536805 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2873,11 +2873,15 @@ D-Bus
M: Marc-André Lureau <marcandre.lureau@redhat.com>
S: Maintained
F: backends/dbus-vmstate.c
-F: tests/dbus-vmstate*
+F: ui/dbus*
+F: audio/dbus*
F: util/dbus.c
+F: include/ui/dbus*
F: include/qemu/dbus.h
-F: docs/interop/dbus.rst
-F: docs/interop/dbus-vmstate.rst
+F: docs/interop/dbus*
+F: docs/sphinx/dbus*
+F: docs/sphinx/fakedbusdoc.py
+F: tests/qtest/dbus*
Seccomp
M: Eduardo Otubo <otubo@redhat.com>
@@ -2983,6 +2987,7 @@ F: docs/colo-proxy.txt
F: net/colo*
F: net/filter-rewriter.c
F: net/filter-mirror.c
+F: tests/qtest/test-filter*
Record/replay
M: Pavel Dovgalyuk <pavel.dovgaluk@ispras.ru>
@@ -3139,6 +3144,11 @@ S: Maintained
F: tcg/i386/
F: disas/i386.c
+LoongArch64 TCG target
+M: WANG Xuerui <git@xen0n.name>
+S: Maintained
+F: tcg/loongarch64/
+
MIPS TCG target
M: Philippe Mathieu-Daudé <f4bug@amsat.org>
R: Aurelien Jarno <aurelien@aurel32.net>
diff --git a/audio/audio.c b/audio/audio.c
index 54a153c0ef..dc28685d22 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -2000,6 +2000,7 @@ void audio_create_pdos(Audiodev *dev)
CASE(NONE, none, );
CASE(ALSA, alsa, Alsa);
CASE(COREAUDIO, coreaudio, Coreaudio);
+ CASE(DBUS, dbus, );
CASE(DSOUND, dsound, );
CASE(JACK, jack, Jack);
CASE(OSS, oss, Oss);
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 6d685e24a3..428a091d05 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -31,6 +31,10 @@
#endif
#include "mixeng.h"
+#ifdef CONFIG_GIO
+#include <gio/gio.h>
+#endif
+
struct audio_pcm_ops;
struct audio_callback {
@@ -140,6 +144,9 @@ struct audio_driver {
const char *descr;
void *(*init) (Audiodev *);
void (*fini) (void *);
+#ifdef CONFIG_GIO
+ void (*set_dbus_server) (AudioState *s, GDBusObjectManagerServer *manager);
+#endif
struct audio_pcm_ops *pcm_ops;
int can_be_default;
int max_voices_out;
diff --git a/audio/audio_template.h b/audio/audio_template.h
index c6714946aa..d2d348638b 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -327,6 +327,8 @@ AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev)
case AUDIODEV_DRIVER_COREAUDIO:
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
dev->u.coreaudio.TYPE);
+ case AUDIODEV_DRIVER_DBUS:
+ return dev->u.dbus.TYPE;
case AUDIODEV_DRIVER_DSOUND:
return dev->u.dsound.TYPE;
case AUDIODEV_DRIVER_JACK:
diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c
new file mode 100644
index 0000000000..f178b47dee
--- /dev/null
+++ b/audio/dbusaudio.c
@@ -0,0 +1,654 @@
+/*
+ * QEMU DBus audio
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/dbus.h"
+
+#include <gio/gunixfdlist.h>
+#include "ui/dbus-display1.h"
+
+#define AUDIO_CAP "dbus"
+#include "audio.h"
+#include "audio_int.h"
+#include "trace.h"
+
+#define DBUS_DISPLAY1_AUDIO_PATH DBUS_DISPLAY1_ROOT "/Audio"
+
+#define DBUS_AUDIO_NSAMPLES 1024 /* could be configured? */
+
+typedef struct DBusAudio {
+ GDBusObjectManagerServer *server;
+ GDBusObjectSkeleton *audio;
+ QemuDBusDisplay1Audio *iface;
+ GHashTable *out_listeners;
+ GHashTable *in_listeners;
+} DBusAudio;
+
+typedef struct DBusVoiceOut {
+ HWVoiceOut hw;
+ bool enabled;
+ RateCtl rate;
+
+ void *buf;
+ size_t buf_pos;
+ size_t buf_size;
+
+ bool has_volume;
+ Volume volume;
+} DBusVoiceOut;
+
+typedef struct DBusVoiceIn {
+ HWVoiceIn hw;
+ bool enabled;
+ RateCtl rate;
+
+ bool has_volume;
+ Volume volume;
+} DBusVoiceIn;
+
+static void *dbus_get_buffer_out(HWVoiceOut *hw, size_t *size)
+{
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+
+ if (!vo->buf) {
+ vo->buf_size = hw->samples * hw->info.bytes_per_frame;
+ vo->buf = g_malloc(vo->buf_size);
+ vo->buf_pos = 0;
+ }
+
+ *size = MIN(vo->buf_size - vo->buf_pos, *size);
+ *size = audio_rate_get_bytes(&hw->info, &vo->rate, *size);
+
+ return vo->buf + vo->buf_pos;
+
+}
+
+static size_t dbus_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ g_autoptr(GVariant) v_data = NULL;
+
+ assert(buf == vo->buf + vo->buf_pos && vo->buf_pos + size <= vo->buf_size);
+ vo->buf_pos += size;
+
+ trace_dbus_audio_put_buffer_out(size);
+
+ if (vo->buf_pos < vo->buf_size) {
+ return size;
+ }
+
+ bytes = g_bytes_new_take(g_steal_pointer(&vo->buf), vo->buf_size);
+ v_data = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+ g_variant_ref_sink(v_data);
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_out_listener_call_write(
+ listener,
+ (uintptr_t)hw,
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+
+ return size;
+}
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define AUDIO_HOST_BE TRUE
+#else
+#define AUDIO_HOST_BE FALSE
+#endif
+
+static void
+dbus_init_out_listener(QemuDBusDisplay1AudioOutListener *listener,
+ HWVoiceOut *hw)
+{
+ qemu_dbus_display1_audio_out_listener_call_init(
+ listener,
+ (uintptr_t)hw,
+ hw->info.bits,
+ hw->info.is_signed,
+ hw->info.is_float,
+ hw->info.freq,
+ hw->info.nchannels,
+ hw->info.bytes_per_frame,
+ hw->info.bytes_per_second,
+ hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ audio_pcm_init_info(&hw->info, as);
+ hw->samples = DBUS_AUDIO_NSAMPLES;
+ audio_rate_start(&vo->rate);
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_init_out_listener(listener, hw);
+ }
+ return 0;
+}
+
+static void
+dbus_fini_out(HWVoiceOut *hw)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_out_listener_call_fini(
+ listener,
+ (uintptr_t)hw,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+
+ g_clear_pointer(&vo->buf, g_free);
+}
+
+static void
+dbus_enable_out(HWVoiceOut *hw, bool enable)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ vo->enabled = enable;
+ if (enable) {
+ audio_rate_start(&vo->rate);
+ }
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_out_listener_call_set_enabled(
+ listener, (uintptr_t)hw, enable,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_volume_out_listener(HWVoiceOut *hw,
+ QemuDBusDisplay1AudioOutListener *listener)
+{
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ Volume *vol = &vo->volume;
+ g_autoptr(GBytes) bytes = NULL;
+ GVariant *v_vol = NULL;
+
+ if (!vo->has_volume) {
+ return;
+ }
+
+ assert(vol->channels < sizeof(vol->vol));
+ bytes = g_bytes_new(vol->vol, vol->channels);
+ v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+ qemu_dbus_display1_audio_out_listener_call_set_volume(
+ listener, (uintptr_t)hw, vol->mute, v_vol,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_out(HWVoiceOut *hw, Volume *vol)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioOutListener *listener = NULL;
+
+ vo->has_volume = true;
+ vo->volume = *vol;
+
+ g_hash_table_iter_init(&iter, da->out_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_volume_out_listener(hw, listener);
+ }
+}
+
+static void
+dbus_init_in_listener(QemuDBusDisplay1AudioInListener *listener, HWVoiceIn *hw)
+{
+ qemu_dbus_display1_audio_in_listener_call_init(
+ listener,
+ (uintptr_t)hw,
+ hw->info.bits,
+ hw->info.is_signed,
+ hw->info.is_float,
+ hw->info.freq,
+ hw->info.nchannels,
+ hw->info.bytes_per_frame,
+ hw->info.bytes_per_second,
+ hw->info.swap_endianness ? !AUDIO_HOST_BE : AUDIO_HOST_BE,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static int
+dbus_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ audio_pcm_init_info(&hw->info, as);
+ hw->samples = DBUS_AUDIO_NSAMPLES;
+ audio_rate_start(&vo->rate);
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_init_in_listener(listener, hw);
+ }
+ return 0;
+}
+
+static void
+dbus_fini_in(HWVoiceIn *hw)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_in_listener_call_fini(
+ listener,
+ (uintptr_t)hw,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_volume_in_listener(HWVoiceIn *hw,
+ QemuDBusDisplay1AudioInListener *listener)
+{
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ Volume *vol = &vo->volume;
+ g_autoptr(GBytes) bytes = NULL;
+ GVariant *v_vol = NULL;
+
+ if (!vo->has_volume) {
+ return;
+ }
+
+ assert(vol->channels < sizeof(vol->vol));
+ bytes = g_bytes_new(vol->vol, vol->channels);
+ v_vol = g_variant_new_from_bytes(G_VARIANT_TYPE("ay"), bytes, TRUE);
+ qemu_dbus_display1_audio_in_listener_call_set_volume(
+ listener, (uintptr_t)hw, vol->mute, v_vol,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+dbus_volume_in(HWVoiceIn *hw, Volume *vol)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ vo->has_volume = true;
+ vo->volume = *vol;
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ dbus_volume_in_listener(hw, listener);
+ }
+}
+
+static size_t
+dbus_read(HWVoiceIn *hw, void *buf, size_t size)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ /* DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw); */
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ trace_dbus_audio_read(size);
+
+ /* size = audio_rate_get_bytes(&hw->info, &vo->rate, size); */
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ g_autoptr(GVariant) v_data = NULL;
+ const char *data;
+ gsize n = 0;
+
+ if (qemu_dbus_display1_audio_in_listener_call_read_sync(
+ listener,
+ (uintptr_t)hw,
+ size,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ &v_data, NULL, NULL)) {
+ data = g_variant_get_fixed_array(v_data, &n, 1);
+ g_warn_if_fail(n <= size);
+ size = MIN(n, size);
+ memcpy(buf, data, size);
+ break;
+ }
+ }
+
+ return size;
+}
+
+static void
+dbus_enable_in(HWVoiceIn *hw, bool enable)
+{
+ DBusAudio *da = (DBusAudio *)hw->s->drv_opaque;
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ GHashTableIter iter;
+ QemuDBusDisplay1AudioInListener *listener = NULL;
+
+ vo->enabled = enable;
+ if (enable) {
+ audio_rate_start(&vo->rate);
+ }
+
+ g_hash_table_iter_init(&iter, da->in_listeners);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&listener)) {
+ qemu_dbus_display1_audio_in_listener_call_set_enabled(
+ listener, (uintptr_t)hw, enable,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+}
+
+static void *
+dbus_audio_init(Audiodev *dev)
+{
+ DBusAudio *da = g_new0(DBusAudio, 1);
+
+ da->out_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ da->in_listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ return da;
+}
+
+static void
+dbus_audio_fini(void *opaque)
+{
+ DBusAudio *da = opaque;
+
+ if (da->server) {
+ g_dbus_object_manager_server_unexport(da->server,
+ DBUS_DISPLAY1_AUDIO_PATH);
+ }
+ g_clear_object(&da->audio);
+ g_clear_object(&da->iface);
+ g_clear_pointer(&da->in_listeners, g_hash_table_unref);
+ g_clear_pointer(&da->out_listeners, g_hash_table_unref);
+ g_clear_object(&da->server);
+ g_free(da);
+}
+
+static void
+listener_out_vanished_cb(GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ DBusAudio *da)
+{
+ char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+ g_hash_table_remove(da->out_listeners, name);
+}
+
+static void
+listener_in_vanished_cb(GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ DBusAudio *da)
+{
+ char *name = g_object_get_data(G_OBJECT(connection), "name");
+
+ g_hash_table_remove(da->in_listeners, name);
+}
+
+static gboolean
+dbus_audio_register_listener(AudioState *s,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener,
+ bool out)
+{
+ DBusAudio *da = s->drv_opaque;
+ const char *sender = g_dbus_method_invocation_get_sender(invocation);
+ g_autoptr(GDBusConnection) listener_conn = NULL;
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) socket_conn = NULL;
+ g_autofree char *guid = g_dbus_generate_guid();
+ GHashTable *listeners = out ? da->out_listeners : da->in_listeners;
+ GObject *listener;
+ int fd;
+
+ trace_dbus_audio_register(sender, out ? "out" : "in");
+
+ if (g_hash_table_contains(listeners, sender)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "`%s` is already registered!",
+ sender);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't get peer fd: %s",
+ err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ socket = g_socket_new_from_fd(fd, &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't make a socket: %s",
+ err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ socket_conn = g_socket_connection_factory_create_connection(socket);
+ if (out) {
+ qemu_dbus_display1_audio_complete_register_out_listener(
+ da->iface, invocation, NULL);
+ } else {
+ qemu_dbus_display1_audio_complete_register_in_listener(
+ da->iface, invocation, NULL);
+ }
+
+ listener_conn =
+ g_dbus_connection_new_sync(
+ G_IO_STREAM(socket_conn),
+ guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL, NULL, &err);
+ if (err) {
+ error_report("Failed to setup peer connection: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ listener = out ?
+ G_OBJECT(qemu_dbus_display1_audio_out_listener_proxy_new_sync(
+ listener_conn,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "/org/qemu/Display1/AudioOutListener",
+ NULL,
+ &err)) :
+ G_OBJECT(qemu_dbus_display1_audio_in_listener_proxy_new_sync(
+ listener_conn,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "/org/qemu/Display1/AudioInListener",
+ NULL,
+ &err));
+ if (!listener) {
+ error_report("Failed to setup proxy: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (out) {
+ HWVoiceOut *hw;
+
+ QLIST_FOREACH(hw, &s->hw_head_out, entries) {
+ DBusVoiceOut *vo = container_of(hw, DBusVoiceOut, hw);
+ QemuDBusDisplay1AudioOutListener *l =
+ QEMU_DBUS_DISPLAY1_AUDIO_OUT_LISTENER(listener);
+
+ dbus_init_out_listener(l, hw);
+ qemu_dbus_display1_audio_out_listener_call_set_enabled(
+ l, (uintptr_t)hw, vo->enabled,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+ } else {
+ HWVoiceIn *hw;
+
+ QLIST_FOREACH(hw, &s->hw_head_in, entries) {
+ DBusVoiceIn *vo = container_of(hw, DBusVoiceIn, hw);
+ QemuDBusDisplay1AudioInListener *l =
+ QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener);
+
+ dbus_init_in_listener(
+ QEMU_DBUS_DISPLAY1_AUDIO_IN_LISTENER(listener), hw);
+ qemu_dbus_display1_audio_in_listener_call_set_enabled(
+ l, (uintptr_t)hw, vo->enabled,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+ }
+
+ g_object_set_data_full(G_OBJECT(listener_conn), "name",
+ g_strdup(sender), g_free);
+ g_hash_table_insert(listeners, g_strdup(sender), listener);
+ g_object_connect(listener_conn,
+ "signal::closed",
+ out ? listener_out_vanished_cb : listener_in_vanished_cb,
+ da,
+ NULL);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_audio_register_out_listener(AudioState *s,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener)
+{
+ return dbus_audio_register_listener(s, invocation,
+ fd_list, arg_listener, true);
+
+}
+
+static gboolean
+dbus_audio_register_in_listener(AudioState *s,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener)
+{
+ return dbus_audio_register_listener(s, invocation,
+ fd_list, arg_listener, false);
+}
+
+static void
+dbus_audio_set_server(AudioState *s, GDBusObjectManagerServer *server)
+{
+ DBusAudio *da = s->drv_opaque;
+
+ g_assert(da);
+ g_assert(!da->server);
+
+ da->server = g_object_ref(server);
+
+ da->audio = g_dbus_object_skeleton_new(DBUS_DISPLAY1_AUDIO_PATH);
+ da->iface = qemu_dbus_display1_audio_skeleton_new();
+ g_object_connect(da->iface,
+ "swapped-signal::handle-register-in-listener",
+ dbus_audio_register_in_listener, s,
+ "swapped-signal::handle-register-out-listener",
+ dbus_audio_register_out_listener, s,
+ NULL);
+
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(da->audio),
+ G_DBUS_INTERFACE_SKELETON(da->iface));
+ g_dbus_object_manager_server_export(da->server, da->audio);
+}
+
+static struct audio_pcm_ops dbus_pcm_ops = {
+ .init_out = dbus_init_out,
+ .fini_out = dbus_fini_out,
+ .write = audio_generic_write,
+ .get_buffer_out = dbus_get_buffer_out,
+ .put_buffer_out = dbus_put_buffer_out,
+ .enable_out = dbus_enable_out,
+ .volume_out = dbus_volume_out,
+
+ .init_in = dbus_init_in,
+ .fini_in = dbus_fini_in,
+ .read = dbus_read,
+ .run_buffer_in = audio_generic_run_buffer_in,
+ .enable_in = dbus_enable_in,
+ .volume_in = dbus_volume_in,
+};
+
+static struct audio_driver dbus_audio_driver = {
+ .name = "dbus",
+ .descr = "Timer based audio exposed with DBus interface",
+ .init = dbus_audio_init,
+ .fini = dbus_audio_fini,
+ .set_dbus_server = dbus_audio_set_server,
+ .pcm_ops = &dbus_pcm_ops,
+ .can_be_default = 1,
+ .max_voices_out = INT_MAX,
+ .max_voices_in = INT_MAX,
+ .voice_size_out = sizeof(DBusVoiceOut),
+ .voice_size_in = sizeof(DBusVoiceIn)
+};
+
+static void register_audio_dbus(void)
+{
+ audio_driver_register(&dbus_audio_driver);
+}
+type_init(register_audio_dbus);
+
+module_dep("ui-dbus")
diff --git a/audio/meson.build b/audio/meson.build
index 462533bb8c..0ac3791d0b 100644
--- a/audio/meson.build
+++ b/audio/meson.build
@@ -26,4 +26,10 @@ foreach m : [
endif
endforeach
+if dbus_display
+ module_ss = ss.source_set()
+ module_ss.add(when: gio, if_true: files('dbusaudio.c'))
+ audio_modules += {'dbus': module_ss}
+endif
+
modules += {'audio': audio_modules}
diff --git a/audio/trace-events b/audio/trace-events
index 957c92337b..e1ab643add 100644
--- a/audio/trace-events
+++ b/audio/trace-events
@@ -13,6 +13,11 @@ alsa_resume_out(void) "Resuming suspended output stream"
# ossaudio.c
oss_version(int version) "OSS version = 0x%x"
+# dbusaudio.c
+dbus_audio_register(const char *s, const char *dir) "sender = %s, dir = %s"
+dbus_audio_put_buffer_out(size_t len) "len = %zu"
+dbus_audio_read(size_t len) "len = %zu"
+
# audio.c
audio_timer_start(int interval) "interval %d ms"
audio_timer_stop(void) ""
diff --git a/backends/dbus-vmstate1.xml b/backends/dbus-vmstate1.xml
new file mode 100644
index 0000000000..601ee8dc7e
--- /dev/null
+++ b/backends/dbus-vmstate1.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <!--
+ org.qemu.VMState1:
+
+ This interface must be implemented at the object path
+ ``/org/qemu/VMState1`` to support helper migration.
+ -->
+ <interface name="org.qemu.VMState1">
+
+ <!--
+ Id:
+
+ A string that identifies the helper uniquely. (maximum 256 bytes
+ including terminating NUL byte)
+
+ .. note::
+
+ The VMState helper ID namespace is its own namespace. In particular,
+ it is not related to QEMU "id" used in -object/-device objects.
+ -->
+ <property name="Id" type="s" access="read"/>
+
+ <!--
+ Load:
+ @data: data to restore the state.
+
+ The method called on destination with the state to restore.
+
+ The helper may be initially started in a waiting state (with an
+ ``-incoming`` argument for example), and it may resume on success.
+
+ An error may be returned to the caller.
+ -->
+ <method name="Load">
+ <arg type="ay" name="data" direction="in"/>
+ </method>
+
+ <!--
+ Save:
+ @data: state data to save for later resume.
+
+ The method called on the source to get the current state to be
+ migrated. The helper should continue to run normally.
+
+ An error may be returned to the caller.
+ -->
+ <method name="Save">
+ <arg type="ay" name="data" direction="out"/>
+ </method>
+ </interface>
+</node>
diff --git a/block/nbd.c b/block/nbd.c
index 5ef462db1b..63dbfa807d 100644
--- a/block/nbd.c
+++ b/block/nbd.c
@@ -80,6 +80,7 @@ typedef struct BDRVNBDState {
NBDClientState state;
QEMUTimer *reconnect_delay_timer;
+ QEMUTimer *open_timer;
NBDClientRequest requests[MAX_NBD_REQUESTS];
NBDReply reply;
@@ -87,6 +88,7 @@ typedef struct BDRVNBDState {
/* Connection parameters */
uint32_t reconnect_delay;
+ uint32_t open_timeout;
SocketAddress *saddr;
char *export, *tlscredsid;
QCryptoTLSCreds *tlscreds;
@@ -218,6 +220,32 @@ static void nbd_teardown_connection(BlockDriverState *bs)
s->state = NBD_CLIENT_QUIT;
}
+static void open_timer_del(BDRVNBDState *s)
+{
+ if (s->open_timer) {
+ timer_free(s->open_timer);
+ s->open_timer = NULL;
+ }
+}
+
+static void open_timer_cb(void *opaque)
+{
+ BDRVNBDState *s = opaque;
+
+ nbd_co_establish_connection_cancel(s->conn);
+ open_timer_del(s);
+}
+
+static void open_timer_init(BDRVNBDState *s, uint64_t expire_time_ns)
+{
+ assert(!s->open_timer);
+ s->open_timer = aio_timer_new(bdrv_get_aio_context(s->bs),
+ QEMU_CLOCK_REALTIME,
+ SCALE_NS,
+ open_timer_cb, s);
+ timer_mod(s->open_timer, expire_time_ns);
+}
+
static bool nbd_client_connecting(BDRVNBDState *s)
{
NBDClientState state = qatomic_load_acquire(&s->state);
@@ -1742,6 +1770,15 @@ static QemuOptsList nbd_runtime_opts = {
"future requests before a successful reconnect will "
"immediately fail. Default 0",
},
+ {
+ .name = "open-timeout",
+ .type = QEMU_OPT_NUMBER,
+ .help = "In seconds. If zero, the nbd driver tries the connection "
+ "only once, and fails to open if the connection fails. "
+ "If non-zero, the nbd driver will repeat connection "
+ "attempts until successful or until @open-timeout seconds "
+ "have elapsed. Default 0",
+ },
{ /* end of list */ }
},
};
@@ -1797,6 +1834,7 @@ static int nbd_process_options(BlockDriverState *bs, QDict *options,
}
s->reconnect_delay = qemu_opt_get_number(opts, "reconnect-delay", 0);
+ s->open_timeout = qemu_opt_get_number(opts, "open-timeout", 0);
ret = 0;
@@ -1828,7 +1866,12 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
s->conn = nbd_client_connection_new(s->saddr, true, s->export,
s->x_dirty_bitmap, s->tlscreds);
- /* TODO: Configurable retry-until-timeout behaviour. */
+ if (s->open_timeout) {
+ nbd_client_connection_enable_retry(s->conn);
+ open_timer_init(s, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) +
+ s->open_timeout * NANOSECONDS_PER_SECOND);
+ }
+
s->state = NBD_CLIENT_CONNECTING_WAIT;
ret = nbd_do_establish_connection(bs, errp);
if (ret < 0) {
diff --git a/chardev/char-socket.c b/chardev/char-socket.c
index 836cfa0bc2..d619088232 100644
--- a/chardev/char-socket.c
+++ b/chardev/char-socket.c
@@ -25,9 +25,7 @@
#include "qemu/osdep.h"
#include "chardev/char.h"
#include "io/channel-socket.h"
-#include "io/channel-tls.h"
#include "io/channel-websock.h"
-#include "io/net-listener.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/option.h"
@@ -37,61 +35,7 @@
#include "qemu/yank.h"
#include "chardev/char-io.h"
-#include "qom/object.h"
-
-/***********************************************************/
-/* TCP Net console */
-
-#define TCP_MAX_FDS 16
-
-typedef struct {
- char buf[21];
- size_t buflen;
-} TCPChardevTelnetInit;
-
-typedef enum {
- TCP_CHARDEV_STATE_DISCONNECTED,
- TCP_CHARDEV_STATE_CONNECTING,
- TCP_CHARDEV_STATE_CONNECTED,
-} TCPChardevState;
-
-struct SocketChardev {
- Chardev parent;
- QIOChannel *ioc; /* Client I/O channel */
- QIOChannelSocket *sioc; /* Client master channel */
- QIONetListener *listener;
- GSource *hup_source;
- QCryptoTLSCreds *tls_creds;
- char *tls_authz;
- TCPChardevState state;
- int max_size;
- int do_telnetopt;
- int do_nodelay;
- int *read_msgfds;
- size_t read_msgfds_num;
- int *write_msgfds;
- size_t write_msgfds_num;
- bool registered_yank;
-
- SocketAddress *addr;
- bool is_listen;
- bool is_telnet;
- bool is_tn3270;
- GSource *telnet_source;
- TCPChardevTelnetInit *telnet_init;
-
- bool is_websock;
-
- GSource *reconnect_timer;
- int64_t reconnect_time;
- bool connect_err_reported;
-
- QIOTask *connect_task;
-};
-typedef struct SocketChardev SocketChardev;
-
-DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
- TYPE_CHARDEV_SOCKET)
+#include "chardev/char-socket.h"
static gboolean socket_reconnect_timeout(gpointer opaque);
static void tcp_chr_telnet_init(Chardev *chr);
@@ -1248,6 +1192,10 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
qio_net_listener_set_name(s->listener, name);
g_free(name);
+ if (s->addr->type == SOCKET_ADDRESS_TYPE_FD && !*s->addr->u.fd.str) {
+ goto skip_listen;
+ }
+
if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
object_unref(OBJECT(s->listener));
s->listener = NULL;
@@ -1256,6 +1204,8 @@ static int qmp_chardev_open_socket_server(Chardev *chr,
qapi_free_SocketAddress(s->addr);
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
+
+skip_listen:
update_disconnected_filename(s);
if (is_waitconnect) {
@@ -1466,9 +1416,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
SocketAddressLegacy *addr;
ChardevSocket *sock;
- if ((!!path + !!fd + !!host) != 1) {
+ if ((!!path + !!fd + !!host) > 1) {
error_setg(errp,
- "Exactly one of 'path', 'fd' or 'host' required");
+ "None or one of 'path', 'fd' or 'host' option required.");
return;
}
@@ -1542,12 +1492,10 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
};
- } else if (fd) {
+ } else {
addr->type = SOCKET_ADDRESS_TYPE_FD;
addr->u.fd.data = g_new(String, 1);
addr->u.fd.data->str = g_strdup(fd);
- } else {
- g_assert_not_reached();
}
sock->addr = addr;
}
diff --git a/common-user/host/loongarch64/safe-syscall.inc.S b/common-user/host/loongarch64/safe-syscall.inc.S
new file mode 100644
index 0000000000..b88a069c45
--- /dev/null
+++ b/common-user/host/loongarch64/safe-syscall.inc.S
@@ -0,0 +1,90 @@
+/*
+ * safe-syscall.inc.S : host-specific assembly fragment
+ * to handle signals occurring at the same time as system calls.
+ * This is intended to be included by common-user/safe-syscall.S
+ *
+ * Ported to LoongArch by WANG Xuerui <git@xen0n.name>
+ *
+ * Based on safe-syscall.inc.S code for RISC-V,
+ * originally written by Richard Henderson <rth@twiddle.net>
+ * Copyright (C) 2018 Linaro, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+ .global safe_syscall_base
+ .global safe_syscall_start
+ .global safe_syscall_end
+ .type safe_syscall_base, @function
+ .type safe_syscall_start, @function
+ .type safe_syscall_end, @function
+
+ /*
+ * This is the entry point for making a system call. The calling
+ * convention here is that of a C varargs function with the
+ * first argument an 'int *' to the signal_pending flag, the
+ * second one the system call number (as a 'long'), and all further
+ * arguments being syscall arguments (also 'long').
+ */
+safe_syscall_base:
+ .cfi_startproc
+ /*
+ * The syscall calling convention is nearly the same as C:
+ * we enter with a0 == &signal_pending
+ * a1 == syscall number
+ * a2 ... a7 == syscall arguments
+ * and return the result in a0
+ * and the syscall instruction needs
+ * a7 == syscall number
+ * a0 ... a5 == syscall arguments
+ * and returns the result in a0
+ * Shuffle everything around appropriately.
+ */
+ move $t0, $a0 /* signal_pending pointer */
+ move $t1, $a1 /* syscall number */
+ move $a0, $a2 /* syscall arguments */
+ move $a1, $a3
+ move $a2, $a4
+ move $a3, $a5
+ move $a4, $a6
+ move $a5, $a7
+ move $a7, $t1
+
+ /*
+ * We need to preserve the signal_pending pointer but t0 is
+ * clobbered by syscalls on LoongArch, so we need to move it
+ * somewhere else, ideally both preserved across syscalls and
+ * clobbered by procedure calls so we don't have to allocate a
+ * stack frame; a6 is just the register we want here.
+ */
+ move $a6, $t0
+
+ /*
+ * This next sequence of code works in conjunction with the
+ * rewind_if_safe_syscall_function(). If a signal is taken
+ * and the interrupted PC is anywhere between 'safe_syscall_start'
+ * and 'safe_syscall_end' then we rewind it to 'safe_syscall_start'.
+ * The code sequence must therefore be able to cope with this, and
+ * the syscall instruction must be the final one in the sequence.
+ */
+safe_syscall_start:
+ /* If signal_pending is non-zero, don't do the call */
+ ld.w $t1, $a6, 0
+ bnez $t1, 2f
+ syscall 0
+safe_syscall_end:
+ /* code path for having successfully executed the syscall */
+ li.w $t2, -4096
+ bgtu $a0, $t2, 0f
+ jr $ra
+
+ /* code path setting errno */
+0: sub.d $a0, $zero, $a0
+ b safe_syscall_set_errno_tail
+
+ /* code path when we didn't execute the syscall */
+2: li.w $a0, QEMU_ERESTARTSYS
+ b safe_syscall_set_errno_tail
+ .cfi_endproc
+ .size safe_syscall_base, .-safe_syscall_base
diff --git a/configure b/configure
index 8ccfe51673..eb977e5b6f 100755
--- a/configure
+++ b/configure
@@ -631,6 +631,8 @@ elif check_define __arm__ ; then
cpu="arm"
elif check_define __aarch64__ ; then
cpu="aarch64"
+elif check_define __loongarch64 ; then
+ cpu="loongarch64"
else
cpu=$(uname -m)
fi
@@ -3694,6 +3696,7 @@ echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak
echo "QEMU_CXXFLAGS=$QEMU_CXXFLAGS" >> $config_host_mak
echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
+echo "GLIB_VERSION=$(pkg-config --modversion glib-2.0)" >> $config_host_mak
echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
echo "LD_I386_EMULATION=$ld_i386_emulation" >> $config_host_mak
echo "EXESUF=$EXESUF" >> $config_host_mak
@@ -3719,6 +3722,9 @@ if test "$linux" = "yes" ; then
aarch64)
linux_arch=arm64
;;
+ loongarch*)
+ linux_arch=loongarch
+ ;;
mips64)
linux_arch=mips
;;
diff --git a/docs/conf.py b/docs/conf.py
index 763e7d2434..e79015975e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -73,6 +73,12 @@ needs_sphinx = '1.6'
# ones.
extensions = ['kerneldoc', 'qmp_lexer', 'hxtool', 'depfile', 'qapidoc']
+if sphinx.version_info[:3] > (4, 0, 0):
+ tags.add('sphinx4')
+ extensions += ['dbusdoc']
+else:
+ extensions += ['fakedbusdoc']
+
# Add any paths that contain templates here, relative to this directory.
templates_path = [os.path.join(qemu_docdir, '_templates')]
@@ -311,3 +317,5 @@ kerneldoc_bin = ['perl', os.path.join(qemu_docdir, '../scripts/kernel-doc')]
kerneldoc_srctree = os.path.join(qemu_docdir, '..')
hxtool_srctree = os.path.join(qemu_docdir, '..')
qapidoc_srctree = os.path.join(qemu_docdir, '..')
+dbusdoc_srctree = os.path.join(qemu_docdir, '..')
+dbus_index_common_prefix = ["org.qemu."]
diff --git a/docs/interop/dbus-display.rst b/docs/interop/dbus-display.rst
new file mode 100644
index 0000000000..8c6e8e0f5a
--- /dev/null
+++ b/docs/interop/dbus-display.rst
@@ -0,0 +1,31 @@
+D-Bus display
+=============
+
+QEMU can export the VM display through D-Bus (when started with ``-display
+dbus``), to allow out-of-process UIs, remote protocol servers or other
+interactive display usages.
+
+Various specialized D-Bus interfaces are available on different object paths
+under ``/org/qemu/Display1/``, depending on the VM configuration.
+
+QEMU also implements the standard interfaces, such as
+`org.freedesktop.DBus.Introspectable
+<https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces>`_.
+
+.. contents::
+ :local:
+ :depth: 1
+
+.. only:: sphinx4
+
+ .. dbus-doc:: ui/dbus-display1.xml
+
+.. only:: not sphinx4
+
+ .. warning::
+ Sphinx 4 is required to build D-Bus documentation.
+
+ This is the content of ``ui/dbus-display1.xml``:
+
+ .. literalinclude:: ../../ui/dbus-display1.xml
+ :language: xml
diff --git a/docs/interop/dbus-vmstate.rst b/docs/interop/dbus-vmstate.rst
index 1d719c1c60..5fb3f279e2 100644
--- a/docs/interop/dbus-vmstate.rst
+++ b/docs/interop/dbus-vmstate.rst
@@ -2,9 +2,6 @@
D-Bus VMState
=============
-Introduction
-============
-
The QEMU dbus-vmstate object's aim is to migrate helpers' data running
on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
some recommendations on D-Bus usage)
@@ -26,49 +23,16 @@ dbus-vmstate object can be configured with the expected list of
helpers by setting its ``id-list`` property, with a comma-separated
``Id`` list.
-Interface
-=========
-
-On object path ``/org/qemu/VMState1``, the following
-``org.qemu.VMState1`` interface should be implemented:
-
-.. code:: xml
-
- <interface name="org.qemu.VMState1">
- <property name="Id" type="s" access="read"/>
- <method name="Load">
- <arg type="ay" name="data" direction="in"/>
- </method>
- <method name="Save">
- <arg type="ay" name="data" direction="out"/>
- </method>
- </interface>
-
-"Id" property
--------------
-
-A string that identifies the helper uniquely. (maximum 256 bytes
-including terminating NUL byte)
-
-.. note::
-
- The helper ID namespace is a separate namespace. In particular, it is not
- related to QEMU "id" used in -object/-device objects.
-
-Load(in u8[] bytes) method
---------------------------
-
-The method called on destination with the state to restore.
+.. only:: sphinx4
-The helper may be initially started in a waiting state (with
-an --incoming argument for example), and it may resume on success.
+ .. dbus-doc:: backends/dbus-vmstate1.xml
-An error may be returned to the caller.
+.. only:: not sphinx4
-Save(out u8[] bytes) method
----------------------------
+ .. warning::
+ Sphinx 4 is required to build D-Bus documentation.
-The method called on the source to get the current state to be
-migrated. The helper should continue to run normally.
+ This is the content of ``backends/dbus-vmstate1.xml``:
-An error may be returned to the caller.
+ .. literalinclude:: ../../backends/dbus-vmstate1.xml
+ :language: xml
diff --git a/docs/interop/dbus.rst b/docs/interop/dbus.rst
index be596d3f41..427debc9c5 100644
--- a/docs/interop/dbus.rst
+++ b/docs/interop/dbus.rst
@@ -108,3 +108,5 @@ QEMU Interfaces
===============
:doc:`dbus-vmstate`
+
+:doc:`dbus-display`
diff --git a/docs/interop/index.rst b/docs/interop/index.rst
index 47b9ed82bb..c59bac9834 100644
--- a/docs/interop/index.rst
+++ b/docs/interop/index.rst
@@ -12,6 +12,7 @@ are useful for making QEMU interoperate with other software.
bitmaps
dbus
dbus-vmstate
+ dbus-display
live-block-operations
pr-helper
qemu-ga
diff --git a/docs/sphinx/dbusdoc.py b/docs/sphinx/dbusdoc.py
new file mode 100644
index 0000000000..be284ed08f
--- /dev/null
+++ b/docs/sphinx/dbusdoc.py
@@ -0,0 +1,166 @@
+# D-Bus XML documentation extension
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
+
+import os
+import re
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Iterator,
+ List,
+ Optional,
+ Sequence,
+ Set,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+)
+
+import sphinx
+from docutils import nodes
+from docutils.nodes import Element, Node
+from docutils.parsers.rst import Directive, directives
+from docutils.parsers.rst.states import RSTState
+from docutils.statemachine import StringList, ViewList
+from sphinx.application import Sphinx
+from sphinx.errors import ExtensionError
+from sphinx.util import logging
+from sphinx.util.docstrings import prepare_docstring
+from sphinx.util.docutils import SphinxDirective, switch_source_input
+from sphinx.util.nodes import nested_parse_with_titles
+
+import dbusdomain
+from dbusparser import parse_dbus_xml
+
+logger = logging.getLogger(__name__)
+
+__version__ = "1.0"
+
+
+class DBusDoc:
+ def __init__(self, sphinx_directive, dbusfile):
+ self._cur_doc = None
+ self._sphinx_directive = sphinx_directive
+ self._dbusfile = dbusfile
+ self._top_node = nodes.section()
+ self.result = StringList()
+ self.indent = ""
+
+ def add_line(self, line: str, *lineno: int) -> None:
+ """Append one line of generated reST to the output."""
+ if line.strip(): # not a blank line
+ self.result.append(self.indent + line, self._dbusfile, *lineno)
+ else:
+ self.result.append("", self._dbusfile, *lineno)
+
+ def add_method(self, method):
+ self.add_line(f".. dbus:method:: {method.name}")
+ self.add_line("")
+ self.indent += " "
+ for arg in method.in_args:
+ self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
+ for arg in method.out_args:
+ self.add_line(f":ret {arg.signature} {arg.name}: {arg.doc_string}")
+ self.add_line("")
+ for line in prepare_docstring("\n" + method.doc_string):
+ self.add_line(line)
+ self.indent = self.indent[:-3]
+
+ def add_signal(self, signal):
+ self.add_line(f".. dbus:signal:: {signal.name}")
+ self.add_line("")
+ self.indent += " "
+ for arg in signal.args:
+ self.add_line(f":arg {arg.signature} {arg.name}: {arg.doc_string}")
+ self.add_line("")
+ for line in prepare_docstring("\n" + signal.doc_string):
+ self.add_line(line)
+ self.indent = self.indent[:-3]
+
+ def add_property(self, prop):
+ self.add_line(f".. dbus:property:: {prop.name}")
+ self.indent += " "
+ self.add_line(f":type: {prop.signature}")
+ access = {"read": "readonly", "write": "writeonly", "readwrite": "readwrite"}[
+ prop.access
+ ]
+ self.add_line(f":{access}:")
+ if prop.emits_changed_signal:
+ self.add_line(f":emits-changed: yes")
+ self.add_line("")
+ for line in prepare_docstring("\n" + prop.doc_string):
+ self.add_line(line)
+ self.indent = self.indent[:-3]
+
+ def add_interface(self, iface):
+ self.add_line(f".. dbus:interface:: {iface.name}")
+ self.add_line("")
+ self.indent += " "
+ for line in prepare_docstring("\n" + iface.doc_string):
+ self.add_line(line)
+ for method in iface.methods:
+ self.add_method(method)
+ for sig in iface.signals:
+ self.add_signal(sig)
+ for prop in iface.properties:
+ self.add_property(prop)
+ self.indent = self.indent[:-3]
+
+
+def parse_generated_content(state: RSTState, content: StringList) -> List[Node]:
+ """Parse a generated content by Documenter."""
+ with switch_source_input(state, content):
+ node = nodes.paragraph()
+ node.document = state.document
+ state.nested_parse(content, 0, node)
+
+ return node.children
+
+
+class DBusDocDirective(SphinxDirective):
+ """Extract documentation from the specified D-Bus XML file"""
+
+ has_content = True
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+
+ def run(self):
+ reporter = self.state.document.reporter
+
+ try:
+ source, lineno = reporter.get_source_and_line(self.lineno) # type: ignore
+ except AttributeError:
+ source, lineno = (None, None)
+
+ logger.debug("[dbusdoc] %s:%s: input:\n%s", source, lineno, self.block_text)
+
+ env = self.state.document.settings.env
+ dbusfile = env.config.qapidoc_srctree + "/" + self.arguments[0]
+ with open(dbusfile, "rb") as f:
+ xml_data = f.read()
+ xml = parse_dbus_xml(xml_data)
+ doc = DBusDoc(self, dbusfile)
+ for iface in xml:
+ doc.add_interface(iface)
+
+ result = parse_generated_content(self.state, doc.result)
+ return result
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+ """Register dbus-doc directive with Sphinx"""
+ app.add_config_value("dbusdoc_srctree", None, "env")
+ app.add_directive("dbus-doc", DBusDocDirective)
+ dbusdomain.setup(app)
+
+ return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True)
diff --git a/docs/sphinx/dbusdomain.py b/docs/sphinx/dbusdomain.py
new file mode 100644
index 0000000000..2ea95af623
--- /dev/null
+++ b/docs/sphinx/dbusdomain.py
@@ -0,0 +1,406 @@
+# D-Bus sphinx domain extension
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+
+from typing import (
+ Any,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ NamedTuple,
+ Optional,
+ Tuple,
+ cast,
+)
+
+from docutils import nodes
+from docutils.nodes import Element, Node
+from docutils.parsers.rst import directives
+from sphinx import addnodes
+from sphinx.addnodes import desc_signature, pending_xref
+from sphinx.directives import ObjectDescription
+from sphinx.domains import Domain, Index, IndexEntry, ObjType
+from sphinx.locale import _
+from sphinx.roles import XRefRole
+from sphinx.util import nodes as node_utils
+from sphinx.util.docfields import Field, TypedField
+from sphinx.util.typing import OptionSpec
+
+
+class DBusDescription(ObjectDescription[str]):
+ """Base class for DBus objects"""
+
+ option_spec: OptionSpec = ObjectDescription.option_spec.copy()
+ option_spec.update(
+ {
+ "deprecated": directives.flag,
+ }
+ )
+
+ def get_index_text(self, modname: str, name: str) -> str:
+ """Return the text for the index entry of the object."""
+ raise NotImplementedError("must be implemented in subclasses")
+
+ def add_target_and_index(
+ self, name: str, sig: str, signode: desc_signature
+ ) -> None:
+ ifacename = self.env.ref_context.get("dbus:interface")
+ node_id = name
+ if ifacename:
+ node_id = f"{ifacename}.{node_id}"
+
+ signode["names"].append(name)
+ signode["ids"].append(node_id)
+
+ if "noindexentry" not in self.options:
+ indextext = self.get_index_text(ifacename, name)
+ if indextext:
+ self.indexnode["entries"].append(
+ ("single", indextext, node_id, "", None)
+ )
+
+ domain = cast(DBusDomain, self.env.get_domain("dbus"))
+ domain.note_object(name, self.objtype, node_id, location=signode)
+
+
+class DBusInterface(DBusDescription):
+ """
+ Implementation of ``dbus:interface``.
+ """
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return ifacename
+
+ def before_content(self) -> None:
+ self.env.ref_context["dbus:interface"] = self.arguments[0]
+
+ def after_content(self) -> None:
+ self.env.ref_context.pop("dbus:interface")
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> str:
+ signode += addnodes.desc_annotation("interface ", "interface ")
+ signode += addnodes.desc_name(sig, sig)
+ return sig
+
+ def run(self) -> List[Node]:
+ _, node = super().run()
+ name = self.arguments[0]
+ section = nodes.section(ids=[name + "-section"])
+ section += nodes.title(name, "%s interface" % name)
+ section += node
+ return [self.indexnode, section]
+
+
+class DBusMember(DBusDescription):
+
+ signal = False
+
+
+class DBusMethod(DBusMember):
+ """
+ Implementation of ``dbus:method``.
+ """
+
+ option_spec: OptionSpec = DBusMember.option_spec.copy()
+ option_spec.update(
+ {
+ "noreply": directives.flag,
+ }
+ )
+
+ doc_field_types: List[Field] = [
+ TypedField(
+ "arg",
+ label=_("Arguments"),
+ names=("arg",),
+ rolename="arg",
+ typerolename=None,
+ typenames=("argtype", "type"),
+ ),
+ TypedField(
+ "ret",
+ label=_("Returns"),
+ names=("ret",),
+ rolename="ret",
+ typerolename=None,
+ typenames=("rettype", "type"),
+ ),
+ ]
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return _("%s() (%s method)") % (name, ifacename)
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> str:
+ params = addnodes.desc_parameterlist()
+ returns = addnodes.desc_parameterlist()
+
+ contentnode = addnodes.desc_content()
+ self.state.nested_parse(self.content, self.content_offset, contentnode)
+ for child in contentnode:
+ if isinstance(child, nodes.field_list):
+ for field in child:
+ ty, sg, name = field[0].astext().split(None, 2)
+ param = addnodes.desc_parameter()
+ param += addnodes.desc_sig_keyword_type(sg, sg)
+ param += addnodes.desc_sig_space()
+ param += addnodes.desc_sig_name(name, name)
+ if ty == "arg":
+ params += param
+ elif ty == "ret":
+ returns += param
+
+ anno = "signal " if self.signal else "method "
+ signode += addnodes.desc_annotation(anno, anno)
+ signode += addnodes.desc_name(sig, sig)
+ signode += params
+ if not self.signal and "noreply" not in self.options:
+ ret = addnodes.desc_returns()
+ ret += returns
+ signode += ret
+
+ return sig
+
+
+class DBusSignal(DBusMethod):
+ """
+ Implementation of ``dbus:signal``.
+ """
+
+ doc_field_types: List[Field] = [
+ TypedField(
+ "arg",
+ label=_("Arguments"),
+ names=("arg",),
+ rolename="arg",
+ typerolename=None,
+ typenames=("argtype", "type"),
+ ),
+ ]
+ signal = True
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return _("%s() (%s signal)") % (name, ifacename)
+
+
+class DBusProperty(DBusMember):
+ """
+ Implementation of ``dbus:property``.
+ """
+
+ option_spec: OptionSpec = DBusMember.option_spec.copy()
+ option_spec.update(
+ {
+ "type": directives.unchanged,
+ "readonly": directives.flag,
+ "writeonly": directives.flag,
+ "readwrite": directives.flag,
+ "emits-changed": directives.unchanged,
+ }
+ )
+
+ doc_field_types: List[Field] = []
+
+ def get_index_text(self, ifacename: str, name: str) -> str:
+ return _("%s (%s property)") % (name, ifacename)
+
+ def transform_content(self, contentnode: addnodes.desc_content) -> None:
+ fieldlist = nodes.field_list()
+ access = None
+ if "readonly" in self.options:
+ access = _("read-only")
+ if "writeonly" in self.options:
+ access = _("write-only")
+ if "readwrite" in self.options:
+ access = _("read & write")
+ if access:
+ content = nodes.Text(access)
+ fieldname = nodes.field_name("", _("Access"))
+ fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
+ field = nodes.field("", fieldname, fieldbody)
+ fieldlist += field
+ emits = self.options.get("emits-changed", None)
+ if emits:
+ content = nodes.Text(emits)
+ fieldname = nodes.field_name("", _("Emits Changed"))
+ fieldbody = nodes.field_body("", nodes.paragraph("", "", content))
+ field = nodes.field("", fieldname, fieldbody)
+ fieldlist += field
+ if len(fieldlist) > 0:
+ contentnode.insert(0, fieldlist)
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> str:
+ contentnode = addnodes.desc_content()
+ self.state.nested_parse(self.content, self.content_offset, contentnode)
+ ty = self.options.get("type")
+
+ signode += addnodes.desc_annotation("property ", "property ")
+ signode += addnodes.desc_name(sig, sig)
+ signode += addnodes.desc_sig_punctuation("", ":")
+ signode += addnodes.desc_sig_keyword_type(ty, ty)
+ return sig
+
+ def run(self) -> List[Node]:
+ self.name = "dbus:member"
+ return super().run()
+
+
+class DBusXRef(XRefRole):
+ def process_link(self, env, refnode, has_explicit_title, title, target):
+ refnode["dbus:interface"] = env.ref_context.get("dbus:interface")
+ if not has_explicit_title:
+ title = title.lstrip(".") # only has a meaning for the target
+ target = target.lstrip("~") # only has a meaning for the title
+ # if the first character is a tilde, don't display the module/class
+ # parts of the contents
+ if title[0:1] == "~":
+ title = title[1:]
+ dot = title.rfind(".")
+ if dot != -1:
+ title = title[dot + 1 :]
+ # if the first character is a dot, search more specific namespaces first
+ # else search builtins first
+ if target[0:1] == ".":
+ target = target[1:]
+ refnode["refspecific"] = True
+ return title, target
+
+
+class DBusIndex(Index):
+ """
+ Index subclass to provide a D-Bus interfaces index.
+ """
+
+ name = "dbusindex"
+ localname = _("D-Bus Interfaces Index")
+ shortname = _("dbus")
+
+ def generate(
+ self, docnames: Iterable[str] = None
+ ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]:
+ content: Dict[str, List[IndexEntry]] = {}
+ # list of prefixes to ignore
+ ignores: List[str] = self.domain.env.config["dbus_index_common_prefix"]
+ ignores = sorted(ignores, key=len, reverse=True)
+
+ ifaces = sorted(
+ [
+ x
+ for x in self.domain.data["objects"].items()
+ if x[1].objtype == "interface"
+ ],
+ key=lambda x: x[0].lower(),
+ )
+ for name, (docname, node_id, _) in ifaces:
+ if docnames and docname not in docnames:
+ continue
+
+ for ignore in ignores:
+ if name.startswith(ignore):
+ name = name[len(ignore) :]
+ stripped = ignore
+ break
+ else:
+ stripped = ""
+
+ entries = content.setdefault(name[0].lower(), [])
+ entries.append(IndexEntry(stripped + name, 0, docname, node_id, "", "", ""))
+
+ # sort by first letter
+ sorted_content = sorted(content.items())
+
+ return sorted_content, False
+
+
+class ObjectEntry(NamedTuple):
+ docname: str
+ node_id: str
+ objtype: str
+
+
+class DBusDomain(Domain):
+ """
+ Implementation of the D-Bus domain.
+ """
+
+ name = "dbus"
+ label = "D-Bus"
+ object_types: Dict[str, ObjType] = {
+ "interface": ObjType(_("interface"), "iface", "obj"),
+ "method": ObjType(_("method"), "meth", "obj"),
+ "signal": ObjType(_("signal"), "sig", "obj"),
+ "property": ObjType(_("property"), "attr", "_prop", "obj"),
+ }
+ directives = {
+ "interface": DBusInterface,
+ "method": DBusMethod,
+ "signal": DBusSignal,
+ "property": DBusProperty,
+ }
+ roles = {
+ "iface": DBusXRef(),
+ "meth": DBusXRef(),
+ "sig": DBusXRef(),
+ "prop": DBusXRef(),
+ }
+ initial_data: Dict[str, Dict[str, Tuple[Any]]] = {
+ "objects": {}, # fullname -> ObjectEntry
+ }
+ indices = [
+ DBusIndex,
+ ]
+
+ @property
+ def objects(self) -> Dict[str, ObjectEntry]:
+ return self.data.setdefault("objects", {}) # fullname -> ObjectEntry
+
+ def note_object(
+ self, name: str, objtype: str, node_id: str, location: Any = None
+ ) -> None:
+ self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
+
+ def clear_doc(self, docname: str) -> None:
+ for fullname, obj in list(self.objects.items()):
+ if obj.docname == docname:
+ del self.objects[fullname]
+
+ def find_obj(self, typ: str, name: str) -> Optional[Tuple[str, ObjectEntry]]:
+ # skip parens
+ if name[-2:] == "()":
+ name = name[:-2]
+ if typ in ("meth", "sig", "prop"):
+ try:
+ ifacename, name = name.rsplit(".", 1)
+ except ValueError:
+ pass
+ return self.objects.get(name)
+
+ def resolve_xref(
+ self,
+ env: "BuildEnvironment",
+ fromdocname: str,
+ builder: "Builder",
+ typ: str,
+ target: str,
+ node: pending_xref,
+ contnode: Element,
+ ) -> Optional[Element]:
+ """Resolve the pending_xref *node* with the given *typ* and *target*."""
+ objdef = self.find_obj(typ, target)
+ if objdef:
+ return node_utils.make_refnode(
+ builder, fromdocname, objdef.docname, objdef.node_id, contnode
+ )
+
+ def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
+ for refname, obj in self.objects.items():
+ yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
+
+
+def setup(app):
+ app.add_domain(DBusDomain)
+ app.add_config_value("dbus_index_common_prefix", [], "env")
diff --git a/docs/sphinx/dbusparser.py b/docs/sphinx/dbusparser.py
new file mode 100644
index 0000000000..024553eae7
--- /dev/null
+++ b/docs/sphinx/dbusparser.py
@@ -0,0 +1,373 @@
+# Based from "GDBus - GLib D-Bus Library":
+#
+# Copyright (C) 2008-2011 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General
+# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Zeuthen <davidz@redhat.com>
+
+import xml.parsers.expat
+
+
+class Annotation:
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+ self.annotations = []
+ self.since = ""
+
+
+class Arg:
+ def __init__(self, name, signature):
+ self.name = name
+ self.signature = signature
+ self.annotations = []
+ self.doc_string = ""
+ self.since = ""
+
+
+class Method:
+ def __init__(self, name, h_type_implies_unix_fd=True):
+ self.name = name
+ self.h_type_implies_unix_fd = h_type_implies_unix_fd
+ self.in_args = []
+ self.out_args = []
+ self.annotations = []
+ self.doc_string = ""
+ self.since = ""
+ self.deprecated = False
+ self.unix_fd = False
+
+
+class Signal:
+ def __init__(self, name):
+ self.name = name
+ self.args = []
+ self.annotations = []
+ self.doc_string = ""
+ self.since = ""
+ self.deprecated = False
+
+
+class Property:
+ def __init__(self, name, signature, access):
+ self.name = name
+ self.signature = signature
+ self.access = access
+ self.annotations = []
+ self.arg = Arg("value", self.signature)
+ self.arg.annotations = self.annotations
+ self.readable = False
+ self.writable = False
+ if self.access == "readwrite":
+ self.readable = True
+ self.writable = True
+ elif self.access == "read":
+ self.readable = True
+ elif self.access == "write":
+ self.writable = True
+ else:
+ raise ValueError('Invalid access type "{}"'.format(self.access))
+ self.doc_string = ""
+ self.since = ""
+ self.deprecated = False
+ self.emits_changed_signal = True
+
+
+class Interface:
+ def __init__(self, name):
+ self.name = name
+ self.methods = []
+ self.signals = []
+ self.properties = []
+ self.annotations = []
+ self.doc_string = ""
+ self.doc_string_brief = ""
+ self.since = ""
+ self.deprecated = False
+
+
+class DBusXMLParser:
+ STATE_TOP = "top"
+ STATE_NODE = "node"
+ STATE_INTERFACE = "interface"
+ STATE_METHOD = "method"
+ STATE_SIGNAL = "signal"
+ STATE_PROPERTY = "property"
+ STATE_ARG = "arg"
+ STATE_ANNOTATION = "annotation"
+ STATE_IGNORED = "ignored"
+
+ def __init__(self, xml_data, h_type_implies_unix_fd=True):
+ self._parser = xml.parsers.expat.ParserCreate()
+ self._parser.CommentHandler = self.handle_comment
+ self._parser.CharacterDataHandler = self.handle_char_data
+ self._parser.StartElementHandler = self.handle_start_element
+ self._parser.EndElementHandler = self.handle_end_element
+
+ self.parsed_interfaces = []
+ self._cur_object = None
+
+ self.state = DBusXMLParser.STATE_TOP
+ self.state_stack = []
+ self._cur_object = None
+ self._cur_object_stack = []
+
+ self.doc_comment_last_symbol = ""
+
+ self._h_type_implies_unix_fd = h_type_implies_unix_fd
+
+ self._parser.Parse(xml_data)
+
+ COMMENT_STATE_BEGIN = "begin"
+ COMMENT_STATE_PARAMS = "params"
+ COMMENT_STATE_BODY = "body"
+ COMMENT_STATE_SKIP = "skip"
+
+ def handle_comment(self, data):
+ comment_state = DBusXMLParser.COMMENT_STATE_BEGIN
+ lines = data.split("\n")
+ symbol = ""
+ body = ""
+ in_para = False
+ params = {}
+ for line in lines:
+ orig_line = line
+ line = line.lstrip()
+ if comment_state == DBusXMLParser.COMMENT_STATE_BEGIN:
+ if len(line) > 0:
+ colon_index = line.find(": ")
+ if colon_index == -1:
+ if line.endswith(":"):
+ symbol = line[0 : len(line) - 1]
+ comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
+ else:
+ comment_state = DBusXMLParser.COMMENT_STATE_SKIP
+ else:
+ symbol = line[0:colon_index]
+ rest_of_line = line[colon_index + 2 :].strip()
+ if len(rest_of_line) > 0:
+ body += rest_of_line + "\n"
+ comment_state = DBusXMLParser.COMMENT_STATE_PARAMS
+ elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS:
+ if line.startswith("@"):
+ colon_index = line.find(": ")
+ if colon_index == -1:
+ comment_state = DBusXMLParser.COMMENT_STATE_BODY
+ if not in_para:
+ in_para = True
+ body += orig_line + "\n"
+ else:
+ param = line[1:colon_index]
+ docs = line[colon_index + 2 :]
+ params[param] = docs
+ else:
+ comment_state = DBusXMLParser.COMMENT_STATE_BODY
+ if len(line) > 0:
+ if not in_para:
+ in_para = True
+ body += orig_line + "\n"
+ elif comment_state == DBusXMLParser.COMMENT_STATE_BODY:
+ if len(line) > 0:
+ if not in_para:
+ in_para = True
+ body += orig_line + "\n"
+ else:
+ if in_para:
+ body += "\n"
+ in_para = False
+ if in_para:
+ body += "\n"
+
+ if symbol != "":
+ self.doc_comment_last_symbol = symbol
+ self.doc_comment_params = params
+ self.doc_comment_body = body
+
+ def handle_char_data(self, data):
+ # print 'char_data=%s'%data
+ pass
+
+ def handle_start_element(self, name, attrs):
+ old_state = self.state
+ old_cur_object = self._cur_object
+ if self.state == DBusXMLParser.STATE_IGNORED:
+ self.state = DBusXMLParser.STATE_IGNORED
+ elif self.state == DBusXMLParser.STATE_TOP:
+ if name == DBusXMLParser.STATE_NODE:
+ self.state = DBusXMLParser.STATE_NODE
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+ elif self.state == DBusXMLParser.STATE_NODE:
+ if name == DBusXMLParser.STATE_INTERFACE:
+ self.state = DBusXMLParser.STATE_INTERFACE
+ iface = Interface(attrs["name"])
+ self._cur_object = iface
+ self.parsed_interfaces.append(iface)
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
+ self._cur_object.doc_string = self.doc_comment_body
+ if "short_description" in self.doc_comment_params:
+ short_description = self.doc_comment_params["short_description"]
+ self._cur_object.doc_string_brief = short_description
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params["since"].strip()
+
+ elif self.state == DBusXMLParser.STATE_INTERFACE:
+ if name == DBusXMLParser.STATE_METHOD:
+ self.state = DBusXMLParser.STATE_METHOD
+ method = Method(
+ attrs["name"], h_type_implies_unix_fd=self._h_type_implies_unix_fd
+ )
+ self._cur_object.methods.append(method)
+ self._cur_object = method
+ elif name == DBusXMLParser.STATE_SIGNAL:
+ self.state = DBusXMLParser.STATE_SIGNAL
+ signal = Signal(attrs["name"])
+ self._cur_object.signals.append(signal)
+ self._cur_object = signal
+ elif name == DBusXMLParser.STATE_PROPERTY:
+ self.state = DBusXMLParser.STATE_PROPERTY
+ prop = Property(attrs["name"], attrs["type"], attrs["access"])
+ self._cur_object.properties.append(prop)
+ self._cur_object = prop
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if "name" in attrs and self.doc_comment_last_symbol == attrs["name"]:
+ self._cur_object.doc_string = self.doc_comment_body
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params["since"].strip()
+
+ elif self.state == DBusXMLParser.STATE_METHOD:
+ if name == DBusXMLParser.STATE_ARG:
+ self.state = DBusXMLParser.STATE_ARG
+ arg_name = None
+ if "name" in attrs:
+ arg_name = attrs["name"]
+ arg = Arg(arg_name, attrs["type"])
+ direction = attrs.get("direction", "in")
+ if direction == "in":
+ self._cur_object.in_args.append(arg)
+ elif direction == "out":
+ self._cur_object.out_args.append(arg)
+ else:
+ raise ValueError('Invalid direction "{}"'.format(direction))
+ self._cur_object = arg
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if self.doc_comment_last_symbol == old_cur_object.name:
+ if "name" in attrs and attrs["name"] in self.doc_comment_params:
+ doc_string = self.doc_comment_params[attrs["name"]]
+ if doc_string is not None:
+ self._cur_object.doc_string = doc_string
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params[
+ "since"
+ ].strip()
+
+ elif self.state == DBusXMLParser.STATE_SIGNAL:
+ if name == DBusXMLParser.STATE_ARG:
+ self.state = DBusXMLParser.STATE_ARG
+ arg_name = None
+ if "name" in attrs:
+ arg_name = attrs["name"]
+ arg = Arg(arg_name, attrs["type"])
+ self._cur_object.args.append(arg)
+ self._cur_object = arg
+ elif name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ # assign docs, if any
+ if self.doc_comment_last_symbol == old_cur_object.name:
+ if "name" in attrs and attrs["name"] in self.doc_comment_params:
+ doc_string = self.doc_comment_params[attrs["name"]]
+ if doc_string is not None:
+ self._cur_object.doc_string = doc_string
+ if "since" in self.doc_comment_params:
+ self._cur_object.since = self.doc_comment_params[
+ "since"
+ ].strip()
+
+ elif self.state == DBusXMLParser.STATE_PROPERTY:
+ if name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ elif self.state == DBusXMLParser.STATE_ARG:
+ if name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ elif self.state == DBusXMLParser.STATE_ANNOTATION:
+ if name == DBusXMLParser.STATE_ANNOTATION:
+ self.state = DBusXMLParser.STATE_ANNOTATION
+ anno = Annotation(attrs["name"], attrs["value"])
+ self._cur_object.annotations.append(anno)
+ self._cur_object = anno
+ else:
+ self.state = DBusXMLParser.STATE_IGNORED
+
+ else:
+ raise ValueError(
+ 'Unhandled state "{}" while entering element with name "{}"'.format(
+ self.state, name
+ )
+ )
+
+ self.state_stack.append(old_state)
+ self._cur_object_stack.append(old_cur_object)
+
+ def handle_end_element(self, name):
+ self.state = self.state_stack.pop()
+ self._cur_object = self._cur_object_stack.pop()
+
+
+def parse_dbus_xml(xml_data):
+ parser = DBusXMLParser(xml_data, True)
+ return parser.parsed_interfaces
diff --git a/docs/sphinx/fakedbusdoc.py b/docs/sphinx/fakedbusdoc.py
new file mode 100644
index 0000000000..a680b25754
--- /dev/null
+++ b/docs/sphinx/fakedbusdoc.py
@@ -0,0 +1,25 @@
+# D-Bus XML documentation extension, compatibility gunk for <sphinx4
+#
+# Copyright (C) 2021, Red Hat Inc.
+#
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# Author: Marc-André Lureau <marcandre.lureau@redhat.com>
+"""dbus-doc is a Sphinx extension that provides documentation from D-Bus XML."""
+
+from sphinx.application import Sphinx
+from sphinx.util.docutils import SphinxDirective
+from typing import Any, Dict
+
+
+class FakeDBusDocDirective(SphinxDirective):
+ has_content = True
+ required_arguments = 1
+
+ def run(self):
+ return []
+
+
+def setup(app: Sphinx) -> Dict[str, Any]:
+ """Register a fake dbus-doc directive with Sphinx"""
+ app.add_directive("dbus-doc", FakeDBusDocDirective)
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 29c80b4289..e2d6e317da 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2171,12 +2171,17 @@ static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
}
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+ Error *err = NULL;
char device_address[256] = "";
- if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
+ if (qemu_console_fill_device_address(qxl->vga.con,
+ device_address, sizeof(device_address),
+ &err)) {
spice_qxl_set_device_info(&qxl->ssd.qxl,
device_address,
0,
qxl->max_outputs);
+ } else {
+ error_report_err(err);
}
#endif
diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c
index 49df56cd14..09818231bd 100644
--- a/hw/display/vhost-user-gpu.c
+++ b/hw/display/vhost-user-gpu.c
@@ -254,8 +254,8 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
vhost_user_gpu_unblock(g);
break;
}
- dpy_gl_update(con, m->x, m->y, m->width, m->height);
g->backend_blocked = true;
+ dpy_gl_update(con, m->x, m->y, m->width, m->height);
break;
}
case VHOST_USER_GPU_UPDATE: {
diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c
index c8da4806e0..fff0fb4a82 100644
--- a/hw/display/virtio-gpu-base.c
+++ b/hw/display/virtio-gpu-base.c
@@ -117,6 +117,10 @@ virtio_gpu_gl_block(void *opaque, bool block)
g->renderer_blocked--;
}
assert(g->renderer_blocked >= 0);
+
+ if (!block && g->renderer_blocked == 0) {
+ virtio_gpu_gl_flushed(g);
+ }
}
static int
@@ -143,7 +147,6 @@ static const GraphicHwOps virtio_gpu_ops = {
.text_update = virtio_gpu_text_update,
.ui_info = virtio_gpu_ui_info,
.gl_block = virtio_gpu_gl_block,
- .gl_flushed = virtio_gpu_gl_flushed,
};
bool
diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
index 18d054922f..73cb92c8d5 100644
--- a/hw/display/virtio-gpu-virgl.c
+++ b/hw/display/virtio-gpu-virgl.c
@@ -175,7 +175,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
virgl_renderer_force_ctx_0();
dpy_gl_scanout_texture(
g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
- info.flags & 1 /* FIXME: Y_0_TOP */,
+ info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
info.width, info.height,
ss.r.x, ss.r.y, ss.r.width, ss.r.height);
} else {
@@ -609,6 +609,7 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
if (ret != 0) {
+ error_report("virgl could not be initialized: %d", ret);
return ret;
}
diff --git a/hw/display/virtio-vga.c b/hw/display/virtio-vga.c
index 9e57f61e9e..b23a75a04b 100644
--- a/hw/display/virtio-vga.c
+++ b/hw/display/virtio-vga.c
@@ -68,16 +68,6 @@ static void virtio_vga_base_gl_block(void *opaque, bool block)
}
}
-static void virtio_vga_base_gl_flushed(void *opaque)
-{
- VirtIOVGABase *vvga = opaque;
- VirtIOGPUBase *g = vvga->vgpu;
-
- if (g->hw_ops->gl_flushed) {
- g->hw_ops->gl_flushed(g);
- }
-}
-
static int virtio_vga_base_get_flags(void *opaque)
{
VirtIOVGABase *vvga = opaque;
@@ -93,7 +83,6 @@ static const GraphicHwOps virtio_vga_base_ops = {
.text_update = virtio_vga_base_text_update,
.ui_info = virtio_vga_base_ui_info,
.gl_block = virtio_vga_base_gl_block,
- .gl_flushed = virtio_vga_base_gl_flushed,
};
static const VMStateDescription vmstate_virtio_vga_base = {
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index f2014d5ea0..cf8ab0f8af 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -44,6 +44,7 @@
#include "hw/pci/pci.h"
#include "net_rx_pkt.h"
#include "hw/virtio/vhost.h"
+#include "sysemu/qtest.h"
#define VIRTIO_NET_VM_VERSION 11
@@ -926,7 +927,11 @@ static void virtio_net_set_features(VirtIODevice *vdev, uint64_t features)
qatomic_set(&n->failover_primary_hidden, false);
failover_add_primary(n, &err);
if (err) {
- warn_report_err(err);
+ if (!qtest_enabled()) {
+ warn_report_err(err);
+ } else {
+ error_free(err);
+ }
}
}
}
diff --git a/include/chardev/char-socket.h b/include/chardev/char-socket.h
new file mode 100644
index 0000000000..6b6e2ceba1
--- /dev/null
+++ b/include/chardev/char-socket.h
@@ -0,0 +1,86 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef CHAR_SOCKET_H_
+#define CHAR_SOCKET_H_
+
+#include "io/channel-socket.h"
+#include "io/channel-tls.h"
+#include "io/net-listener.h"
+#include "chardev/char.h"
+#include "qom/object.h"
+
+#define TCP_MAX_FDS 16
+
+typedef struct {
+ char buf[21];
+ size_t buflen;
+} TCPChardevTelnetInit;
+
+typedef enum {
+ TCP_CHARDEV_STATE_DISCONNECTED,
+ TCP_CHARDEV_STATE_CONNECTING,
+ TCP_CHARDEV_STATE_CONNECTED,
+} TCPChardevState;
+
+typedef ChardevClass SocketChardevClass;
+
+struct SocketChardev {
+ Chardev parent;
+ QIOChannel *ioc; /* Client I/O channel */
+ QIOChannelSocket *sioc; /* Client master channel */
+ QIONetListener *listener;
+ GSource *hup_source;
+ QCryptoTLSCreds *tls_creds;
+ char *tls_authz;
+ TCPChardevState state;
+ int max_size;
+ int do_telnetopt;
+ int do_nodelay;
+ int *read_msgfds;
+ size_t read_msgfds_num;
+ int *write_msgfds;
+ size_t write_msgfds_num;
+ bool registered_yank;
+
+ SocketAddress *addr;
+ bool is_listen;
+ bool is_telnet;
+ bool is_tn3270;
+ GSource *telnet_source;
+ TCPChardevTelnetInit *telnet_init;
+
+ bool is_websock;
+
+ GSource *reconnect_timer;
+ int64_t reconnect_time;
+ bool connect_err_reported;
+
+ QIOTask *connect_task;
+};
+typedef struct SocketChardev SocketChardev;
+
+DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
+ TYPE_CHARDEV_SOCKET)
+
+#endif /* CHAR_SOCKET_H_ */
diff --git a/include/elf.h b/include/elf.h
index 811bf4a1cb..3a4bcb646a 100644
--- a/include/elf.h
+++ b/include/elf.h
@@ -182,6 +182,8 @@ typedef struct mips_elf_abiflags_v0 {
#define EM_NANOMIPS 249 /* Wave Computing nanoMIPS */
+#define EM_LOONGARCH 258 /* LoongArch */
+
/*
* This is an interim value that we will use until the committee comes
* up with a final number.
diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h
index 986ed8e15f..320543950c 100644
--- a/include/qemu/cutils.h
+++ b/include/qemu/cutils.h
@@ -209,4 +209,9 @@ int qemu_pstrcmp0(const char **str1, const char **str2);
*/
char *get_relocated_path(const char *dir);
+static inline const char *yes_no(bool b)
+{
+ return b ? "yes" : "no";
+}
+
#endif
diff --git a/include/qemu/dbus.h b/include/qemu/dbus.h
index 9d591f9ee4..08f00dfd53 100644
--- a/include/qemu/dbus.h
+++ b/include/qemu/dbus.h
@@ -12,6 +12,30 @@
#include <gio/gio.h>
+#include "qom/object.h"
+#include "chardev/char.h"
+#include "qemu/notify.h"
+#include "qemu/typedefs.h"
+
+/* glib/gio 2.68 */
+#define DBUS_METHOD_INVOCATION_HANDLED TRUE
+#define DBUS_METHOD_INVOCATION_UNHANDLED FALSE
+
+/* in msec */
+#define DBUS_DEFAULT_TIMEOUT 1000
+
+#define DBUS_DISPLAY1_ROOT "/org/qemu/Display1"
+
+#define DBUS_DISPLAY_ERROR (dbus_display_error_quark())
+GQuark dbus_display_error_quark(void);
+
+typedef enum {
+ DBUS_DISPLAY_ERROR_FAILED,
+ DBUS_DISPLAY_ERROR_INVALID,
+ DBUS_DISPLAY_ERROR_UNSUPPORTED,
+ DBUS_DISPLAY_N_ERRORS,
+} DBusDisplayError;
+
GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
const char *name,
Error **errp);
diff --git a/include/qemu/option.h b/include/qemu/option.h
index 306bf07575..bbd86e1c4e 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -150,4 +150,6 @@ QDict *keyval_parse(const char *params, const char *implied_key,
bool *help, Error **errp);
void keyval_merge(QDict *old, const QDict *new, Error **errp);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(QemuOpts, qemu_opts_del)
+
#endif
diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h
index 6298986b15..ce76aa451f 100644
--- a/include/ui/clipboard.h
+++ b/include/ui/clipboard.h
@@ -20,8 +20,10 @@
*/
typedef enum QemuClipboardType QemuClipboardType;
+typedef enum QemuClipboardNotifyType QemuClipboardNotifyType;
typedef enum QemuClipboardSelection QemuClipboardSelection;
typedef struct QemuClipboardPeer QemuClipboardPeer;
+typedef struct QemuClipboardNotify QemuClipboardNotify;
typedef struct QemuClipboardInfo QemuClipboardInfo;
/**
@@ -55,25 +57,55 @@ enum QemuClipboardSelection {
* struct QemuClipboardPeer
*
* @name: peer name.
- * @update: notifier for clipboard updates.
+ * @notifier: notifier for clipboard updates.
* @request: callback for clipboard data requests.
*
* Clipboard peer description.
*/
struct QemuClipboardPeer {
const char *name;
- Notifier update;
+ Notifier notifier;
void (*request)(QemuClipboardInfo *info,
QemuClipboardType type);
};
/**
+ * enum QemuClipboardNotifyType
+ *
+ * @QEMU_CLIPBOARD_UPDATE_INFO: clipboard info update
+ * @QEMU_CLIPBOARD_RESET_SERIAL: reset clipboard serial
+ *
+ * Clipboard notify type.
+ */
+enum QemuClipboardNotifyType {
+ QEMU_CLIPBOARD_UPDATE_INFO,
+ QEMU_CLIPBOARD_RESET_SERIAL,
+};
+
+/**
+ * struct QemuClipboardNotify
+ *
+ * @type: the type of event.
+ * @info: a QemuClipboardInfo event.
+ *
+ * Clipboard notify data.
+ */
+struct QemuClipboardNotify {
+ QemuClipboardNotifyType type;
+ union {
+ QemuClipboardInfo *info;
+ };
+};
+
+/**
* struct QemuClipboardInfo
*
* @refcount: reference counter.
* @owner: clipboard owner.
* @selection: clipboard selection.
* @types: clipboard data array (one entry per type).
+ * @has_serial: whether @serial is available.
+ * @serial: the grab serial counter.
*
* Clipboard content data and metadata.
*/
@@ -81,6 +113,8 @@ struct QemuClipboardInfo {
uint32_t refcount;
QemuClipboardPeer *owner;
QemuClipboardSelection selection;
+ bool has_serial;
+ uint32_t serial;
struct {
bool available;
bool requested;
@@ -141,6 +175,16 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection);
/**
+ * qemu_clipboard_check_serial
+ *
+ * @info: clipboard info.
+ * @client: whether to check from the client context and priority.
+ *
+ * Return TRUE if the @info has a higher serial than the current clipboard.
+ */
+bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client);
+
+/**
* qemu_clipboard_info_new
*
* @owner: clipboard owner.
@@ -189,6 +233,13 @@ void qemu_clipboard_info_unref(QemuClipboardInfo *info);
void qemu_clipboard_update(QemuClipboardInfo *info);
/**
+ * qemu_clipboard_reset_serial
+ *
+ * Reset the clipboard serial.
+ */
+void qemu_clipboard_reset_serial(void);
+
+/**
* qemu_clipboard_request
*
* @info: clipboard info.
diff --git a/include/ui/console.h b/include/ui/console.h
index 6d678924f6..f590819880 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -108,6 +108,17 @@ struct QemuConsoleClass {
#define QEMU_ALLOCATED_FLAG 0x01
#define QEMU_PLACEHOLDER_FLAG 0x02
+typedef struct ScanoutTexture {
+ uint32_t backing_id;
+ bool backing_y_0_top;
+ uint32_t backing_width;
+ uint32_t backing_height;
+ uint32_t x;
+ uint32_t y;
+ uint32_t width;
+ uint32_t height;
+} ScanoutTexture;
+
typedef struct DisplaySurface {
pixman_format_code_t format;
pixman_image_t *image;
@@ -178,7 +189,24 @@ typedef struct QemuDmaBuf {
bool draw_submitted;
} QemuDmaBuf;
+enum display_scanout {
+ SCANOUT_NONE,
+ SCANOUT_SURFACE,
+ SCANOUT_TEXTURE,
+ SCANOUT_DMABUF,
+};
+
+typedef struct DisplayScanout {
+ enum display_scanout kind;
+ union {
+ /* DisplaySurface *surface; is kept in QemuConsole */
+ ScanoutTexture texture;
+ QemuDmaBuf *dmabuf;
+ };
+} DisplayScanout;
+
typedef struct DisplayState DisplayState;
+typedef struct DisplayGLCtx DisplayGLCtx;
typedef struct DisplayChangeListenerOps {
const char *dpy_name;
@@ -214,16 +242,6 @@ typedef struct DisplayChangeListenerOps {
QEMUCursor *cursor);
/* required if GL */
- QEMUGLContext (*dpy_gl_ctx_create)(DisplayChangeListener *dcl,
- QEMUGLParams *params);
- /* required if GL */
- void (*dpy_gl_ctx_destroy)(DisplayChangeListener *dcl,
- QEMUGLContext ctx);
- /* required if GL */
- int (*dpy_gl_ctx_make_current)(DisplayChangeListener *dcl,
- QEMUGLContext ctx);
-
- /* required if GL */
void (*dpy_gl_scanout_disable)(DisplayChangeListener *dcl);
/* required if GL */
void (*dpy_gl_scanout_texture)(DisplayChangeListener *dcl,
@@ -263,6 +281,26 @@ struct DisplayChangeListener {
QLIST_ENTRY(DisplayChangeListener) next;
};
+typedef struct DisplayGLCtxOps {
+ /*
+ * We only check if the GLCtx is compatible with a DCL via ops. A natural
+ * evolution of this would be a callback to check some runtime requirements
+ * and allow various DCL kinds.
+ */
+ const DisplayChangeListenerOps *compatible_dcl;
+
+ QEMUGLContext (*dpy_gl_ctx_create)(DisplayGLCtx *dgc,
+ QEMUGLParams *params);
+ void (*dpy_gl_ctx_destroy)(DisplayGLCtx *dgc,
+ QEMUGLContext ctx);
+ int (*dpy_gl_ctx_make_current)(DisplayGLCtx *dgc,
+ QEMUGLContext ctx);
+} DisplayGLCtxOps;
+
+struct DisplayGLCtx {
+ const DisplayGLCtxOps *ops;
+};
+
DisplayState *init_displaystate(void);
DisplaySurface *qemu_create_displaysurface_from(int width, int height,
pixman_format_code_t format,
@@ -292,7 +330,7 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl);
bool dpy_ui_info_supported(QemuConsole *con);
const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con);
-int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info);
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay);
void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h);
void dpy_gfx_update_full(QemuConsole *con);
@@ -391,7 +429,6 @@ typedef struct GraphicHwOps {
void (*update_interval)(void *opaque, uint64_t interval);
int (*ui_info)(void *opaque, uint32_t head, QemuUIInfo *info);
void (*gl_block)(void *opaque, bool block);
- void (*gl_flushed)(void *opaque);
} GraphicHwOps;
QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
@@ -407,10 +444,11 @@ void graphic_hw_update_done(QemuConsole *con);
void graphic_hw_invalidate(QemuConsole *con);
void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata);
void graphic_hw_gl_block(QemuConsole *con, bool block);
-void graphic_hw_gl_flushed(QemuConsole *con);
void qemu_console_early_init(void);
+void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *ctx);
+
QemuConsole *qemu_console_lookup_by_index(unsigned int index);
QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head);
QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
@@ -484,4 +522,10 @@ int index_from_key(const char *key, size_t key_length);
int udmabuf_fd(void);
#endif
+/* util.c */
+bool qemu_console_fill_device_address(QemuConsole *con,
+ char *device_address,
+ size_t size,
+ Error **errp);
+
#endif
diff --git a/include/ui/dbus-display.h b/include/ui/dbus-display.h
new file mode 100644
index 0000000000..88f153c237
--- /dev/null
+++ b/include/ui/dbus-display.h
@@ -0,0 +1,17 @@
+#ifndef DBUS_DISPLAY_H_
+#define DBUS_DISPLAY_H_
+
+#include "qapi/error.h"
+#include "ui/dbus-module.h"
+
+static inline bool qemu_using_dbus_display(Error **errp)
+{
+ if (!using_dbus_display) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_ACTIVE,
+ "D-Bus display is not in use");
+ return false;
+ }
+ return true;
+}
+
+#endif /* DBUS_DISPLAY_H_ */
diff --git a/include/ui/dbus-module.h b/include/ui/dbus-module.h
new file mode 100644
index 0000000000..ace4a17a5c
--- /dev/null
+++ b/include/ui/dbus-module.h
@@ -0,0 +1,11 @@
+#ifndef DBUS_MODULE_H_
+#define DBUS_MODULE_H_
+
+struct QemuDBusDisplayOps {
+ bool (*add_client)(int csock, Error **errp);
+};
+
+extern int using_dbus_display;
+extern struct QemuDBusDisplayOps qemu_dbus_display;
+
+#endif /* DBUS_MODULE_H_*/
diff --git a/include/ui/egl-context.h b/include/ui/egl-context.h
index 9374fe41e3..c2761d747a 100644
--- a/include/ui/egl-context.h
+++ b/include/ui/egl-context.h
@@ -4,10 +4,10 @@
#include "ui/console.h"
#include "ui/egl-helpers.h"
-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
-int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
+int qemu_egl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
#endif /* EGL_CONTEXT_H */
diff --git a/include/ui/gtk.h b/include/ui/gtk.h
index 7d22affd38..101b147d1b 100644
--- a/include/ui/gtk.h
+++ b/include/ui/gtk.h
@@ -35,6 +35,7 @@ typedef struct GtkDisplayState GtkDisplayState;
typedef struct VirtualGfxConsole {
GtkWidget *drawing_area;
+ DisplayGLCtx dgc;
DisplayChangeListener dcl;
QKbdState *kbd;
DisplaySurface *ds;
@@ -165,7 +166,7 @@ void gd_egl_update(DisplayChangeListener *dcl,
void gd_egl_refresh(DisplayChangeListener *dcl);
void gd_egl_switch(DisplayChangeListener *dcl,
DisplaySurface *surface);
-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
void gd_egl_scanout_disable(DisplayChangeListener *dcl);
void gd_egl_scanout_texture(DisplayChangeListener *dcl,
@@ -187,7 +188,7 @@ void gd_egl_flush(DisplayChangeListener *dcl,
void gd_egl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void gtk_egl_init(DisplayGLMode mode);
-int gd_egl_make_current(DisplayChangeListener *dcl,
+int gd_egl_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
/* ui/gtk-gl-area.c */
@@ -198,9 +199,9 @@ void gd_gl_area_update(DisplayChangeListener *dcl,
void gd_gl_area_refresh(DisplayChangeListener *dcl);
void gd_gl_area_switch(DisplayChangeListener *dcl,
DisplaySurface *surface);
-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
-void gd_gl_area_destroy_context(DisplayChangeListener *dcl,
+void gd_gl_area_destroy_context(DisplayGLCtx *dgc,
QEMUGLContext ctx);
void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf);
@@ -215,7 +216,7 @@ void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void gtk_gl_area_init(void);
-int gd_gl_area_make_current(DisplayChangeListener *dcl,
+int gd_gl_area_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
/* gtk-clipboard.c */
diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
index f85c117a78..71bcf7ebda 100644
--- a/include/ui/sdl2.h
+++ b/include/ui/sdl2.h
@@ -16,6 +16,7 @@
#endif
struct sdl2_console {
+ DisplayGLCtx dgc;
DisplayChangeListener dcl;
DisplaySurface *surface;
DisplayOptions *opts;
@@ -65,10 +66,10 @@ void sdl2_gl_switch(DisplayChangeListener *dcl,
void sdl2_gl_refresh(DisplayChangeListener *dcl);
void sdl2_gl_redraw(struct sdl2_console *scon);
-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params);
-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx);
-int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
+int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx);
void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
index ed298d58f0..e271e011da 100644
--- a/include/ui/spice-display.h
+++ b/include/ui/spice-display.h
@@ -86,6 +86,7 @@ typedef struct SimpleSpiceCursor SimpleSpiceCursor;
struct SimpleSpiceDisplay {
DisplaySurface *ds;
+ DisplayGLCtx dgc;
DisplayChangeListener dcl;
void *buf;
int bufsize;
@@ -183,8 +184,4 @@ void qemu_spice_display_start(void);
void qemu_spice_display_stop(void);
int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
-bool qemu_spice_fill_device_address(QemuConsole *con,
- char *device_address,
- size_t size);
-
#endif
diff --git a/linux-user/host/loongarch64/host-signal.h b/linux-user/host/loongarch64/host-signal.h
new file mode 100644
index 0000000000..05e2c82371
--- /dev/null
+++ b/linux-user/host/loongarch64/host-signal.h
@@ -0,0 +1,87 @@
+/*
+ * host-signal.h: signal info dependent on the host architecture
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ * Copyright (c) 2021 WANG Xuerui <git@xen0n.name>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef LOONGARCH64_HOST_SIGNAL_H
+#define LOONGARCH64_HOST_SIGNAL_H
+
+static inline uintptr_t host_signal_pc(ucontext_t *uc)
+{
+ return uc->uc_mcontext.__pc;
+}
+
+static inline void host_signal_set_pc(ucontext_t *uc, uintptr_t pc)
+{
+ uc->uc_mcontext.__pc = pc;
+}
+
+static inline bool host_signal_write(siginfo_t *info, ucontext_t *uc)
+{
+ const uint32_t *pinsn = (const uint32_t *)host_signal_pc(uc);
+ uint32_t insn = pinsn[0];
+
+ /* Detect store by reading the instruction at the program counter. */
+ switch ((insn >> 26) & 0b111111) {
+ case 0b001000: /* {ll,sc}.[wd] */
+ switch ((insn >> 24) & 0b11) {
+ case 0b01: /* sc.w */
+ case 0b11: /* sc.d */
+ return true;
+ }
+ break;
+ case 0b001001: /* {ld,st}ox4.[wd] ({ld,st}ptr.[wd]) */
+ switch ((insn >> 24) & 0b11) {
+ case 0b01: /* stox4.w (stptr.w) */
+ case 0b11: /* stox4.d (stptr.d) */
+ return true;
+ }
+ break;
+ case 0b001010: /* {ld,st}.* family */
+ switch ((insn >> 22) & 0b1111) {
+ case 0b0100: /* st.b */
+ case 0b0101: /* st.h */
+ case 0b0110: /* st.w */
+ case 0b0111: /* st.d */
+ case 0b1101: /* fst.s */
+ case 0b1111: /* fst.d */
+ return true;
+ }
+ break;
+ case 0b001110: /* indexed, atomic, bounds-checking memory operations */
+ uint32_t sel = (insn >> 15) & 0b11111111111;
+
+ switch (sel) {
+ case 0b00000100000: /* stx.b */
+ case 0b00000101000: /* stx.h */
+ case 0b00000110000: /* stx.w */
+ case 0b00000111000: /* stx.d */
+ case 0b00001110000: /* fstx.s */
+ case 0b00001111000: /* fstx.d */
+ case 0b00011101100: /* fstgt.s */
+ case 0b00011101101: /* fstgt.d */
+ case 0b00011101110: /* fstle.s */
+ case 0b00011101111: /* fstle.d */
+ case 0b00011111000: /* stgt.b */
+ case 0b00011111001: /* stgt.h */
+ case 0b00011111010: /* stgt.w */
+ case 0b00011111011: /* stgt.d */
+ case 0b00011111100: /* stle.b */
+ case 0b00011111101: /* stle.h */
+ case 0b00011111110: /* stle.w */
+ case 0b00011111111: /* stle.d */
+ case 0b00011000000 ... 0b00011100011: /* am* insns */
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+#endif
diff --git a/meson.build b/meson.build
index f0f1d5ba9d..886f0a9343 100644
--- a/meson.build
+++ b/meson.build
@@ -59,7 +59,7 @@ python = import('python').find_installation()
supported_oses = ['windows', 'freebsd', 'netbsd', 'openbsd', 'darwin', 'sunos', 'linux']
supported_cpus = ['ppc', 'ppc64', 's390x', 'riscv', 'x86', 'x86_64',
- 'arm', 'aarch64', 'mips', 'mips64', 'sparc', 'sparc64']
+ 'arm', 'aarch64', 'loongarch64', 'mips', 'mips64', 'sparc', 'sparc64']
cpu = host_machine.cpu_family()
@@ -407,14 +407,16 @@ endif
add_project_arguments(config_host['GLIB_CFLAGS'].split(),
native: false, language: ['c', 'cpp', 'objc'])
glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
- link_args: config_host['GLIB_LIBS'].split())
+ link_args: config_host['GLIB_LIBS'].split(),
+ version: config_host['GLIB_VERSION'])
# override glib dep with the configure results (for subprojects)
meson.override_dependency('glib-2.0', glib)
gio = not_found
if 'CONFIG_GIO' in config_host
gio = declare_dependency(compile_args: config_host['GIO_CFLAGS'].split(),
- link_args: config_host['GIO_LIBS'].split())
+ link_args: config_host['GIO_LIBS'].split(),
+ version: config_host['GLIB_VERSION'])
endif
lttng = not_found
if 'ust' in get_option('trace_backends')
@@ -1398,6 +1400,15 @@ endif
have_host_block_device = (targetos != 'darwin' or
cc.has_header('IOKit/storage/IOMedia.h'))
+dbus_display = false
+if not get_option('dbus_display').disabled()
+ # FIXME enable_modules shouldn't be necessary, but: https://github.com/mesonbuild/meson/issues/8333
+ dbus_display = gio.version().version_compare('>=2.64') and config_host.has_key('GDBUS_CODEGEN') and enable_modules
+ if get_option('dbus_display').enabled() and not dbus_display
+ error('Requirements missing to enable -display dbus (glib>=2.64 && --enable-modules)')
+ endif
+endif
+
have_virtfs = (targetos == 'linux' and
have_system and
libattr.found() and
@@ -1500,8 +1511,14 @@ config_host_data.set('CONFIG_ZSTD', zstd.found())
config_host_data.set('CONFIG_FUSE', fuse.found())
config_host_data.set('CONFIG_FUSE_LSEEK', fuse_lseek.found())
config_host_data.set('CONFIG_SPICE_PROTOCOL', spice_protocol.found())
+if spice_protocol.found()
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MAJOR', spice_protocol.version().split('.')[0])
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MINOR', spice_protocol.version().split('.')[1])
+config_host_data.set('CONFIG_SPICE_PROTOCOL_MICRO', spice_protocol.version().split('.')[2])
+endif
config_host_data.set('CONFIG_SPICE', spice.found())
config_host_data.set('CONFIG_X11', x11.found())
+config_host_data.set('CONFIG_DBUS_DISPLAY', dbus_display)
config_host_data.set('CONFIG_CFI', get_option('cfi'))
config_host_data.set('CONFIG_SELINUX', selinux.found())
config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
@@ -3225,6 +3242,7 @@ summary_info += {'Trace backends': ','.join(get_option('trace_backends'))}
if 'simple' in get_option('trace_backends')
summary_info += {'Trace output file': get_option('trace_file') + '-<pid>'}
endif
+summary_info += {'D-Bus display': dbus_display}
summary_info += {'QOM debugging': config_host.has_key('CONFIG_QOM_CAST_DEBUG')}
summary_info += {'vhost-kernel support': config_host.has_key('CONFIG_VHOST_KERNEL')}
summary_info += {'vhost-net support': config_host.has_key('CONFIG_VHOST_NET')}
diff --git a/meson_options.txt b/meson_options.txt
index 4114bfcaa4..921967eddb 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -66,6 +66,8 @@ option('cfi_debug', type: 'boolean', value: 'false',
description: 'Verbose errors in case of CFI violation')
option('multiprocess', type: 'feature', value: 'auto',
description: 'Out of process device emulation support')
+option('dbus_display', type: 'feature', value: 'auto',
+ description: '-display dbus support')
option('attr', type : 'feature', value : 'auto',
description: 'attr/xattr support')
diff --git a/migration/migration.c b/migration/migration.c
index 3de11ae921..0652165610 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -60,6 +60,7 @@
#include "qemu/yank.h"
#include "sysemu/cpus.h"
#include "yank_functions.h"
+#include "sysemu/qtest.h"
#define MAX_THROTTLE (128 << 20) /* Migration transfer speed throttling */
@@ -3766,7 +3767,8 @@ static void qemu_savevm_wait_unplug(MigrationState *s, int old_state,
while (timeout-- && qemu_savevm_state_guest_unplug_pending()) {
qemu_sem_timedwait(&s->wait_unplug_sem, 250);
}
- if (qemu_savevm_state_guest_unplug_pending()) {
+ if (qemu_savevm_state_guest_unplug_pending() &&
+ !qtest_enabled()) {
warn_report("migration: partially unplugged device on "
"failure");
}
diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c
index 343353e27a..14e3beeaaf 100644
--- a/monitor/qmp-cmds.c
+++ b/monitor/qmp-cmds.c
@@ -24,6 +24,7 @@
#include "chardev/char.h"
#include "ui/qemu-spice.h"
#include "ui/console.h"
+#include "ui/dbus-display.h"
#include "sysemu/kvm.h"
#include "sysemu/runstate.h"
#include "sysemu/runstate-action.h"
@@ -286,6 +287,18 @@ void qmp_add_client(const char *protocol, const char *fdname,
vnc_display_add_client(NULL, fd, skipauth);
return;
#endif
+#ifdef CONFIG_DBUS_DISPLAY
+ } else if (strcmp(protocol, "@dbus-display") == 0) {
+ if (!qemu_using_dbus_display(errp)) {
+ close(fd);
+ return;
+ }
+ if (!qemu_dbus_display.add_client(fd, errp)) {
+ close(fd);
+ return;
+ }
+ return;
+#endif
} else if ((s = qemu_chr_find(protocol)) != NULL) {
if (qemu_chr_add_client(s, fd) < 0) {
error_setg(errp, "failed to add client");
diff --git a/nbd/client-connection.c b/nbd/client-connection.c
index 695f855754..2bda42641d 100644
--- a/nbd/client-connection.c
+++ b/nbd/client-connection.c
@@ -39,16 +39,18 @@ struct NBDClientConnection {
QemuMutex mutex;
+ NBDExportInfo updated_info;
/*
- * @sioc and @err represent a connection attempt. While running
- * is true, they are only used by the connection thread, and mutex
- * locking is not needed. Once the thread finishes,
- * nbd_co_establish_connection then steals these pointers while
- * under the mutex.
+ * @sioc represents a successful result. While thread is running, @sioc is
+ * used only by thread and not protected by mutex. When thread is not
+ * running, @sioc is stolen by nbd_co_establish_connection() under mutex.
*/
- NBDExportInfo updated_info;
QIOChannelSocket *sioc;
QIOChannel *ioc;
+ /*
+ * @err represents previous attempt. It may be copied by
+ * nbd_co_establish_connection() when it reports failure.
+ */
Error *err;
/* All further fields are accessed only under mutex */
@@ -170,18 +172,18 @@ static void *connect_thread_func(void *opaque)
qemu_mutex_lock(&conn->mutex);
while (!conn->detached) {
+ Error *local_err = NULL;
+
assert(!conn->sioc);
conn->sioc = qio_channel_socket_new();
qemu_mutex_unlock(&conn->mutex);
- error_free(conn->err);
- conn->err = NULL;
conn->updated_info = conn->initial_info;
ret = nbd_connect(conn->sioc, conn->saddr,
conn->do_negotiation ? &conn->updated_info : NULL,
- conn->tlscreds, &conn->ioc, &conn->err);
+ conn->tlscreds, &conn->ioc, &local_err);
/*
* conn->updated_info will finally be returned to the user. Clear the
@@ -194,6 +196,10 @@ static void *connect_thread_func(void *opaque)
qemu_mutex_lock(&conn->mutex);
+ error_free(conn->err);
+ conn->err = NULL;
+ error_propagate(&conn->err, local_err);
+
if (ret < 0) {
object_unref(OBJECT(conn->sioc));
conn->sioc = NULL;
@@ -311,14 +317,17 @@ nbd_co_establish_connection(NBDClientConnection *conn, NBDExportInfo *info,
}
conn->running = true;
- error_free(conn->err);
- conn->err = NULL;
qemu_thread_create(&thread, "nbd-connect",
connect_thread_func, conn, QEMU_THREAD_DETACHED);
}
if (!blocking) {
- error_setg(errp, "No connection at the moment");
+ if (conn->err) {
+ error_propagate(errp, error_copy(conn->err));
+ } else {
+ error_setg(errp, "No connection at the moment");
+ }
+
return NULL;
}
@@ -339,14 +348,30 @@ nbd_co_establish_connection(NBDClientConnection *conn, NBDExportInfo *info,
* attempt as failed, but leave the connection thread running,
* to reuse it for the next connection attempt.
*/
- error_setg(errp, "Connection attempt cancelled by other operation");
+ if (conn->err) {
+ error_propagate(errp, error_copy(conn->err));
+ } else {
+ /*
+ * The only possible case here is cancelling by open_timer
+ * during nbd_open(). So, the error message is for that case.
+ * If we have more use cases, we can refactor
+ * nbd_co_establish_connection_cancel() to take an additional
+ * parameter cancel_reason, that would be passed than to the
+ * caller of cancelled nbd_co_establish_connection().
+ */
+ error_setg(errp, "Connection attempt cancelled by timeout");
+ }
+
return NULL;
} else {
- error_propagate(errp, conn->err);
- conn->err = NULL;
- if (!conn->sioc) {
+ /* Thread finished. There must be either error or sioc */
+ assert(!conn->err != !conn->sioc);
+
+ if (conn->err) {
+ error_propagate(errp, error_copy(conn->err));
return NULL;
}
+
if (conn->do_negotiation) {
memcpy(info, &conn->updated_info, sizeof(*info));
if (conn->ioc) {
diff --git a/qapi/audio.json b/qapi/audio.json
index 9cba0df8a4..693e327c6b 100644
--- a/qapi/audio.json
+++ b/qapi/audio.json
@@ -386,7 +386,7 @@
# Since: 4.0
##
{ 'enum': 'AudiodevDriver',
- 'data': [ 'none', 'alsa', 'coreaudio', 'dsound', 'jack', 'oss', 'pa',
+ 'data': [ 'none', 'alsa', 'coreaudio', 'dbus', 'dsound', 'jack', 'oss', 'pa',
'sdl', 'spice', 'wav' ] }
##
@@ -412,6 +412,7 @@
'none': 'AudiodevGenericOptions',
'alsa': 'AudiodevAlsaOptions',
'coreaudio': 'AudiodevCoreaudioOptions',
+ 'dbus': 'AudiodevGenericOptions',
'dsound': 'AudiodevDsoundOptions',
'jack': 'AudiodevJackOptions',
'oss': 'AudiodevOssOptions',
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 1d3dd9cb48..bd0b285245 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -4096,6 +4096,12 @@
# future requests before a successful reconnect will
# immediately fail. Default 0 (Since 4.2)
#
+# @open-timeout: In seconds. If zero, the nbd driver tries the connection
+# only once, and fails to open if the connection fails.
+# If non-zero, the nbd driver will repeat connection attempts
+# until successful or until @open-timeout seconds have elapsed.
+# Default 0 (Since 7.0)
+#
# Features:
# @unstable: Member @x-dirty-bitmap is experimental.
#
@@ -4106,7 +4112,8 @@
'*export': 'str',
'*tls-creds': 'str',
'*x-dirty-bitmap': { 'type': 'str', 'features': [ 'unstable' ] },
- '*reconnect-delay': 'uint32' } }
+ '*reconnect-delay': 'uint32',
+ '*open-timeout': 'uint32' } }
##
# @BlockdevOptionsRaw:
diff --git a/qapi/char.json b/qapi/char.json
index f5133a5eeb..7b42151575 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -359,6 +359,20 @@
'if': 'CONFIG_SPICE' }
##
+# @ChardevDBus:
+#
+# Configuration info for DBus chardevs.
+#
+# @name: name of the channel (following docs/spice-port-fqdn.txt)
+#
+# Since: 7.0
+##
+{ 'struct': 'ChardevDBus',
+ 'data': { 'name': 'str' },
+ 'base': 'ChardevCommon',
+ 'if': 'CONFIG_DBUS_DISPLAY' }
+
+##
# @ChardevVC:
#
# Configuration info for virtual console chardevs.
@@ -422,6 +436,7 @@
# @spicevmc: Since 1.5
# @spiceport: Since 1.5
# @qemu-vdagent: Since 6.1
+# @dbus: Since 7.0
# @vc: v1.5
# @ringbuf: Since 1.6
# @memory: Since 1.5
@@ -447,6 +462,7 @@
{ 'name': 'spicevmc', 'if': 'CONFIG_SPICE' },
{ 'name': 'spiceport', 'if': 'CONFIG_SPICE' },
{ 'name': 'qemu-vdagent', 'if': 'CONFIG_SPICE_PROTOCOL' },
+ { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' },
'vc',
'ringbuf',
# next one is just for compatibility
@@ -536,6 +552,15 @@
'if': 'CONFIG_SPICE_PROTOCOL' }
##
+# @ChardevDBusWrapper:
+#
+# Since: 7.0
+##
+{ 'struct': 'ChardevDBusWrapper',
+ 'data': { 'data': 'ChardevDBus' },
+ 'if': 'CONFIG_DBUS_DISPLAY' }
+
+##
# @ChardevVCWrapper:
#
# Since: 1.5
@@ -582,6 +607,8 @@
'if': 'CONFIG_SPICE' },
'qemu-vdagent': { 'type': 'ChardevQemuVDAgentWrapper',
'if': 'CONFIG_SPICE_PROTOCOL' },
+ 'dbus': { 'type': 'ChardevDBusWrapper',
+ 'if': 'CONFIG_DBUS_DISPLAY' },
'vc': 'ChardevVCWrapper',
'ringbuf': 'ChardevRingbufWrapper',
# next one is just for compatibility
diff --git a/qapi/misc.json b/qapi/misc.json
index 358548abe1..e8054f415b 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -14,8 +14,8 @@
# Allow client connections for VNC, Spice and socket based
# character devices to be passed in to QEMU via SCM_RIGHTS.
#
-# @protocol: protocol name. Valid names are "vnc", "spice" or the
-# name of a character device (eg. from -chardev id=XXXX)
+# @protocol: protocol name. Valid names are "vnc", "spice", "@dbus-display" or
+# the name of a character device (eg. from -chardev id=XXXX)
#
# @fdname: file descriptor name previously passed via 'getfd' command
#
diff --git a/qapi/ui.json b/qapi/ui.json
index d7567ac866..2b4371da37 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -1121,6 +1121,30 @@
{ 'struct' : 'DisplayEGLHeadless',
'data' : { '*rendernode' : 'str' } }
+##
+# @DisplayDBus:
+#
+# DBus display options.
+#
+# @addr: The D-Bus bus address (default to the session bus).
+#
+# @rendernode: Which DRM render node should be used. Default is the first
+# available node on the host.
+#
+# @p2p: Whether to use peer-to-peer connections (accepted through
+# ``add_client``).
+#
+# @audiodev: Use the specified DBus audiodev to export audio.
+#
+# Since: 7.0
+#
+##
+{ 'struct' : 'DisplayDBus',
+ 'data' : { '*rendernode' : 'str',
+ '*addr': 'str',
+ '*p2p': 'bool',
+ '*audiodev': 'str' } }
+
##
# @DisplayGLMode:
#
@@ -1186,6 +1210,8 @@
# application to connect to it. The server will redirect
# the serial console and QEMU monitors. (Since 4.0)
#
+# @dbus: Start a D-Bus service for the display. (Since 7.0)
+#
# Since: 2.12
#
##
@@ -1199,7 +1225,10 @@
'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
{ 'name': 'curses', 'if': 'CONFIG_CURSES' },
{ 'name': 'cocoa', 'if': 'CONFIG_COCOA' },
- { 'name': 'spice-app', 'if': 'CONFIG_SPICE'} ] }
+ { 'name': 'spice-app', 'if': 'CONFIG_SPICE' },
+ { 'name': 'dbus', 'if': 'CONFIG_DBUS_DISPLAY' }
+ ]
+}
##
# @DisplayOptions:
@@ -1227,7 +1256,8 @@
'gtk': { 'type': 'DisplayGTK', 'if': 'CONFIG_GTK' },
'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' },
'egl-headless': { 'type': 'DisplayEGLHeadless',
- 'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }
+ 'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } },
+ 'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' }
}
}
diff --git a/qemu-options.hx b/qemu-options.hx
index 489b58e151..7d47510947 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -660,6 +660,9 @@ DEF("audiodev", HAS_ARG, QEMU_OPTION_audiodev,
#ifdef CONFIG_SPICE
"-audiodev spice,id=id[,prop[=value][,...]]\n"
#endif
+#ifdef CONFIG_DBUS_DISPLAY
+ "-audiodev dbus,id=id[,prop[=value][,...]]\n"
+#endif
"-audiodev wav,id=id[,prop[=value][,...]]\n"
" path= path of wav file to record\n",
QEMU_ARCH_ALL)
@@ -1863,6 +1866,10 @@ DEF("display", HAS_ARG, QEMU_OPTION_display,
#if defined(CONFIG_OPENGL)
"-display egl-headless[,rendernode=<file>]\n"
#endif
+#if defined(CONFIG_DBUS_DISPLAY)
+ "-display dbus[,addr=<dbusaddr>]\n"
+ " [,gl=on|core|es|off][,rendernode=<file>]\n"
+#endif
"-display none\n"
" select display backend type\n"
" The default display is equivalent to\n "
@@ -1889,6 +1896,19 @@ SRST
application. The Spice server will redirect the serial consoles
and QEMU monitors. (Since 4.0)
+ ``dbus``
+ Export the display over D-Bus interfaces. (Since 7.0)
+
+ The connection is registered with the "org.qemu" name (and queued when
+ already owned).
+
+ ``addr=<dbusaddr>`` : D-Bus bus address to connect to.
+
+ ``p2p=yes|no`` : Use peer-to-peer connection, accepted via QMP ``add_client``.
+
+ ``gl=on|off|core|es`` : Use OpenGL for rendering (the D-Bus interface
+ will share framebuffers with DMABUF file descriptors).
+
``sdl``
Display video output via SDL (usually in a separate graphics
window; see the SDL documentation for other possibilities).
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index ae8f18edc2..50bd7bed4d 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -33,6 +33,7 @@ meson_options_help() {
printf "%s\n" ' coreaudio CoreAudio sound support'
printf "%s\n" ' curl CURL block device driver'
printf "%s\n" ' curses curses UI'
+ printf "%s\n" ' dbus-display -display dbus support'
printf "%s\n" ' docs Documentations build support'
printf "%s\n" ' dsound DirectSound sound support'
printf "%s\n" ' fuse FUSE block device export'
@@ -131,6 +132,8 @@ _meson_option_parse() {
--disable-curl) printf "%s" -Dcurl=disabled ;;
--enable-curses) printf "%s" -Dcurses=enabled ;;
--disable-curses) printf "%s" -Dcurses=disabled ;;
+ --enable-dbus-display) printf "%s" -Ddbus_display=enabled ;;
+ --disable-dbus-display) printf "%s" -Ddbus_display=disabled ;;
--enable-docs) printf "%s" -Ddocs=enabled ;;
--disable-docs) printf "%s" -Ddocs=disabled ;;
--enable-dsound) printf "%s" -Ddsound=enabled ;;
diff --git a/scripts/modinfo-collect.py b/scripts/modinfo-collect.py
index 4acb188c3e..61b90688c6 100755
--- a/scripts/modinfo-collect.py
+++ b/scripts/modinfo-collect.py
@@ -51,6 +51,9 @@ def main(args):
with open('compile_commands.json') as f:
compile_commands = json.load(f)
for src in args:
+ if not src.endswith('.c'):
+ print("MODINFO_DEBUG skip %s" % src)
+ continue
print("MODINFO_DEBUG src %s" % src)
command = find_command(src, target, compile_commands)
cmdline = process_command(src, command)
diff --git a/tcg/loongarch64/tcg-insn-defs.c.inc b/tcg/loongarch64/tcg-insn-defs.c.inc
new file mode 100644
index 0000000000..d162571856
--- /dev/null
+++ b/tcg/loongarch64/tcg-insn-defs.c.inc
@@ -0,0 +1,979 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * LoongArch instruction formats, opcodes, and encoders for TCG use.
+ *
+ * This file is auto-generated by genqemutcgdefs from
+ * https://github.com/loongson-community/loongarch-opcodes,
+ * from commit 961f0c60f5b63e574d785995600c71ad5413fdc4.
+ * DO NOT EDIT.
+ */
+
+typedef enum {
+ OPC_CLZ_W = 0x00001400,
+ OPC_CTZ_W = 0x00001c00,
+ OPC_CLZ_D = 0x00002400,
+ OPC_CTZ_D = 0x00002c00,
+ OPC_REVB_2H = 0x00003000,
+ OPC_REVB_2W = 0x00003800,
+ OPC_REVB_D = 0x00003c00,
+ OPC_SEXT_H = 0x00005800,
+ OPC_SEXT_B = 0x00005c00,
+ OPC_ADD_W = 0x00100000,
+ OPC_ADD_D = 0x00108000,
+ OPC_SUB_W = 0x00110000,
+ OPC_SUB_D = 0x00118000,
+ OPC_SLT = 0x00120000,
+ OPC_SLTU = 0x00128000,
+ OPC_MASKEQZ = 0x00130000,
+ OPC_MASKNEZ = 0x00138000,
+ OPC_NOR = 0x00140000,
+ OPC_AND = 0x00148000,
+ OPC_OR = 0x00150000,
+ OPC_XOR = 0x00158000,
+ OPC_ORN = 0x00160000,
+ OPC_ANDN = 0x00168000,
+ OPC_SLL_W = 0x00170000,
+ OPC_SRL_W = 0x00178000,
+ OPC_SRA_W = 0x00180000,
+ OPC_SLL_D = 0x00188000,
+ OPC_SRL_D = 0x00190000,
+ OPC_SRA_D = 0x00198000,
+ OPC_ROTR_W = 0x001b0000,
+ OPC_ROTR_D = 0x001b8000,
+ OPC_MUL_W = 0x001c0000,
+ OPC_MULH_W = 0x001c8000,
+ OPC_MULH_WU = 0x001d0000,
+ OPC_MUL_D = 0x001d8000,
+ OPC_MULH_D = 0x001e0000,
+ OPC_MULH_DU = 0x001e8000,
+ OPC_DIV_W = 0x00200000,
+ OPC_MOD_W = 0x00208000,
+ OPC_DIV_WU = 0x00210000,
+ OPC_MOD_WU = 0x00218000,
+ OPC_DIV_D = 0x00220000,
+ OPC_MOD_D = 0x00228000,
+ OPC_DIV_DU = 0x00230000,
+ OPC_MOD_DU = 0x00238000,
+ OPC_SLLI_W = 0x00408000,
+ OPC_SLLI_D = 0x00410000,
+ OPC_SRLI_W = 0x00448000,
+ OPC_SRLI_D = 0x00450000,
+ OPC_SRAI_W = 0x00488000,
+ OPC_SRAI_D = 0x00490000,
+ OPC_ROTRI_W = 0x004c8000,
+ OPC_ROTRI_D = 0x004d0000,
+ OPC_BSTRINS_W = 0x00600000,
+ OPC_BSTRPICK_W = 0x00608000,
+ OPC_BSTRINS_D = 0x00800000,
+ OPC_BSTRPICK_D = 0x00c00000,
+ OPC_SLTI = 0x02000000,
+ OPC_SLTUI = 0x02400000,
+ OPC_ADDI_W = 0x02800000,
+ OPC_ADDI_D = 0x02c00000,
+ OPC_CU52I_D = 0x03000000,
+ OPC_ANDI = 0x03400000,
+ OPC_ORI = 0x03800000,
+ OPC_XORI = 0x03c00000,
+ OPC_LU12I_W = 0x14000000,
+ OPC_CU32I_D = 0x16000000,
+ OPC_PCADDU2I = 0x18000000,
+ OPC_PCALAU12I = 0x1a000000,
+ OPC_PCADDU12I = 0x1c000000,
+ OPC_PCADDU18I = 0x1e000000,
+ OPC_LD_B = 0x28000000,
+ OPC_LD_H = 0x28400000,
+ OPC_LD_W = 0x28800000,
+ OPC_LD_D = 0x28c00000,
+ OPC_ST_B = 0x29000000,
+ OPC_ST_H = 0x29400000,
+ OPC_ST_W = 0x29800000,
+ OPC_ST_D = 0x29c00000,
+ OPC_LD_BU = 0x2a000000,
+ OPC_LD_HU = 0x2a400000,
+ OPC_LD_WU = 0x2a800000,
+ OPC_LDX_B = 0x38000000,
+ OPC_LDX_H = 0x38040000,
+ OPC_LDX_W = 0x38080000,
+ OPC_LDX_D = 0x380c0000,
+ OPC_STX_B = 0x38100000,
+ OPC_STX_H = 0x38140000,
+ OPC_STX_W = 0x38180000,
+ OPC_STX_D = 0x381c0000,
+ OPC_LDX_BU = 0x38200000,
+ OPC_LDX_HU = 0x38240000,
+ OPC_LDX_WU = 0x38280000,
+ OPC_DBAR = 0x38720000,
+ OPC_JIRL = 0x4c000000,
+ OPC_B = 0x50000000,
+ OPC_BL = 0x54000000,
+ OPC_BEQ = 0x58000000,
+ OPC_BNE = 0x5c000000,
+ OPC_BGT = 0x60000000,
+ OPC_BLE = 0x64000000,
+ OPC_BGTU = 0x68000000,
+ OPC_BLEU = 0x6c000000,
+} LoongArchInsn;
+
+static int32_t __attribute__((unused))
+encode_d_slot(LoongArchInsn opc, uint32_t d)
+{
+ return opc | d;
+}
+
+static int32_t __attribute__((unused))
+encode_dj_slots(LoongArchInsn opc, uint32_t d, uint32_t j)
+{
+ return opc | d | j << 5;
+}
+
+static int32_t __attribute__((unused))
+encode_djk_slots(LoongArchInsn opc, uint32_t d, uint32_t j, uint32_t k)
+{
+ return opc | d | j << 5 | k << 10;
+}
+
+static int32_t __attribute__((unused))
+encode_djkm_slots(LoongArchInsn opc, uint32_t d, uint32_t j, uint32_t k,
+ uint32_t m)
+{
+ return opc | d | j << 5 | k << 10 | m << 16;
+}
+
+static int32_t __attribute__((unused))
+encode_dk_slots(LoongArchInsn opc, uint32_t d, uint32_t k)
+{
+ return opc | d | k << 10;
+}
+
+static int32_t __attribute__((unused))
+encode_dj_insn(LoongArchInsn opc, TCGReg d, TCGReg j)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ return encode_dj_slots(opc, d, j);
+}
+
+static int32_t __attribute__((unused))
+encode_djk_insn(LoongArchInsn opc, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(k >= 0 && k <= 0x1f);
+ return encode_djk_slots(opc, d, j, k);
+}
+
+static int32_t __attribute__((unused))
+encode_djsk12_insn(LoongArchInsn opc, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(sk12 >= -0x800 && sk12 <= 0x7ff);
+ return encode_djk_slots(opc, d, j, sk12 & 0xfff);
+}
+
+static int32_t __attribute__((unused))
+encode_djsk16_insn(LoongArchInsn opc, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(sk16 >= -0x8000 && sk16 <= 0x7fff);
+ return encode_djk_slots(opc, d, j, sk16 & 0xffff);
+}
+
+static int32_t __attribute__((unused))
+encode_djuk12_insn(LoongArchInsn opc, TCGReg d, TCGReg j, uint32_t uk12)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(uk12 <= 0xfff);
+ return encode_djk_slots(opc, d, j, uk12);
+}
+
+static int32_t __attribute__((unused))
+encode_djuk5_insn(LoongArchInsn opc, TCGReg d, TCGReg j, uint32_t uk5)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(uk5 <= 0x1f);
+ return encode_djk_slots(opc, d, j, uk5);
+}
+
+static int32_t __attribute__((unused))
+encode_djuk5um5_insn(LoongArchInsn opc, TCGReg d, TCGReg j, uint32_t uk5,
+ uint32_t um5)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(uk5 <= 0x1f);
+ tcg_debug_assert(um5 <= 0x1f);
+ return encode_djkm_slots(opc, d, j, uk5, um5);
+}
+
+static int32_t __attribute__((unused))
+encode_djuk6_insn(LoongArchInsn opc, TCGReg d, TCGReg j, uint32_t uk6)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(uk6 <= 0x3f);
+ return encode_djk_slots(opc, d, j, uk6);
+}
+
+static int32_t __attribute__((unused))
+encode_djuk6um6_insn(LoongArchInsn opc, TCGReg d, TCGReg j, uint32_t uk6,
+ uint32_t um6)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(j >= 0 && j <= 0x1f);
+ tcg_debug_assert(uk6 <= 0x3f);
+ tcg_debug_assert(um6 <= 0x3f);
+ return encode_djkm_slots(opc, d, j, uk6, um6);
+}
+
+static int32_t __attribute__((unused))
+encode_dsj20_insn(LoongArchInsn opc, TCGReg d, int32_t sj20)
+{
+ tcg_debug_assert(d >= 0 && d <= 0x1f);
+ tcg_debug_assert(sj20 >= -0x80000 && sj20 <= 0x7ffff);
+ return encode_dj_slots(opc, d, sj20 & 0xfffff);
+}
+
+static int32_t __attribute__((unused))
+encode_sd10k16_insn(LoongArchInsn opc, int32_t sd10k16)
+{
+ tcg_debug_assert(sd10k16 >= -0x2000000 && sd10k16 <= 0x1ffffff);
+ return encode_dk_slots(opc, (sd10k16 >> 16) & 0x3ff, sd10k16 & 0xffff);
+}
+
+static int32_t __attribute__((unused))
+encode_ud15_insn(LoongArchInsn opc, uint32_t ud15)
+{
+ tcg_debug_assert(ud15 <= 0x7fff);
+ return encode_d_slot(opc, ud15);
+}
+
+/* Emits the `clz.w d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_clz_w(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_CLZ_W, d, j));
+}
+
+/* Emits the `ctz.w d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ctz_w(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_CTZ_W, d, j));
+}
+
+/* Emits the `clz.d d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_clz_d(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_CLZ_D, d, j));
+}
+
+/* Emits the `ctz.d d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ctz_d(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_CTZ_D, d, j));
+}
+
+/* Emits the `revb.2h d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_revb_2h(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_REVB_2H, d, j));
+}
+
+/* Emits the `revb.2w d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_revb_2w(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_REVB_2W, d, j));
+}
+
+/* Emits the `revb.d d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_revb_d(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_REVB_D, d, j));
+}
+
+/* Emits the `sext.h d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sext_h(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_SEXT_H, d, j));
+}
+
+/* Emits the `sext.b d, j` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sext_b(TCGContext *s, TCGReg d, TCGReg j)
+{
+ tcg_out32(s, encode_dj_insn(OPC_SEXT_B, d, j));
+}
+
+/* Emits the `add.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_add_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_ADD_W, d, j, k));
+}
+
+/* Emits the `add.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_add_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_ADD_D, d, j, k));
+}
+
+/* Emits the `sub.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sub_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SUB_W, d, j, k));
+}
+
+/* Emits the `sub.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sub_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SUB_D, d, j, k));
+}
+
+/* Emits the `slt d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_slt(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SLT, d, j, k));
+}
+
+/* Emits the `sltu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sltu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SLTU, d, j, k));
+}
+
+/* Emits the `maskeqz d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_maskeqz(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MASKEQZ, d, j, k));
+}
+
+/* Emits the `masknez d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_masknez(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MASKNEZ, d, j, k));
+}
+
+/* Emits the `nor d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_nor(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_NOR, d, j, k));
+}
+
+/* Emits the `and d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_and(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_AND, d, j, k));
+}
+
+/* Emits the `or d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_or(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_OR, d, j, k));
+}
+
+/* Emits the `xor d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_xor(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_XOR, d, j, k));
+}
+
+/* Emits the `orn d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_orn(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_ORN, d, j, k));
+}
+
+/* Emits the `andn d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_andn(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_ANDN, d, j, k));
+}
+
+/* Emits the `sll.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sll_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SLL_W, d, j, k));
+}
+
+/* Emits the `srl.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_srl_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SRL_W, d, j, k));
+}
+
+/* Emits the `sra.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sra_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SRA_W, d, j, k));
+}
+
+/* Emits the `sll.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sll_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SLL_D, d, j, k));
+}
+
+/* Emits the `srl.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_srl_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SRL_D, d, j, k));
+}
+
+/* Emits the `sra.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sra_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_SRA_D, d, j, k));
+}
+
+/* Emits the `rotr.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_rotr_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_ROTR_W, d, j, k));
+}
+
+/* Emits the `rotr.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_rotr_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_ROTR_D, d, j, k));
+}
+
+/* Emits the `mul.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mul_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MUL_W, d, j, k));
+}
+
+/* Emits the `mulh.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mulh_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MULH_W, d, j, k));
+}
+
+/* Emits the `mulh.wu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mulh_wu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MULH_WU, d, j, k));
+}
+
+/* Emits the `mul.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mul_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MUL_D, d, j, k));
+}
+
+/* Emits the `mulh.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mulh_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MULH_D, d, j, k));
+}
+
+/* Emits the `mulh.du d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mulh_du(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MULH_DU, d, j, k));
+}
+
+/* Emits the `div.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_div_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_DIV_W, d, j, k));
+}
+
+/* Emits the `mod.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mod_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MOD_W, d, j, k));
+}
+
+/* Emits the `div.wu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_div_wu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_DIV_WU, d, j, k));
+}
+
+/* Emits the `mod.wu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mod_wu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MOD_WU, d, j, k));
+}
+
+/* Emits the `div.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_div_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_DIV_D, d, j, k));
+}
+
+/* Emits the `mod.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mod_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MOD_D, d, j, k));
+}
+
+/* Emits the `div.du d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_div_du(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_DIV_DU, d, j, k));
+}
+
+/* Emits the `mod.du d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_mod_du(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_MOD_DU, d, j, k));
+}
+
+/* Emits the `slli.w d, j, uk5` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_slli_w(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk5)
+{
+ tcg_out32(s, encode_djuk5_insn(OPC_SLLI_W, d, j, uk5));
+}
+
+/* Emits the `slli.d d, j, uk6` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_slli_d(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk6)
+{
+ tcg_out32(s, encode_djuk6_insn(OPC_SLLI_D, d, j, uk6));
+}
+
+/* Emits the `srli.w d, j, uk5` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_srli_w(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk5)
+{
+ tcg_out32(s, encode_djuk5_insn(OPC_SRLI_W, d, j, uk5));
+}
+
+/* Emits the `srli.d d, j, uk6` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_srli_d(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk6)
+{
+ tcg_out32(s, encode_djuk6_insn(OPC_SRLI_D, d, j, uk6));
+}
+
+/* Emits the `srai.w d, j, uk5` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_srai_w(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk5)
+{
+ tcg_out32(s, encode_djuk5_insn(OPC_SRAI_W, d, j, uk5));
+}
+
+/* Emits the `srai.d d, j, uk6` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_srai_d(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk6)
+{
+ tcg_out32(s, encode_djuk6_insn(OPC_SRAI_D, d, j, uk6));
+}
+
+/* Emits the `rotri.w d, j, uk5` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_rotri_w(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk5)
+{
+ tcg_out32(s, encode_djuk5_insn(OPC_ROTRI_W, d, j, uk5));
+}
+
+/* Emits the `rotri.d d, j, uk6` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_rotri_d(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk6)
+{
+ tcg_out32(s, encode_djuk6_insn(OPC_ROTRI_D, d, j, uk6));
+}
+
+/* Emits the `bstrins.w d, j, uk5, um5` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bstrins_w(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk5,
+ uint32_t um5)
+{
+ tcg_out32(s, encode_djuk5um5_insn(OPC_BSTRINS_W, d, j, uk5, um5));
+}
+
+/* Emits the `bstrpick.w d, j, uk5, um5` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bstrpick_w(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk5,
+ uint32_t um5)
+{
+ tcg_out32(s, encode_djuk5um5_insn(OPC_BSTRPICK_W, d, j, uk5, um5));
+}
+
+/* Emits the `bstrins.d d, j, uk6, um6` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bstrins_d(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk6,
+ uint32_t um6)
+{
+ tcg_out32(s, encode_djuk6um6_insn(OPC_BSTRINS_D, d, j, uk6, um6));
+}
+
+/* Emits the `bstrpick.d d, j, uk6, um6` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bstrpick_d(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk6,
+ uint32_t um6)
+{
+ tcg_out32(s, encode_djuk6um6_insn(OPC_BSTRPICK_D, d, j, uk6, um6));
+}
+
+/* Emits the `slti d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_slti(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_SLTI, d, j, sk12));
+}
+
+/* Emits the `sltui d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_sltui(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_SLTUI, d, j, sk12));
+}
+
+/* Emits the `addi.w d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_addi_w(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_ADDI_W, d, j, sk12));
+}
+
+/* Emits the `addi.d d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_addi_d(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_ADDI_D, d, j, sk12));
+}
+
+/* Emits the `cu52i.d d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_cu52i_d(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_CU52I_D, d, j, sk12));
+}
+
+/* Emits the `andi d, j, uk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_andi(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk12)
+{
+ tcg_out32(s, encode_djuk12_insn(OPC_ANDI, d, j, uk12));
+}
+
+/* Emits the `ori d, j, uk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ori(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk12)
+{
+ tcg_out32(s, encode_djuk12_insn(OPC_ORI, d, j, uk12));
+}
+
+/* Emits the `xori d, j, uk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_xori(TCGContext *s, TCGReg d, TCGReg j, uint32_t uk12)
+{
+ tcg_out32(s, encode_djuk12_insn(OPC_XORI, d, j, uk12));
+}
+
+/* Emits the `lu12i.w d, sj20` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_lu12i_w(TCGContext *s, TCGReg d, int32_t sj20)
+{
+ tcg_out32(s, encode_dsj20_insn(OPC_LU12I_W, d, sj20));
+}
+
+/* Emits the `cu32i.d d, sj20` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_cu32i_d(TCGContext *s, TCGReg d, int32_t sj20)
+{
+ tcg_out32(s, encode_dsj20_insn(OPC_CU32I_D, d, sj20));
+}
+
+/* Emits the `pcaddu2i d, sj20` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_pcaddu2i(TCGContext *s, TCGReg d, int32_t sj20)
+{
+ tcg_out32(s, encode_dsj20_insn(OPC_PCADDU2I, d, sj20));
+}
+
+/* Emits the `pcalau12i d, sj20` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_pcalau12i(TCGContext *s, TCGReg d, int32_t sj20)
+{
+ tcg_out32(s, encode_dsj20_insn(OPC_PCALAU12I, d, sj20));
+}
+
+/* Emits the `pcaddu12i d, sj20` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_pcaddu12i(TCGContext *s, TCGReg d, int32_t sj20)
+{
+ tcg_out32(s, encode_dsj20_insn(OPC_PCADDU12I, d, sj20));
+}
+
+/* Emits the `pcaddu18i d, sj20` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_pcaddu18i(TCGContext *s, TCGReg d, int32_t sj20)
+{
+ tcg_out32(s, encode_dsj20_insn(OPC_PCADDU18I, d, sj20));
+}
+
+/* Emits the `ld.b d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_b(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_B, d, j, sk12));
+}
+
+/* Emits the `ld.h d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_h(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_H, d, j, sk12));
+}
+
+/* Emits the `ld.w d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_w(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_W, d, j, sk12));
+}
+
+/* Emits the `ld.d d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_d(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_D, d, j, sk12));
+}
+
+/* Emits the `st.b d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_st_b(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_ST_B, d, j, sk12));
+}
+
+/* Emits the `st.h d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_st_h(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_ST_H, d, j, sk12));
+}
+
+/* Emits the `st.w d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_st_w(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_ST_W, d, j, sk12));
+}
+
+/* Emits the `st.d d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_st_d(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_ST_D, d, j, sk12));
+}
+
+/* Emits the `ld.bu d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_bu(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_BU, d, j, sk12));
+}
+
+/* Emits the `ld.hu d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_hu(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_HU, d, j, sk12));
+}
+
+/* Emits the `ld.wu d, j, sk12` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ld_wu(TCGContext *s, TCGReg d, TCGReg j, int32_t sk12)
+{
+ tcg_out32(s, encode_djsk12_insn(OPC_LD_WU, d, j, sk12));
+}
+
+/* Emits the `ldx.b d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_b(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_B, d, j, k));
+}
+
+/* Emits the `ldx.h d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_h(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_H, d, j, k));
+}
+
+/* Emits the `ldx.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_W, d, j, k));
+}
+
+/* Emits the `ldx.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_D, d, j, k));
+}
+
+/* Emits the `stx.b d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_stx_b(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_STX_B, d, j, k));
+}
+
+/* Emits the `stx.h d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_stx_h(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_STX_H, d, j, k));
+}
+
+/* Emits the `stx.w d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_stx_w(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_STX_W, d, j, k));
+}
+
+/* Emits the `stx.d d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_stx_d(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_STX_D, d, j, k));
+}
+
+/* Emits the `ldx.bu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_bu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_BU, d, j, k));
+}
+
+/* Emits the `ldx.hu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_hu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_HU, d, j, k));
+}
+
+/* Emits the `ldx.wu d, j, k` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ldx_wu(TCGContext *s, TCGReg d, TCGReg j, TCGReg k)
+{
+ tcg_out32(s, encode_djk_insn(OPC_LDX_WU, d, j, k));
+}
+
+/* Emits the `dbar ud15` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_dbar(TCGContext *s, uint32_t ud15)
+{
+ tcg_out32(s, encode_ud15_insn(OPC_DBAR, ud15));
+}
+
+/* Emits the `jirl d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_jirl(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_JIRL, d, j, sk16));
+}
+
+/* Emits the `b sd10k16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_b(TCGContext *s, int32_t sd10k16)
+{
+ tcg_out32(s, encode_sd10k16_insn(OPC_B, sd10k16));
+}
+
+/* Emits the `bl sd10k16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bl(TCGContext *s, int32_t sd10k16)
+{
+ tcg_out32(s, encode_sd10k16_insn(OPC_BL, sd10k16));
+}
+
+/* Emits the `beq d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_beq(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_BEQ, d, j, sk16));
+}
+
+/* Emits the `bne d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bne(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_BNE, d, j, sk16));
+}
+
+/* Emits the `bgt d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bgt(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_BGT, d, j, sk16));
+}
+
+/* Emits the `ble d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_ble(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_BLE, d, j, sk16));
+}
+
+/* Emits the `bgtu d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bgtu(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_BGTU, d, j, sk16));
+}
+
+/* Emits the `bleu d, j, sk16` instruction. */
+static void __attribute__((unused))
+tcg_out_opc_bleu(TCGContext *s, TCGReg d, TCGReg j, int32_t sk16)
+{
+ tcg_out32(s, encode_djsk16_insn(OPC_BLEU, d, j, sk16));
+}
+
+/* End of generated code. */
diff --git a/tcg/loongarch64/tcg-target-con-set.h b/tcg/loongarch64/tcg-target-con-set.h
new file mode 100644
index 0000000000..349c672687
--- /dev/null
+++ b/tcg/loongarch64/tcg-target-con-set.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Define LoongArch target-specific constraint sets.
+ *
+ * Copyright (c) 2021 WANG Xuerui <git@xen0n.name>
+ *
+ * Based on tcg/riscv/tcg-target-con-set.h
+ *
+ * Copyright (c) 2021 Linaro
+ */
+
+/*
+ * C_On_Im(...) defines a constraint set with <n> outputs and <m> inputs.
+ * Each operand should be a sequence of constraint letters as defined by
+ * tcg-target-con-str.h; the constraint combination is inclusive or.
+ */
+C_O0_I1(r)
+C_O0_I2(rZ, r)
+C_O0_I2(rZ, rZ)
+C_O0_I2(LZ, L)
+C_O1_I1(r, r)
+C_O1_I1(r, L)
+C_O1_I2(r, r, rC)
+C_O1_I2(r, r, ri)
+C_O1_I2(r, r, rI)
+C_O1_I2(r, r, rU)
+C_O1_I2(r, r, rW)
+C_O1_I2(r, r, rZ)
+C_O1_I2(r, 0, rZ)
+C_O1_I2(r, rZ, rN)
+C_O1_I2(r, rZ, rZ)
diff --git a/tcg/loongarch64/tcg-target-con-str.h b/tcg/loongarch64/tcg-target-con-str.h
new file mode 100644
index 0000000000..c3986a4fd4
--- /dev/null
+++ b/tcg/loongarch64/tcg-target-con-str.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Define LoongArch target-specific operand constraints.
+ *
+ * Copyright (c) 2021 WANG Xuerui <git@xen0n.name>
+ *
+ * Based on tcg/riscv/tcg-target-con-str.h
+ *
+ * Copyright (c) 2021 Linaro
+ */
+
+/*
+ * Define constraint letters for register sets:
+ * REGS(letter, register_mask)
+ */
+REGS('r', ALL_GENERAL_REGS)
+REGS('L', ALL_GENERAL_REGS & ~SOFTMMU_RESERVE_REGS)
+
+/*
+ * Define constraint letters for constants:
+ * CONST(letter, TCG_CT_CONST_* bit set)
+ */
+CONST('I', TCG_CT_CONST_S12)
+CONST('N', TCG_CT_CONST_N12)
+CONST('U', TCG_CT_CONST_U12)
+CONST('Z', TCG_CT_CONST_ZERO)
+CONST('C', TCG_CT_CONST_C12)
+CONST('W', TCG_CT_CONST_WSZ)
diff --git a/tcg/loongarch64/tcg-target.c.inc b/tcg/loongarch64/tcg-target.c.inc
new file mode 100644
index 0000000000..9cd46c9be3
--- /dev/null
+++ b/tcg/loongarch64/tcg-target.c.inc
@@ -0,0 +1,1677 @@
+/*
+ * Tiny Code Generator for QEMU
+ *
+ * Copyright (c) 2021 WANG Xuerui <git@xen0n.name>
+ *
+ * Based on tcg/riscv/tcg-target.c.inc
+ *
+ * Copyright (c) 2018 SiFive, Inc
+ * Copyright (c) 2008-2009 Arnaud Patard <arnaud.patard@rtp-net.org>
+ * Copyright (c) 2009 Aurelien Jarno <aurelien@aurel32.net>
+ * Copyright (c) 2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifdef CONFIG_DEBUG_TCG
+static const char * const tcg_target_reg_names[TCG_TARGET_NB_REGS] = {
+ "zero",
+ "ra",
+ "tp",
+ "sp",
+ "a0",
+ "a1",
+ "a2",
+ "a3",
+ "a4",
+ "a5",
+ "a6",
+ "a7",
+ "t0",
+ "t1",
+ "t2",
+ "t3",
+ "t4",
+ "t5",
+ "t6",
+ "t7",
+ "t8",
+ "r21", /* reserved in the LP64* ABI, hence no ABI name */
+ "s9",
+ "s0",
+ "s1",
+ "s2",
+ "s3",
+ "s4",
+ "s5",
+ "s6",
+ "s7",
+ "s8"
+};
+#endif
+
+static const int tcg_target_reg_alloc_order[] = {
+ /* Registers preserved across calls */
+ /* TCG_REG_S0 reserved for TCG_AREG0 */
+ TCG_REG_S1,
+ TCG_REG_S2,
+ TCG_REG_S3,
+ TCG_REG_S4,
+ TCG_REG_S5,
+ TCG_REG_S6,
+ TCG_REG_S7,
+ TCG_REG_S8,
+ TCG_REG_S9,
+
+ /* Registers (potentially) clobbered across calls */
+ TCG_REG_T0,
+ TCG_REG_T1,
+ TCG_REG_T2,
+ TCG_REG_T3,
+ TCG_REG_T4,
+ TCG_REG_T5,
+ TCG_REG_T6,
+ TCG_REG_T7,
+ TCG_REG_T8,
+
+ /* Argument registers, opposite order of allocation. */
+ TCG_REG_A7,
+ TCG_REG_A6,
+ TCG_REG_A5,
+ TCG_REG_A4,
+ TCG_REG_A3,
+ TCG_REG_A2,
+ TCG_REG_A1,
+ TCG_REG_A0,
+};
+
+static const int tcg_target_call_iarg_regs[] = {
+ TCG_REG_A0,
+ TCG_REG_A1,
+ TCG_REG_A2,
+ TCG_REG_A3,
+ TCG_REG_A4,
+ TCG_REG_A5,
+ TCG_REG_A6,
+ TCG_REG_A7,
+};
+
+static const int tcg_target_call_oarg_regs[] = {
+ TCG_REG_A0,
+ TCG_REG_A1,
+};
+
+#ifndef CONFIG_SOFTMMU
+#define USE_GUEST_BASE (guest_base != 0)
+#define TCG_GUEST_BASE_REG TCG_REG_S1
+#endif
+
+#define TCG_CT_CONST_ZERO 0x100
+#define TCG_CT_CONST_S12 0x200
+#define TCG_CT_CONST_N12 0x400
+#define TCG_CT_CONST_U12 0x800
+#define TCG_CT_CONST_C12 0x1000
+#define TCG_CT_CONST_WSZ 0x2000
+
+#define ALL_GENERAL_REGS MAKE_64BIT_MASK(0, 32)
+/*
+ * For softmmu, we need to avoid conflicts with the first 5
+ * argument registers to call the helper. Some of these are
+ * also used for the tlb lookup.
+ */
+#ifdef CONFIG_SOFTMMU
+#define SOFTMMU_RESERVE_REGS MAKE_64BIT_MASK(TCG_REG_A0, 5)
+#else
+#define SOFTMMU_RESERVE_REGS 0
+#endif
+
+
+static inline tcg_target_long sextreg(tcg_target_long val, int pos, int len)
+{
+ return sextract64(val, pos, len);
+}
+
+/* test if a constant matches the constraint */
+static bool tcg_target_const_match(int64_t val, TCGType type, int ct)
+{
+ if (ct & TCG_CT_CONST) {
+ return true;
+ }
+ if ((ct & TCG_CT_CONST_ZERO) && val == 0) {
+ return true;
+ }
+ if ((ct & TCG_CT_CONST_S12) && val == sextreg(val, 0, 12)) {
+ return true;
+ }
+ if ((ct & TCG_CT_CONST_N12) && -val == sextreg(-val, 0, 12)) {
+ return true;
+ }
+ if ((ct & TCG_CT_CONST_U12) && val >= 0 && val <= 0xfff) {
+ return true;
+ }
+ if ((ct & TCG_CT_CONST_C12) && ~val >= 0 && ~val <= 0xfff) {
+ return true;
+ }
+ if ((ct & TCG_CT_CONST_WSZ) && val == (type == TCG_TYPE_I32 ? 32 : 64)) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Relocations
+ */
+
+/*
+ * Relocation records defined in LoongArch ELF psABI v1.00 is way too
+ * complicated; a whopping stack machine is needed to stuff the fields, at
+ * the very least one SOP_PUSH and one SOP_POP (of the correct format) are
+ * needed.
+ *
+ * Hence, define our own simpler relocation types. Numbers are chosen as to
+ * not collide with potential future additions to the true ELF relocation
+ * type enum.
+ */
+
+/* Field Sk16, shifted right by 2; suitable for conditional jumps */
+#define R_LOONGARCH_BR_SK16 256
+/* Field Sd10k16, shifted right by 2; suitable for B and BL */
+#define R_LOONGARCH_BR_SD10K16 257
+
+static bool reloc_br_sk16(tcg_insn_unit *src_rw, const tcg_insn_unit *target)
+{
+ const tcg_insn_unit *src_rx = tcg_splitwx_to_rx(src_rw);
+ intptr_t offset = (intptr_t)target - (intptr_t)src_rx;
+
+ tcg_debug_assert((offset & 3) == 0);
+ offset >>= 2;
+ if (offset == sextreg(offset, 0, 16)) {
+ *src_rw = deposit64(*src_rw, 10, 16, offset);
+ return true;
+ }
+
+ return false;
+}
+
+static bool reloc_br_sd10k16(tcg_insn_unit *src_rw,
+ const tcg_insn_unit *target)
+{
+ const tcg_insn_unit *src_rx = tcg_splitwx_to_rx(src_rw);
+ intptr_t offset = (intptr_t)target - (intptr_t)src_rx;
+
+ tcg_debug_assert((offset & 3) == 0);
+ offset >>= 2;
+ if (offset == sextreg(offset, 0, 26)) {
+ *src_rw = deposit64(*src_rw, 0, 10, offset >> 16); /* slot d10 */
+ *src_rw = deposit64(*src_rw, 10, 16, offset); /* slot k16 */
+ return true;
+ }
+
+ return false;
+}
+
+static bool patch_reloc(tcg_insn_unit *code_ptr, int type,
+ intptr_t value, intptr_t addend)
+{
+ tcg_debug_assert(addend == 0);
+ switch (type) {
+ case R_LOONGARCH_BR_SK16:
+ return reloc_br_sk16(code_ptr, (tcg_insn_unit *)value);
+ case R_LOONGARCH_BR_SD10K16:
+ return reloc_br_sd10k16(code_ptr, (tcg_insn_unit *)value);
+ default:
+ g_assert_not_reached();
+ }
+}
+
+#include "tcg-insn-defs.c.inc"
+
+/*
+ * TCG intrinsics
+ */
+
+static void tcg_out_mb(TCGContext *s, TCGArg a0)
+{
+ /* Baseline LoongArch only has the full barrier, unfortunately. */
+ tcg_out_opc_dbar(s, 0);
+}
+
+static bool tcg_out_mov(TCGContext *s, TCGType type, TCGReg ret, TCGReg arg)
+{
+ if (ret == arg) {
+ return true;
+ }
+ switch (type) {
+ case TCG_TYPE_I32:
+ case TCG_TYPE_I64:
+ /*
+ * Conventional register-register move used in LoongArch is
+ * `or dst, src, zero`.
+ */
+ tcg_out_opc_or(s, ret, arg, TCG_REG_ZERO);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ return true;
+}
+
+static bool imm_part_needs_loading(bool high_bits_are_ones,
+ tcg_target_long part)
+{
+ if (high_bits_are_ones) {
+ return part != -1;
+ } else {
+ return part != 0;
+ }
+}
+
+/* Loads a 32-bit immediate into rd, sign-extended. */
+static void tcg_out_movi_i32(TCGContext *s, TCGReg rd, int32_t val)
+{
+ tcg_target_long lo = sextreg(val, 0, 12);
+ tcg_target_long hi12 = sextreg(val, 12, 20);
+
+ /* Single-instruction cases. */
+ if (lo == val) {
+ /* val fits in simm12: addi.w rd, zero, val */
+ tcg_out_opc_addi_w(s, rd, TCG_REG_ZERO, val);
+ return;
+ }
+ if (0x800 <= val && val <= 0xfff) {
+ /* val fits in uimm12: ori rd, zero, val */
+ tcg_out_opc_ori(s, rd, TCG_REG_ZERO, val);
+ return;
+ }
+
+ /* High bits must be set; load with lu12i.w + optional ori. */
+ tcg_out_opc_lu12i_w(s, rd, hi12);
+ if (lo != 0) {
+ tcg_out_opc_ori(s, rd, rd, lo & 0xfff);
+ }
+}
+
+static void tcg_out_movi(TCGContext *s, TCGType type, TCGReg rd,
+ tcg_target_long val)
+{
+ /*
+ * LoongArch conventionally loads 64-bit immediates in at most 4 steps,
+ * with dedicated instructions for filling the respective bitfields
+ * below:
+ *
+ * 6 5 4 3
+ * 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2
+ * +-----------------------+---------------------------------------+...
+ * | hi52 | hi32 |
+ * +-----------------------+---------------------------------------+...
+ * 3 2 1
+ * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * ...+-------------------------------------+-------------------------+
+ * | hi12 | lo |
+ * ...+-------------------------------------+-------------------------+
+ *
+ * Check if val belong to one of the several fast cases, before falling
+ * back to the slow path.
+ */
+
+ intptr_t pc_offset;
+ tcg_target_long val_lo, val_hi, pc_hi, offset_hi;
+ tcg_target_long hi32, hi52;
+ bool rd_high_bits_are_ones;
+
+ /* Value fits in signed i32. */
+ if (type == TCG_TYPE_I32 || val == (int32_t)val) {
+ tcg_out_movi_i32(s, rd, val);
+ return;
+ }
+
+ /* PC-relative cases. */
+ pc_offset = tcg_pcrel_diff(s, (void *)val);
+ if (pc_offset == sextreg(pc_offset, 0, 22) && (pc_offset & 3) == 0) {
+ /* Single pcaddu2i. */
+ tcg_out_opc_pcaddu2i(s, rd, pc_offset >> 2);
+ return;
+ }
+
+ if (pc_offset == (int32_t)pc_offset) {
+ /* Offset within 32 bits; load with pcalau12i + ori. */
+ val_lo = sextreg(val, 0, 12);
+ val_hi = val >> 12;
+ pc_hi = (val - pc_offset) >> 12;
+ offset_hi = val_hi - pc_hi;
+
+ tcg_debug_assert(offset_hi == sextreg(offset_hi, 0, 20));
+ tcg_out_opc_pcalau12i(s, rd, offset_hi);
+ if (val_lo != 0) {
+ tcg_out_opc_ori(s, rd, rd, val_lo & 0xfff);
+ }
+ return;
+ }
+
+ hi32 = sextreg(val, 32, 20);
+ hi52 = sextreg(val, 52, 12);
+
+ /* Single cu52i.d case. */
+ if (ctz64(val) >= 52) {
+ tcg_out_opc_cu52i_d(s, rd, TCG_REG_ZERO, hi52);
+ return;
+ }
+
+ /* Slow path. Initialize the low 32 bits, then concat high bits. */
+ tcg_out_movi_i32(s, rd, val);
+ rd_high_bits_are_ones = (int32_t)val < 0;
+
+ if (imm_part_needs_loading(rd_high_bits_are_ones, hi32)) {
+ tcg_out_opc_cu32i_d(s, rd, hi32);
+ rd_high_bits_are_ones = hi32 < 0;
+ }
+
+ if (imm_part_needs_loading(rd_high_bits_are_ones, hi52)) {
+ tcg_out_opc_cu52i_d(s, rd, rd, hi52);
+ }
+}
+
+static void tcg_out_ext8u(TCGContext *s, TCGReg ret, TCGReg arg)
+{
+ tcg_out_opc_andi(s, ret, arg, 0xff);
+}
+
+static void tcg_out_ext16u(TCGContext *s, TCGReg ret, TCGReg arg)
+{
+ tcg_out_opc_bstrpick_w(s, ret, arg, 0, 15);
+}
+
+static void tcg_out_ext32u(TCGContext *s, TCGReg ret, TCGReg arg)
+{
+ tcg_out_opc_bstrpick_d(s, ret, arg, 0, 31);
+}
+
+static void tcg_out_ext8s(TCGContext *s, TCGReg ret, TCGReg arg)
+{
+ tcg_out_opc_sext_b(s, ret, arg);
+}
+
+static void tcg_out_ext16s(TCGContext *s, TCGReg ret, TCGReg arg)
+{
+ tcg_out_opc_sext_h(s, ret, arg);
+}
+
+static void tcg_out_ext32s(TCGContext *s, TCGReg ret, TCGReg arg)
+{
+ tcg_out_opc_addi_w(s, ret, arg, 0);
+}
+
+static void tcg_out_clzctz(TCGContext *s, LoongArchInsn opc,
+ TCGReg a0, TCGReg a1, TCGReg a2,
+ bool c2, bool is_32bit)
+{
+ if (c2) {
+ /*
+ * Fast path: semantics already satisfied due to constraint and
+ * insn behavior, single instruction is enough.
+ */
+ tcg_debug_assert(a2 == (is_32bit ? 32 : 64));
+ /* all clz/ctz insns belong to DJ-format */
+ tcg_out32(s, encode_dj_insn(opc, a0, a1));
+ return;
+ }
+
+ tcg_out32(s, encode_dj_insn(opc, TCG_REG_TMP0, a1));
+ /* a0 = a1 ? REG_TMP0 : a2 */
+ tcg_out_opc_maskeqz(s, TCG_REG_TMP0, TCG_REG_TMP0, a1);
+ tcg_out_opc_masknez(s, a0, a2, a1);
+ tcg_out_opc_or(s, a0, TCG_REG_TMP0, a0);
+}
+
+static void tcg_out_setcond(TCGContext *s, TCGCond cond, TCGReg ret,
+ TCGReg arg1, TCGReg arg2, bool c2)
+{
+ TCGReg tmp;
+
+ if (c2) {
+ tcg_debug_assert(arg2 == 0);
+ }
+
+ switch (cond) {
+ case TCG_COND_EQ:
+ if (c2) {
+ tmp = arg1;
+ } else {
+ tcg_out_opc_sub_d(s, ret, arg1, arg2);
+ tmp = ret;
+ }
+ tcg_out_opc_sltui(s, ret, tmp, 1);
+ break;
+ case TCG_COND_NE:
+ if (c2) {
+ tmp = arg1;
+ } else {
+ tcg_out_opc_sub_d(s, ret, arg1, arg2);
+ tmp = ret;
+ }
+ tcg_out_opc_sltu(s, ret, TCG_REG_ZERO, tmp);
+ break;
+ case TCG_COND_LT:
+ tcg_out_opc_slt(s, ret, arg1, arg2);
+ break;
+ case TCG_COND_GE:
+ tcg_out_opc_slt(s, ret, arg1, arg2);
+ tcg_out_opc_xori(s, ret, ret, 1);
+ break;
+ case TCG_COND_LE:
+ tcg_out_setcond(s, TCG_COND_GE, ret, arg2, arg1, false);
+ break;
+ case TCG_COND_GT:
+ tcg_out_setcond(s, TCG_COND_LT, ret, arg2, arg1, false);
+ break;
+ case TCG_COND_LTU:
+ tcg_out_opc_sltu(s, ret, arg1, arg2);
+ break;
+ case TCG_COND_GEU:
+ tcg_out_opc_sltu(s, ret, arg1, arg2);
+ tcg_out_opc_xori(s, ret, ret, 1);
+ break;
+ case TCG_COND_LEU:
+ tcg_out_setcond(s, TCG_COND_GEU, ret, arg2, arg1, false);
+ break;
+ case TCG_COND_GTU:
+ tcg_out_setcond(s, TCG_COND_LTU, ret, arg2, arg1, false);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+/*
+ * Branch helpers
+ */
+
+static const struct {
+ LoongArchInsn op;
+ bool swap;
+} tcg_brcond_to_loongarch[] = {
+ [TCG_COND_EQ] = { OPC_BEQ, false },
+ [TCG_COND_NE] = { OPC_BNE, false },
+ [TCG_COND_LT] = { OPC_BGT, true },
+ [TCG_COND_GE] = { OPC_BLE, true },
+ [TCG_COND_LE] = { OPC_BLE, false },
+ [TCG_COND_GT] = { OPC_BGT, false },
+ [TCG_COND_LTU] = { OPC_BGTU, true },
+ [TCG_COND_GEU] = { OPC_BLEU, true },
+ [TCG_COND_LEU] = { OPC_BLEU, false },
+ [TCG_COND_GTU] = { OPC_BGTU, false }
+};
+
+static void tcg_out_brcond(TCGContext *s, TCGCond cond, TCGReg arg1,
+ TCGReg arg2, TCGLabel *l)
+{
+ LoongArchInsn op = tcg_brcond_to_loongarch[cond].op;
+
+ tcg_debug_assert(op != 0);
+
+ if (tcg_brcond_to_loongarch[cond].swap) {
+ TCGReg t = arg1;
+ arg1 = arg2;
+ arg2 = t;
+ }
+
+ /* all conditional branch insns belong to DJSk16-format */
+ tcg_out_reloc(s, s->code_ptr, R_LOONGARCH_BR_SK16, l, 0);
+ tcg_out32(s, encode_djsk16_insn(op, arg1, arg2, 0));
+}
+
+static void tcg_out_call_int(TCGContext *s, const tcg_insn_unit *arg, bool tail)
+{
+ TCGReg link = tail ? TCG_REG_ZERO : TCG_REG_RA;
+ ptrdiff_t offset = tcg_pcrel_diff(s, arg);
+
+ tcg_debug_assert((offset & 3) == 0);
+ if (offset == sextreg(offset, 0, 28)) {
+ /* short jump: +/- 256MiB */
+ if (tail) {
+ tcg_out_opc_b(s, offset >> 2);
+ } else {
+ tcg_out_opc_bl(s, offset >> 2);
+ }
+ } else if (offset == sextreg(offset, 0, 38)) {
+ /* long jump: +/- 256GiB */
+ tcg_target_long lo = sextreg(offset, 0, 18);
+ tcg_target_long hi = offset - lo;
+ tcg_out_opc_pcaddu18i(s, TCG_REG_TMP0, hi >> 18);
+ tcg_out_opc_jirl(s, link, TCG_REG_TMP0, lo >> 2);
+ } else {
+ /* far jump: 64-bit */
+ tcg_target_long lo = sextreg((tcg_target_long)arg, 0, 18);
+ tcg_target_long hi = (tcg_target_long)arg - lo;
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_TMP0, hi);
+ tcg_out_opc_jirl(s, link, TCG_REG_TMP0, lo >> 2);
+ }
+}
+
+static void tcg_out_call(TCGContext *s, const tcg_insn_unit *arg)
+{
+ tcg_out_call_int(s, arg, false);
+}
+
+/*
+ * Load/store helpers
+ */
+
+static void tcg_out_ldst(TCGContext *s, LoongArchInsn opc, TCGReg data,
+ TCGReg addr, intptr_t offset)
+{
+ intptr_t imm12 = sextreg(offset, 0, 12);
+
+ if (offset != imm12) {
+ intptr_t diff = offset - (uintptr_t)s->code_ptr;
+
+ if (addr == TCG_REG_ZERO && diff == (int32_t)diff) {
+ imm12 = sextreg(diff, 0, 12);
+ tcg_out_opc_pcaddu12i(s, TCG_REG_TMP2, (diff - imm12) >> 12);
+ } else {
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_TMP2, offset - imm12);
+ if (addr != TCG_REG_ZERO) {
+ tcg_out_opc_add_d(s, TCG_REG_TMP2, TCG_REG_TMP2, addr);
+ }
+ }
+ addr = TCG_REG_TMP2;
+ }
+
+ switch (opc) {
+ case OPC_LD_B:
+ case OPC_LD_BU:
+ case OPC_LD_H:
+ case OPC_LD_HU:
+ case OPC_LD_W:
+ case OPC_LD_WU:
+ case OPC_LD_D:
+ case OPC_ST_B:
+ case OPC_ST_H:
+ case OPC_ST_W:
+ case OPC_ST_D:
+ tcg_out32(s, encode_djsk12_insn(opc, data, addr, imm12));
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void tcg_out_ld(TCGContext *s, TCGType type, TCGReg arg,
+ TCGReg arg1, intptr_t arg2)
+{
+ bool is_32bit = type == TCG_TYPE_I32;
+ tcg_out_ldst(s, is_32bit ? OPC_LD_W : OPC_LD_D, arg, arg1, arg2);
+}
+
+static void tcg_out_st(TCGContext *s, TCGType type, TCGReg arg,
+ TCGReg arg1, intptr_t arg2)
+{
+ bool is_32bit = type == TCG_TYPE_I32;
+ tcg_out_ldst(s, is_32bit ? OPC_ST_W : OPC_ST_D, arg, arg1, arg2);
+}
+
+static bool tcg_out_sti(TCGContext *s, TCGType type, TCGArg val,
+ TCGReg base, intptr_t ofs)
+{
+ if (val == 0) {
+ tcg_out_st(s, type, TCG_REG_ZERO, base, ofs);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Load/store helpers for SoftMMU, and qemu_ld/st implementations
+ */
+
+#if defined(CONFIG_SOFTMMU)
+#include "../tcg-ldst.c.inc"
+
+/*
+ * helper signature: helper_ret_ld_mmu(CPUState *env, target_ulong addr,
+ * MemOpIdx oi, uintptr_t ra)
+ */
+static void * const qemu_ld_helpers[4] = {
+ [MO_8] = helper_ret_ldub_mmu,
+ [MO_16] = helper_le_lduw_mmu,
+ [MO_32] = helper_le_ldul_mmu,
+ [MO_64] = helper_le_ldq_mmu,
+};
+
+/*
+ * helper signature: helper_ret_st_mmu(CPUState *env, target_ulong addr,
+ * uintxx_t val, MemOpIdx oi,
+ * uintptr_t ra)
+ */
+static void * const qemu_st_helpers[4] = {
+ [MO_8] = helper_ret_stb_mmu,
+ [MO_16] = helper_le_stw_mmu,
+ [MO_32] = helper_le_stl_mmu,
+ [MO_64] = helper_le_stq_mmu,
+};
+
+/* We expect to use a 12-bit negative offset from ENV. */
+QEMU_BUILD_BUG_ON(TLB_MASK_TABLE_OFS(0) > 0);
+QEMU_BUILD_BUG_ON(TLB_MASK_TABLE_OFS(0) < -(1 << 11));
+
+static bool tcg_out_goto(TCGContext *s, const tcg_insn_unit *target)
+{
+ tcg_out_opc_b(s, 0);
+ return reloc_br_sd10k16(s->code_ptr - 1, target);
+}
+
+/*
+ * Emits common code for TLB addend lookup, that eventually loads the
+ * addend in TCG_REG_TMP2.
+ */
+static void tcg_out_tlb_load(TCGContext *s, TCGReg addrl, MemOpIdx oi,
+ tcg_insn_unit **label_ptr, bool is_load)
+{
+ MemOp opc = get_memop(oi);
+ unsigned s_bits = opc & MO_SIZE;
+ unsigned a_bits = get_alignment_bits(opc);
+ tcg_target_long compare_mask;
+ int mem_index = get_mmuidx(oi);
+ int fast_ofs = TLB_MASK_TABLE_OFS(mem_index);
+ int mask_ofs = fast_ofs + offsetof(CPUTLBDescFast, mask);
+ int table_ofs = fast_ofs + offsetof(CPUTLBDescFast, table);
+
+ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP0, TCG_AREG0, mask_ofs);
+ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP1, TCG_AREG0, table_ofs);
+
+ tcg_out_opc_srli_d(s, TCG_REG_TMP2, addrl,
+ TARGET_PAGE_BITS - CPU_TLB_ENTRY_BITS);
+ tcg_out_opc_and(s, TCG_REG_TMP2, TCG_REG_TMP2, TCG_REG_TMP0);
+ tcg_out_opc_add_d(s, TCG_REG_TMP2, TCG_REG_TMP2, TCG_REG_TMP1);
+
+ /* Load the tlb comparator and the addend. */
+ tcg_out_ld(s, TCG_TYPE_TL, TCG_REG_TMP0, TCG_REG_TMP2,
+ is_load ? offsetof(CPUTLBEntry, addr_read)
+ : offsetof(CPUTLBEntry, addr_write));
+ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP2, TCG_REG_TMP2,
+ offsetof(CPUTLBEntry, addend));
+
+ /* We don't support unaligned accesses. */
+ if (a_bits < s_bits) {
+ a_bits = s_bits;
+ }
+ /* Clear the non-page, non-alignment bits from the address. */
+ compare_mask = (tcg_target_long)TARGET_PAGE_MASK | ((1 << a_bits) - 1);
+ tcg_out_movi(s, TCG_TYPE_TL, TCG_REG_TMP1, compare_mask);
+ tcg_out_opc_and(s, TCG_REG_TMP1, TCG_REG_TMP1, addrl);
+
+ /* Compare masked address with the TLB entry. */
+ label_ptr[0] = s->code_ptr;
+ tcg_out_opc_bne(s, TCG_REG_TMP0, TCG_REG_TMP1, 0);
+
+ /* TLB Hit - addend in TCG_REG_TMP2, ready for use. */
+}
+
+static void add_qemu_ldst_label(TCGContext *s, int is_ld, MemOpIdx oi,
+ TCGType type,
+ TCGReg datalo, TCGReg addrlo,
+ void *raddr, tcg_insn_unit **label_ptr)
+{
+ TCGLabelQemuLdst *label = new_ldst_label(s);
+
+ label->is_ld = is_ld;
+ label->oi = oi;
+ label->type = type;
+ label->datalo_reg = datalo;
+ label->datahi_reg = 0; /* unused */
+ label->addrlo_reg = addrlo;
+ label->addrhi_reg = 0; /* unused */
+ label->raddr = tcg_splitwx_to_rx(raddr);
+ label->label_ptr[0] = label_ptr[0];
+}
+
+static bool tcg_out_qemu_ld_slow_path(TCGContext *s, TCGLabelQemuLdst *l)
+{
+ MemOpIdx oi = l->oi;
+ MemOp opc = get_memop(oi);
+ MemOp size = opc & MO_SIZE;
+ TCGType type = l->type;
+
+ /* resolve label address */
+ if (!reloc_br_sk16(l->label_ptr[0], tcg_splitwx_to_rx(s->code_ptr))) {
+ return false;
+ }
+
+ /* call load helper */
+ tcg_out_mov(s, TCG_TYPE_PTR, TCG_REG_A0, TCG_AREG0);
+ tcg_out_mov(s, TCG_TYPE_PTR, TCG_REG_A1, l->addrlo_reg);
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_A2, oi);
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_A3, (tcg_target_long)l->raddr);
+
+ tcg_out_call(s, qemu_ld_helpers[size]);
+
+ switch (opc & MO_SSIZE) {
+ case MO_SB:
+ tcg_out_ext8s(s, l->datalo_reg, TCG_REG_A0);
+ break;
+ case MO_SW:
+ tcg_out_ext16s(s, l->datalo_reg, TCG_REG_A0);
+ break;
+ case MO_SL:
+ tcg_out_ext32s(s, l->datalo_reg, TCG_REG_A0);
+ break;
+ case MO_UL:
+ if (type == TCG_TYPE_I32) {
+ /* MO_UL loads of i32 should be sign-extended too */
+ tcg_out_ext32s(s, l->datalo_reg, TCG_REG_A0);
+ break;
+ }
+ /* fallthrough */
+ default:
+ tcg_out_mov(s, type, l->datalo_reg, TCG_REG_A0);
+ break;
+ }
+
+ return tcg_out_goto(s, l->raddr);
+}
+
+static bool tcg_out_qemu_st_slow_path(TCGContext *s, TCGLabelQemuLdst *l)
+{
+ MemOpIdx oi = l->oi;
+ MemOp opc = get_memop(oi);
+ MemOp size = opc & MO_SIZE;
+
+ /* resolve label address */
+ if (!reloc_br_sk16(l->label_ptr[0], tcg_splitwx_to_rx(s->code_ptr))) {
+ return false;
+ }
+
+ /* call store helper */
+ tcg_out_mov(s, TCG_TYPE_PTR, TCG_REG_A0, TCG_AREG0);
+ tcg_out_mov(s, TCG_TYPE_PTR, TCG_REG_A1, l->addrlo_reg);
+ switch (size) {
+ case MO_8:
+ tcg_out_ext8u(s, TCG_REG_A2, l->datalo_reg);
+ break;
+ case MO_16:
+ tcg_out_ext16u(s, TCG_REG_A2, l->datalo_reg);
+ break;
+ case MO_32:
+ tcg_out_ext32u(s, TCG_REG_A2, l->datalo_reg);
+ break;
+ case MO_64:
+ tcg_out_mov(s, TCG_TYPE_I64, TCG_REG_A2, l->datalo_reg);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_A3, oi);
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_A4, (tcg_target_long)l->raddr);
+
+ tcg_out_call(s, qemu_st_helpers[size]);
+
+ return tcg_out_goto(s, l->raddr);
+}
+#endif /* CONFIG_SOFTMMU */
+
+/*
+ * `ext32u` the address register into the temp register given,
+ * if target is 32-bit, no-op otherwise.
+ *
+ * Returns the address register ready for use with TLB addend.
+ */
+static TCGReg tcg_out_zext_addr_if_32_bit(TCGContext *s,
+ TCGReg addr, TCGReg tmp)
+{
+ if (TARGET_LONG_BITS == 32) {
+ tcg_out_ext32u(s, tmp, addr);
+ return tmp;
+ }
+ return addr;
+}
+
+static void tcg_out_qemu_ld_indexed(TCGContext *s, TCGReg rd, TCGReg rj,
+ TCGReg rk, MemOp opc, TCGType type)
+{
+ /* Byte swapping is left to middle-end expansion. */
+ tcg_debug_assert((opc & MO_BSWAP) == 0);
+
+ switch (opc & MO_SSIZE) {
+ case MO_UB:
+ tcg_out_opc_ldx_bu(s, rd, rj, rk);
+ break;
+ case MO_SB:
+ tcg_out_opc_ldx_b(s, rd, rj, rk);
+ break;
+ case MO_UW:
+ tcg_out_opc_ldx_hu(s, rd, rj, rk);
+ break;
+ case MO_SW:
+ tcg_out_opc_ldx_h(s, rd, rj, rk);
+ break;
+ case MO_UL:
+ if (type == TCG_TYPE_I64) {
+ tcg_out_opc_ldx_wu(s, rd, rj, rk);
+ break;
+ }
+ /* fallthrough */
+ case MO_SL:
+ tcg_out_opc_ldx_w(s, rd, rj, rk);
+ break;
+ case MO_Q:
+ tcg_out_opc_ldx_d(s, rd, rj, rk);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void tcg_out_qemu_ld(TCGContext *s, const TCGArg *args, TCGType type)
+{
+ TCGReg addr_regl;
+ TCGReg data_regl;
+ MemOpIdx oi;
+ MemOp opc;
+#if defined(CONFIG_SOFTMMU)
+ tcg_insn_unit *label_ptr[1];
+#endif
+ TCGReg base;
+
+ data_regl = *args++;
+ addr_regl = *args++;
+ oi = *args++;
+ opc = get_memop(oi);
+
+#if defined(CONFIG_SOFTMMU)
+ tcg_out_tlb_load(s, addr_regl, oi, label_ptr, 1);
+ base = tcg_out_zext_addr_if_32_bit(s, addr_regl, TCG_REG_TMP0);
+ tcg_out_qemu_ld_indexed(s, data_regl, base, TCG_REG_TMP2, opc, type);
+ add_qemu_ldst_label(s, 1, oi, type,
+ data_regl, addr_regl,
+ s->code_ptr, label_ptr);
+#else
+ base = tcg_out_zext_addr_if_32_bit(s, addr_regl, TCG_REG_TMP0);
+ TCGReg guest_base_reg = USE_GUEST_BASE ? TCG_GUEST_BASE_REG : TCG_REG_ZERO;
+ tcg_out_qemu_ld_indexed(s, data_regl, base, guest_base_reg, opc, type);
+#endif
+}
+
+static void tcg_out_qemu_st_indexed(TCGContext *s, TCGReg data,
+ TCGReg rj, TCGReg rk, MemOp opc)
+{
+ /* Byte swapping is left to middle-end expansion. */
+ tcg_debug_assert((opc & MO_BSWAP) == 0);
+
+ switch (opc & MO_SIZE) {
+ case MO_8:
+ tcg_out_opc_stx_b(s, data, rj, rk);
+ break;
+ case MO_16:
+ tcg_out_opc_stx_h(s, data, rj, rk);
+ break;
+ case MO_32:
+ tcg_out_opc_stx_w(s, data, rj, rk);
+ break;
+ case MO_64:
+ tcg_out_opc_stx_d(s, data, rj, rk);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void tcg_out_qemu_st(TCGContext *s, const TCGArg *args)
+{
+ TCGReg addr_regl;
+ TCGReg data_regl;
+ MemOpIdx oi;
+ MemOp opc;
+#if defined(CONFIG_SOFTMMU)
+ tcg_insn_unit *label_ptr[1];
+#endif
+ TCGReg base;
+
+ data_regl = *args++;
+ addr_regl = *args++;
+ oi = *args++;
+ opc = get_memop(oi);
+
+#if defined(CONFIG_SOFTMMU)
+ tcg_out_tlb_load(s, addr_regl, oi, label_ptr, 0);
+ base = tcg_out_zext_addr_if_32_bit(s, addr_regl, TCG_REG_TMP0);
+ tcg_out_qemu_st_indexed(s, data_regl, base, TCG_REG_TMP2, opc);
+ add_qemu_ldst_label(s, 0, oi,
+ 0, /* type param is unused for stores */
+ data_regl, addr_regl,
+ s->code_ptr, label_ptr);
+#else
+ base = tcg_out_zext_addr_if_32_bit(s, addr_regl, TCG_REG_TMP0);
+ TCGReg guest_base_reg = USE_GUEST_BASE ? TCG_GUEST_BASE_REG : TCG_REG_ZERO;
+ tcg_out_qemu_st_indexed(s, data_regl, base, guest_base_reg, opc);
+#endif
+}
+
+/*
+ * Entry-points
+ */
+
+static const tcg_insn_unit *tb_ret_addr;
+
+static void tcg_out_op(TCGContext *s, TCGOpcode opc,
+ const TCGArg args[TCG_MAX_OP_ARGS],
+ const int const_args[TCG_MAX_OP_ARGS])
+{
+ TCGArg a0 = args[0];
+ TCGArg a1 = args[1];
+ TCGArg a2 = args[2];
+ int c2 = const_args[2];
+
+ switch (opc) {
+ case INDEX_op_exit_tb:
+ /* Reuse the zeroing that exists for goto_ptr. */
+ if (a0 == 0) {
+ tcg_out_call_int(s, tcg_code_gen_epilogue, true);
+ } else {
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_REG_A0, a0);
+ tcg_out_call_int(s, tb_ret_addr, true);
+ }
+ break;
+
+ case INDEX_op_goto_tb:
+ assert(s->tb_jmp_insn_offset == 0);
+ /* indirect jump method */
+ tcg_out_ld(s, TCG_TYPE_PTR, TCG_REG_TMP0, TCG_REG_ZERO,
+ (uintptr_t)(s->tb_jmp_target_addr + a0));
+ tcg_out_opc_jirl(s, TCG_REG_ZERO, TCG_REG_TMP0, 0);
+ set_jmp_reset_offset(s, a0);
+ break;
+
+ case INDEX_op_mb:
+ tcg_out_mb(s, a0);
+ break;
+
+ case INDEX_op_goto_ptr:
+ tcg_out_opc_jirl(s, TCG_REG_ZERO, a0, 0);
+ break;
+
+ case INDEX_op_br:
+ tcg_out_reloc(s, s->code_ptr, R_LOONGARCH_BR_SD10K16, arg_label(a0),
+ 0);
+ tcg_out_opc_b(s, 0);
+ break;
+
+ case INDEX_op_brcond_i32:
+ case INDEX_op_brcond_i64:
+ tcg_out_brcond(s, a2, a0, a1, arg_label(args[3]));
+ break;
+
+ case INDEX_op_ext8s_i32:
+ case INDEX_op_ext8s_i64:
+ tcg_out_ext8s(s, a0, a1);
+ break;
+
+ case INDEX_op_ext8u_i32:
+ case INDEX_op_ext8u_i64:
+ tcg_out_ext8u(s, a0, a1);
+ break;
+
+ case INDEX_op_ext16s_i32:
+ case INDEX_op_ext16s_i64:
+ tcg_out_ext16s(s, a0, a1);
+ break;
+
+ case INDEX_op_ext16u_i32:
+ case INDEX_op_ext16u_i64:
+ tcg_out_ext16u(s, a0, a1);
+ break;
+
+ case INDEX_op_ext32u_i64:
+ case INDEX_op_extu_i32_i64:
+ tcg_out_ext32u(s, a0, a1);
+ break;
+
+ case INDEX_op_ext32s_i64:
+ case INDEX_op_extrl_i64_i32:
+ case INDEX_op_ext_i32_i64:
+ tcg_out_ext32s(s, a0, a1);
+ break;
+
+ case INDEX_op_extrh_i64_i32:
+ tcg_out_opc_srai_d(s, a0, a1, 32);
+ break;
+
+ case INDEX_op_not_i32:
+ case INDEX_op_not_i64:
+ tcg_out_opc_nor(s, a0, a1, TCG_REG_ZERO);
+ break;
+
+ case INDEX_op_nor_i32:
+ case INDEX_op_nor_i64:
+ if (c2) {
+ tcg_out_opc_ori(s, a0, a1, a2);
+ tcg_out_opc_nor(s, a0, a0, TCG_REG_ZERO);
+ } else {
+ tcg_out_opc_nor(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_andc_i32:
+ case INDEX_op_andc_i64:
+ if (c2) {
+ /* guaranteed to fit due to constraint */
+ tcg_out_opc_andi(s, a0, a1, ~a2);
+ } else {
+ tcg_out_opc_andn(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_orc_i32:
+ case INDEX_op_orc_i64:
+ if (c2) {
+ /* guaranteed to fit due to constraint */
+ tcg_out_opc_ori(s, a0, a1, ~a2);
+ } else {
+ tcg_out_opc_orn(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_and_i32:
+ case INDEX_op_and_i64:
+ if (c2) {
+ tcg_out_opc_andi(s, a0, a1, a2);
+ } else {
+ tcg_out_opc_and(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_or_i32:
+ case INDEX_op_or_i64:
+ if (c2) {
+ tcg_out_opc_ori(s, a0, a1, a2);
+ } else {
+ tcg_out_opc_or(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_xor_i32:
+ case INDEX_op_xor_i64:
+ if (c2) {
+ tcg_out_opc_xori(s, a0, a1, a2);
+ } else {
+ tcg_out_opc_xor(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_extract_i32:
+ tcg_out_opc_bstrpick_w(s, a0, a1, a2, a2 + args[3] - 1);
+ break;
+ case INDEX_op_extract_i64:
+ tcg_out_opc_bstrpick_d(s, a0, a1, a2, a2 + args[3] - 1);
+ break;
+
+ case INDEX_op_deposit_i32:
+ tcg_out_opc_bstrins_w(s, a0, a2, args[3], args[3] + args[4] - 1);
+ break;
+ case INDEX_op_deposit_i64:
+ tcg_out_opc_bstrins_d(s, a0, a2, args[3], args[3] + args[4] - 1);
+ break;
+
+ case INDEX_op_bswap16_i32:
+ case INDEX_op_bswap16_i64:
+ tcg_out_opc_revb_2h(s, a0, a1);
+ if (a2 & TCG_BSWAP_OS) {
+ tcg_out_ext16s(s, a0, a0);
+ } else if ((a2 & (TCG_BSWAP_IZ | TCG_BSWAP_OZ)) == TCG_BSWAP_OZ) {
+ tcg_out_ext16u(s, a0, a0);
+ }
+ break;
+
+ case INDEX_op_bswap32_i32:
+ /* All 32-bit values are computed sign-extended in the register. */
+ a2 = TCG_BSWAP_OS;
+ /* fallthrough */
+ case INDEX_op_bswap32_i64:
+ tcg_out_opc_revb_2w(s, a0, a1);
+ if (a2 & TCG_BSWAP_OS) {
+ tcg_out_ext32s(s, a0, a0);
+ } else if ((a2 & (TCG_BSWAP_IZ | TCG_BSWAP_OZ)) == TCG_BSWAP_OZ) {
+ tcg_out_ext32u(s, a0, a0);
+ }
+ break;
+
+ case INDEX_op_bswap64_i64:
+ tcg_out_opc_revb_d(s, a0, a1);
+ break;
+
+ case INDEX_op_clz_i32:
+ tcg_out_clzctz(s, OPC_CLZ_W, a0, a1, a2, c2, true);
+ break;
+ case INDEX_op_clz_i64:
+ tcg_out_clzctz(s, OPC_CLZ_D, a0, a1, a2, c2, false);
+ break;
+
+ case INDEX_op_ctz_i32:
+ tcg_out_clzctz(s, OPC_CTZ_W, a0, a1, a2, c2, true);
+ break;
+ case INDEX_op_ctz_i64:
+ tcg_out_clzctz(s, OPC_CTZ_D, a0, a1, a2, c2, false);
+ break;
+
+ case INDEX_op_shl_i32:
+ if (c2) {
+ tcg_out_opc_slli_w(s, a0, a1, a2 & 0x1f);
+ } else {
+ tcg_out_opc_sll_w(s, a0, a1, a2);
+ }
+ break;
+ case INDEX_op_shl_i64:
+ if (c2) {
+ tcg_out_opc_slli_d(s, a0, a1, a2 & 0x3f);
+ } else {
+ tcg_out_opc_sll_d(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_shr_i32:
+ if (c2) {
+ tcg_out_opc_srli_w(s, a0, a1, a2 & 0x1f);
+ } else {
+ tcg_out_opc_srl_w(s, a0, a1, a2);
+ }
+ break;
+ case INDEX_op_shr_i64:
+ if (c2) {
+ tcg_out_opc_srli_d(s, a0, a1, a2 & 0x3f);
+ } else {
+ tcg_out_opc_srl_d(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_sar_i32:
+ if (c2) {
+ tcg_out_opc_srai_w(s, a0, a1, a2 & 0x1f);
+ } else {
+ tcg_out_opc_sra_w(s, a0, a1, a2);
+ }
+ break;
+ case INDEX_op_sar_i64:
+ if (c2) {
+ tcg_out_opc_srai_d(s, a0, a1, a2 & 0x3f);
+ } else {
+ tcg_out_opc_sra_d(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_rotl_i32:
+ /* transform into equivalent rotr/rotri */
+ if (c2) {
+ tcg_out_opc_rotri_w(s, a0, a1, (32 - a2) & 0x1f);
+ } else {
+ tcg_out_opc_sub_w(s, TCG_REG_TMP0, TCG_REG_ZERO, a2);
+ tcg_out_opc_rotr_w(s, a0, a1, TCG_REG_TMP0);
+ }
+ break;
+ case INDEX_op_rotl_i64:
+ /* transform into equivalent rotr/rotri */
+ if (c2) {
+ tcg_out_opc_rotri_d(s, a0, a1, (64 - a2) & 0x3f);
+ } else {
+ tcg_out_opc_sub_w(s, TCG_REG_TMP0, TCG_REG_ZERO, a2);
+ tcg_out_opc_rotr_d(s, a0, a1, TCG_REG_TMP0);
+ }
+ break;
+
+ case INDEX_op_rotr_i32:
+ if (c2) {
+ tcg_out_opc_rotri_w(s, a0, a1, a2 & 0x1f);
+ } else {
+ tcg_out_opc_rotr_w(s, a0, a1, a2);
+ }
+ break;
+ case INDEX_op_rotr_i64:
+ if (c2) {
+ tcg_out_opc_rotri_d(s, a0, a1, a2 & 0x3f);
+ } else {
+ tcg_out_opc_rotr_d(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_add_i32:
+ if (c2) {
+ tcg_out_opc_addi_w(s, a0, a1, a2);
+ } else {
+ tcg_out_opc_add_w(s, a0, a1, a2);
+ }
+ break;
+ case INDEX_op_add_i64:
+ if (c2) {
+ tcg_out_opc_addi_d(s, a0, a1, a2);
+ } else {
+ tcg_out_opc_add_d(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_sub_i32:
+ if (c2) {
+ tcg_out_opc_addi_w(s, a0, a1, -a2);
+ } else {
+ tcg_out_opc_sub_w(s, a0, a1, a2);
+ }
+ break;
+ case INDEX_op_sub_i64:
+ if (c2) {
+ tcg_out_opc_addi_d(s, a0, a1, -a2);
+ } else {
+ tcg_out_opc_sub_d(s, a0, a1, a2);
+ }
+ break;
+
+ case INDEX_op_mul_i32:
+ tcg_out_opc_mul_w(s, a0, a1, a2);
+ break;
+ case INDEX_op_mul_i64:
+ tcg_out_opc_mul_d(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_mulsh_i32:
+ tcg_out_opc_mulh_w(s, a0, a1, a2);
+ break;
+ case INDEX_op_mulsh_i64:
+ tcg_out_opc_mulh_d(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_muluh_i32:
+ tcg_out_opc_mulh_wu(s, a0, a1, a2);
+ break;
+ case INDEX_op_muluh_i64:
+ tcg_out_opc_mulh_du(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_div_i32:
+ tcg_out_opc_div_w(s, a0, a1, a2);
+ break;
+ case INDEX_op_div_i64:
+ tcg_out_opc_div_d(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_divu_i32:
+ tcg_out_opc_div_wu(s, a0, a1, a2);
+ break;
+ case INDEX_op_divu_i64:
+ tcg_out_opc_div_du(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_rem_i32:
+ tcg_out_opc_mod_w(s, a0, a1, a2);
+ break;
+ case INDEX_op_rem_i64:
+ tcg_out_opc_mod_d(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_remu_i32:
+ tcg_out_opc_mod_wu(s, a0, a1, a2);
+ break;
+ case INDEX_op_remu_i64:
+ tcg_out_opc_mod_du(s, a0, a1, a2);
+ break;
+
+ case INDEX_op_setcond_i32:
+ case INDEX_op_setcond_i64:
+ tcg_out_setcond(s, args[3], a0, a1, a2, c2);
+ break;
+
+ case INDEX_op_ld8s_i32:
+ case INDEX_op_ld8s_i64:
+ tcg_out_ldst(s, OPC_LD_B, a0, a1, a2);
+ break;
+ case INDEX_op_ld8u_i32:
+ case INDEX_op_ld8u_i64:
+ tcg_out_ldst(s, OPC_LD_BU, a0, a1, a2);
+ break;
+ case INDEX_op_ld16s_i32:
+ case INDEX_op_ld16s_i64:
+ tcg_out_ldst(s, OPC_LD_H, a0, a1, a2);
+ break;
+ case INDEX_op_ld16u_i32:
+ case INDEX_op_ld16u_i64:
+ tcg_out_ldst(s, OPC_LD_HU, a0, a1, a2);
+ break;
+ case INDEX_op_ld_i32:
+ case INDEX_op_ld32s_i64:
+ tcg_out_ldst(s, OPC_LD_W, a0, a1, a2);
+ break;
+ case INDEX_op_ld32u_i64:
+ tcg_out_ldst(s, OPC_LD_WU, a0, a1, a2);
+ break;
+ case INDEX_op_ld_i64:
+ tcg_out_ldst(s, OPC_LD_D, a0, a1, a2);
+ break;
+
+ case INDEX_op_st8_i32:
+ case INDEX_op_st8_i64:
+ tcg_out_ldst(s, OPC_ST_B, a0, a1, a2);
+ break;
+ case INDEX_op_st16_i32:
+ case INDEX_op_st16_i64:
+ tcg_out_ldst(s, OPC_ST_H, a0, a1, a2);
+ break;
+ case INDEX_op_st_i32:
+ case INDEX_op_st32_i64:
+ tcg_out_ldst(s, OPC_ST_W, a0, a1, a2);
+ break;
+ case INDEX_op_st_i64:
+ tcg_out_ldst(s, OPC_ST_D, a0, a1, a2);
+ break;
+
+ case INDEX_op_qemu_ld_i32:
+ tcg_out_qemu_ld(s, args, TCG_TYPE_I32);
+ break;
+ case INDEX_op_qemu_ld_i64:
+ tcg_out_qemu_ld(s, args, TCG_TYPE_I64);
+ break;
+ case INDEX_op_qemu_st_i32:
+ tcg_out_qemu_st(s, args);
+ break;
+ case INDEX_op_qemu_st_i64:
+ tcg_out_qemu_st(s, args);
+ break;
+
+ case INDEX_op_mov_i32: /* Always emitted via tcg_out_mov. */
+ case INDEX_op_mov_i64:
+ case INDEX_op_call: /* Always emitted via tcg_out_call. */
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static TCGConstraintSetIndex tcg_target_op_def(TCGOpcode op)
+{
+ switch (op) {
+ case INDEX_op_goto_ptr:
+ return C_O0_I1(r);
+
+ case INDEX_op_st8_i32:
+ case INDEX_op_st8_i64:
+ case INDEX_op_st16_i32:
+ case INDEX_op_st16_i64:
+ case INDEX_op_st32_i64:
+ case INDEX_op_st_i32:
+ case INDEX_op_st_i64:
+ return C_O0_I2(rZ, r);
+
+ case INDEX_op_brcond_i32:
+ case INDEX_op_brcond_i64:
+ return C_O0_I2(rZ, rZ);
+
+ case INDEX_op_qemu_st_i32:
+ case INDEX_op_qemu_st_i64:
+ return C_O0_I2(LZ, L);
+
+ case INDEX_op_ext8s_i32:
+ case INDEX_op_ext8s_i64:
+ case INDEX_op_ext8u_i32:
+ case INDEX_op_ext8u_i64:
+ case INDEX_op_ext16s_i32:
+ case INDEX_op_ext16s_i64:
+ case INDEX_op_ext16u_i32:
+ case INDEX_op_ext16u_i64:
+ case INDEX_op_ext32s_i64:
+ case INDEX_op_ext32u_i64:
+ case INDEX_op_extu_i32_i64:
+ case INDEX_op_extrl_i64_i32:
+ case INDEX_op_extrh_i64_i32:
+ case INDEX_op_ext_i32_i64:
+ case INDEX_op_not_i32:
+ case INDEX_op_not_i64:
+ case INDEX_op_extract_i32:
+ case INDEX_op_extract_i64:
+ case INDEX_op_bswap16_i32:
+ case INDEX_op_bswap16_i64:
+ case INDEX_op_bswap32_i32:
+ case INDEX_op_bswap32_i64:
+ case INDEX_op_bswap64_i64:
+ case INDEX_op_ld8s_i32:
+ case INDEX_op_ld8s_i64:
+ case INDEX_op_ld8u_i32:
+ case INDEX_op_ld8u_i64:
+ case INDEX_op_ld16s_i32:
+ case INDEX_op_ld16s_i64:
+ case INDEX_op_ld16u_i32:
+ case INDEX_op_ld16u_i64:
+ case INDEX_op_ld32s_i64:
+ case INDEX_op_ld32u_i64:
+ case INDEX_op_ld_i32:
+ case INDEX_op_ld_i64:
+ return C_O1_I1(r, r);
+
+ case INDEX_op_qemu_ld_i32:
+ case INDEX_op_qemu_ld_i64:
+ return C_O1_I1(r, L);
+
+ case INDEX_op_andc_i32:
+ case INDEX_op_andc_i64:
+ case INDEX_op_orc_i32:
+ case INDEX_op_orc_i64:
+ /*
+ * LoongArch insns for these ops don't have reg-imm forms, but we
+ * can express using andi/ori if ~constant satisfies
+ * TCG_CT_CONST_U12.
+ */
+ return C_O1_I2(r, r, rC);
+
+ case INDEX_op_shl_i32:
+ case INDEX_op_shl_i64:
+ case INDEX_op_shr_i32:
+ case INDEX_op_shr_i64:
+ case INDEX_op_sar_i32:
+ case INDEX_op_sar_i64:
+ case INDEX_op_rotl_i32:
+ case INDEX_op_rotl_i64:
+ case INDEX_op_rotr_i32:
+ case INDEX_op_rotr_i64:
+ return C_O1_I2(r, r, ri);
+
+ case INDEX_op_add_i32:
+ case INDEX_op_add_i64:
+ return C_O1_I2(r, r, rI);
+
+ case INDEX_op_and_i32:
+ case INDEX_op_and_i64:
+ case INDEX_op_nor_i32:
+ case INDEX_op_nor_i64:
+ case INDEX_op_or_i32:
+ case INDEX_op_or_i64:
+ case INDEX_op_xor_i32:
+ case INDEX_op_xor_i64:
+ /* LoongArch reg-imm bitops have their imms ZERO-extended */
+ return C_O1_I2(r, r, rU);
+
+ case INDEX_op_clz_i32:
+ case INDEX_op_clz_i64:
+ case INDEX_op_ctz_i32:
+ case INDEX_op_ctz_i64:
+ return C_O1_I2(r, r, rW);
+
+ case INDEX_op_setcond_i32:
+ case INDEX_op_setcond_i64:
+ return C_O1_I2(r, r, rZ);
+
+ case INDEX_op_deposit_i32:
+ case INDEX_op_deposit_i64:
+ /* Must deposit into the same register as input */
+ return C_O1_I2(r, 0, rZ);
+
+ case INDEX_op_sub_i32:
+ case INDEX_op_sub_i64:
+ return C_O1_I2(r, rZ, rN);
+
+ case INDEX_op_mul_i32:
+ case INDEX_op_mul_i64:
+ case INDEX_op_mulsh_i32:
+ case INDEX_op_mulsh_i64:
+ case INDEX_op_muluh_i32:
+ case INDEX_op_muluh_i64:
+ case INDEX_op_div_i32:
+ case INDEX_op_div_i64:
+ case INDEX_op_divu_i32:
+ case INDEX_op_divu_i64:
+ case INDEX_op_rem_i32:
+ case INDEX_op_rem_i64:
+ case INDEX_op_remu_i32:
+ case INDEX_op_remu_i64:
+ return C_O1_I2(r, rZ, rZ);
+
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const int tcg_target_callee_save_regs[] = {
+ TCG_REG_S0, /* used for the global env (TCG_AREG0) */
+ TCG_REG_S1,
+ TCG_REG_S2,
+ TCG_REG_S3,
+ TCG_REG_S4,
+ TCG_REG_S5,
+ TCG_REG_S6,
+ TCG_REG_S7,
+ TCG_REG_S8,
+ TCG_REG_S9,
+ TCG_REG_RA, /* should be last for ABI compliance */
+};
+
+/* Stack frame parameters. */
+#define REG_SIZE (TCG_TARGET_REG_BITS / 8)
+#define SAVE_SIZE ((int)ARRAY_SIZE(tcg_target_callee_save_regs) * REG_SIZE)
+#define TEMP_SIZE (CPU_TEMP_BUF_NLONGS * (int)sizeof(long))
+#define FRAME_SIZE ((TCG_STATIC_CALL_ARGS_SIZE + TEMP_SIZE + SAVE_SIZE \
+ + TCG_TARGET_STACK_ALIGN - 1) \
+ & -TCG_TARGET_STACK_ALIGN)
+#define SAVE_OFS (TCG_STATIC_CALL_ARGS_SIZE + TEMP_SIZE)
+
+/* We're expecting to be able to use an immediate for frame allocation. */
+QEMU_BUILD_BUG_ON(FRAME_SIZE > 0x7ff);
+
+/* Generate global QEMU prologue and epilogue code */
+static void tcg_target_qemu_prologue(TCGContext *s)
+{
+ int i;
+
+ tcg_set_frame(s, TCG_REG_SP, TCG_STATIC_CALL_ARGS_SIZE, TEMP_SIZE);
+
+ /* TB prologue */
+ tcg_out_opc_addi_d(s, TCG_REG_SP, TCG_REG_SP, -FRAME_SIZE);
+ for (i = 0; i < ARRAY_SIZE(tcg_target_callee_save_regs); i++) {
+ tcg_out_st(s, TCG_TYPE_REG, tcg_target_callee_save_regs[i],
+ TCG_REG_SP, SAVE_OFS + i * REG_SIZE);
+ }
+
+#if !defined(CONFIG_SOFTMMU)
+ if (USE_GUEST_BASE) {
+ tcg_out_movi(s, TCG_TYPE_PTR, TCG_GUEST_BASE_REG, guest_base);
+ tcg_regset_set_reg(s->reserved_regs, TCG_GUEST_BASE_REG);
+ }
+#endif
+
+ /* Call generated code */
+ tcg_out_mov(s, TCG_TYPE_PTR, TCG_AREG0, tcg_target_call_iarg_regs[0]);
+ tcg_out_opc_jirl(s, TCG_REG_ZERO, tcg_target_call_iarg_regs[1], 0);
+
+ /* Return path for goto_ptr. Set return value to 0 */
+ tcg_code_gen_epilogue = tcg_splitwx_to_rx(s->code_ptr);
+ tcg_out_mov(s, TCG_TYPE_REG, TCG_REG_A0, TCG_REG_ZERO);
+
+ /* TB epilogue */
+ tb_ret_addr = tcg_splitwx_to_rx(s->code_ptr);
+ for (i = 0; i < ARRAY_SIZE(tcg_target_callee_save_regs); i++) {
+ tcg_out_ld(s, TCG_TYPE_REG, tcg_target_callee_save_regs[i],
+ TCG_REG_SP, SAVE_OFS + i * REG_SIZE);
+ }
+
+ tcg_out_opc_addi_d(s, TCG_REG_SP, TCG_REG_SP, FRAME_SIZE);
+ tcg_out_opc_jirl(s, TCG_REG_ZERO, TCG_REG_RA, 0);
+}
+
+static void tcg_target_init(TCGContext *s)
+{
+ tcg_target_available_regs[TCG_TYPE_I32] = ALL_GENERAL_REGS;
+ tcg_target_available_regs[TCG_TYPE_I64] = ALL_GENERAL_REGS;
+
+ tcg_target_call_clobber_regs = ALL_GENERAL_REGS;
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S0);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S1);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S2);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S3);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S4);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S5);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S6);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S7);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S8);
+ tcg_regset_reset_reg(tcg_target_call_clobber_regs, TCG_REG_S9);
+
+ s->reserved_regs = 0;
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_ZERO);
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_TMP0);
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_TMP1);
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_TMP2);
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_SP);
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_TP);
+ tcg_regset_set_reg(s->reserved_regs, TCG_REG_RESERVED);
+}
+
+typedef struct {
+ DebugFrameHeader h;
+ uint8_t fde_def_cfa[4];
+ uint8_t fde_reg_ofs[ARRAY_SIZE(tcg_target_callee_save_regs) * 2];
+} DebugFrame;
+
+#define ELF_HOST_MACHINE EM_LOONGARCH
+
+static const DebugFrame debug_frame = {
+ .h.cie.len = sizeof(DebugFrameCIE) - 4, /* length after .len member */
+ .h.cie.id = -1,
+ .h.cie.version = 1,
+ .h.cie.code_align = 1,
+ .h.cie.data_align = -(TCG_TARGET_REG_BITS / 8) & 0x7f, /* sleb128 */
+ .h.cie.return_column = TCG_REG_RA,
+
+ /* Total FDE size does not include the "len" member. */
+ .h.fde.len = sizeof(DebugFrame) - offsetof(DebugFrame, h.fde.cie_offset),
+
+ .fde_def_cfa = {
+ 12, TCG_REG_SP, /* DW_CFA_def_cfa sp, ... */
+ (FRAME_SIZE & 0x7f) | 0x80, /* ... uleb128 FRAME_SIZE */
+ (FRAME_SIZE >> 7)
+ },
+ .fde_reg_ofs = {
+ 0x80 + 23, 11, /* DW_CFA_offset, s0, -88 */
+ 0x80 + 24, 10, /* DW_CFA_offset, s1, -80 */
+ 0x80 + 25, 9, /* DW_CFA_offset, s2, -72 */
+ 0x80 + 26, 8, /* DW_CFA_offset, s3, -64 */
+ 0x80 + 27, 7, /* DW_CFA_offset, s4, -56 */
+ 0x80 + 28, 6, /* DW_CFA_offset, s5, -48 */
+ 0x80 + 29, 5, /* DW_CFA_offset, s6, -40 */
+ 0x80 + 30, 4, /* DW_CFA_offset, s7, -32 */
+ 0x80 + 31, 3, /* DW_CFA_offset, s8, -24 */
+ 0x80 + 22, 2, /* DW_CFA_offset, s9, -16 */
+ 0x80 + 1 , 1, /* DW_CFA_offset, ra, -8 */
+ }
+};
+
+void tcg_register_jit(const void *buf, size_t buf_size)
+{
+ tcg_register_jit_int(buf, buf_size, &debug_frame, sizeof(debug_frame));
+}
diff --git a/tcg/loongarch64/tcg-target.h b/tcg/loongarch64/tcg-target.h
new file mode 100644
index 0000000000..05010805e7
--- /dev/null
+++ b/tcg/loongarch64/tcg-target.h
@@ -0,0 +1,180 @@
+/*
+ * Tiny Code Generator for QEMU
+ *
+ * Copyright (c) 2021 WANG Xuerui <git@xen0n.name>
+ *
+ * Based on tcg/riscv/tcg-target.h
+ *
+ * Copyright (c) 2018 SiFive, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef LOONGARCH_TCG_TARGET_H
+#define LOONGARCH_TCG_TARGET_H
+
+/*
+ * Loongson removed the (incomplete) 32-bit support from kernel and toolchain
+ * for the initial upstreaming of this architecture, so don't bother and just
+ * support the LP64* ABI for now.
+ */
+#if defined(__loongarch64)
+# define TCG_TARGET_REG_BITS 64
+#else
+# error unsupported LoongArch register size
+#endif
+
+#define TCG_TARGET_INSN_UNIT_SIZE 4
+#define TCG_TARGET_NB_REGS 32
+#define MAX_CODE_GEN_BUFFER_SIZE SIZE_MAX
+
+typedef enum {
+ TCG_REG_ZERO,
+ TCG_REG_RA,
+ TCG_REG_TP,
+ TCG_REG_SP,
+ TCG_REG_A0,
+ TCG_REG_A1,
+ TCG_REG_A2,
+ TCG_REG_A3,
+ TCG_REG_A4,
+ TCG_REG_A5,
+ TCG_REG_A6,
+ TCG_REG_A7,
+ TCG_REG_T0,
+ TCG_REG_T1,
+ TCG_REG_T2,
+ TCG_REG_T3,
+ TCG_REG_T4,
+ TCG_REG_T5,
+ TCG_REG_T6,
+ TCG_REG_T7,
+ TCG_REG_T8,
+ TCG_REG_RESERVED,
+ TCG_REG_S9,
+ TCG_REG_S0,
+ TCG_REG_S1,
+ TCG_REG_S2,
+ TCG_REG_S3,
+ TCG_REG_S4,
+ TCG_REG_S5,
+ TCG_REG_S6,
+ TCG_REG_S7,
+ TCG_REG_S8,
+
+ /* aliases */
+ TCG_AREG0 = TCG_REG_S0,
+ TCG_REG_TMP0 = TCG_REG_T8,
+ TCG_REG_TMP1 = TCG_REG_T7,
+ TCG_REG_TMP2 = TCG_REG_T6,
+} TCGReg;
+
+/* used for function call generation */
+#define TCG_REG_CALL_STACK TCG_REG_SP
+#define TCG_TARGET_STACK_ALIGN 16
+#define TCG_TARGET_CALL_ALIGN_ARGS 1
+#define TCG_TARGET_CALL_STACK_OFFSET 0
+
+/* optional instructions */
+#define TCG_TARGET_HAS_movcond_i32 0
+#define TCG_TARGET_HAS_div_i32 1
+#define TCG_TARGET_HAS_rem_i32 1
+#define TCG_TARGET_HAS_div2_i32 0
+#define TCG_TARGET_HAS_rot_i32 1
+#define TCG_TARGET_HAS_deposit_i32 1
+#define TCG_TARGET_HAS_extract_i32 1
+#define TCG_TARGET_HAS_sextract_i32 0
+#define TCG_TARGET_HAS_extract2_i32 0
+#define TCG_TARGET_HAS_add2_i32 0
+#define TCG_TARGET_HAS_sub2_i32 0
+#define TCG_TARGET_HAS_mulu2_i32 0
+#define TCG_TARGET_HAS_muls2_i32 0
+#define TCG_TARGET_HAS_muluh_i32 1
+#define TCG_TARGET_HAS_mulsh_i32 1
+#define TCG_TARGET_HAS_ext8s_i32 1
+#define TCG_TARGET_HAS_ext16s_i32 1
+#define TCG_TARGET_HAS_ext8u_i32 1
+#define TCG_TARGET_HAS_ext16u_i32 1
+#define TCG_TARGET_HAS_bswap16_i32 1
+#define TCG_TARGET_HAS_bswap32_i32 1
+#define TCG_TARGET_HAS_not_i32 1
+#define TCG_TARGET_HAS_neg_i32 0
+#define TCG_TARGET_HAS_andc_i32 1
+#define TCG_TARGET_HAS_orc_i32 1
+#define TCG_TARGET_HAS_eqv_i32 0
+#define TCG_TARGET_HAS_nand_i32 0
+#define TCG_TARGET_HAS_nor_i32 1
+#define TCG_TARGET_HAS_clz_i32 1
+#define TCG_TARGET_HAS_ctz_i32 1
+#define TCG_TARGET_HAS_ctpop_i32 0
+#define TCG_TARGET_HAS_direct_jump 0
+#define TCG_TARGET_HAS_brcond2 0
+#define TCG_TARGET_HAS_setcond2 0
+#define TCG_TARGET_HAS_qemu_st8_i32 0
+
+/* 64-bit operations */
+#define TCG_TARGET_HAS_movcond_i64 0
+#define TCG_TARGET_HAS_div_i64 1
+#define TCG_TARGET_HAS_rem_i64 1
+#define TCG_TARGET_HAS_div2_i64 0
+#define TCG_TARGET_HAS_rot_i64 1
+#define TCG_TARGET_HAS_deposit_i64 1
+#define TCG_TARGET_HAS_extract_i64 1
+#define TCG_TARGET_HAS_sextract_i64 0
+#define TCG_TARGET_HAS_extract2_i64 0
+#define TCG_TARGET_HAS_extrl_i64_i32 1
+#define TCG_TARGET_HAS_extrh_i64_i32 1
+#define TCG_TARGET_HAS_ext8s_i64 1
+#define TCG_TARGET_HAS_ext16s_i64 1
+#define TCG_TARGET_HAS_ext32s_i64 1
+#define TCG_TARGET_HAS_ext8u_i64 1
+#define TCG_TARGET_HAS_ext16u_i64 1
+#define TCG_TARGET_HAS_ext32u_i64 1
+#define TCG_TARGET_HAS_bswap16_i64 1
+#define TCG_TARGET_HAS_bswap32_i64 1
+#define TCG_TARGET_HAS_bswap64_i64 1
+#define TCG_TARGET_HAS_not_i64 1
+#define TCG_TARGET_HAS_neg_i64 0
+#define TCG_TARGET_HAS_andc_i64 1
+#define TCG_TARGET_HAS_orc_i64 1
+#define TCG_TARGET_HAS_eqv_i64 0
+#define TCG_TARGET_HAS_nand_i64 0
+#define TCG_TARGET_HAS_nor_i64 1
+#define TCG_TARGET_HAS_clz_i64 1
+#define TCG_TARGET_HAS_ctz_i64 1
+#define TCG_TARGET_HAS_ctpop_i64 0
+#define TCG_TARGET_HAS_add2_i64 0
+#define TCG_TARGET_HAS_sub2_i64 0
+#define TCG_TARGET_HAS_mulu2_i64 0
+#define TCG_TARGET_HAS_muls2_i64 0
+#define TCG_TARGET_HAS_muluh_i64 1
+#define TCG_TARGET_HAS_mulsh_i64 1
+
+/* not defined -- call should be eliminated at compile time */
+void tb_target_set_jmp_target(uintptr_t, uintptr_t, uintptr_t, uintptr_t);
+
+#define TCG_TARGET_DEFAULT_MO (0)
+
+#ifdef CONFIG_SOFTMMU
+#define TCG_TARGET_NEED_LDST_LABELS
+#endif
+
+#define TCG_TARGET_HAS_MEMORY_BSWAP 0
+
+#endif /* LOONGARCH_TCG_TARGET_H */
diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check
index 43a4b694cc..0c27721a41 100755
--- a/tests/qemu-iotests/check
+++ b/tests/qemu-iotests/check
@@ -34,6 +34,8 @@ def make_argparser() -> argparse.ArgumentParser:
help='show me, do not run tests')
p.add_argument('-makecheck', action='store_true',
help='pretty print output for make check')
+ p.add_argument('-j', dest='jobs', type=int, default=1,
+ help='run tests in multiple parallel jobs')
p.add_argument('-d', dest='debug', action='store_true', help='debug')
p.add_argument('-p', dest='print', action='store_true',
@@ -165,6 +167,6 @@ if __name__ == '__main__':
with TestRunner(env, makecheck=args.makecheck,
color=args.color) as tr:
paths = [os.path.join(env.source_iotests, t) for t in tests]
- ok = tr.run_tests(paths)
+ ok = tr.run_tests(paths, args.jobs)
if not ok:
sys.exit(1)
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 83bfedb902..1e2f2391d1 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -138,14 +138,22 @@ def unarchive_sample_image(sample, fname):
shutil.copyfileobj(f_in, f_out)
+def qemu_tool_popen(args: Sequence[str],
+ connect_stderr: bool = True) -> 'subprocess.Popen[str]':
+ stderr = subprocess.STDOUT if connect_stderr else None
+ # pylint: disable=consider-using-with
+ return subprocess.Popen(args,
+ stdout=subprocess.PIPE,
+ stderr=stderr,
+ universal_newlines=True)
+
+
def qemu_tool_pipe_and_status(tool: str, args: Sequence[str],
connect_stderr: bool = True) -> Tuple[str, int]:
"""
Run a tool and return both its output and its exit code
"""
- stderr = subprocess.STDOUT if connect_stderr else None
- with subprocess.Popen(args, stdout=subprocess.PIPE,
- stderr=stderr, universal_newlines=True) as subp:
+ with qemu_tool_popen(args, connect_stderr) as subp:
output = subp.communicate()[0]
if subp.returncode < 0:
cmd = ' '.join(args)
@@ -233,10 +241,18 @@ def img_info_log(filename, filter_path=None, imgopts=False, extra_args=()):
filter_path = filename
log(filter_img_info(output, filter_path))
+def qemu_io_wrap_args(args: Sequence[str]) -> List[str]:
+ if '-f' in args or '--image-opts' in args:
+ return qemu_io_args_no_fmt + list(args)
+ else:
+ return qemu_io_args + list(args)
+
+def qemu_io_popen(*args):
+ return qemu_tool_popen(qemu_io_wrap_args(args))
+
def qemu_io(*args):
'''Run qemu-io and return the stdout data'''
- args = qemu_io_args + list(args)
- return qemu_tool_pipe_and_status('qemu-io', args)[0]
+ return qemu_tool_pipe_and_status('qemu-io', qemu_io_wrap_args(args))[0]
def qemu_io_log(*args):
result = qemu_io(*args)
@@ -245,12 +261,7 @@ def qemu_io_log(*args):
def qemu_io_silent(*args):
'''Run qemu-io and return the exit code, suppressing stdout'''
- if '-f' in args or '--image-opts' in args:
- default_args = qemu_io_args_no_fmt
- else:
- default_args = qemu_io_args
-
- args = default_args + list(args)
+ args = qemu_io_wrap_args(args)
result = subprocess.run(args, stdout=subprocess.DEVNULL, check=False)
if result.returncode < 0:
sys.stderr.write('qemu-io received signal %i: %s\n' %
@@ -259,14 +270,14 @@ def qemu_io_silent(*args):
def qemu_io_silent_check(*args):
'''Run qemu-io and return the true if subprocess returned 0'''
- args = qemu_io_args + list(args)
+ args = qemu_io_wrap_args(args)
result = subprocess.run(args, stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT, check=False)
return result.returncode == 0
class QemuIoInteractive:
def __init__(self, *args):
- self.args = qemu_io_args_no_fmt + list(args)
+ self.args = qemu_io_wrap_args(args)
# We need to keep the Popen objext around, and not
# close it immediately. Therefore, disable the pylint check:
# pylint: disable=consider-using-with
diff --git a/tests/qemu-iotests/testrunner.py b/tests/qemu-iotests/testrunner.py
index 0e29c2fddd..0feaa396d0 100644
--- a/tests/qemu-iotests/testrunner.py
+++ b/tests/qemu-iotests/testrunner.py
@@ -26,6 +26,7 @@ import contextlib
import json
import termios
import sys
+from multiprocessing import Pool
from contextlib import contextmanager
from typing import List, Optional, Iterator, Any, Sequence, Dict, \
ContextManager
@@ -126,6 +127,31 @@ class TestResult:
class TestRunner(ContextManager['TestRunner']):
+ shared_self = None
+
+ @staticmethod
+ def proc_run_test(test: str, test_field_width: int) -> TestResult:
+ # We are in a subprocess, we can't change the runner object!
+ runner = TestRunner.shared_self
+ assert runner is not None
+ return runner.run_test(test, test_field_width, mp=True)
+
+ def run_tests_pool(self, tests: List[str],
+ test_field_width: int, jobs: int) -> List[TestResult]:
+
+ # passing self directly to Pool.starmap() just doesn't work, because
+ # it's a context manager.
+ assert TestRunner.shared_self is None
+ TestRunner.shared_self = self
+
+ with Pool(jobs) as p:
+ results = p.starmap(self.proc_run_test,
+ zip(tests, [test_field_width] * len(tests)))
+
+ TestRunner.shared_self = None
+
+ return results
+
def __init__(self, env: TestEnv, makecheck: bool = False,
color: str = 'auto') -> None:
self.env = env
@@ -219,7 +245,18 @@ class TestRunner(ContextManager['TestRunner']):
return f'{test}.out'
- def do_run_test(self, test: str) -> TestResult:
+ def do_run_test(self, test: str, mp: bool) -> TestResult:
+ """
+ Run one test
+
+ :param test: test file path
+ :param mp: if true, we are in a multiprocessing environment, use
+ personal subdirectories for test run
+
+ Note: this method may be called from subprocess, so it does not
+ change ``self`` object in any way!
+ """
+
f_test = Path(test)
f_bad = Path(f_test.name + '.out.bad')
f_notrun = Path(f_test.name + '.notrun')
@@ -243,6 +280,12 @@ class TestRunner(ContextManager['TestRunner']):
args = [str(f_test.resolve())]
env = self.env.prepare_subprocess(args)
+ if mp:
+ # Split test directories, so that tests running in parallel don't
+ # break each other.
+ for d in ['TEST_DIR', 'SOCK_DIR']:
+ env[d] = os.path.join(env[d], f_test.name)
+ Path(env[d]).mkdir(parents=True, exist_ok=True)
t0 = time.time()
with f_bad.open('w', encoding="utf-8") as f:
@@ -281,21 +324,36 @@ class TestRunner(ContextManager['TestRunner']):
diff=diff, casenotrun=casenotrun)
else:
f_bad.unlink()
- self.last_elapsed.update(test, elapsed)
return TestResult(status='pass', elapsed=elapsed,
casenotrun=casenotrun)
def run_test(self, test: str,
- test_field_width: Optional[int] = None) -> TestResult:
+ test_field_width: Optional[int] = None,
+ mp: bool = False) -> TestResult:
+ """
+ Run one test and print short status
+
+ :param test: test file path
+ :param test_field_width: width for first field of status format
+ :param mp: if true, we are in a multiprocessing environment, don't try
+ to rewrite things in stdout
+
+ Note: this method may be called from subprocess, so it does not
+ change ``self`` object in any way!
+ """
+
last_el = self.last_elapsed.get(test)
start = datetime.datetime.now().strftime('%H:%M:%S')
if not self.makecheck:
- self.test_print_one_line(test=test, starttime=start,
- lasttime=last_el, end='\r',
+ self.test_print_one_line(test=test,
+ status = 'started' if mp else '...',
+ starttime=start,
+ lasttime=last_el,
+ end = '\n' if mp else '\r',
test_field_width=test_field_width)
- res = self.do_run_test(test)
+ res = self.do_run_test(test, mp)
end = datetime.datetime.now().strftime('%H:%M:%S')
self.test_print_one_line(test=test, status=res.status,
@@ -309,7 +367,7 @@ class TestRunner(ContextManager['TestRunner']):
return res
- def run_tests(self, tests: List[str]) -> bool:
+ def run_tests(self, tests: List[str], jobs: int = 1) -> bool:
n_run = 0
failed = []
notrun = []
@@ -320,9 +378,16 @@ class TestRunner(ContextManager['TestRunner']):
test_field_width = max(len(os.path.basename(t)) for t in tests) + 2
- for t in tests:
+ if jobs > 1:
+ results = self.run_tests_pool(tests, test_field_width, jobs)
+
+ for i, t in enumerate(tests):
name = os.path.basename(t)
- res = self.run_test(t, test_field_width=test_field_width)
+
+ if jobs > 1:
+ res = results[i]
+ else:
+ res = self.run_test(t, test_field_width)
assert res.status in ('pass', 'fail', 'not run')
@@ -340,6 +405,9 @@ class TestRunner(ContextManager['TestRunner']):
print('\n'.join(res.diff))
elif res.status == 'not run':
notrun.append(name)
+ elif res.status == 'pass':
+ assert res.elapsed is not None
+ self.last_elapsed.update(t, res.elapsed)
sys.stdout.flush()
if res.interrupted:
diff --git a/tests/qemu-iotests/tests/nbd-reconnect-on-open b/tests/qemu-iotests/tests/nbd-reconnect-on-open
new file mode 100755
index 0000000000..8be721a24f
--- /dev/null
+++ b/tests/qemu-iotests/tests/nbd-reconnect-on-open
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# Test nbd reconnect on open
+#
+# Copyright (c) 2020 Virtuozzo International GmbH
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import time
+
+import iotests
+from iotests import qemu_img_create, file_path, qemu_io_popen, qemu_nbd, \
+ qemu_io_log, log
+
+iotests.script_initialize(supported_fmts=['qcow2'])
+
+disk, nbd_sock = file_path('disk', 'nbd-sock')
+
+
+def create_args(open_timeout):
+ return ['--image-opts', '-c', 'read 0 1M',
+ f'driver=nbd,open-timeout={open_timeout},'
+ f'server.type=unix,server.path={nbd_sock}']
+
+
+def check_fail_to_connect(open_timeout):
+ log(f'Check fail to connect with {open_timeout} seconds of timeout')
+
+ start_t = time.time()
+ qemu_io_log(*create_args(open_timeout))
+ delta_t = time.time() - start_t
+
+ max_delta = open_timeout + 0.2
+ if open_timeout <= delta_t <= max_delta:
+ log(f'qemu_io finished in {open_timeout}..{max_delta} seconds, OK')
+ else:
+ note = 'too early' if delta_t < open_timeout else 'too long'
+ log(f'qemu_io finished in {delta_t:.1f} seconds, {note}')
+
+
+qemu_img_create('-f', iotests.imgfmt, disk, '1M')
+
+# Start NBD client when NBD server is not yet running. It should not fail, but
+# wait for 5 seconds for the server to be available.
+client = qemu_io_popen(*create_args(5))
+
+time.sleep(1)
+qemu_nbd('-k', nbd_sock, '-f', iotests.imgfmt, disk)
+
+# client should succeed
+log(client.communicate()[0], filters=[iotests.filter_qemu_io])
+
+# Server was started without --persistent flag, so it should be off now. Let's
+# check it and at the same time check that with open-timeout=0 client fails
+# immediately.
+check_fail_to_connect(0)
+
+# Check that we will fail after non-zero timeout if server is still unavailable
+check_fail_to_connect(1)
diff --git a/tests/qemu-iotests/tests/nbd-reconnect-on-open.out b/tests/qemu-iotests/tests/nbd-reconnect-on-open.out
new file mode 100644
index 0000000000..a35ae30ea4
--- /dev/null
+++ b/tests/qemu-iotests/tests/nbd-reconnect-on-open.out
@@ -0,0 +1,11 @@
+read 1048576/1048576 bytes at offset 0
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+Check fail to connect with 0 seconds of timeout
+qemu-io: can't open: Failed to connect to 'TEST_DIR/PID-nbd-sock': No such file or directory
+
+qemu_io finished in 0..0.2 seconds, OK
+Check fail to connect with 1 seconds of timeout
+qemu-io: can't open: Failed to connect to 'TEST_DIR/PID-nbd-sock': No such file or directory
+
+qemu_io finished in 1..1.2 seconds, OK
diff --git a/tests/qtest/boot-order-test.c b/tests/qtest/boot-order-test.c
index fac580d6c4..f1f59b1261 100644
--- a/tests/qtest/boot-order-test.c
+++ b/tests/qtest/boot-order-test.c
@@ -34,6 +34,11 @@ static void test_a_boot_order(const char *machine,
uint64_t actual;
QTestState *qts;
+ if (machine && !qtest_has_machine(machine)) {
+ g_test_skip("Machine is not available");
+ return;
+ }
+
qts = qtest_initf("-nodefaults%s%s %s", machine ? " -M " : "",
machine ?: "", test_args);
actual = read_boot_order(qts);
diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
index 4d8e1343bd..d72a82d629 100644
--- a/tests/qtest/boot-serial-test.c
+++ b/tests/qtest/boot-serial-test.c
@@ -157,11 +157,11 @@ static testdef_t tests[] = {
{ "ppc64", "powernv8", "", "OPAL" },
{ "ppc64", "powernv9", "", "OPAL" },
{ "ppc64", "sam460ex", "-device e1000", "8086 100e" },
- { "i386", "isapc", "-cpu qemu32 -device sga", "SGABIOS" },
- { "i386", "pc", "-device sga", "SGABIOS" },
- { "i386", "q35", "-device sga", "SGABIOS" },
- { "x86_64", "isapc", "-cpu qemu32 -device sga", "SGABIOS" },
- { "x86_64", "q35", "-device sga", "SGABIOS" },
+ { "i386", "isapc", "-cpu qemu32 -M graphics=off", "SeaBIOS" },
+ { "i386", "pc", "-M graphics=off", "SeaBIOS" },
+ { "i386", "q35", "-M graphics=off", "SeaBIOS" },
+ { "x86_64", "isapc", "-cpu qemu32 -M graphics=off", "SeaBIOS" },
+ { "x86_64", "q35", "-M graphics=off", "SeaBIOS" },
{ "sparc", "LX", "", "TMS390S10" },
{ "sparc", "SS-4", "", "MB86904" },
{ "sparc", "SS-600MP", "", "TMS390Z55" },
diff --git a/tests/qtest/cdrom-test.c b/tests/qtest/cdrom-test.c
index c1fcac5c45..cfca24fa94 100644
--- a/tests/qtest/cdrom-test.c
+++ b/tests/qtest/cdrom-test.c
@@ -142,21 +142,36 @@ static void add_x86_tests(void)
qtest_add_data_func("cdrom/boot/isapc", "-M isapc "
"-drive if=ide,media=cdrom,file=", test_cdboot);
}
- qtest_add_data_func("cdrom/boot/am53c974",
- "-device am53c974 -device scsi-cd,drive=cd1 "
- "-drive if=none,id=cd1,format=raw,file=", test_cdboot);
- qtest_add_data_func("cdrom/boot/dc390",
- "-device dc390 -device scsi-cd,drive=cd1 "
- "-blockdev file,node-name=cd1,filename=", test_cdboot);
- qtest_add_data_func("cdrom/boot/lsi53c895a",
- "-device lsi53c895a -device scsi-cd,drive=cd1 "
- "-blockdev file,node-name=cd1,filename=", test_cdboot);
- qtest_add_data_func("cdrom/boot/megasas", "-M q35 "
- "-device megasas -device scsi-cd,drive=cd1 "
- "-blockdev file,node-name=cd1,filename=", test_cdboot);
- qtest_add_data_func("cdrom/boot/megasas-gen2", "-M q35 "
- "-device megasas-gen2 -device scsi-cd,drive=cd1 "
- "-blockdev file,node-name=cd1,filename=", test_cdboot);
+ if (qtest_has_device("am53c974")) {
+ qtest_add_data_func("cdrom/boot/am53c974",
+ "-device am53c974 -device scsi-cd,drive=cd1 "
+ "-drive if=none,id=cd1,format=raw,file=",
+ test_cdboot);
+ }
+ if (qtest_has_device("dc390")) {
+ qtest_add_data_func("cdrom/boot/dc390",
+ "-device dc390 -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=",
+ test_cdboot);
+ }
+ if (qtest_has_device("lsi53c895a")) {
+ qtest_add_data_func("cdrom/boot/lsi53c895a",
+ "-device lsi53c895a -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=",
+ test_cdboot);
+ }
+ if (qtest_has_device("megasas")) {
+ qtest_add_data_func("cdrom/boot/megasas", "-M q35 "
+ "-device megasas -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=",
+ test_cdboot);
+ }
+ if (qtest_has_device("megasas-gen2")) {
+ qtest_add_data_func("cdrom/boot/megasas-gen2", "-M q35 "
+ "-device megasas-gen2 -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=",
+ test_cdboot);
+ }
}
static void add_s390x_tests(void)
@@ -171,12 +186,15 @@ static void add_s390x_tests(void)
"-drive driver=null-co,read-zeroes=on,if=none,id=d1 "
"-device virtio-blk,drive=d2,bootindex=1 "
"-drive if=none,id=d2,media=cdrom,file=", test_cdboot);
- qtest_add_data_func("cdrom/boot/without-bootindex",
- "-device virtio-scsi -device virtio-serial "
- "-device x-terminal3270 -device virtio-blk,drive=d1 "
- "-drive driver=null-co,read-zeroes=on,if=none,id=d1 "
- "-device virtio-blk,drive=d2 "
- "-drive if=none,id=d2,media=cdrom,file=", test_cdboot);
+ if (qtest_has_device("x-terminal3270")) {
+ qtest_add_data_func("cdrom/boot/without-bootindex",
+ "-device virtio-scsi -device virtio-serial "
+ "-device x-terminal3270 -device virtio-blk,drive=d1 "
+ "-drive driver=null-co,read-zeroes=on,if=none,id=d1 "
+ "-device virtio-blk,drive=d2 "
+ "-drive if=none,id=d2,media=cdrom,file=",
+ test_cdboot);
+ }
}
int main(int argc, char **argv)
diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c
new file mode 100644
index 0000000000..43c77aff04
--- /dev/null
+++ b/tests/qtest/dbus-display-test.c
@@ -0,0 +1,257 @@
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include "libqos/libqtest.h"
+#include "qemu-common.h"
+#include "dbus-display1.h"
+
+static GDBusConnection*
+test_dbus_p2p_from_fd(int fd)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) socketc = NULL;
+ GDBusConnection *conn;
+
+ socket = g_socket_new_from_fd(fd, &err);
+ g_assert_no_error(err);
+
+ socketc = g_socket_connection_factory_create_connection(socket);
+ g_assert(socketc != NULL);
+
+ conn = g_dbus_connection_new_sync(
+ G_IO_STREAM(socketc), NULL,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
+ NULL, NULL, &err);
+ g_assert_no_error(err);
+
+ return conn;
+}
+
+static void
+test_setup(QTestState **qts, GDBusConnection **conn)
+{
+ int pair[2];
+
+ *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
+
+ g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
+
+ qtest_qmp_add_client(*qts, "@dbus-display", pair[1]);
+
+ *conn = test_dbus_p2p_from_fd(pair[0]);
+ g_dbus_connection_start_message_processing(*conn);
+}
+
+static void
+test_dbus_display_vm(void)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
+ QTestState *qts = NULL;
+
+ test_setup(&qts, &conn);
+
+ vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
+ qemu_dbus_display1_vm_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ DBUS_DISPLAY1_ROOT "/VM",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+ g_assert_cmpstr(
+ qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)),
+ ==,
+ "dbus-test");
+ qtest_quit(qts);
+}
+
+typedef struct TestDBusConsoleRegister {
+ GMainLoop *loop;
+ GThread *thread;
+ GDBusConnection *listener_conn;
+ GDBusObjectManagerServer *server;
+} TestDBusConsoleRegister;
+
+static gboolean listener_handle_scanout(
+ QemuDBusDisplay1Listener *object,
+ GDBusMethodInvocation *invocation,
+ guint arg_width,
+ guint arg_height,
+ guint arg_stride,
+ guint arg_pixman_format,
+ GVariant *arg_data,
+ TestDBusConsoleRegister *test)
+{
+ g_main_loop_quit(test->loop);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+test_dbus_console_setup_listener(TestDBusConsoleRegister *test)
+{
+ g_autoptr(GDBusObjectSkeleton) listener = NULL;
+ g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL;
+
+ test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
+ listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener");
+ iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON(
+ qemu_dbus_display1_listener_skeleton_new());
+ g_object_connect(iface,
+ "signal::handle-scanout", listener_handle_scanout, test,
+ NULL);
+ g_dbus_object_skeleton_add_interface(listener,
+ G_DBUS_INTERFACE_SKELETON(iface));
+ g_dbus_object_manager_server_export(test->server, listener);
+ g_dbus_object_manager_server_set_connection(test->server,
+ test->listener_conn);
+
+ g_dbus_connection_start_message_processing(test->listener_conn);
+}
+
+static void
+test_dbus_console_registered(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TestDBusConsoleRegister *test = user_data;
+ g_autoptr(GError) err = NULL;
+
+ qemu_dbus_display1_console_call_register_listener_finish(
+ QEMU_DBUS_DISPLAY1_CONSOLE(source_object),
+ NULL, res, &err);
+ g_assert_no_error(err);
+
+ test->listener_conn = g_thread_join(test->thread);
+ test_dbus_console_setup_listener(test);
+}
+
+static gpointer
+test_dbus_p2p_server_setup_thread(gpointer data)
+{
+ return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data));
+}
+
+static void
+test_dbus_display_console(void)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL;
+ g_autoptr(GUnixFDList) fd_list = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ QTestState *qts = NULL;
+ int pair[2], idx;
+ TestDBusConsoleRegister test;
+
+ test_setup(&qts, &conn);
+
+ g_assert_cmpint(socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
+ fd_list = g_unix_fd_list_new();
+ idx = g_unix_fd_list_append(fd_list, pair[1], NULL);
+
+ console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY(
+ qemu_dbus_display1_console_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "/org/qemu/Display1/Console_0",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+ test.loop = loop = g_main_loop_new(NULL, FALSE);
+ test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
+ GINT_TO_POINTER(pair[0]));
+
+ qemu_dbus_display1_console_call_register_listener(
+ QEMU_DBUS_DISPLAY1_CONSOLE(console),
+ g_variant_new_handle(idx),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ fd_list,
+ NULL,
+ test_dbus_console_registered,
+ &test);
+
+ g_main_loop_run(loop);
+
+ g_clear_object(&test.server);
+ g_clear_object(&test.listener_conn);
+ qtest_quit(qts);
+}
+
+static void
+test_dbus_display_keyboard(void)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+ g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL;
+ QTestState *qts = NULL;
+
+ test_setup(&qts, &conn);
+
+ keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY(
+ qemu_dbus_display1_keyboard_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "/org/qemu/Display1/Console_0",
+ NULL,
+ &err));
+ g_assert_no_error(err);
+
+
+ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0);
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0);
+
+ qemu_dbus_display1_keyboard_call_press_sync(
+ QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
+ 0x1C, /* qnum enter */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+ g_assert_no_error(err);
+
+ /* may be should wait for interrupt? */
+ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
+
+ qemu_dbus_display1_keyboard_call_release_sync(
+ QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
+ 0x1C, /* qnum enter */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &err);
+ g_assert_no_error(err);
+
+ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */
+ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
+
+ g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers(
+ QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0);
+
+ qtest_quit(qts);
+}
+
+int
+main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/dbus-display/vm", test_dbus_display_vm);
+ qtest_add_func("/dbus-display/console", test_dbus_display_console);
+ qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/dbus-vmstate1.xml b/tests/qtest/dbus-vmstate1.xml
deleted file mode 100644
index cc8563be4c..0000000000
--- a/tests/qtest/dbus-vmstate1.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
- <interface name="org.qemu.VMState1">
- <property name="Id" type="s" access="read"/>
- <method name="Load">
- <arg type="ay" name="data" direction="in"/>
- </method>
- <method name="Save">
- <arg type="ay" name="data" direction="out"/>
- </method>
- </interface>
-</node>
diff --git a/tests/qtest/endianness-test.c b/tests/qtest/endianness-test.c
index 09ecb531f1..9c03b72dc9 100644
--- a/tests/qtest/endianness-test.c
+++ b/tests/qtest/endianness-test.c
@@ -281,7 +281,10 @@ int main(int argc, char **argv)
for (i = 0; test_cases[i].arch; i++) {
gchar *path;
- if (strcmp(test_cases[i].arch, arch) != 0) {
+
+ if (!g_str_equal(test_cases[i].arch, arch) ||
+ !qtest_has_machine(test_cases[i].machine) ||
+ (test_cases[i].superio && !qtest_has_device(test_cases[i].superio))) {
continue;
}
path = g_strdup_printf("endianness/%s",
diff --git a/tests/qtest/libqos/libqtest.h b/tests/qtest/libqos/libqtest.h
index dff6b31cf0..cf38d273f5 100644
--- a/tests/qtest/libqos/libqtest.h
+++ b/tests/qtest/libqos/libqtest.h
@@ -719,6 +719,14 @@ void qtest_cb_for_every_machine(void (*cb)(const char *machine),
bool qtest_has_machine(const char *machine);
/**
+ * qtest_has_device:
+ * @device: The device to look for
+ *
+ * Returns: true if the device is available in the target binary.
+ */
+bool qtest_has_device(const char *device);
+
+/**
* qtest_qmp_device_add_qdict:
* @qts: QTestState instance to operate on
* @drv: Name of the device that should be added
@@ -745,6 +753,16 @@ void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
const char *fmt, ...) GCC_FMT_ATTR(4, 5);
/**
+ * qtest_qmp_add_client:
+ * @qts: QTestState instance to operate on
+ * @protocol: the protocol to add to
+ * @fd: the client file-descriptor
+ *
+ * Call QMP ``getfd`` followed by ``add_client`` with the given @fd.
+ */
+void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd);
+
+/**
* qtest_qmp_device_del:
* @qts: QTestState instance to operate on
* @id: Identification string
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 65ed949685..41f4da4e54 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -1418,6 +1418,50 @@ bool qtest_has_machine(const char *machine)
return false;
}
+bool qtest_has_device(const char *device)
+{
+ static QList *list;
+ const QListEntry *p;
+ QObject *qobj;
+ QString *qstr;
+ QDict *devinfo;
+ int idx;
+
+ if (!list) {
+ QDict *resp;
+ QDict *args;
+ QTestState *qts = qtest_init("-machine none");
+
+ args = qdict_new();
+ qdict_put_bool(args, "abstract", false);
+ qdict_put_str(args, "implements", "device");
+
+ resp = qtest_qmp(qts, "{'execute': 'qom-list-types', 'arguments': %p }",
+ args);
+ g_assert(qdict_haskey(resp, "return"));
+ list = qdict_get_qlist(resp, "return");
+ qobject_ref(list);
+ qobject_unref(resp);
+
+ qtest_quit(qts);
+ }
+
+ for (p = qlist_first(list), idx = 0; p; p = qlist_next(p), idx++) {
+ devinfo = qobject_to(QDict, qlist_entry_obj(p));
+ g_assert(devinfo);
+
+ qobj = qdict_get(devinfo, "name");
+ g_assert(qobj);
+ qstr = qobject_to(QString, qobj);
+ g_assert(qstr);
+ if (g_str_equal(qstring_get_str(qstr), device)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
/*
* Generic hot-plugging test via the device_add QMP commands.
*/
@@ -1453,6 +1497,25 @@ void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
qobject_unref(args);
}
+void qtest_qmp_add_client(QTestState *qts, const char *protocol, int fd)
+{
+ QDict *resp;
+
+ resp = qtest_qmp_fds(qts, &fd, 1, "{'execute': 'getfd',"
+ "'arguments': {'fdname': 'fdname'}}");
+ g_assert(resp);
+ g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+ g_assert(!qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+
+ resp = qtest_qmp(
+ qts, "{'execute': 'add_client',"
+ "'arguments': {'protocol': %s, 'fdname': 'fdname'}}", protocol);
+ g_assert(resp);
+ g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+ g_assert(!qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
/*
* Generic hot-unplugging test via the device_del QMP command.
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index ebeac59b3f..37e1eaa449 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -47,7 +47,6 @@ qtests_i386 = \
(have_tools ? ['ahci-test'] : []) + \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
(config_all_devices.has_key('CONFIG_SGA') ? ['boot-serial-test'] : []) + \
- (config_all_devices.has_key('CONFIG_RTL8139_PCI') ? ['test-filter-redirector'] : []) + \
(config_all_devices.has_key('CONFIG_ISA_IPMI_KCS') ? ['ipmi-kcs-test'] : []) + \
(config_host.has_key('CONFIG_LINUX') and \
config_all_devices.has_key('CONFIG_ISA_IPMI_BT') ? ['ipmi-bt-test'] : []) + \
@@ -90,7 +89,13 @@ qtests_i386 = \
'vmgenid-test',
'migration-test',
'test-x86-cpuid-compat',
- 'numa-test']
+ 'numa-test',
+ 'test-filter-redirector'
+ ]
+
+if dbus_display
+ qtests_i386 += ['dbus-display-test']
+endif
dbus_daemon = find_program('dbus-daemon', required: false)
if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
@@ -98,7 +103,7 @@ if dbus_daemon.found() and config_host.has_key('GDBUS_CODEGEN')
#qtests_i386 += ['dbus-vmstate-test']
dbus_vmstate1 = custom_target('dbus-vmstate description',
output: ['dbus-vmstate1.h', 'dbus-vmstate1.c'],
- input: files('dbus-vmstate1.xml'),
+ input: meson.source_root() / 'backends/dbus-vmstate1.xml',
command: [config_host['GDBUS_CODEGEN'],
'@INPUT@',
'--interface-prefix', 'org.qemu',
@@ -109,31 +114,49 @@ endif
qtests_x86_64 = qtests_i386
-qtests_alpha = [ 'boot-serial-test' ] + \
+qtests_alpha = ['boot-serial-test'] + \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
(config_all_devices.has_key('CONFIG_VGA') ? ['display-vga-test'] : [])
qtests_avr = [ 'boot-serial-test' ]
-qtests_hppa = [ 'boot-serial-test' ] + \
+qtests_hppa = ['boot-serial-test'] + \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
(config_all_devices.has_key('CONFIG_VGA') ? ['display-vga-test'] : [])
-qtests_m68k = [ 'boot-serial-test' ]
-qtests_microblaze = [ 'boot-serial-test' ]
+qtests_m68k = ['boot-serial-test'] + \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : [])
+
+qtests_microblaze = ['boot-serial-test'] + \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : [])
+
qtests_microblazeel = qtests_microblaze
qtests_mips = \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
(config_all_devices.has_key('CONFIG_VGA') ? ['display-vga-test'] : [])
qtests_mips64 = \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
(config_all_devices.has_key('CONFIG_VGA') ? ['display-vga-test'] : [])
qtests_mips64el = \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
(config_all_devices.has_key('CONFIG_VGA') ? ['display-vga-test'] : [])
qtests_ppc = \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
(config_all_devices.has_key('CONFIG_M48T59') ? ['m48t59-test'] : []) + \
['boot-order-test', 'prom-env-test', 'boot-serial-test'] \
@@ -143,19 +166,22 @@ qtests_ppc64 = \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['device-plug-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-xscom-test'] : []) + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['rtas-test'] : []) + \
- (slirp.found() ? ['pxe-test', 'test-netfilter'] : []) + \
+ (slirp.found() ? ['pxe-test'] : []) + \
(config_all_devices.has_key('CONFIG_USB_UHCI') ? ['usb-hcd-uhci-test'] : []) + \
(config_all_devices.has_key('CONFIG_USB_XHCI_NEC') ? ['usb-hcd-xhci-test'] : []) + \
- (config_host.has_key('CONFIG_POSIX') ? ['test-filter-mirror'] : []) + \
qtests_pci + ['migration-test', 'numa-test', 'cpu-plug-test', 'drive_del-test']
qtests_sh4 = (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : [])
qtests_sh4eb = (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : [])
-qtests_sparc = ['prom-env-test', 'm48t59-test', 'boot-serial-test']
+qtests_sparc = ['prom-env-test', 'm48t59-test', 'boot-serial-test'] + \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
+ (slirp.found() ? ['test-netfilter'] : [])
qtests_sparc64 = \
(config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \
+ (slirp.found() ? ['test-netfilter'] : []) + \
+ ['test-filter-mirror', 'test-filter-redirector'] + \
['prom-env-test', 'boot-serial-test']
qtests_npcm7xx = \
@@ -265,6 +291,10 @@ qtests = {
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
}
+if dbus_display
+qtests += {'dbus-display-test': [dbus_display1, gio]}
+endif
+
qtest_executables = {}
foreach dir : target_dirs
if not dir.endswith('-softmmu')
diff --git a/tests/qtest/test-filter-mirror.c b/tests/qtest/test-filter-mirror.c
index bc0dee64dd..95367d14d3 100644
--- a/tests/qtest/test-filter-mirror.c
+++ b/tests/qtest/test-filter-mirror.c
@@ -28,13 +28,8 @@ static void test_mirror(void)
char *recv_buf;
uint32_t size = sizeof(send_buf);
size = htonl(size);
- const char *devstr = "e1000";
QTestState *qts;
- if (g_str_equal(qtest_get_arch(), "s390x")) {
- devstr = "virtio-net-ccw";
- }
-
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, send_sock);
g_assert_cmpint(ret, !=, -1);
@@ -42,11 +37,10 @@ static void test_mirror(void)
g_assert_cmpint(ret, !=, -1);
qts = qtest_initf(
- "-netdev socket,id=qtest-bn0,fd=%d "
- "-device %s,netdev=qtest-bn0,id=qtest-e0 "
+ "-nic socket,id=qtest-bn0,fd=%d "
"-chardev socket,id=mirror0,fd=%d "
"-object filter-mirror,id=qtest-f0,netdev=qtest-bn0,queue=tx,outdev=mirror0 "
- , send_sock[1], devstr, recv_sock[1]);
+ , send_sock[1], recv_sock[1]);
struct iovec iov[] = {
{
diff --git a/tests/qtest/test-filter-redirector.c b/tests/qtest/test-filter-redirector.c
index 4269b2cdd9..4f3f59cba8 100644
--- a/tests/qtest/test-filter-redirector.c
+++ b/tests/qtest/test-filter-redirector.c
@@ -62,16 +62,6 @@
/* TODO actually test the results and get rid of this */
#define qmp_discard_response(qs, ...) qobject_unref(qtest_qmp(qs, __VA_ARGS__))
-static const char *get_devstr(void)
-{
- if (g_str_equal(qtest_get_arch(), "s390x")) {
- return "virtio-net-ccw";
- }
-
- return "rtl8139";
-}
-
-
static void test_redirector_tx(void)
{
int backend_sock[2], recv_sock;
@@ -93,8 +83,7 @@ static void test_redirector_tx(void)
g_assert_cmpint(ret, !=, -1);
qts = qtest_initf(
- "-netdev socket,id=qtest-bn0,fd=%d "
- "-device %s,netdev=qtest-bn0,id=qtest-e0 "
+ "-nic socket,id=qtest-bn0,fd=%d "
"-chardev socket,id=redirector0,path=%s,server=on,wait=off "
"-chardev socket,id=redirector1,path=%s,server=on,wait=off "
"-chardev socket,id=redirector2,path=%s "
@@ -103,7 +92,7 @@ static void test_redirector_tx(void)
"-object filter-redirector,id=qtest-f1,netdev=qtest-bn0,"
"queue=tx,indev=redirector2 "
"-object filter-redirector,id=qtest-f2,netdev=qtest-bn0,"
- "queue=tx,outdev=redirector1 ", backend_sock[1], get_devstr(),
+ "queue=tx,outdev=redirector1 ", backend_sock[1],
sock_path0, sock_path1, sock_path0);
recv_sock = unix_connect(sock_path1, NULL);
@@ -163,8 +152,7 @@ static void test_redirector_rx(void)
g_assert_cmpint(ret, !=, -1);
qts = qtest_initf(
- "-netdev socket,id=qtest-bn0,fd=%d "
- "-device %s,netdev=qtest-bn0,id=qtest-e0 "
+ "-nic socket,id=qtest-bn0,fd=%d "
"-chardev socket,id=redirector0,path=%s,server=on,wait=off "
"-chardev socket,id=redirector1,path=%s,server=on,wait=off "
"-chardev socket,id=redirector2,path=%s "
@@ -173,7 +161,7 @@ static void test_redirector_rx(void)
"-object filter-redirector,id=qtest-f1,netdev=qtest-bn0,"
"queue=rx,outdev=redirector2 "
"-object filter-redirector,id=qtest-f2,netdev=qtest-bn0,"
- "queue=rx,indev=redirector1 ", backend_sock[1], get_devstr(),
+ "queue=rx,indev=redirector1 ", backend_sock[1],
sock_path0, sock_path1, sock_path0);
struct iovec iov[] = {
diff --git a/tests/qtest/test-netfilter.c b/tests/qtest/test-netfilter.c
index 785b6f3226..b09ef7fae9 100644
--- a/tests/qtest/test-netfilter.c
+++ b/tests/qtest/test-netfilter.c
@@ -178,11 +178,6 @@ int main(int argc, char **argv)
{
int ret;
char *args;
- const char *devstr = "e1000";
-
- if (g_str_equal(qtest_get_arch(), "s390x")) {
- devstr = "virtio-net-ccw";
- }
g_test_init(&argc, &argv, NULL);
qtest_add_func("/netfilter/addremove_one", add_one_netfilter);
@@ -192,8 +187,7 @@ int main(int argc, char **argv)
qtest_add_func("/netfilter/remove_netdev_multi",
remove_netdev_with_multi_netfilter);
- args = g_strdup_printf("-netdev user,id=qtest-bn0 "
- "-device %s,netdev=qtest-bn0", devstr);
+ args = g_strdup_printf("-nic user,id=qtest-bn0");
qtest_start(args);
ret = g_test_run();
diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c
index 4b2ba8a106..22ad54bb95 100644
--- a/tests/qtest/virtio-net-failover.c
+++ b/tests/qtest/virtio-net-failover.c
@@ -1306,13 +1306,15 @@ static void test_multi_in(gconstpointer opaque)
int main(int argc, char **argv)
{
- const gchar *tmpdir = g_get_tmp_dir();
- gchar *tmpfile = g_strdup_printf("%s/failover_test_migrate-%u-%u",
- tmpdir, getpid(), g_test_rand_int());
+ gchar *tmpfile;
int ret;
g_test_init(&argc, &argv, NULL);
+ ret = g_file_open_tmp("failover_test_migrate-XXXXXX", &tmpfile, NULL);
+ g_assert_true(ret >= 0);
+ close(ret);
+
qtest_add_func("failover-virtio-net/params/error/id", test_error_id);
qtest_add_func("failover-virtio-net/params/error/pcie", test_error_pcie);
qtest_add_func("failover-virtio-net/params/on", test_on);
diff --git a/ui/clipboard.c b/ui/clipboard.c
index d7b008d62a..82572ea116 100644
--- a/ui/clipboard.c
+++ b/ui/clipboard.c
@@ -8,7 +8,7 @@ static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
{
- notifier_list_add(&clipboard_notifiers, &peer->update);
+ notifier_list_add(&clipboard_notifiers, &peer->notifier);
}
void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
@@ -18,8 +18,7 @@ void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
qemu_clipboard_peer_release(peer, i);
}
-
- notifier_remove(&peer->update);
+ notifier_remove(&peer->notifier);
}
bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
@@ -42,12 +41,32 @@ void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
}
}
+bool qemu_clipboard_check_serial(QemuClipboardInfo *info, bool client)
+{
+ if (!info->has_serial ||
+ !cbinfo[info->selection] ||
+ !cbinfo[info->selection]->has_serial) {
+ return true;
+ }
+
+ if (client) {
+ return cbinfo[info->selection]->serial >= info->serial;
+ } else {
+ return cbinfo[info->selection]->serial > info->serial;
+ }
+}
+
void qemu_clipboard_update(QemuClipboardInfo *info)
{
+ QemuClipboardNotify notify = {
+ .type = QEMU_CLIPBOARD_UPDATE_INFO,
+ .info = info,
+ };
g_autoptr(QemuClipboardInfo) old = NULL;
+
assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
- notifier_list_notify(&clipboard_notifiers, info);
+ notifier_list_notify(&clipboard_notifiers, &notify);
old = cbinfo[info->selection];
cbinfo[info->selection] = qemu_clipboard_info_ref(info);
@@ -110,6 +129,13 @@ void qemu_clipboard_request(QemuClipboardInfo *info,
info->owner->request(info, type);
}
+void qemu_clipboard_reset_serial(void)
+{
+ QemuClipboardNotify notify = { .type = QEMU_CLIPBOARD_RESET_SERIAL };
+
+ notifier_list_notify(&clipboard_notifiers, &notify);
+}
+
void qemu_clipboard_set_data(QemuClipboardPeer *peer,
QemuClipboardInfo *info,
QemuClipboardType type,
diff --git a/ui/cocoa.m b/ui/cocoa.m
index 68a6302184..69745c483b 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -552,7 +552,7 @@ QemuCocoaView *cocoaView;
info.width = frameSize.width;
info.height = frameSize.height;
- dpy_set_ui_info(dcl.con, &info);
+ dpy_set_ui_info(dcl.con, &info, TRUE);
}
- (void)viewDidMoveToWindow
@@ -1808,14 +1808,12 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
static QemuClipboardPeer cbpeer = {
.name = "cocoa",
- .update = { .notify = cocoa_clipboard_notify },
+ .notifier = { .notify = cocoa_clipboard_notify },
.request = cocoa_clipboard_request
};
-static void cocoa_clipboard_notify(Notifier *notifier, void *data)
+static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
{
- QemuClipboardInfo *info = data;
-
if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
return;
}
@@ -1831,6 +1829,20 @@ static void cocoa_clipboard_notify(Notifier *notifier, void *data)
qemu_event_set(&cbevent);
}
+static void cocoa_clipboard_notify(Notifier *notifier, void *data)
+{
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ cocoa_clipboard_update_info(notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ /* ignore */
+ return;
+ }
+}
+
static void cocoa_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType type)
{
diff --git a/ui/console.c b/ui/console.c
index 29a3e3f0f5..40eebb6d2c 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -77,9 +77,11 @@ struct QemuConsole {
console_type_t console_type;
DisplayState *ds;
DisplaySurface *surface;
+ DisplayScanout scanout;
int dcls;
- DisplayChangeListener *gl;
- bool gl_block;
+ DisplayGLCtx *gl;
+ int gl_block;
+ QEMUTimer *gl_unblock_timer;
int window_id;
/* Graphic console state. */
@@ -145,6 +147,7 @@ static void dpy_refresh(DisplayState *s);
static DisplayState *get_alloc_displaystate(void);
static void text_console_update_cursor_timer(void);
static void text_console_update_cursor(void *opaque);
+static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl);
static void gui_update(void *opaque)
{
@@ -233,22 +236,36 @@ void graphic_hw_update(QemuConsole *con)
}
}
-void graphic_hw_gl_block(QemuConsole *con, bool block)
+static void graphic_hw_gl_unblock_timer(void *opaque)
{
- assert(con != NULL);
-
- con->gl_block = block;
- if (con->hw_ops->gl_block) {
- con->hw_ops->gl_block(con->hw, block);
- }
+ warn_report("console: no gl-unblock within one second");
}
-void graphic_hw_gl_flushed(QemuConsole *con)
+void graphic_hw_gl_block(QemuConsole *con, bool block)
{
+ uint64_t timeout;
assert(con != NULL);
- if (con->hw_ops->gl_flushed) {
- con->hw_ops->gl_flushed(con->hw);
+ if (block) {
+ con->gl_block++;
+ } else {
+ con->gl_block--;
+ }
+ assert(con->gl_block >= 0);
+ if (!con->hw_ops->gl_block) {
+ return;
+ }
+ if ((block && con->gl_block != 1) || (!block && con->gl_block != 0)) {
+ return;
+ }
+ con->hw_ops->gl_block(con->hw, block);
+
+ if (block) {
+ timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+ timeout += 1000; /* one sec */
+ timer_mod(con->gl_unblock_timer, timeout);
+ } else {
+ timer_del(con->gl_unblock_timer);
}
}
@@ -466,6 +483,8 @@ static void text_console_resize(QemuConsole *s)
TextCell *cells, *c, *c1;
int w1, x, y, last_width;
+ assert(s->scanout.kind == SCANOUT_SURFACE);
+
last_width = s->width;
s->width = surface_width(s->surface) / FONT_WIDTH;
s->height = surface_height(s->surface) / FONT_HEIGHT;
@@ -1037,6 +1056,48 @@ static void console_putchar(QemuConsole *s, int ch)
}
}
+static void displaychangelistener_display_console(DisplayChangeListener *dcl,
+ QemuConsole *con)
+{
+ static const char nodev[] =
+ "This VM has no graphic display device.";
+ static DisplaySurface *dummy;
+
+ if (!con) {
+ if (!dcl->ops->dpy_gfx_switch) {
+ return;
+ }
+ if (!dummy) {
+ dummy = qemu_create_placeholder_surface(640, 480, nodev);
+ }
+ dcl->ops->dpy_gfx_switch(dcl, dummy);
+ return;
+ }
+
+ if (con->scanout.kind == SCANOUT_DMABUF &&
+ displaychangelistener_has_dmabuf(dcl)) {
+ dcl->ops->dpy_gl_scanout_dmabuf(dcl, con->scanout.dmabuf);
+ } else if (con->scanout.kind == SCANOUT_TEXTURE &&
+ dcl->ops->dpy_gl_scanout_texture) {
+ dcl->ops->dpy_gl_scanout_texture(dcl,
+ con->scanout.texture.backing_id,
+ con->scanout.texture.backing_y_0_top,
+ con->scanout.texture.backing_width,
+ con->scanout.texture.backing_height,
+ con->scanout.texture.x,
+ con->scanout.texture.y,
+ con->scanout.texture.width,
+ con->scanout.texture.height);
+ } else if (con->scanout.kind == SCANOUT_SURFACE &&
+ dcl->ops->dpy_gfx_switch) {
+ dcl->ops->dpy_gfx_switch(dcl, con->surface);
+ }
+
+ dcl->ops->dpy_gfx_update(dcl, 0, 0,
+ qemu_console_get_width(con, 0),
+ qemu_console_get_height(con, 0));
+}
+
void console_select(unsigned int index)
{
DisplayChangeListener *dcl;
@@ -1053,13 +1114,7 @@ void console_select(unsigned int index)
if (dcl->con != NULL) {
continue;
}
- if (dcl->ops->dpy_gfx_switch) {
- dcl->ops->dpy_gfx_switch(dcl, s->surface);
- }
- }
- if (s->surface) {
- dpy_gfx_update(s, 0, 0, surface_width(s->surface),
- surface_height(s->surface));
+ displaychangelistener_display_console(dcl, s);
}
}
if (ds->have_text) {
@@ -1443,24 +1498,36 @@ static bool dpy_compatible_with(QemuConsole *con,
return true;
}
+void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl)
+{
+ /* display has opengl support */
+ assert(con);
+ if (con->gl) {
+ error_report("The console already has an OpenGL context.");
+ exit(1);
+ }
+ con->gl = gl;
+}
+
+static bool dpy_gl_compatible_with(QemuConsole *con, DisplayChangeListener *dcl)
+{
+ if (!con->gl) {
+ return true;
+ }
+
+ return con->gl->ops->compatible_dcl == dcl->ops;
+}
+
void register_displaychangelistener(DisplayChangeListener *dcl)
{
- static const char nodev[] =
- "This VM has no graphic display device.";
- static DisplaySurface *dummy;
QemuConsole *con;
assert(!dcl->ds);
- if (dcl->ops->dpy_gl_ctx_create) {
- /* display has opengl support */
- assert(dcl->con);
- if (dcl->con->gl) {
- fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
- dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
- exit(1);
- }
- dcl->con->gl = dcl;
+ if (dcl->con && !dpy_gl_compatible_with(dcl->con, dcl)) {
+ error_report("Display %s is incompatible with the GL context",
+ dcl->ops->dpy_name);
+ exit(1);
}
if (dcl->con) {
@@ -1477,16 +1544,7 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
} else {
con = active_console;
}
- if (dcl->ops->dpy_gfx_switch) {
- if (con) {
- dcl->ops->dpy_gfx_switch(dcl, con->surface);
- } else {
- if (!dummy) {
- dummy = qemu_create_placeholder_surface(640, 480, nodev);
- }
- dcl->ops->dpy_gfx_switch(dcl, dummy);
- }
- }
+ displaychangelistener_display_console(dcl, con);
text_console_update_cursor(NULL);
}
@@ -1538,7 +1596,7 @@ const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
return &con->ui_info;
}
-int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info, bool delay)
{
if (con == NULL) {
con = active_console;
@@ -1558,7 +1616,8 @@ int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
* go notify the guest.
*/
con->ui_info = *info;
- timer_mod(con->ui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
+ timer_mod(con->ui_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + (delay ? 1000 : 0));
return 0;
}
@@ -1566,13 +1625,9 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
{
DisplayState *s = con->ds;
DisplayChangeListener *dcl;
- int width = w;
- int height = h;
+ int width = qemu_console_get_width(con, x + w);
+ int height = qemu_console_get_height(con, y + h);
- if (con->surface) {
- width = surface_width(con->surface);
- height = surface_height(con->surface);
- }
x = MAX(x, 0);
y = MAX(y, 0);
x = MIN(x, width);
@@ -1595,12 +1650,10 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
void dpy_gfx_update_full(QemuConsole *con)
{
- if (!con->surface) {
- return;
- }
- dpy_gfx_update(con, 0, 0,
- surface_width(con->surface),
- surface_height(con->surface));
+ int w = qemu_console_get_width(con, 0);
+ int h = qemu_console_get_height(con, 0);
+
+ dpy_gfx_update(con, 0, 0, w, h);
}
void dpy_gfx_replace_surface(QemuConsole *con,
@@ -1627,6 +1680,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
assert(old_surface != surface);
+ con->scanout.kind = SCANOUT_SURFACE;
con->surface = surface;
QLIST_FOREACH(dcl, &s->listeners, next) {
if (con != (dcl->con ? dcl->con : active_console)) {
@@ -1799,8 +1853,15 @@ int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx)
void dpy_gl_scanout_disable(QemuConsole *con)
{
- assert(con->gl);
- con->gl->ops->dpy_gl_scanout_disable(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (con->scanout.kind != SCANOUT_SURFACE) {
+ con->scanout.kind = SCANOUT_NONE;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_scanout_disable(dcl);
+ }
}
void dpy_gl_scanout_texture(QemuConsole *con,
@@ -1811,56 +1872,88 @@ void dpy_gl_scanout_texture(QemuConsole *con,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height)
{
- assert(con->gl);
- con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id,
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ con->scanout.kind = SCANOUT_TEXTURE;
+ con->scanout.texture = (ScanoutTexture) {
+ backing_id, backing_y_0_top, backing_width, backing_height,
+ x, y, width, height
+ };
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
backing_y_0_top,
backing_width, backing_height,
x, y, width, height);
+ }
}
void dpy_gl_scanout_dmabuf(QemuConsole *con,
QemuDmaBuf *dmabuf)
{
- assert(con->gl);
- con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ con->scanout.kind = SCANOUT_DMABUF;
+ con->scanout.dmabuf = dmabuf;
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
+ }
}
void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
bool have_hot, uint32_t hot_x, uint32_t hot_y)
{
- assert(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
- if (con->gl->ops->dpy_gl_cursor_dmabuf) {
- con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf,
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_gl_cursor_dmabuf) {
+ dcl->ops->dpy_gl_cursor_dmabuf(dcl, dmabuf,
have_hot, hot_x, hot_y);
+ }
}
}
void dpy_gl_cursor_position(QemuConsole *con,
uint32_t pos_x, uint32_t pos_y)
{
- assert(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
- if (con->gl->ops->dpy_gl_cursor_position) {
- con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y);
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_gl_cursor_position) {
+ dcl->ops->dpy_gl_cursor_position(dcl, pos_x, pos_y);
+ }
}
}
void dpy_gl_release_dmabuf(QemuConsole *con,
QemuDmaBuf *dmabuf)
{
- assert(con->gl);
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
- if (con->gl->ops->dpy_gl_release_dmabuf) {
- con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf);
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_gl_release_dmabuf) {
+ dcl->ops->dpy_gl_release_dmabuf(dcl, dmabuf);
+ }
}
}
void dpy_gl_update(QemuConsole *con,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
assert(con->gl);
- con->gl->ops->dpy_gl_update(con->gl, x, y, w, h);
+
+ graphic_hw_gl_block(con, true);
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ dcl->ops->dpy_gl_update(dcl, x, y, w, h);
+ }
+ graphic_hw_gl_block(con, false);
}
/***********************************************************/
@@ -1929,10 +2022,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
s = qemu_console_lookup_unused();
if (s) {
trace_console_gfx_reuse(s->index);
- if (s->surface) {
- width = surface_width(s->surface);
- height = surface_height(s->surface);
- }
+ width = qemu_console_get_width(s, 0);
+ height = qemu_console_get_height(s, 0);
} else {
trace_console_gfx_new();
s = new_console(ds, GRAPHIC_CONSOLE, head);
@@ -1947,6 +2038,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
surface = qemu_create_placeholder_surface(width, height, noinit);
dpy_gfx_replace_surface(s, surface);
+ s->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ graphic_hw_gl_unblock_timer, s);
return s;
}
@@ -1959,13 +2052,8 @@ void graphic_console_close(QemuConsole *con)
static const char unplugged[] =
"Guest display has been unplugged";
DisplaySurface *surface;
- int width = 640;
- int height = 480;
-
- if (con->surface) {
- width = surface_width(con->surface);
- height = surface_height(con->surface);
- }
+ int width = qemu_console_get_width(con, 640);
+ int height = qemu_console_get_height(con, 480);
trace_console_gfx_close(con->index);
object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
@@ -2117,7 +2205,19 @@ int qemu_console_get_width(QemuConsole *con, int fallback)
if (con == NULL) {
con = active_console;
}
- return con ? surface_width(con->surface) : fallback;
+ if (con == NULL) {
+ return fallback;
+ }
+ switch (con->scanout.kind) {
+ case SCANOUT_DMABUF:
+ return con->scanout.dmabuf->width;
+ case SCANOUT_TEXTURE:
+ return con->scanout.texture.width;
+ case SCANOUT_SURFACE:
+ return surface_width(con->surface);
+ default:
+ return fallback;
+ }
}
int qemu_console_get_height(QemuConsole *con, int fallback)
@@ -2125,7 +2225,19 @@ int qemu_console_get_height(QemuConsole *con, int fallback)
if (con == NULL) {
con = active_console;
}
- return con ? surface_height(con->surface) : fallback;
+ if (con == NULL) {
+ return fallback;
+ }
+ switch (con->scanout.kind) {
+ case SCANOUT_DMABUF:
+ return con->scanout.dmabuf->height;
+ case SCANOUT_TEXTURE:
+ return con->scanout.texture.height;
+ case SCANOUT_SURFACE:
+ return surface_height(con->surface);
+ default:
+ return fallback;
+ }
}
static void vc_chr_accept_input(Chardev *chr)
@@ -2191,12 +2303,13 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds)
s->total_height = DEFAULT_BACKSCROLL;
s->x = 0;
s->y = 0;
- if (!s->surface) {
- if (active_console && active_console->surface) {
- g_width = surface_width(active_console->surface);
- g_height = surface_height(active_console->surface);
+ if (s->scanout.kind != SCANOUT_SURFACE) {
+ if (active_console && active_console->scanout.kind == SCANOUT_SURFACE) {
+ g_width = qemu_console_get_width(active_console, g_width);
+ g_height = qemu_console_get_height(active_console, g_height);
}
s->surface = qemu_create_displaysurface(g_width, g_height);
+ s->scanout.kind = SCANOUT_SURFACE;
}
s->hw_ops = &text_console_ops;
@@ -2255,6 +2368,7 @@ static void vc_chr_open(Chardev *chr,
s = new_console(NULL, TEXT_CONSOLE, 0);
} else {
s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
+ s->scanout.kind = SCANOUT_SURFACE;
s->surface = qemu_create_displaysurface(width, height);
}
@@ -2278,13 +2392,13 @@ static void vc_chr_open(Chardev *chr,
void qemu_console_resize(QemuConsole *s, int width, int height)
{
- DisplaySurface *surface;
+ DisplaySurface *surface = qemu_console_surface(s);
assert(s->console_type == GRAPHIC_CONSOLE);
- if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
- pixman_image_get_width(s->surface->image) == width &&
- pixman_image_get_height(s->surface->image) == height) {
+ if (surface && (surface->flags & QEMU_ALLOCATED_FLAG) &&
+ pixman_image_get_width(surface->image) == width &&
+ pixman_image_get_height(surface->image) == height) {
return;
}
@@ -2294,7 +2408,12 @@ void qemu_console_resize(QemuConsole *s, int width, int height)
DisplaySurface *qemu_console_surface(QemuConsole *console)
{
- return console->surface;
+ switch (console->scanout.kind) {
+ case SCANOUT_SURFACE:
+ return console->surface;
+ default:
+ return NULL;
+ }
}
PixelFormat qemu_default_pixelformat(int bpp)
diff --git a/ui/dbus-chardev.c b/ui/dbus-chardev.c
new file mode 100644
index 0000000000..940ef937cd
--- /dev/null
+++ b/ui/dbus-chardev.c
@@ -0,0 +1,296 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "dbus.h"
+
+static char *
+dbus_display_chardev_path(DBusChardev *chr)
+{
+ return g_strdup_printf(DBUS_DISPLAY1_ROOT "/Chardev_%s",
+ CHARDEV(chr)->label);
+}
+
+static void
+dbus_display_chardev_export(DBusDisplay *dpy, DBusChardev *chr)
+{
+ g_autoptr(GDBusObjectSkeleton) sk = NULL;
+ g_autofree char *path = dbus_display_chardev_path(chr);
+
+ if (chr->exported) {
+ return;
+ }
+
+ sk = g_dbus_object_skeleton_new(path);
+ g_dbus_object_skeleton_add_interface(
+ sk, G_DBUS_INTERFACE_SKELETON(chr->iface));
+ g_dbus_object_manager_server_export(dpy->server, sk);
+ chr->exported = true;
+}
+
+static void
+dbus_display_chardev_unexport(DBusDisplay *dpy, DBusChardev *chr)
+{
+ g_autofree char *path = dbus_display_chardev_path(chr);
+
+ if (!chr->exported) {
+ return;
+ }
+
+ g_dbus_object_manager_server_unexport(dpy->server, path);
+ chr->exported = false;
+}
+
+static int
+dbus_display_chardev_foreach(Object *obj, void *data)
+{
+ DBusDisplay *dpy = DBUS_DISPLAY(data);
+
+ if (!CHARDEV_IS_DBUS(obj)) {
+ return 0;
+ }
+
+ dbus_display_chardev_export(dpy, DBUS_CHARDEV(obj));
+
+ return 0;
+}
+
+static void
+dbus_display_on_notify(Notifier *notifier, void *data)
+{
+ DBusDisplay *dpy = container_of(notifier, DBusDisplay, notifier);
+ DBusDisplayEvent *event = data;
+
+ switch (event->type) {
+ case DBUS_DISPLAY_CHARDEV_OPEN:
+ dbus_display_chardev_export(dpy, event->chardev);
+ break;
+ case DBUS_DISPLAY_CHARDEV_CLOSE:
+ dbus_display_chardev_unexport(dpy, event->chardev);
+ break;
+ }
+}
+
+void
+dbus_chardev_init(DBusDisplay *dpy)
+{
+ dpy->notifier.notify = dbus_display_on_notify;
+ dbus_display_notifier_add(&dpy->notifier);
+
+ object_child_foreach(container_get(object_get_root(), "/chardevs"),
+ dbus_display_chardev_foreach, dpy);
+}
+
+static gboolean
+dbus_chr_register(
+ DBusChardev *dc,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_stream,
+ QemuDBusDisplay1Chardev *object)
+{
+ g_autoptr(GError) err = NULL;
+ int fd;
+
+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_stream), &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't get peer FD: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (qemu_chr_add_client(CHARDEV(dc), fd) < 0) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't register FD!");
+ close(fd);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_object_set(dc->iface,
+ "owner", g_dbus_method_invocation_get_sender(invocation),
+ NULL);
+
+ qemu_dbus_display1_chardev_complete_register(object, invocation, NULL);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_chr_send_break(
+ DBusChardev *dc,
+ GDBusMethodInvocation *invocation,
+ QemuDBusDisplay1Chardev *object)
+{
+ qemu_chr_be_event(CHARDEV(dc), CHR_EVENT_BREAK);
+
+ qemu_dbus_display1_chardev_complete_send_break(object, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_chr_open(Chardev *chr, ChardevBackend *backend,
+ bool *be_opened, Error **errp)
+{
+ ERRP_GUARD();
+
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+ DBusDisplayEvent event = {
+ .type = DBUS_DISPLAY_CHARDEV_OPEN,
+ .chardev = dc,
+ };
+ g_autoptr(ChardevBackend) be = NULL;
+ g_autoptr(QemuOpts) opts = NULL;
+
+ dc->iface = qemu_dbus_display1_chardev_skeleton_new();
+ g_object_set(dc->iface, "name", backend->u.dbus.data->name, NULL);
+ g_object_connect(dc->iface,
+ "swapped-signal::handle-register",
+ dbus_chr_register, dc,
+ "swapped-signal::handle-send-break",
+ dbus_chr_send_break, dc,
+ NULL);
+
+ dbus_display_notify(&event);
+
+ be = g_new0(ChardevBackend, 1);
+ opts = qemu_opts_create(qemu_find_opts("chardev"), NULL, 0, &error_abort);
+ qemu_opt_set(opts, "server", "on", &error_abort);
+ qemu_opt_set(opts, "wait", "off", &error_abort);
+ CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->parse(
+ opts, be, errp);
+ if (*errp) {
+ return;
+ }
+ CHARDEV_CLASS(object_class_by_name(TYPE_CHARDEV_SOCKET))->open(
+ chr, be, be_opened, errp);
+}
+
+static void
+dbus_chr_set_fe_open(Chardev *chr, int fe_open)
+{
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+
+ g_object_set(dc->iface, "feopened", fe_open, NULL);
+}
+
+static void
+dbus_chr_set_echo(Chardev *chr, bool echo)
+{
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+
+ g_object_set(dc->iface, "echo", echo, NULL);
+}
+
+static void
+dbus_chr_be_event(Chardev *chr, QEMUChrEvent event)
+{
+ DBusChardev *dc = DBUS_CHARDEV(chr);
+ DBusChardevClass *klass = DBUS_CHARDEV_GET_CLASS(chr);
+
+ switch (event) {
+ case CHR_EVENT_CLOSED:
+ if (dc->iface) {
+ /* on finalize, iface is set to NULL */
+ g_object_set(dc->iface, "owner", "", NULL);
+ }
+ break;
+ default:
+ break;
+ };
+
+ klass->parent_chr_be_event(chr, event);
+}
+
+static void
+dbus_chr_parse(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *name = qemu_opt_get(opts, "name");
+ ChardevDBus *dbus;
+
+ if (name == NULL) {
+ error_setg(errp, "chardev: dbus: no name given");
+ return;
+ }
+
+ backend->type = CHARDEV_BACKEND_KIND_DBUS;
+ dbus = backend->u.dbus.data = g_new0(ChardevDBus, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevDBus_base(dbus));
+ dbus->name = g_strdup(name);
+}
+
+static void
+char_dbus_class_init(ObjectClass *oc, void *data)
+{
+ DBusChardevClass *klass = DBUS_CHARDEV_CLASS(oc);
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = dbus_chr_parse;
+ cc->open = dbus_chr_open;
+ cc->chr_set_fe_open = dbus_chr_set_fe_open;
+ cc->chr_set_echo = dbus_chr_set_echo;
+ klass->parent_chr_be_event = cc->chr_be_event;
+ cc->chr_be_event = dbus_chr_be_event;
+}
+
+static void
+char_dbus_finalize(Object *obj)
+{
+ DBusChardev *dc = DBUS_CHARDEV(obj);
+ DBusDisplayEvent event = {
+ .type = DBUS_DISPLAY_CHARDEV_CLOSE,
+ .chardev = dc,
+ };
+
+ dbus_display_notify(&event);
+ g_clear_object(&dc->iface);
+}
+
+static const TypeInfo char_dbus_type_info = {
+ .name = TYPE_CHARDEV_DBUS,
+ .parent = TYPE_CHARDEV_SOCKET,
+ .class_size = sizeof(DBusChardevClass),
+ .instance_size = sizeof(DBusChardev),
+ .instance_finalize = char_dbus_finalize,
+ .class_init = char_dbus_class_init,
+};
+module_obj(TYPE_CHARDEV_DBUS);
+
+static void
+register_types(void)
+{
+ type_register_static(&char_dbus_type_info);
+}
+
+type_init(register_types);
diff --git a/ui/dbus-clipboard.c b/ui/dbus-clipboard.c
new file mode 100644
index 0000000000..5843d26cd2
--- /dev/null
+++ b/ui/dbus-clipboard.c
@@ -0,0 +1,457 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include "qemu/main-loop.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/sysemu.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include "dbus.h"
+
+#define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
+
+static void
+dbus_clipboard_complete_request(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation,
+ QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ GVariant *v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ info->types[type].data,
+ info->types[type].size,
+ TRUE,
+ (GDestroyNotify)qemu_clipboard_info_unref,
+ qemu_clipboard_info_ref(info));
+
+ qemu_dbus_display1_clipboard_complete_request(
+ dpy->clipboard, invocation,
+ MIME_TEXT_PLAIN_UTF8, v_data);
+}
+
+static void
+dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
+{
+ bool self_update = info->owner == &dpy->clipboard_peer;
+ const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
+ DBusClipboardRequest *req;
+ int i = 0;
+
+ if (info->owner == NULL) {
+ if (dpy->clipboard_proxy) {
+ qemu_dbus_display1_clipboard_call_release(
+ dpy->clipboard_proxy,
+ info->selection,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+ return;
+ }
+
+ if (self_update || !info->has_serial) {
+ return;
+ }
+
+ req = &dpy->clipboard_request[info->selection];
+ if (req->invocation && info->types[req->type].data) {
+ dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
+ g_clear_object(&req->invocation);
+ g_source_remove(req->timeout_id);
+ req->timeout_id = 0;
+ return;
+ }
+
+ if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ mime[i++] = MIME_TEXT_PLAIN_UTF8;
+ }
+
+ if (i > 0) {
+ if (dpy->clipboard_proxy) {
+ qemu_dbus_display1_clipboard_call_grab(
+ dpy->clipboard_proxy,
+ info->selection,
+ info->serial,
+ mime,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ }
+ }
+}
+
+static void
+dbus_clipboard_reset_serial(DBusDisplay *dpy)
+{
+ if (dpy->clipboard_proxy) {
+ qemu_dbus_display1_clipboard_call_register(
+ dpy->clipboard_proxy,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_clipboard_notify(Notifier *notifier, void *data)
+{
+ DBusDisplay *dpy =
+ container_of(notifier, DBusDisplay, clipboard_peer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ dbus_clipboard_update_info(dpy, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ dbus_clipboard_reset_serial(dpy);
+ return;
+ }
+}
+
+static void
+dbus_clipboard_qemu_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer);
+ g_autofree char *mime = NULL;
+ g_autoptr(GVariant) v_data = NULL;
+ g_autoptr(GError) err = NULL;
+ const char *data = NULL;
+ const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
+ size_t n;
+
+ if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
+ /* unsupported atm */
+ return;
+ }
+
+ if (dpy->clipboard_proxy) {
+ if (!qemu_dbus_display1_clipboard_call_request_sync(
+ dpy->clipboard_proxy,
+ info->selection,
+ mimes,
+ G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
+ error_report("Failed to request clipboard: %s", err->message);
+ return;
+ }
+
+ if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
+ error_report("Unsupported returned MIME: %s", mime);
+ return;
+ }
+
+ data = g_variant_get_fixed_array(v_data, &n, 1);
+ qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
+ n, data, true);
+ }
+}
+
+static void
+dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
+{
+ if (!req->invocation) {
+ return;
+ }
+
+ g_dbus_method_invocation_return_error(
+ req->invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Cancelled clipboard request");
+
+ g_clear_object(&req->invocation);
+ g_source_remove(req->timeout_id);
+ req->timeout_id = 0;
+}
+
+static void
+dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
+{
+ const char *name = NULL;
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
+ dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
+ }
+
+ if (!dpy->clipboard_proxy) {
+ return;
+ }
+
+ name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
+ trace_dbus_clipboard_unregister(name);
+ g_clear_object(&dpy->clipboard_proxy);
+}
+
+static void
+dbus_on_clipboard_proxy_name_owner_changed(
+ DBusDisplay *dpy,
+ GObject *object,
+ GParamSpec *pspec)
+{
+ dbus_clipboard_unregister_proxy(dpy);
+}
+
+static gboolean
+dbus_clipboard_register(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation)
+{
+ g_autoptr(GError) err = NULL;
+ const char *name = NULL;
+
+ if (dpy->clipboard_proxy) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Clipboard peer already registered!");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ dpy->clipboard_proxy =
+ qemu_dbus_display1_clipboard_proxy_new_sync(
+ g_dbus_method_invocation_get_connection(invocation),
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ g_dbus_method_invocation_get_sender(invocation),
+ "/org/qemu/Display1/Clipboard",
+ NULL,
+ &err);
+ if (!dpy->clipboard_proxy) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Failed to setup proxy: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
+ trace_dbus_clipboard_register(name);
+
+ g_object_connect(dpy->clipboard_proxy,
+ "swapped-signal::notify::g-name-owner",
+ dbus_on_clipboard_proxy_name_owner_changed, dpy,
+ NULL);
+ qemu_clipboard_reset_serial();
+
+ qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
+{
+ if (!dpy->clipboard_proxy ||
+ g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
+ g_dbus_method_invocation_get_sender(invocation))) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Unregistered caller");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+dbus_clipboard_unregister(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation)
+{
+ if (!dbus_clipboard_check_caller(dpy, invocation)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ dbus_clipboard_unregister_proxy(dpy);
+
+ qemu_dbus_display1_clipboard_complete_unregister(
+ dpy->clipboard, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_grab(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation,
+ gint arg_selection,
+ guint arg_serial,
+ const gchar *const *arg_mimes)
+{
+ QemuClipboardSelection s = arg_selection;
+ g_autoptr(QemuClipboardInfo) info = NULL;
+
+ if (!dbus_clipboard_check_caller(dpy, invocation)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Invalid clipboard selection: %d", arg_selection);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
+ if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
+ info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+ }
+ info->serial = arg_serial;
+ info->has_serial = true;
+ if (qemu_clipboard_check_serial(info, true)) {
+ qemu_clipboard_update(info);
+ } else {
+ trace_dbus_clipboard_grab_failed();
+ }
+
+ qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_release(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation,
+ gint arg_selection)
+{
+ if (!dbus_clipboard_check_caller(dpy, invocation)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
+
+ qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_clipboard_request_timeout(gpointer user_data)
+{
+ dbus_clipboard_request_cancelled(user_data);
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+dbus_clipboard_request(
+ DBusDisplay *dpy,
+ GDBusMethodInvocation *invocation,
+ gint arg_selection,
+ const gchar *const *arg_mimes)
+{
+ QemuClipboardSelection s = arg_selection;
+ QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
+ QemuClipboardInfo *info = NULL;
+
+ if (!dbus_clipboard_check_caller(dpy, invocation)) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Invalid clipboard selection: %d", arg_selection);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (dpy->clipboard_request[s].invocation) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Pending request");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ info = qemu_clipboard_info(s);
+ if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Empty clipboard");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
+ !info->types[type].available) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Unhandled MIME types requested");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ if (info->types[type].data) {
+ dbus_clipboard_complete_request(dpy, invocation, info, type);
+ } else {
+ qemu_clipboard_request(info, type);
+
+ dpy->clipboard_request[s].invocation = g_object_ref(invocation);
+ dpy->clipboard_request[s].type = type;
+ dpy->clipboard_request[s].timeout_id =
+ g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
+ &dpy->clipboard_request[s]);
+ }
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+void
+dbus_clipboard_init(DBusDisplay *dpy)
+{
+ g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
+
+ assert(!dpy->clipboard);
+
+ clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
+ dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
+ g_object_connect(dpy->clipboard,
+ "swapped-signal::handle-register",
+ dbus_clipboard_register, dpy,
+ "swapped-signal::handle-unregister",
+ dbus_clipboard_unregister, dpy,
+ "swapped-signal::handle-grab",
+ dbus_clipboard_grab, dpy,
+ "swapped-signal::handle-release",
+ dbus_clipboard_release, dpy,
+ "swapped-signal::handle-request",
+ dbus_clipboard_request, dpy,
+ NULL);
+
+ g_dbus_object_skeleton_add_interface(
+ G_DBUS_OBJECT_SKELETON(clipboard),
+ G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
+ g_dbus_object_manager_server_export(dpy->server, clipboard);
+ dpy->clipboard_peer.name = "dbus";
+ dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
+ dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
+ qemu_clipboard_peer_register(&dpy->clipboard_peer);
+}
diff --git a/ui/dbus-console.c b/ui/dbus-console.c
new file mode 100644
index 0000000000..e062f721d7
--- /dev/null
+++ b/ui/dbus-console.c
@@ -0,0 +1,497 @@
+/*
+ * QEMU DBus display console
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/input.h"
+#include "ui/kbd-state.h"
+#include "trace.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "dbus.h"
+
+struct _DBusDisplayConsole {
+ GDBusObjectSkeleton parent_instance;
+ DisplayChangeListener dcl;
+
+ DBusDisplay *display;
+ QemuConsole *con;
+ GHashTable *listeners;
+ QemuDBusDisplay1Console *iface;
+
+ QemuDBusDisplay1Keyboard *iface_kbd;
+ QKbdState *kbd;
+
+ QemuDBusDisplay1Mouse *iface_mouse;
+ gboolean last_set;
+ guint last_x;
+ guint last_y;
+ Notifier mouse_mode_notifier;
+};
+
+G_DEFINE_TYPE(DBusDisplayConsole,
+ dbus_display_console,
+ G_TYPE_DBUS_OBJECT_SKELETON)
+
+static void
+dbus_display_console_set_size(DBusDisplayConsole *ddc,
+ uint32_t width, uint32_t height)
+{
+ g_object_set(ddc->iface,
+ "width", width,
+ "height", height,
+ NULL);
+}
+
+static void
+dbus_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+ dbus_display_console_set_size(ddc,
+ surface_width(new_surface),
+ surface_height(new_surface));
+}
+
+static void
+dbus_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+}
+
+static void
+dbus_gl_scanout_disable(DisplayChangeListener *dcl)
+{
+}
+
+static void
+dbus_gl_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t tex_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+ dbus_display_console_set_size(ddc, w, h);
+}
+
+static void
+dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
+
+ dbus_display_console_set_size(ddc,
+ dmabuf->width,
+ dmabuf->height);
+}
+
+static void
+dbus_gl_scanout_update(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+}
+
+static const DisplayChangeListenerOps dbus_console_dcl_ops = {
+ .dpy_name = "dbus-console",
+ .dpy_gfx_switch = dbus_gfx_switch,
+ .dpy_gfx_update = dbus_gfx_update,
+ .dpy_gl_scanout_disable = dbus_gl_scanout_disable,
+ .dpy_gl_scanout_texture = dbus_gl_scanout_texture,
+ .dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf,
+ .dpy_gl_update = dbus_gl_scanout_update,
+};
+
+static void
+dbus_display_console_init(DBusDisplayConsole *object)
+{
+ DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
+
+ ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, g_object_unref);
+ ddc->dcl.ops = &dbus_console_dcl_ops;
+}
+
+static void
+dbus_display_console_dispose(GObject *object)
+{
+ DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
+
+ unregister_displaychangelistener(&ddc->dcl);
+ g_clear_object(&ddc->iface_kbd);
+ g_clear_object(&ddc->iface);
+ g_clear_pointer(&ddc->listeners, g_hash_table_unref);
+ g_clear_pointer(&ddc->kbd, qkbd_state_free);
+
+ G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
+}
+
+static void
+dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->dispose = dbus_display_console_dispose;
+}
+
+static void
+listener_vanished_cb(DBusDisplayListener *listener)
+{
+ DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
+ const char *name = dbus_display_listener_get_bus_name(listener);
+
+ trace_dbus_listener_vanished(name);
+
+ g_hash_table_remove(ddc->listeners, name);
+ qkbd_state_lift_all_keys(ddc->kbd);
+}
+
+static gboolean
+dbus_console_set_ui_info(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint16 arg_width_mm,
+ guint16 arg_height_mm,
+ gint arg_xoff,
+ gint arg_yoff,
+ guint arg_width,
+ guint arg_height)
+{
+ QemuUIInfo info = {
+ .width_mm = arg_width_mm,
+ .height_mm = arg_height_mm,
+ .xoff = arg_xoff,
+ .yoff = arg_yoff,
+ .width = arg_width,
+ .height = arg_height,
+ };
+
+ if (!dpy_ui_info_supported(ddc->con)) {
+ g_dbus_method_invocation_return_error(invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_UNSUPPORTED,
+ "SetUIInfo is not supported");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ dpy_set_ui_info(ddc->con, &info, false);
+ qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_console_register_listener(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *arg_listener)
+{
+ const char *sender = g_dbus_method_invocation_get_sender(invocation);
+ GDBusConnection *listener_conn;
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) socket_conn = NULL;
+ g_autofree char *guid = g_dbus_generate_guid();
+ DBusDisplayListener *listener;
+ int fd;
+
+ if (sender && g_hash_table_contains(ddc->listeners, sender)) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "`%s` is already registered!",
+ sender);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't get peer fd: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ socket = g_socket_new_from_fd(fd, &err);
+ if (err) {
+ g_dbus_method_invocation_return_error(
+ invocation,
+ DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_FAILED,
+ "Couldn't make a socket: %s", err->message);
+ close(fd);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ socket_conn = g_socket_connection_factory_create_connection(socket);
+
+ qemu_dbus_display1_console_complete_register_listener(
+ ddc->iface, invocation, NULL);
+
+ listener_conn = g_dbus_connection_new_sync(
+ G_IO_STREAM(socket_conn),
+ guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL, NULL, &err);
+ if (err) {
+ error_report("Failed to setup peer connection: %s", err->message);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ listener = dbus_display_listener_new(sender, listener_conn, ddc);
+ if (!listener) {
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_hash_table_insert(ddc->listeners,
+ (gpointer)dbus_display_listener_get_bus_name(listener),
+ listener);
+ g_object_connect(listener_conn,
+ "swapped-signal::closed", listener_vanished_cb, listener,
+ NULL);
+
+ trace_dbus_registered_listener(sender);
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_kbd_press(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint arg_keycode)
+{
+ QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
+
+ trace_dbus_kbd_press(arg_keycode);
+
+ qkbd_state_key_event(ddc->kbd, qcode, true);
+
+ qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_kbd_release(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint arg_keycode)
+{
+ QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
+
+ trace_dbus_kbd_release(arg_keycode);
+
+ qkbd_state_key_event(ddc->kbd, qcode, false);
+
+ qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_kbd_qemu_leds_updated(void *data, int ledstate)
+{
+ DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
+
+ qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
+}
+
+static gboolean
+dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ int dx, int dy)
+{
+ trace_dbus_mouse_rel_motion(dx, dy);
+
+ if (qemu_input_is_absolute()) {
+ g_dbus_method_invocation_return_error(
+ invocation, DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "Mouse is not relative");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ qemu_input_queue_rel(ddc->con, INPUT_AXIS_X, dx);
+ qemu_input_queue_rel(ddc->con, INPUT_AXIS_Y, dy);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
+ invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_set_pos(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint x, guint y)
+{
+ int width, height;
+
+ trace_dbus_mouse_set_pos(x, y);
+
+ if (!qemu_input_is_absolute()) {
+ g_dbus_method_invocation_return_error(
+ invocation, DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "Mouse is not absolute");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ width = qemu_console_get_width(ddc->con, 0);
+ height = qemu_console_get_height(ddc->con, 0);
+ if (x >= width || y >= height) {
+ g_dbus_method_invocation_return_error(
+ invocation, DBUS_DISPLAY_ERROR,
+ DBUS_DISPLAY_ERROR_INVALID,
+ "Invalid mouse position");
+ return DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ qemu_input_queue_abs(ddc->con, INPUT_AXIS_X, x, 0, width);
+ qemu_input_queue_abs(ddc->con, INPUT_AXIS_Y, y, 0, height);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
+ invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_press(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint button)
+{
+ trace_dbus_mouse_press(button);
+
+ qemu_input_queue_btn(ddc->con, button, true);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+dbus_mouse_release(DBusDisplayConsole *ddc,
+ GDBusMethodInvocation *invocation,
+ guint button)
+{
+ trace_dbus_mouse_release(button);
+
+ qemu_input_queue_btn(ddc->con, button, false);
+ qemu_input_event_sync();
+
+ qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation);
+
+ return DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+dbus_mouse_mode_change(Notifier *notify, void *data)
+{
+ DBusDisplayConsole *ddc =
+ container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
+
+ g_object_set(ddc->iface_mouse,
+ "is-absolute", qemu_input_is_absolute(),
+ NULL);
+}
+
+int dbus_display_console_get_index(DBusDisplayConsole *ddc)
+{
+ return qemu_console_get_index(ddc->con);
+}
+
+DBusDisplayConsole *
+dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
+{
+ g_autofree char *path = NULL;
+ g_autofree char *label = NULL;
+ char device_addr[256] = "";
+ DBusDisplayConsole *ddc;
+ int idx;
+
+ assert(display);
+ assert(con);
+
+ label = qemu_console_get_label(con);
+ idx = qemu_console_get_index(con);
+ path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
+ ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
+ "g-object-path", path,
+ NULL);
+ ddc->display = display;
+ ddc->con = con;
+ /* handle errors, and skip non graphics? */
+ qemu_console_fill_device_address(
+ con, device_addr, sizeof(device_addr), NULL);
+
+ ddc->iface = qemu_dbus_display1_console_skeleton_new();
+ g_object_set(ddc->iface,
+ "label", label,
+ "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
+ "head", qemu_console_get_head(con),
+ "width", qemu_console_get_width(con, 0),
+ "height", qemu_console_get_height(con, 0),
+ "device-address", device_addr,
+ NULL);
+ g_object_connect(ddc->iface,
+ "swapped-signal::handle-register-listener",
+ dbus_console_register_listener, ddc,
+ "swapped-signal::handle-set-uiinfo",
+ dbus_console_set_ui_info, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface));
+
+ ddc->kbd = qkbd_state_init(con);
+ ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
+ qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
+ g_object_connect(ddc->iface_kbd,
+ "swapped-signal::handle-press", dbus_kbd_press, ddc,
+ "swapped-signal::handle-release", dbus_kbd_release, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
+
+ ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
+ g_object_connect(ddc->iface_mouse,
+ "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
+ "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
+ "swapped-signal::handle-press", dbus_mouse_press, ddc,
+ "swapped-signal::handle-release", dbus_mouse_release, ddc,
+ NULL);
+ g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
+ G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
+
+ register_displaychangelistener(&ddc->dcl);
+ ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
+ qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
+
+ return ddc;
+}
diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml
new file mode 100644
index 0000000000..c3b2293376
--- /dev/null
+++ b/ui/dbus-display1.xml
@@ -0,0 +1,761 @@
+<?xml version="1.0" encoding="utf-8"?>
+<node>
+ <!--
+ org.qemu.Display1.VM:
+
+ This interface is implemented on ``/org/qemu/Display1/VM``.
+ -->
+ <interface name="org.qemu.Display1.VM">
+ <!--
+ Name:
+
+ The name of the VM.
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ UUID:
+
+ The UUID of the VM.
+ -->
+ <property name="UUID" type="s" access="read"/>
+
+ <!--
+ ConsoleIDs:
+
+ The list of consoles available on ``/org/qemu/Display1/Console_$id``.
+ -->
+ <property name="ConsoleIDs" type="au" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Console:
+
+ This interface is implemented on ``/org/qemu/Display1/Console_$id``. You
+ may discover available consoles through introspection or with the
+ :dbus:prop:`org.qemu.Display1.VM.ConsoleIDs` property.
+
+ A console is attached to a video device head. It may be "Graphic" or
+ "Text" (see :dbus:prop:`Type` and other properties).
+
+ Interactions with a console may be done with
+ :dbus:iface:`org.qemu.Display1.Keyboard` and
+ :dbus:iface:`org.qemu.Display1.Mouse` interfaces when available.
+ -->
+ <interface name="org.qemu.Display1.Console">
+ <!--
+ RegisterListener:
+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+ Register a console listener, which will receive display updates, until
+ it is disconnected.
+
+ Multiple listeners may be registered simultaneously.
+
+ The listener is expected to implement the
+ :dbus:iface:`org.qemu.Display1.Listener` interface.
+ -->
+ <method name="RegisterListener">
+ <arg type="h" name="listener" direction="in"/>
+ </method>
+
+ <!--
+ SetUIInfo:
+ @width_mm: the physical display width in millimeters.
+ @height_mm: the physical display height in millimeters.
+ @xoff: horizontal offset, in pixels.
+ @yoff: vertical offset, in pixels.
+ @width: console width, in pixels.
+ @height: console height, in pixels.
+
+ Modify the dimensions and display settings.
+ -->
+ <method name="SetUIInfo">
+ <arg name="width_mm" type="q" direction="in"/>
+ <arg name="height_mm" type="q" direction="in"/>
+ <arg name="xoff" type="i" direction="in"/>
+ <arg name="yoff" type="i" direction="in"/>
+ <arg name="width" type="u" direction="in"/>
+ <arg name="height" type="u" direction="in"/>
+ </method>
+
+ <!--
+ Label:
+
+ A user-friendly name for the console (for ex: "VGA").
+ -->
+ <property name="Label" type="s" access="read"/>
+
+ <!--
+ Head:
+
+ Graphical device head number.
+ -->
+ <property name="Head" type="u" access="read"/>
+
+ <!--
+ Type:
+
+ Console type ("Graphic" or "Text").
+ -->
+ <property name="Type" type="s" access="read"/>
+
+ <!--
+ Width:
+
+ Console width, in pixels.
+ -->
+ <property name="Width" type="u" access="read"/>
+
+ <!--
+ Height:
+
+ Console height, in pixels.
+ -->
+ <property name="Height" type="u" access="read"/>
+
+ <!--
+ DeviceAddress:
+
+ The device address (ex: "pci/0000/02.0").
+ -->
+ <property name="DeviceAddress" type="s" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Keyboard:
+
+ This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
+ :dbus:iface:`~org.qemu.Display1.Console`).
+ -->
+ <interface name="org.qemu.Display1.Keyboard">
+ <!--
+ Press:
+ @keycode: QEMU key number (xtkbd + special re-encoding of high bit)
+
+ Send a key press event.
+ -->
+ <method name="Press">
+ <arg type="u" name="keycode" direction="in"/>
+ </method>
+
+ <!--
+ Release:
+ @keycode: QEMU key number (xtkbd + special re-encoding of high bit)
+
+ Send a key release event.
+ -->
+ <method name="Release">
+ <arg type="u" name="keycode" direction="in"/>
+ </method>
+
+ <!--
+ Modifiers:
+
+ The active keyboard modifiers::
+
+ Scroll = 1 << 0
+ Num = 1 << 1
+ Caps = 1 << 2
+ -->
+ <property name="Modifiers" type="u" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Mouse:
+
+ This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see
+ :dbus:iface:`~org.qemu.Display1.Console` documentation).
+
+ .. _dbus-button-values:
+
+ **Button values**::
+
+ Left = 0
+ Middle = 1
+ Right = 2
+ Wheel-up = 3
+ Wheel-down = 4
+ Side = 5
+ Extra = 6
+ -->
+ <interface name="org.qemu.Display1.Mouse">
+ <!--
+ Press:
+ @button: :ref:`button value<dbus-button-values>`.
+
+ Send a mouse button press event.
+ -->
+ <method name="Press">
+ <arg type="u" name="button" direction="in"/>
+ </method>
+
+ <!--
+ Release:
+ @button: :ref:`button value<dbus-button-values>`.
+
+ Send a mouse button release event.
+ -->
+ <method name="Release">
+ <arg type="u" name="button" direction="in"/>
+ </method>
+
+ <!--
+ SetAbsPosition:
+ @x: X position, in pixels.
+ @y: Y position, in pixels.
+
+ Set the mouse pointer position.
+
+ Returns an error if not :dbus:prop:`IsAbsolute`.
+ -->
+ <method name="SetAbsPosition">
+ <arg type="u" name="x" direction="in"/>
+ <arg type="u" name="y" direction="in"/>
+ </method>
+
+ <!--
+ RelMotion:
+ @dx: X-delta, in pixels.
+ @dy: Y-delta, in pixels.
+
+ Move the mouse pointer position, relative to the current position.
+
+ Returns an error if :dbus:prop:`IsAbsolute`.
+ -->
+ <method name="RelMotion">
+ <arg type="i" name="dx" direction="in"/>
+ <arg type="i" name="dy" direction="in"/>
+ </method>
+
+ <!--
+ IsAbsolute:
+
+ Whether the mouse is using absolute movements.
+ -->
+ <property name="IsAbsolute" type="b" access="read"/>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Listener:
+
+ This client-side interface must be available on
+ ``/org/qemu/Display1/Listener`` when registering the peer-to-peer
+ connection with :dbus:meth:`~org.qemu.Display1.Console.Register`.
+ -->
+ <interface name="org.qemu.Display1.Listener">
+ <!--
+ Scanout:
+ @width: display width, in pixels.
+ @height: display height, in pixels.
+ @stride: data stride, in bytes.
+ @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
+ @data: image data.
+
+ Resize and update the display content.
+
+ The data to transfer for the display update may be large. The preferred
+ scanout method is :dbus:meth:`ScanoutDMABUF`, used whenever possible.
+ -->
+ <method name="Scanout">
+ <arg type="u" name="width" direction="in"/>
+ <arg type="u" name="height" direction="in"/>
+ <arg type="u" name="stride" direction="in"/>
+ <arg type="u" name="pixman_format" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ Update:
+ @x: X update position, in pixels.
+ @y: Y update position, in pixels.
+ @width: update width, in pixels.
+ @height: update height, in pixels.
+ @stride: data stride, in bytes.
+ @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
+ @data: display image data.
+
+ Update the display content.
+
+ This method is only called after a :dbus:meth:`Scanout` call.
+ -->
+ <method name="Update">
+ <arg type="i" name="x" direction="in"/>
+ <arg type="i" name="y" direction="in"/>
+ <arg type="i" name="width" direction="in"/>
+ <arg type="i" name="height" direction="in"/>
+ <arg type="u" name="stride" direction="in"/>
+ <arg type="u" name="pixman_format" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ ScanoutDMABUF:
+ @dmabuf: the DMABUF file descriptor.
+ @width: display width, in pixels.
+ @height: display height, in pixels.
+ @stride: stride, in bytes.
+ @fourcc: DMABUF fourcc.
+ @modifier: DMABUF modifier.
+ @y0_top: whether Y position 0 is the top or not.
+
+ Resize and update the display content with a DMABUF.
+ -->
+ <method name="ScanoutDMABUF">
+ <arg type="h" name="dmabuf" direction="in"/>
+ <arg type="u" name="width" direction="in"/>
+ <arg type="u" name="height" direction="in"/>
+ <arg type="u" name="stride" direction="in"/>
+ <arg type="u" name="fourcc" direction="in"/>
+ <!-- xywh? -->
+ <arg type="t" name="modifier" direction="in"/>
+ <arg type="b" name="y0_top" direction="in"/>
+ </method>
+
+ <!--
+ UpdateDMABUF:
+ @x: the X update position, in pixels.
+ @y: the Y update position, in pixels.
+ @width: the update width, in pixels.
+ @height: the update height, in pixels.
+
+ Update the display content with the current DMABUF and the given region.
+ -->
+ <method name="UpdateDMABUF">
+ <arg type="i" name="x" direction="in"/>
+ <arg type="i" name="y" direction="in"/>
+ <arg type="i" name="width" direction="in"/>
+ <arg type="i" name="height" direction="in"/>
+ </method>
+
+ <!--
+ Disable:
+
+ Disable the display (turn it off).
+ -->
+ <method name="Disable">
+ </method>
+
+ <!--
+ MouseSet:
+ @x: X mouse position, in pixels.
+ @y: Y mouse position, in pixels.
+ @on: whether the mouse is visible or not.
+
+ Set the mouse position and visibility.
+ -->
+ <method name="MouseSet">
+ <arg type="i" name="x" direction="in"/>
+ <arg type="i" name="y" direction="in"/>
+ <arg type="i" name="on" direction="in"/>
+ </method>
+
+ <!--
+ CursorDefine:
+ @width: cursor width, in pixels.
+ @height: cursor height, in pixels.
+ @hot_x: hot-spot X position, in pixels.
+ @hot_y: hot-spot Y position, in pixels.
+ @data: the cursor data.
+
+ Set the mouse cursor shape and hot-spot. The "data" must be ARGB, 32-bit
+ per pixel.
+ -->
+ <method name="CursorDefine">
+ <arg type="i" name="width" direction="in"/>
+ <arg type="i" name="height" direction="in"/>
+ <arg type="i" name="hot_x" direction="in"/>
+ <arg type="i" name="hot_y" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Clipboard:
+
+ This interface must be implemented by both the client and the server on
+ ``/org/qemu/Display1/Clipboard`` to support clipboard sharing between
+ the client and the guest.
+
+ Once :dbus:meth:`Register`'ed, method calls may be sent and received in both
+ directions. Unregistered callers will get error replies.
+
+ .. _dbus-clipboard-selection:
+
+ **Selection values**::
+
+ Clipboard = 0
+ Primary = 1
+ Secondary = 2
+
+ .. _dbus-clipboard-serial:
+
+ **Serial counter**
+
+ To solve potential clipboard races, clipboard grabs have an associated
+ serial counter. It is set to 0 on registration, and incremented by 1 for
+ each grab. The peer with the highest serial is the clipboard grab owner.
+
+ When a grab with a lower serial is received, it should be discarded.
+
+ When a grab is attempted with the same serial number as the current grab,
+ the one coming from the client should have higher priority, and the client
+ should gain clipboard grab ownership.
+ -->
+ <interface name="org.qemu.Display1.Clipboard">
+ <!--
+ Register:
+
+ Register a clipboard session and reinitialize the serial counter.
+
+ The client must register itself, and is granted an exclusive
+ access for handling the clipboard.
+
+ The server can reinitialize the session as well (to reset the counter).
+ -->
+ <method name="Register"/>
+
+ <!--
+ Unregister:
+
+ Unregister the clipboard session.
+ -->
+ <method name="Unregister"/>
+ <!--
+ Grab:
+ @selection: a :ref:`selection value<dbus-clipboard-selection>`.
+ @serial: the current grab :ref:`serial<dbus-clipboard-serial>`.
+ @mimes: the list of available content MIME types.
+
+ Grab the clipboard, claiming current clipboard content.
+ -->
+ <method name="Grab">
+ <arg type="u" name="selection"/>
+ <arg type="u" name="serial"/>
+ <arg type="as" name="mimes"/>
+ </method>
+
+ <!--
+ Release:
+ @selection: a :ref:`selection value<dbus-clipboard-selection>`.
+
+ Release the clipboard (does nothing if not the current owner).
+ -->
+ <method name="Release">
+ <arg type="u" name="selection"/>
+ </method>
+
+ <!--
+ Request:
+ @selection: a :ref:`selection value<dbus-clipboard-selection>`
+ @mimes: requested MIME types (by order of preference).
+ @reply_mime: the returned data MIME type.
+ @data: the clipboard data.
+
+ Request the clipboard content.
+
+ Return an error if the clipboard is empty, or the requested MIME types
+ are unavailable.
+ -->
+ <method name="Request">
+ <arg type="u" name="selection"/>
+ <arg type="as" name="mimes"/>
+ <arg type="s" name="reply_mime" direction="out"/>
+ <arg type="ay" name="data" direction="out">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Audio:
+
+ Audio backend may be available on ``/org/qemu/Display1/Audio``.
+ -->
+ <interface name="org.qemu.Display1.Audio">
+ <!--
+ RegisterOutListener:
+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+ Register an audio backend playback handler.
+
+ Multiple listeners may be registered simultaneously.
+
+ The listener is expected to implement the
+ :dbus:iface:`org.qemu.Display1.AudioOutListener` interface.
+ -->
+ <method name="RegisterOutListener">
+ <arg type="h" name="listener" direction="in"/>
+ </method>
+
+ <!--
+ RegisterInListener:
+ @listener: a Unix socket FD, for peer-to-peer D-Bus communication.
+
+ Register an audio backend record handler.
+
+ Multiple listeners may be registered simultaneously.
+
+ The listener is expected to implement the
+ :dbus:iface:`org.qemu.Display1.AudioInListener` interface.
+ -->
+ <method name="RegisterInListener">
+ <arg type="h" name="listener" direction="in"/>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.AudioOutListener:
+
+ This client-side interface must be available on
+ ``/org/qemu/Display1/AudioOutListener`` when registering the peer-to-peer
+ connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterOutListener`.
+ -->
+ <interface name="org.qemu.Display1.AudioOutListener">
+ <!--
+ Init:
+ @id: the stream ID.
+ @bits: PCM bits per sample.
+ @is_signed: whether the PCM data is signed.
+ @is_float: PCM floating point format.
+ @freq: the PCM frequency in Hz.
+ @nchannels: the number of channels.
+ @bytes_per_frame: the bytes per frame.
+ @bytes_per_second: the bytes per second.
+ @be: whether using big-endian format.
+
+ Initializes a PCM playback stream.
+ -->
+ <method name="Init">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="bits" type="y" direction="in"/>
+ <arg name="is_signed" type="b" direction="in"/>
+ <arg name="is_float" type="b" direction="in"/>
+ <arg name="freq" type="u" direction="in"/>
+ <arg name="nchannels" type="y" direction="in"/>
+ <arg name="bytes_per_frame" type="u" direction="in"/>
+ <arg name="bytes_per_second" type="u" direction="in"/>
+ <arg name="be" type="b" direction="in"/>
+ </method>
+
+ <!--
+ Fini:
+ @id: the stream ID.
+
+ Finish & close a playback stream.
+ -->
+ <method name="Fini">
+ <arg name="id" type="t" direction="in"/>
+ </method>
+
+ <!--
+ SetEnabled:
+ @id: the stream ID.
+
+ Resume or suspend the playback stream.
+ -->
+ <method name="SetEnabled">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="enabled" type="b" direction="in"/>
+ </method>
+
+ <!--
+ SetVolume:
+ @id: the stream ID.
+ @mute: whether the stream is muted.
+ @volume: the volume per-channel.
+
+ Set the stream volume and mute state (volume without unit, 0-255).
+ -->
+ <method name="SetVolume">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="mute" type="b" direction="in"/>
+ <arg name="volume" type="ay" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ Write:
+ @id: the stream ID.
+ @data: the PCM data.
+
+ PCM stream to play.
+ -->
+ <method name="Write">
+ <arg name="id" type="t" direction="in"/>
+ <arg type="ay" name="data" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.AudioInListener:
+
+ This client-side interface must be available on
+ ``/org/qemu/Display1/AudioInListener`` when registering the peer-to-peer
+ connection with :dbus:meth:`~org.qemu.Display1.Audio.RegisterInListener`.
+ -->
+ <interface name="org.qemu.Display1.AudioInListener">
+ <!--
+ Init:
+ @id: the stream ID.
+ @bits: PCM bits per sample.
+ @is_signed: whether the PCM data is signed.
+ @is_float: PCM floating point format.
+ @freq: the PCM frequency in Hz.
+ @nchannels: the number of channels.
+ @bytes_per_frame: the bytes per frame.
+ @bytes_per_second: the bytes per second.
+ @be: whether using big-endian format.
+
+ Initializes a PCM record stream.
+ -->
+ <method name="Init">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="bits" type="y" direction="in"/>
+ <arg name="is_signed" type="b" direction="in"/>
+ <arg name="is_float" type="b" direction="in"/>
+ <arg name="freq" type="u" direction="in"/>
+ <arg name="nchannels" type="y" direction="in"/>
+ <arg name="bytes_per_frame" type="u" direction="in"/>
+ <arg name="bytes_per_second" type="u" direction="in"/>
+ <arg name="be" type="b" direction="in"/>
+ </method>
+
+ <!--
+ Fini:
+ @id: the stream ID.
+
+ Finish & close a record stream.
+ -->
+ <method name="Fini">
+ <arg name="id" type="t" direction="in"/>
+ </method>
+
+ <!--
+ SetEnabled:
+ @id: the stream ID.
+
+ Resume or suspend the record stream.
+ -->
+ <method name="SetEnabled">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="enabled" type="b" direction="in"/>
+ </method>
+
+ <!--
+ SetVolume:
+ @id: the stream ID.
+ @mute: whether the stream is muted.
+ @volume: the volume per-channel.
+
+ Set the stream volume and mute state (volume without unit, 0-255).
+ -->
+ <method name="SetVolume">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="mute" type="b" direction="in"/>
+ <arg name="volume" type="ay" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+
+ <!--
+ Read:
+ @id: the stream ID.
+ @size: the amount to read, in bytes.
+ @data: the recorded data (which may be less than requested).
+
+ Read "size" bytes from the record stream.
+ -->
+ <method name="Read">
+ <arg name="id" type="t" direction="in"/>
+ <arg name="size" type="t" direction="in"/>
+ <arg type="ay" name="data" direction="out">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </arg>
+ </method>
+ </interface>
+
+ <!--
+ org.qemu.Display1.Chardev:
+
+ Character devices may be available on ``/org/qemu/Display1/Chardev_$id``.
+
+ They may be used for different kind of streams, which are identified via
+ their FQDN :dbus:prop:`Name`.
+
+ .. _dbus-chardev-fqdn:
+
+ Here are some known reserved kind names (the ``org.qemu`` prefix is
+ reserved by QEMU):
+
+ org.qemu.console.serial.0
+ A serial console stream.
+
+ org.qemu.monitor.hmp.0
+ A QEMU HMP human monitor.
+
+ org.qemu.monitor.qmp.0
+ A QEMU QMP monitor.
+
+ org.qemu.usbredir
+ A usbredir stream.
+ -->
+ <interface name="org.qemu.Display1.Chardev">
+ <!--
+ Register:
+ @stream: a Unix FD to redirect the stream to.
+
+ Register a file-descriptor for the stream handling.
+
+ The current handler, if any, will be replaced.
+ -->
+ <method name="Register">
+ <arg type="h" name="stream" direction="in"/>
+ </method>
+
+ <!--
+ SendBreak:
+
+ Send a break event to the character device.
+ -->
+ <method name="SendBreak"/>
+
+ <!--
+ Name:
+
+ The FQDN name to identify the kind of stream. See :ref:`reserved
+ names<dbus-chardev-fqdn>`.
+ -->
+ <property name="Name" type="s" access="read"/>
+
+ <!--
+ FEOpened:
+
+ Whether the front-end side is opened.
+ -->
+ <property name="FEOpened" type="b" access="read"/>
+
+ <!--
+ Echo:
+
+ Whether the input should be echo'ed (for serial streams).
+ -->
+ <property name="Echo" type="b" access="read"/>
+
+ <!--
+ Owner:
+
+ The D-Bus unique name of the registered handler.
+ -->
+ <property name="Owner" type="s" access="read"/>
+ </interface>
+</node>
diff --git a/ui/dbus-error.c b/ui/dbus-error.c
new file mode 100644
index 0000000000..85a9194d57
--- /dev/null
+++ b/ui/dbus-error.c
@@ -0,0 +1,48 @@
+/*
+ * QEMU DBus display errors
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "dbus.h"
+
+static const GDBusErrorEntry dbus_display_error_entries[] = {
+ { DBUS_DISPLAY_ERROR_FAILED, "org.qemu.Display1.Error.Failed" },
+ { DBUS_DISPLAY_ERROR_INVALID, "org.qemu.Display1.Error.Invalid" },
+ { DBUS_DISPLAY_ERROR_UNSUPPORTED, "org.qemu.Display1.Error.Unsupported" },
+};
+
+G_STATIC_ASSERT(G_N_ELEMENTS(dbus_display_error_entries) ==
+ DBUS_DISPLAY_N_ERRORS);
+
+GQuark
+dbus_display_error_quark(void)
+{
+ static gsize quark;
+
+ g_dbus_error_register_error_domain(
+ "dbus-display-error-quark",
+ &quark,
+ dbus_display_error_entries,
+ G_N_ELEMENTS(dbus_display_error_entries));
+
+ return (GQuark)quark;
+}
diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
new file mode 100644
index 0000000000..81c119b13a
--- /dev/null
+++ b/ui/dbus-listener.c
@@ -0,0 +1,486 @@
+/*
+ * QEMU DBus display console
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "dbus.h"
+#include <gio/gunixfdlist.h>
+
+#include "ui/shader.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "trace.h"
+
+struct _DBusDisplayListener {
+ GObject parent;
+
+ char *bus_name;
+ DBusDisplayConsole *console;
+ GDBusConnection *conn;
+
+ QemuDBusDisplay1Listener *proxy;
+
+ DisplayChangeListener dcl;
+ DisplaySurface *ds;
+ QemuGLShader *gls;
+ int gl_updates;
+};
+
+G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
+
+static void dbus_update_gl_cb(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ DBusDisplayListener *ddl = user_data;
+
+ if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy,
+ res, &err)) {
+ error_report("Failed to call update: %s", err->message);
+ }
+
+ graphic_hw_gl_block(ddl->dcl.con, false);
+ g_object_unref(ddl);
+}
+
+static void dbus_call_update_gl(DBusDisplayListener *ddl,
+ int x, int y, int w, int h)
+{
+ graphic_hw_gl_block(ddl->dcl.con, true);
+ glFlush();
+ qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy,
+ x, y, w, h,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_DEFAULT_TIMEOUT, NULL,
+ dbus_update_gl_cb,
+ g_object_ref(ddl));
+}
+
+static void dbus_scanout_disable(DisplayChangeListener *dcl)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ ddl->ds = NULL;
+ qemu_dbus_display1_listener_call_disable(
+ ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GUnixFDList) fd_list = NULL;
+
+ fd_list = g_unix_fd_list_new();
+ if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) {
+ error_report("Failed to setup dmabuf fdlist: %s", err->message);
+ return;
+ }
+
+ qemu_dbus_display1_listener_call_scanout_dmabuf(
+ ddl->proxy,
+ g_variant_new_handle(0),
+ dmabuf->width,
+ dmabuf->height,
+ dmabuf->stride,
+ dmabuf->fourcc,
+ dmabuf->modifier,
+ dmabuf->y0_top,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ fd_list,
+ NULL, NULL, NULL);
+}
+
+static void dbus_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t tex_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ QemuDmaBuf dmabuf = {
+ .width = backing_width,
+ .height = backing_height,
+ .y0_top = backing_y_0_top,
+ };
+
+ assert(tex_id);
+ dmabuf.fd = egl_get_fd_for_texture(
+ tex_id, (EGLint *)&dmabuf.stride,
+ (EGLint *)&dmabuf.fourcc,
+ &dmabuf.modifier);
+ if (dmabuf.fd < 0) {
+ error_report("%s: failed to get fd for texture", __func__);
+ return;
+ }
+
+ dbus_scanout_dmabuf(dcl, &dmabuf);
+ close(dmabuf.fd);
+}
+
+static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf, bool have_hot,
+ uint32_t hot_x, uint32_t hot_y)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ DisplaySurface *ds;
+ GVariant *v_data = NULL;
+ egl_fb cursor_fb;
+
+ if (!dmabuf) {
+ qemu_dbus_display1_listener_call_mouse_set(
+ ddl->proxy, 0, 0, false,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ return;
+ }
+
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+ egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height,
+ dmabuf->texture, false);
+ ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height);
+ egl_fb_read(ds, &cursor_fb);
+
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ surface_data(ds),
+ surface_width(ds) * surface_height(ds) * 4,
+ TRUE,
+ (GDestroyNotify)qemu_free_displaysurface,
+ ds);
+ qemu_dbus_display1_listener_call_cursor_define(
+ ddl->proxy,
+ surface_width(ds),
+ surface_height(ds),
+ hot_x,
+ hot_y,
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void dbus_cursor_position(DisplayChangeListener *dcl,
+ uint32_t pos_x, uint32_t pos_y)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ qemu_dbus_display1_listener_call_mouse_set(
+ ddl->proxy, pos_x, pos_y, true,
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_release_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ dbus_scanout_disable(dcl);
+}
+
+static void dbus_scanout_update(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ dbus_call_update_gl(ddl, x, y, w, h);
+}
+
+static void dbus_gl_refresh(DisplayChangeListener *dcl)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ graphic_hw_update(dcl->con);
+
+ if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) {
+ return;
+ }
+
+ if (ddl->gl_updates) {
+ dbus_call_update_gl(ddl, 0, 0,
+ surface_width(ddl->ds), surface_height(ddl->ds));
+ ddl->gl_updates = 0;
+ }
+}
+
+static void dbus_refresh(DisplayChangeListener *dcl)
+{
+ graphic_hw_update(dcl->con);
+}
+
+static void dbus_gl_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ if (ddl->ds) {
+ surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h);
+ }
+
+ ddl->gl_updates++;
+}
+
+static void dbus_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ pixman_image_t *img;
+ GVariant *v_data;
+ size_t stride;
+
+ assert(ddl->ds);
+ stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
+
+ trace_dbus_update(x, y, w, h);
+
+ /* make a copy, since gvariant only handles linear data */
+ img = pixman_image_create_bits(surface_format(ddl->ds),
+ w, h, NULL, stride);
+ pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
+ x, y, 0, 0, 0, 0, w, h);
+
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ pixman_image_get_data(img),
+ pixman_image_get_stride(img) * h,
+ TRUE,
+ (GDestroyNotify)pixman_image_unref,
+ img);
+ qemu_dbus_display1_listener_call_update(ddl->proxy,
+ x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img),
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
+}
+
+static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ if (ddl->ds) {
+ surface_gl_destroy_texture(ddl->gls, ddl->ds);
+ }
+ ddl->ds = new_surface;
+ if (ddl->ds) {
+ int width = surface_width(ddl->ds);
+ int height = surface_height(ddl->ds);
+
+ surface_gl_create_texture(ddl->gls, ddl->ds);
+ /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
+ dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
+ width, height, 0, 0, width, height);
+ }
+}
+
+static void dbus_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ GVariant *v_data = NULL;
+
+ ddl->ds = new_surface;
+ if (!ddl->ds) {
+ /* why not call disable instead? */
+ return;
+ }
+
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ surface_data(ddl->ds),
+ surface_stride(ddl->ds) * surface_height(ddl->ds),
+ TRUE,
+ (GDestroyNotify)pixman_image_unref,
+ pixman_image_ref(ddl->ds->image));
+ qemu_dbus_display1_listener_call_scanout(ddl->proxy,
+ surface_width(ddl->ds),
+ surface_height(ddl->ds),
+ surface_stride(ddl->ds),
+ surface_format(ddl->ds),
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
+}
+
+static void dbus_mouse_set(DisplayChangeListener *dcl,
+ int x, int y, int on)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+
+ qemu_dbus_display1_listener_call_mouse_set(
+ ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void dbus_cursor_define(DisplayChangeListener *dcl,
+ QEMUCursor *c)
+{
+ DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
+ GVariant *v_data = NULL;
+
+ cursor_get(c);
+ v_data = g_variant_new_from_data(
+ G_VARIANT_TYPE("ay"),
+ c->data,
+ c->width * c->height * 4,
+ TRUE,
+ (GDestroyNotify)cursor_put,
+ c);
+
+ qemu_dbus_display1_listener_call_cursor_define(
+ ddl->proxy,
+ c->width,
+ c->height,
+ c->hot_x,
+ c->hot_y,
+ v_data,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL,
+ NULL);
+}
+
+const DisplayChangeListenerOps dbus_gl_dcl_ops = {
+ .dpy_name = "dbus-gl",
+ .dpy_gfx_update = dbus_gl_gfx_update,
+ .dpy_gfx_switch = dbus_gl_gfx_switch,
+ .dpy_gfx_check_format = console_gl_check_format,
+ .dpy_refresh = dbus_gl_refresh,
+ .dpy_mouse_set = dbus_mouse_set,
+ .dpy_cursor_define = dbus_cursor_define,
+
+ .dpy_gl_scanout_disable = dbus_scanout_disable,
+ .dpy_gl_scanout_texture = dbus_scanout_texture,
+ .dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf,
+ .dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf,
+ .dpy_gl_cursor_position = dbus_cursor_position,
+ .dpy_gl_release_dmabuf = dbus_release_dmabuf,
+ .dpy_gl_update = dbus_scanout_update,
+};
+
+const DisplayChangeListenerOps dbus_dcl_ops = {
+ .dpy_name = "dbus",
+ .dpy_gfx_update = dbus_gfx_update,
+ .dpy_gfx_switch = dbus_gfx_switch,
+ .dpy_refresh = dbus_refresh,
+ .dpy_mouse_set = dbus_mouse_set,
+ .dpy_cursor_define = dbus_cursor_define,
+};
+
+static void
+dbus_display_listener_dispose(GObject *object)
+{
+ DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
+
+ unregister_displaychangelistener(&ddl->dcl);
+ g_clear_object(&ddl->conn);
+ g_clear_pointer(&ddl->bus_name, g_free);
+ g_clear_object(&ddl->proxy);
+ g_clear_pointer(&ddl->gls, qemu_gl_fini_shader);
+
+ G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
+}
+
+static void
+dbus_display_listener_constructed(GObject *object)
+{
+ DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object);
+
+ if (display_opengl) {
+ ddl->gls = qemu_gl_init_shader();
+ ddl->dcl.ops = &dbus_gl_dcl_ops;
+ } else {
+ ddl->dcl.ops = &dbus_dcl_ops;
+ }
+
+ G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object);
+}
+
+static void
+dbus_display_listener_class_init(DBusDisplayListenerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->dispose = dbus_display_listener_dispose;
+ object_class->constructed = dbus_display_listener_constructed;
+}
+
+static void
+dbus_display_listener_init(DBusDisplayListener *ddl)
+{
+}
+
+const char *
+dbus_display_listener_get_bus_name(DBusDisplayListener *ddl)
+{
+ return ddl->bus_name ?: "p2p";
+}
+
+DBusDisplayConsole *
+dbus_display_listener_get_console(DBusDisplayListener *ddl)
+{
+ return ddl->console;
+}
+
+DBusDisplayListener *
+dbus_display_listener_new(const char *bus_name,
+ GDBusConnection *conn,
+ DBusDisplayConsole *console)
+{
+ DBusDisplayListener *ddl;
+ QemuConsole *con;
+ g_autoptr(GError) err = NULL;
+
+ ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL);
+ ddl->proxy =
+ qemu_dbus_display1_listener_proxy_new_sync(conn,
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "/org/qemu/Display1/Listener",
+ NULL,
+ &err);
+ if (!ddl->proxy) {
+ error_report("Failed to setup proxy: %s", err->message);
+ g_object_unref(conn);
+ g_object_unref(ddl);
+ return NULL;
+ }
+
+ ddl->bus_name = g_strdup(bus_name);
+ ddl->conn = conn;
+ ddl->console = console;
+
+ con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
+ assert(con);
+ ddl->dcl.con = con;
+ register_displaychangelistener(&ddl->dcl);
+
+ return ddl;
+}
diff --git a/ui/dbus-module.c b/ui/dbus-module.c
new file mode 100644
index 0000000000..c8771fe48c
--- /dev/null
+++ b/ui/dbus-module.c
@@ -0,0 +1,35 @@
+/*
+ * D-Bus module support.
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/dbus-module.h"
+
+int using_dbus_display;
+
+static bool
+qemu_dbus_display_add_client(int csock, Error **errp)
+{
+ error_setg(errp, "D-Bus display isn't enabled");
+ return false;
+}
+
+struct QemuDBusDisplayOps qemu_dbus_display = {
+ .add_client = qemu_dbus_display_add_client,
+};
diff --git a/ui/dbus.c b/ui/dbus.c
new file mode 100644
index 0000000000..b2c1c9fb52
--- /dev/null
+++ b/ui/dbus.c
@@ -0,0 +1,482 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "qemu/dbus.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/sysemu.h"
+#include "ui/dbus-module.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "audio/audio.h"
+#include "audio/audio_int.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include "dbus.h"
+
+static DBusDisplay *dbus_display;
+
+static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc,
+ QEMUGLParams *params)
+{
+ eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ qemu_egl_rn_ctx);
+ return qemu_egl_create_context(dgc, params);
+}
+
+static const DisplayGLCtxOps dbus_gl_ops = {
+ .compatible_dcl = &dbus_gl_dcl_ops,
+ .dpy_gl_ctx_create = dbus_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
+static NotifierList dbus_display_notifiers =
+ NOTIFIER_LIST_INITIALIZER(dbus_display_notifiers);
+
+void
+dbus_display_notifier_add(Notifier *notifier)
+{
+ notifier_list_add(&dbus_display_notifiers, notifier);
+}
+
+static void
+dbus_display_notifier_remove(Notifier *notifier)
+{
+ notifier_remove(notifier);
+}
+
+void
+dbus_display_notify(DBusDisplayEvent *event)
+{
+ notifier_list_notify(&dbus_display_notifiers, event);
+}
+
+static void
+dbus_display_init(Object *o)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+ g_autoptr(GDBusObjectSkeleton) vm = NULL;
+
+ dd->glctx.ops = &dbus_gl_ops;
+ dd->iface = qemu_dbus_display1_vm_skeleton_new();
+ dd->consoles = g_ptr_array_new_with_free_func(g_object_unref);
+
+ dd->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
+
+ vm = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/VM");
+ g_dbus_object_skeleton_add_interface(
+ vm, G_DBUS_INTERFACE_SKELETON(dd->iface));
+ g_dbus_object_manager_server_export(dd->server, vm);
+
+ dbus_clipboard_init(dd);
+ dbus_chardev_init(dd);
+}
+
+static void
+dbus_display_finalize(Object *o)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ if (dd->notifier.notify) {
+ dbus_display_notifier_remove(&dd->notifier);
+ }
+
+ qemu_clipboard_peer_unregister(&dd->clipboard_peer);
+ g_clear_object(&dd->clipboard);
+
+ g_clear_object(&dd->server);
+ g_clear_pointer(&dd->consoles, g_ptr_array_unref);
+ if (dd->add_client_cancellable) {
+ g_cancellable_cancel(dd->add_client_cancellable);
+ }
+ g_clear_object(&dd->add_client_cancellable);
+ g_clear_object(&dd->bus);
+ g_clear_object(&dd->iface);
+ g_free(dd->dbus_addr);
+ g_free(dd->audiodev);
+ dbus_display = NULL;
+}
+
+static bool
+dbus_display_add_console(DBusDisplay *dd, int idx, Error **errp)
+{
+ QemuConsole *con;
+ DBusDisplayConsole *dbus_console;
+
+ con = qemu_console_lookup_by_index(idx);
+ assert(con);
+
+ if (qemu_console_is_graphic(con) &&
+ dd->gl_mode != DISPLAYGL_MODE_OFF) {
+ qemu_console_set_display_gl_ctx(con, &dd->glctx);
+ }
+
+ dbus_console = dbus_display_console_new(dd, con);
+ g_ptr_array_insert(dd->consoles, idx, dbus_console);
+ g_dbus_object_manager_server_export(dd->server,
+ G_DBUS_OBJECT_SKELETON(dbus_console));
+ return true;
+}
+
+static void
+dbus_display_complete(UserCreatable *uc, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(uc);
+ g_autoptr(GError) err = NULL;
+ g_autofree char *uuid = qemu_uuid_unparse_strdup(&qemu_uuid);
+ g_autoptr(GArray) consoles = NULL;
+ GVariant *console_ids;
+ int idx;
+
+ if (!object_resolve_path_type("", TYPE_DBUS_DISPLAY, NULL)) {
+ error_setg(errp, "There is already an instance of %s",
+ TYPE_DBUS_DISPLAY);
+ return;
+ }
+
+ if (dd->p2p) {
+ /* wait for dbus_display_add_client() */
+ dbus_display = dd;
+ } else if (dd->dbus_addr && *dd->dbus_addr) {
+ dd->bus = g_dbus_connection_new_for_address_sync(dd->dbus_addr,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+ NULL, NULL, &err);
+ } else {
+ dd->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
+ }
+ if (err) {
+ error_setg(errp, "failed to connect to DBus: %s", err->message);
+ return;
+ }
+
+ if (dd->audiodev && *dd->audiodev) {
+ AudioState *audio_state = audio_state_by_name(dd->audiodev);
+ if (!audio_state) {
+ error_setg(errp, "Audiodev '%s' not found", dd->audiodev);
+ return;
+ }
+ if (!g_str_equal(audio_state->drv->name, "dbus")) {
+ error_setg(errp, "Audiodev '%s' is not compatible with DBus",
+ dd->audiodev);
+ return;
+ }
+ audio_state->drv->set_dbus_server(audio_state, dd->server);
+ }
+
+ consoles = g_array_new(FALSE, FALSE, sizeof(guint32));
+ for (idx = 0;; idx++) {
+ if (!qemu_console_lookup_by_index(idx)) {
+ break;
+ }
+ if (!dbus_display_add_console(dd, idx, errp)) {
+ return;
+ }
+ g_array_append_val(consoles, idx);
+ }
+
+ console_ids = g_variant_new_from_data(
+ G_VARIANT_TYPE("au"),
+ consoles->data, consoles->len * sizeof(guint32), TRUE,
+ (GDestroyNotify)g_array_unref, consoles);
+ g_steal_pointer(&consoles);
+ g_object_set(dd->iface,
+ "name", qemu_name ?: "QEMU " QEMU_VERSION,
+ "uuid", uuid,
+ "console-ids", console_ids,
+ NULL);
+
+ if (dd->bus) {
+ g_dbus_object_manager_server_set_connection(dd->server, dd->bus);
+ g_bus_own_name_on_connection(dd->bus, "org.qemu",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL, NULL, NULL, NULL);
+ }
+}
+
+static void
+dbus_display_add_client_ready(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GDBusConnection) conn = NULL;
+
+ g_clear_object(&dbus_display->add_client_cancellable);
+
+ conn = g_dbus_connection_new_finish(res, &err);
+ if (!conn) {
+ error_printf("Failed to accept D-Bus client: %s", err->message);
+ }
+
+ g_dbus_object_manager_server_set_connection(dbus_display->server, conn);
+}
+
+
+static bool
+dbus_display_add_client(int csock, Error **errp)
+{
+ g_autoptr(GError) err = NULL;
+ g_autoptr(GSocket) socket = NULL;
+ g_autoptr(GSocketConnection) conn = NULL;
+ g_autofree char *guid = g_dbus_generate_guid();
+
+ if (!dbus_display) {
+ error_setg(errp, "p2p connections not accepted in bus mode");
+ return false;
+ }
+
+ if (dbus_display->add_client_cancellable) {
+ g_cancellable_cancel(dbus_display->add_client_cancellable);
+ }
+
+ socket = g_socket_new_from_fd(csock, &err);
+ if (!socket) {
+ error_setg(errp, "Failed to setup D-Bus socket: %s", err->message);
+ return false;
+ }
+
+ conn = g_socket_connection_factory_create_connection(socket);
+
+ dbus_display->add_client_cancellable = g_cancellable_new();
+
+ g_dbus_connection_new(G_IO_STREAM(conn),
+ guid,
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+ NULL,
+ dbus_display->add_client_cancellable,
+ dbus_display_add_client_ready,
+ NULL);
+
+ return true;
+}
+
+static bool
+get_dbus_p2p(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return dd->p2p;
+}
+
+static void
+set_dbus_p2p(Object *o, bool p2p, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ dd->p2p = p2p;
+}
+
+static char *
+get_dbus_addr(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return g_strdup(dd->dbus_addr);
+}
+
+static void
+set_dbus_addr(Object *o, const char *str, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ g_free(dd->dbus_addr);
+ dd->dbus_addr = g_strdup(str);
+}
+
+static char *
+get_audiodev(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return g_strdup(dd->audiodev);
+}
+
+static void
+set_audiodev(Object *o, const char *str, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ g_free(dd->audiodev);
+ dd->audiodev = g_strdup(str);
+}
+
+
+static int
+get_gl_mode(Object *o, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ return dd->gl_mode;
+}
+
+static void
+set_gl_mode(Object *o, int val, Error **errp)
+{
+ DBusDisplay *dd = DBUS_DISPLAY(o);
+
+ dd->gl_mode = val;
+}
+
+static void
+dbus_display_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+ ucc->complete = dbus_display_complete;
+ object_class_property_add_bool(oc, "p2p", get_dbus_p2p, set_dbus_p2p);
+ object_class_property_add_str(oc, "addr", get_dbus_addr, set_dbus_addr);
+ object_class_property_add_str(oc, "audiodev", get_audiodev, set_audiodev);
+ object_class_property_add_enum(oc, "gl-mode",
+ "DisplayGLMode", &DisplayGLMode_lookup,
+ get_gl_mode, set_gl_mode);
+}
+
+#define TYPE_CHARDEV_VC "chardev-vc"
+
+typedef struct DBusVCClass {
+ DBusChardevClass parent_class;
+
+ void (*parent_parse)(QemuOpts *opts, ChardevBackend *b, Error **errp);
+} DBusVCClass;
+
+DECLARE_CLASS_CHECKERS(DBusVCClass, DBUS_VC,
+ TYPE_CHARDEV_VC)
+
+static void
+dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ DBusVCClass *klass = DBUS_VC_CLASS(object_class_by_name(TYPE_CHARDEV_VC));
+ const char *name = qemu_opt_get(opts, "name");
+ const char *id = qemu_opts_id(opts);
+
+ if (name == NULL) {
+ if (g_str_has_prefix(id, "compat_monitor")) {
+ name = "org.qemu.monitor.hmp.0";
+ } else if (g_str_has_prefix(id, "serial")) {
+ name = "org.qemu.console.serial.0";
+ } else {
+ name = "";
+ }
+ if (!qemu_opt_set(opts, "name", name, errp)) {
+ return;
+ }
+ }
+
+ klass->parent_parse(opts, backend, errp);
+}
+
+static void
+dbus_vc_class_init(ObjectClass *oc, void *data)
+{
+ DBusVCClass *klass = DBUS_VC_CLASS(oc);
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ klass->parent_parse = cc->parse;
+ cc->parse = dbus_vc_parse;
+}
+
+static const TypeInfo dbus_vc_type_info = {
+ .name = TYPE_CHARDEV_VC,
+ .parent = TYPE_CHARDEV_DBUS,
+ .class_init = dbus_vc_class_init,
+};
+
+static void
+early_dbus_init(DisplayOptions *opts)
+{
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+ if (mode != DISPLAYGL_MODE_OFF) {
+ if (egl_rendernode_init(opts->u.dbus.rendernode, mode) < 0) {
+ error_report("dbus: render node init failed");
+ exit(1);
+ }
+
+ display_opengl = 1;
+ }
+
+ type_register(&dbus_vc_type_info);
+}
+
+static void
+dbus_init(DisplayState *ds, DisplayOptions *opts)
+{
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_OFF;
+
+ if (opts->u.dbus.addr && opts->u.dbus.p2p) {
+ error_report("dbus: can't accept both addr=X and p2p=yes options");
+ exit(1);
+ }
+
+ using_dbus_display = 1;
+
+ object_new_with_props(TYPE_DBUS_DISPLAY,
+ object_get_objects_root(),
+ "dbus-display", &error_fatal,
+ "addr", opts->u.dbus.addr ?: "",
+ "audiodev", opts->u.dbus.audiodev ?: "",
+ "gl-mode", DisplayGLMode_str(mode),
+ "p2p", yes_no(opts->u.dbus.p2p),
+ NULL);
+}
+
+static const TypeInfo dbus_display_info = {
+ .name = TYPE_DBUS_DISPLAY,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(DBusDisplay),
+ .instance_init = dbus_display_init,
+ .instance_finalize = dbus_display_finalize,
+ .class_init = dbus_display_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+static QemuDisplay qemu_display_dbus = {
+ .type = DISPLAY_TYPE_DBUS,
+ .early_init = early_dbus_init,
+ .init = dbus_init,
+};
+
+static void register_dbus(void)
+{
+ qemu_dbus_display = (struct QemuDBusDisplayOps) {
+ .add_client = dbus_display_add_client,
+ };
+ type_register_static(&dbus_display_info);
+ qemu_display_register(&qemu_display_dbus);
+}
+
+type_init(register_dbus);
+
+#ifdef CONFIG_OPENGL
+module_dep("ui-opengl");
+#endif
diff --git a/ui/dbus.h b/ui/dbus.h
new file mode 100644
index 0000000000..64c77cab44
--- /dev/null
+++ b/ui/dbus.h
@@ -0,0 +1,144 @@
+/*
+ * QEMU DBus display
+ *
+ * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef UI_DBUS_H_
+#define UI_DBUS_H_
+
+#include "chardev/char-socket.h"
+#include "qemu/dbus.h"
+#include "qom/object.h"
+#include "ui/console.h"
+#include "ui/clipboard.h"
+
+#include "dbus-display1.h"
+
+typedef struct DBusClipboardRequest {
+ GDBusMethodInvocation *invocation;
+ QemuClipboardType type;
+ guint timeout_id;
+} DBusClipboardRequest;
+
+struct DBusDisplay {
+ Object parent;
+
+ DisplayGLMode gl_mode;
+ bool p2p;
+ char *dbus_addr;
+ char *audiodev;
+ DisplayGLCtx glctx;
+
+ GDBusConnection *bus;
+ GDBusObjectManagerServer *server;
+ QemuDBusDisplay1VM *iface;
+ GPtrArray *consoles;
+ GCancellable *add_client_cancellable;
+
+ QemuClipboardPeer clipboard_peer;
+ QemuDBusDisplay1Clipboard *clipboard;
+ QemuDBusDisplay1Clipboard *clipboard_proxy;
+ DBusClipboardRequest clipboard_request[QEMU_CLIPBOARD_SELECTION__COUNT];
+
+ Notifier notifier;
+};
+
+#define TYPE_DBUS_DISPLAY "dbus-display"
+OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY)
+
+void dbus_display_notifier_add(Notifier *notifier);
+
+#define DBUS_DISPLAY_TYPE_CONSOLE dbus_display_console_get_type()
+G_DECLARE_FINAL_TYPE(DBusDisplayConsole,
+ dbus_display_console,
+ DBUS_DISPLAY,
+ CONSOLE,
+ GDBusObjectSkeleton)
+
+DBusDisplayConsole *
+dbus_display_console_new(DBusDisplay *display, QemuConsole *con);
+
+int
+dbus_display_console_get_index(DBusDisplayConsole *ddc);
+
+#define DBUS_DISPLAY_TYPE_LISTENER dbus_display_listener_get_type()
+G_DECLARE_FINAL_TYPE(DBusDisplayListener,
+ dbus_display_listener,
+ DBUS_DISPLAY,
+ LISTENER,
+ GObject)
+
+DBusDisplayListener *
+dbus_display_listener_new(const char *bus_name,
+ GDBusConnection *conn,
+ DBusDisplayConsole *console);
+
+DBusDisplayConsole *
+dbus_display_listener_get_console(DBusDisplayListener *ddl);
+
+const char *
+dbus_display_listener_get_bus_name(DBusDisplayListener *ddl);
+
+extern const DisplayChangeListenerOps dbus_gl_dcl_ops;
+extern const DisplayChangeListenerOps dbus_dcl_ops;
+
+#define TYPE_CHARDEV_DBUS "chardev-dbus"
+
+typedef struct DBusChardevClass {
+ SocketChardevClass parent_class;
+
+ void (*parent_chr_be_event)(Chardev *s, QEMUChrEvent event);
+} DBusChardevClass;
+
+DECLARE_CLASS_CHECKERS(DBusChardevClass, DBUS_CHARDEV,
+ TYPE_CHARDEV_DBUS)
+
+typedef struct DBusChardev {
+ SocketChardev parent;
+
+ bool exported;
+ QemuDBusDisplay1Chardev *iface;
+} DBusChardev;
+
+DECLARE_INSTANCE_CHECKER(DBusChardev, DBUS_CHARDEV, TYPE_CHARDEV_DBUS)
+
+#define CHARDEV_IS_DBUS(chr) \
+ object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_DBUS)
+
+typedef enum {
+ DBUS_DISPLAY_CHARDEV_OPEN,
+ DBUS_DISPLAY_CHARDEV_CLOSE,
+} DBusDisplayEventType;
+
+typedef struct DBusDisplayEvent {
+ DBusDisplayEventType type;
+ union {
+ DBusChardev *chardev;
+ };
+} DBusDisplayEvent;
+
+void dbus_display_notify(DBusDisplayEvent *event);
+
+void dbus_chardev_init(DBusDisplay *dpy);
+
+void dbus_clipboard_init(DBusDisplay *dpy);
+
+#endif /* UI_DBUS_H_ */
diff --git a/ui/egl-context.c b/ui/egl-context.c
index 368ffa49d8..eb5f520fc4 100644
--- a/ui/egl-context.c
+++ b/ui/egl-context.c
@@ -1,7 +1,7 @@
#include "qemu/osdep.h"
#include "ui/egl-context.h"
-QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
EGLContext ctx;
@@ -24,12 +24,12 @@ QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
return ctx;
}
-void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
eglDestroyContext(qemu_egl_display, ctx);
}
-int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+int qemu_egl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
return eglMakeCurrent(qemu_egl_display,
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
index a26a2520c4..94082a9da9 100644
--- a/ui/egl-headless.c
+++ b/ui/egl-headless.c
@@ -38,12 +38,12 @@ static void egl_gfx_switch(DisplayChangeListener *dcl,
edpy->ds = new_surface;
}
-static QEMUGLContext egl_create_context(DisplayChangeListener *dcl,
+static QEMUGLContext egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
qemu_egl_rn_ctx);
- return qemu_egl_create_context(dcl, params);
+ return qemu_egl_create_context(dgc, params);
}
static void egl_scanout_disable(DisplayChangeListener *dcl)
@@ -157,10 +157,6 @@ static const DisplayChangeListenerOps egl_ops = {
.dpy_gfx_update = egl_gfx_update,
.dpy_gfx_switch = egl_gfx_switch,
- .dpy_gl_ctx_create = egl_create_context,
- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
- .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-
.dpy_gl_scanout_disable = egl_scanout_disable,
.dpy_gl_scanout_texture = egl_scanout_texture,
.dpy_gl_scanout_dmabuf = egl_scanout_dmabuf,
@@ -170,6 +166,13 @@ static const DisplayChangeListenerOps egl_ops = {
.dpy_gl_update = egl_scanout_flush,
};
+static const DisplayGLCtxOps eglctx_ops = {
+ .compatible_dcl = &egl_ops,
+ .dpy_gl_ctx_create = egl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
static void early_egl_headless_init(DisplayOptions *opts)
{
display_opengl = 1;
@@ -188,6 +191,8 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
}
for (idx = 0;; idx++) {
+ DisplayGLCtx *ctx;
+
con = qemu_console_lookup_by_index(idx);
if (!con || !qemu_console_is_graphic(con)) {
break;
@@ -197,6 +202,9 @@ static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
edpy->dcl.con = con;
edpy->dcl.ops = &egl_ops;
edpy->gls = qemu_gl_init_shader();
+ ctx = g_new0(DisplayGLCtx, 1);
+ ctx->ops = &eglctx_ops;
+ qemu_console_set_display_gl_ctx(con, ctx);
register_displaychangelistener(&edpy->dcl);
}
}
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
index 35b7a2c228..e0b8b283fe 100644
--- a/ui/gtk-clipboard.c
+++ b/ui/gtk-clipboard.c
@@ -74,10 +74,9 @@ static void gd_clipboard_clear(GtkClipboard *clipboard,
gd->cbowner[s] = false;
}
-static void gd_clipboard_notify(Notifier *notifier, void *data)
+static void gd_clipboard_update_info(GtkDisplayState *gd,
+ QemuClipboardInfo *info)
{
- GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);
- QemuClipboardInfo *info = data;
QemuClipboardSelection s = info->selection;
bool self_update = info->owner == &gd->cbpeer;
@@ -118,6 +117,22 @@ static void gd_clipboard_notify(Notifier *notifier, void *data)
*/
}
+static void gd_clipboard_notify(Notifier *notifier, void *data)
+{
+ GtkDisplayState *gd =
+ container_of(notifier, GtkDisplayState, cbpeer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ gd_clipboard_update_info(gd, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ /* ignore */
+ return;
+ }
+}
+
static void gd_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType type)
{
@@ -172,7 +187,7 @@ static void gd_owner_change(GtkClipboard *clipboard,
void gd_clipboard_init(GtkDisplayState *gd)
{
gd->cbpeer.name = "gtk";
- gd->cbpeer.update.notify = gd_clipboard_notify;
+ gd->cbpeer.notifier.notify = gd_clipboard_notify;
gd->cbpeer.request = gd_clipboard_request;
qemu_clipboard_peer_register(&gd->cbpeer);
diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
index 45cb67712d..e3bd4bc274 100644
--- a/ui/gtk-egl.c
+++ b/ui/gtk-egl.c
@@ -119,8 +119,6 @@ void gd_egl_draw(VirtualConsole *vc)
glFlush();
}
-
- graphic_hw_gl_flushed(vc->gfx.dcl.con);
}
void gd_egl_update(DisplayChangeListener *dcl,
@@ -199,14 +197,14 @@ void gd_egl_switch(DisplayChangeListener *dcl,
}
}
-QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
- return qemu_egl_create_context(dcl, params);
+ return qemu_egl_create_context(dgc, params);
}
void gd_egl_scanout_disable(DisplayChangeListener *dcl)
@@ -362,10 +360,10 @@ void gtk_egl_init(DisplayGLMode mode)
display_opengl = 1;
}
-int gd_egl_make_current(DisplayChangeListener *dcl,
+int gd_egl_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, ctx);
diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
index 01e4e74ee3..fc5a082eb8 100644
--- a/ui/gtk-gl-area.c
+++ b/ui/gtk-gl-area.c
@@ -101,8 +101,6 @@ void gd_gl_area_draw(VirtualConsole *vc)
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
}
-
- graphic_hw_gl_flushed(vc->gfx.dcl.con);
}
void gd_gl_area_update(DisplayChangeListener *dcl,
@@ -172,10 +170,10 @@ void gd_gl_area_switch(DisplayChangeListener *dcl,
}
}
-QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
GdkWindow *window;
GdkGLContext *ctx;
GError *err = NULL;
@@ -201,7 +199,7 @@ QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
return ctx;
}
-void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
/* FIXME */
}
@@ -280,7 +278,7 @@ void gtk_gl_area_init(void)
display_opengl = 1;
}
-int gd_gl_area_make_current(DisplayChangeListener *dcl,
+int gd_gl_area_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
gdk_gl_context_make_current(ctx);
diff --git a/ui/gtk.c b/ui/gtk.c
index 428f02f2df..6a1f65d518 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -593,7 +593,6 @@ void gd_hw_gl_flushed(void *vcon)
close(dmabuf->fence_fd);
dmabuf->fence_fd = -1;
graphic_hw_gl_block(vc->gfx.dcl.con, false);
- graphic_hw_gl_flushed(vc->gfx.dcl.con);
}
/** DisplayState Callbacks (opengl version) **/
@@ -607,9 +606,6 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
.dpy_mouse_set = gd_mouse_set,
.dpy_cursor_define = gd_cursor_define,
- .dpy_gl_ctx_create = gd_gl_area_create_context,
- .dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
- .dpy_gl_ctx_make_current = gd_gl_area_make_current,
.dpy_gl_scanout_texture = gd_gl_area_scanout_texture,
.dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
.dpy_gl_update = gd_gl_area_scanout_flush,
@@ -618,8 +614,14 @@ static const DisplayChangeListenerOps dcl_gl_area_ops = {
.dpy_has_dmabuf = gd_has_dmabuf,
};
-#ifdef CONFIG_X11
+static const DisplayGLCtxOps gl_area_ctx_ops = {
+ .compatible_dcl = &dcl_gl_area_ops,
+ .dpy_gl_ctx_create = gd_gl_area_create_context,
+ .dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
+ .dpy_gl_ctx_make_current = gd_gl_area_make_current,
+};
+#ifdef CONFIG_X11
static const DisplayChangeListenerOps dcl_egl_ops = {
.dpy_name = "gtk-egl",
.dpy_gfx_update = gd_egl_update,
@@ -629,9 +631,6 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
.dpy_mouse_set = gd_mouse_set,
.dpy_cursor_define = gd_cursor_define,
- .dpy_gl_ctx_create = gd_egl_create_context,
- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
- .dpy_gl_ctx_make_current = gd_egl_make_current,
.dpy_gl_scanout_disable = gd_egl_scanout_disable,
.dpy_gl_scanout_texture = gd_egl_scanout_texture,
.dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
@@ -642,6 +641,12 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
.dpy_has_dmabuf = gd_has_dmabuf,
};
+static const DisplayGLCtxOps egl_ctx_ops = {
+ .compatible_dcl = &dcl_egl_ops,
+ .dpy_gl_ctx_create = gd_egl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = gd_egl_make_current,
+};
#endif
#endif /* CONFIG_OPENGL */
@@ -698,7 +703,7 @@ static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
memset(&info, 0, sizeof(info));
info.width = width;
info.height = height;
- dpy_set_ui_info(vc->gfx.dcl.con, &info);
+ dpy_set_ui_info(vc->gfx.dcl.con, &info, true);
}
#if defined(CONFIG_OPENGL)
@@ -2035,6 +2040,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
g_signal_connect(vc->gfx.drawing_area, "realize",
G_CALLBACK(gl_area_realize), vc);
vc->gfx.dcl.ops = &dcl_gl_area_ops;
+ vc->gfx.dgc.ops = &gl_area_ctx_ops;
} else {
#ifdef CONFIG_X11
vc->gfx.drawing_area = gtk_drawing_area_new();
@@ -2049,6 +2055,7 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
#pragma GCC diagnostic pop
vc->gfx.dcl.ops = &dcl_egl_ops;
+ vc->gfx.dgc.ops = &egl_ctx_ops;
vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
#else
abort();
@@ -2083,6 +2090,9 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
vc->gfx.kbd = qkbd_state_init(con);
vc->gfx.dcl.con = con;
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(con, &vc->gfx.dgc);
+ }
register_displaychangelistener(&vc->gfx.dcl);
gd_connect_vc_gfx_signals(vc);
diff --git a/ui/meson.build b/ui/meson.build
index ee8ef27714..64286ba150 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -12,7 +12,11 @@ softmmu_ss.add(files(
'kbd-state.c',
'keymaps.c',
'qemu-pixman.c',
+ 'util.c',
))
+if dbus_display
+ softmmu_ss.add(files('dbus-module.c'))
+endif
softmmu_ss.add([spice_headers, files('spice-module.c')])
softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
@@ -64,6 +68,30 @@ if config_host.has_key('CONFIG_OPENGL') and gbm.found()
ui_modules += {'egl-headless' : egl_headless_ss}
endif
+if dbus_display
+ dbus_ss = ss.source_set()
+ dbus_display1 = custom_target('dbus-display gdbus-codegen',
+ output: ['dbus-display1.h', 'dbus-display1.c'],
+ input: files('dbus-display1.xml'),
+ command: [config_host['GDBUS_CODEGEN'],
+ '@INPUT@',
+ '--glib-min-required', '2.64',
+ '--output-directory', meson.current_build_dir(),
+ '--interface-prefix', 'org.qemu.',
+ '--c-namespace', 'QemuDBus',
+ '--generate-c-code', '@BASENAME@'])
+ dbus_ss.add(when: [gio, pixman, opengl, 'CONFIG_GIO'],
+ if_true: [files(
+ 'dbus-chardev.c',
+ 'dbus-clipboard.c',
+ 'dbus-console.c',
+ 'dbus-error.c',
+ 'dbus-listener.c',
+ 'dbus.c',
+ ), dbus_display1])
+ ui_modules += {'dbus' : dbus_ss}
+endif
+
if gtk.found()
softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
index a21d2deed9..39cab8cde7 100644
--- a/ui/sdl2-gl.c
+++ b/ui/sdl2-gl.c
@@ -58,7 +58,6 @@ static void sdl2_gl_render_surface(struct sdl2_console *scon)
surface_gl_render_texture(scon->gls, scon->surface);
SDL_GL_SwapWindow(scon->real_window);
- graphic_hw_gl_flushed(scon->dcl.con);
}
void sdl2_gl_update(DisplayChangeListener *dcl,
@@ -133,10 +132,10 @@ void sdl2_gl_redraw(struct sdl2_console *scon)
}
}
-QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+QEMUGLContext sdl2_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
SDL_GLContext ctx;
assert(scon->opengl);
@@ -168,17 +167,17 @@ QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
return (QEMUGLContext)ctx;
}
-void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+void sdl2_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
{
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
SDL_GL_DeleteContext(sdlctx);
}
-int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ struct sdl2_console *scon = container_of(dgc, struct sdl2_console, dgc);
SDL_GLContext sdlctx = (SDL_GLContext)ctx;
assert(scon->opengl);
@@ -241,5 +240,4 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
SDL_GL_SwapWindow(scon->real_window);
- graphic_hw_gl_flushed(dcl->con);
}
diff --git a/ui/sdl2.c b/ui/sdl2.c
index 17c0ec30eb..0bd30504cf 100644
--- a/ui/sdl2.c
+++ b/ui/sdl2.c
@@ -561,7 +561,7 @@ static void handle_windowevent(SDL_Event *ev)
memset(&info, 0, sizeof(info));
info.width = ev->window.data1;
info.height = ev->window.data2;
- dpy_set_ui_info(scon->dcl.con, &info);
+ dpy_set_ui_info(scon->dcl.con, &info, true);
}
sdl2_redraw(scon);
break;
@@ -778,13 +778,17 @@ static const DisplayChangeListenerOps dcl_gl_ops = {
.dpy_mouse_set = sdl_mouse_warp,
.dpy_cursor_define = sdl_mouse_define,
- .dpy_gl_ctx_create = sdl2_gl_create_context,
- .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
- .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
.dpy_gl_scanout_disable = sdl2_gl_scanout_disable,
.dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
.dpy_gl_update = sdl2_gl_scanout_flush,
};
+
+static const DisplayGLCtxOps gl_ctx_ops = {
+ .compatible_dcl = &dcl_gl_ops,
+ .dpy_gl_ctx_create = sdl2_gl_create_context,
+ .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
+ .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
+};
#endif
static void sdl2_display_early_init(DisplayOptions *o)
@@ -860,12 +864,16 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
#ifdef CONFIG_OPENGL
sdl2_console[i].opengl = display_opengl;
sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
+ sdl2_console[i].dgc.ops = display_opengl ? &gl_ctx_ops : NULL;
#else
sdl2_console[i].opengl = 0;
sdl2_console[i].dcl.ops = &dcl_2d_ops;
#endif
sdl2_console[i].dcl.con = con;
sdl2_console[i].kbd = qkbd_state_init(con);
+ if (display_opengl) {
+ qemu_console_set_display_gl_ctx(con, &sdl2_console[i].dgc);
+ }
register_displaychangelistener(&sdl2_console[i].dcl);
#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
diff --git a/ui/spice-core.c b/ui/spice-core.c
index 31974b8d6c..c3ac20ad43 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -884,56 +884,6 @@ bool qemu_spice_have_display_interface(QemuConsole *con)
return false;
}
-/*
- * Recursively (in reverse order) appends addresses of PCI devices as it moves
- * up in the PCI hierarchy.
- *
- * @returns true on success, false when the buffer wasn't large enough
- */
-static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
-{
- PCIBus *bus = pci_get_bus(pci);
- /*
- * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
- * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
- */
- if (bus->parent_dev != NULL) {
- append_pci_address(buf, buf_size, bus->parent_dev);
- }
-
- size_t len = strlen(buf);
- ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
- PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
-
- return written > 0 && written < buf_size - len;
-}
-
-bool qemu_spice_fill_device_address(QemuConsole *con,
- char *device_address,
- size_t size)
-{
- DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
- "device",
- &error_abort));
- PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
- TYPE_PCI_DEVICE);
-
- if (pci == NULL) {
- warn_report("Setting device address of a display device to SPICE: "
- "Not a PCI device.");
- return false;
- }
-
- strncpy(device_address, "pci/0000", size);
- if (!append_pci_address(device_address, size, pci)) {
- warn_report("Setting device address of a display device to SPICE: "
- "Too many PCI devices in the chain.");
- return false;
- }
-
- return true;
-}
-
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
{
if (g_slist_find(spice_consoles, con)) {
diff --git a/ui/spice-display.c b/ui/spice-display.c
index f59c69882d..1043f47f94 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -692,7 +692,7 @@ static int interface_client_monitors_config(QXLInstance *sin,
}
trace_qemu_spice_ui_info(ssd->qxl.id, info.width, info.height);
- dpy_set_ui_info(ssd->dcl.con, &info);
+ dpy_set_ui_info(ssd->dcl.con, &info, false);
return 1;
}
@@ -830,7 +830,6 @@ static void qemu_spice_gl_unblock_bh(void *opaque)
SimpleSpiceDisplay *ssd = opaque;
qemu_spice_gl_block(ssd, false);
- graphic_hw_gl_flushed(ssd->dcl.con);
}
static void qemu_spice_gl_block_timer(void *opaque)
@@ -909,12 +908,12 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
}
}
-static QEMUGLContext qemu_spice_gl_create_context(DisplayChangeListener *dcl,
+static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
qemu_egl_rn_ctx);
- return qemu_egl_create_context(dcl, params);
+ return qemu_egl_create_context(dgc, params);
}
static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
@@ -1106,10 +1105,6 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
.dpy_mouse_set = display_mouse_set,
.dpy_cursor_define = display_mouse_define,
- .dpy_gl_ctx_create = qemu_spice_gl_create_context,
- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
- .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-
.dpy_gl_scanout_disable = qemu_spice_gl_scanout_disable,
.dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
.dpy_gl_scanout_dmabuf = qemu_spice_gl_scanout_dmabuf,
@@ -1119,6 +1114,13 @@ static const DisplayChangeListenerOps display_listener_gl_ops = {
.dpy_gl_update = qemu_spice_gl_update,
};
+static const DisplayGLCtxOps gl_ctx_ops = {
+ .compatible_dcl = &display_listener_gl_ops,
+ .dpy_gl_ctx_create = qemu_spice_gl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+};
+
#endif /* HAVE_SPICE_GL */
static void qemu_spice_display_init_one(QemuConsole *con)
@@ -1131,6 +1133,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
#ifdef HAVE_SPICE_GL
if (spice_opengl) {
ssd->dcl.ops = &display_listener_gl_ops;
+ ssd->dgc.ops = &gl_ctx_ops;
ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
qemu_spice_gl_block_timer, ssd);
@@ -1145,17 +1148,23 @@ static void qemu_spice_display_init_one(QemuConsole *con)
qemu_spice_add_display_interface(&ssd->qxl, con);
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+ Error *err = NULL;
char device_address[256] = "";
- if (qemu_spice_fill_device_address(con, device_address, 256)) {
+ if (qemu_console_fill_device_address(con, device_address, 256, &err)) {
spice_qxl_set_device_info(&ssd->qxl,
device_address,
qemu_console_get_head(con),
1);
+ } else {
+ error_report_err(err);
}
#endif
qemu_spice_create_host_memslot(ssd);
+ if (spice_opengl) {
+ qemu_console_set_display_gl_ctx(con, &ssd->dgc);
+ }
register_displaychangelistener(&ssd->dcl);
}
diff --git a/ui/trace-events b/ui/trace-events
index b9c0dd0fa1..f78b5e6606 100644
--- a/ui/trace-events
+++ b/ui/trace-events
@@ -135,3 +135,18 @@ vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d"
vdagent_peer_cap(const char *name) "cap %s"
vdagent_cb_grab_selection(const char *name) "selection %s"
vdagent_cb_grab_type(const char *name) "type %s"
+vdagent_cb_serial_discard(uint32_t current, uint32_t received) "current=%u, received=%u"
+
+# dbus.c
+dbus_registered_listener(const char *bus_name) "peer %s"
+dbus_listener_vanished(const char *bus_name) "peer %s"
+dbus_kbd_press(unsigned int keycode) "keycode %u"
+dbus_kbd_release(unsigned int keycode) "keycode %u"
+dbus_mouse_press(unsigned int button) "button %u"
+dbus_mouse_release(unsigned int button) "button %u"
+dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u"
+dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d"
+dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d"
+dbus_clipboard_grab_failed(void) ""
+dbus_clipboard_register(const char *bus_name) "peer %s"
+dbus_clipboard_unregister(const char *bus_name) "peer %s"
diff --git a/ui/util.c b/ui/util.c
new file mode 100644
index 0000000000..7e8fc1ea53
--- /dev/null
+++ b/ui/util.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+
+/*
+ * Recursively (in reverse order) appends addresses of PCI devices as it moves
+ * up in the PCI hierarchy.
+ *
+ * @returns true on success, false when the buffer wasn't large enough
+ */
+static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
+{
+ PCIBus *bus = pci_get_bus(pci);
+ /*
+ * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
+ * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
+ */
+ if (bus->parent_dev != NULL) {
+ append_pci_address(buf, buf_size, bus->parent_dev);
+ }
+
+ size_t len = strlen(buf);
+ ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
+ PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
+
+ return written > 0 && written < buf_size - len;
+}
+
+bool qemu_console_fill_device_address(QemuConsole *con,
+ char *device_address,
+ size_t size,
+ Error **errp)
+{
+ ERRP_GUARD();
+ DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
+ "device",
+ &error_abort));
+ PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
+ TYPE_PCI_DEVICE);
+
+ if (pci == NULL) {
+ error_setg(errp, "Setting device address of a display device: "
+ "Not a PCI device.");
+ return false;
+ }
+
+ strncpy(device_address, "pci/0000", size);
+ if (!append_pci_address(device_address, size, pci)) {
+ error_setg(errp, "Setting device address of a display device: "
+ "Too many PCI devices in the chain.");
+ return false;
+ }
+
+ return true;
+}
diff --git a/ui/vdagent.c b/ui/vdagent.c
index 19e8fbfc96..7ea4bc5d9a 100644
--- a/ui/vdagent.c
+++ b/ui/vdagent.c
@@ -17,6 +17,14 @@
#include "spice/vd_agent.h"
+#define CHECK_SPICE_PROTOCOL_VERSION(major, minor, micro) \
+ (CONFIG_SPICE_PROTOCOL_MAJOR > (major) || \
+ (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \
+ CONFIG_SPICE_PROTOCOL_MINOR > (minor)) || \
+ (CONFIG_SPICE_PROTOCOL_MAJOR == (major) && \
+ CONFIG_SPICE_PROTOCOL_MINOR == (minor) && \
+ CONFIG_SPICE_PROTOCOL_MICRO >= (micro)))
+
#define VDAGENT_BUFFER_LIMIT (1 * MiB)
#define VDAGENT_MOUSE_DEFAULT true
#define VDAGENT_CLIPBOARD_DEFAULT false
@@ -51,6 +59,7 @@ struct VDAgentChardev {
/* clipboard */
QemuClipboardPeer cbpeer;
+ uint32_t last_serial[QEMU_CLIPBOARD_SELECTION__COUNT];
uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
};
typedef struct VDAgentChardev VDAgentChardev;
@@ -79,8 +88,10 @@ static const char *cap_name[] = {
[VD_AGENT_CAP_MONITORS_CONFIG_POSITION] = "monitors-config-position",
[VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled",
[VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
[VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
+#endif
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
[VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
[VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial",
#endif
@@ -102,7 +113,7 @@ static const char *msg_name[] = {
[VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected",
[VD_AGENT_MAX_CLIPBOARD] = "max-clipboard",
[VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 0)
[VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
#endif
};
@@ -120,7 +131,7 @@ static const char *type_name[] = {
[VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp",
[VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
[VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg",
-#if 0
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 3)
[VD_AGENT_CLIPBOARD_FILE_LIST] = "files",
#endif
};
@@ -193,6 +204,9 @@ static void vdagent_send_caps(VDAgentChardev *vd)
if (vd->clipboard) {
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+ caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL);
+#endif
}
vdagent_send_msg(vd, msg);
@@ -323,7 +337,8 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
{
g_autofree VDAgentMessage *msg =
g_malloc0(sizeof(VDAgentMessage) +
- sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));
+ sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1) +
+ sizeof(uint32_t));
uint8_t *s = msg->data;
uint32_t *data = (uint32_t *)msg->data;
uint32_t q, type;
@@ -336,6 +351,19 @@ static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
return;
}
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+ if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
+ if (!info->has_serial) {
+ /* client should win */
+ info->serial = vd->last_serial[info->selection]++;
+ info->has_serial = true;
+ }
+ *data = info->serial;
+ data++;
+ msg->size += sizeof(uint32_t);
+ }
+#endif
+
for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
type = type_qemu_to_vdagent(q);
if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
@@ -407,10 +435,9 @@ static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd,
vdagent_send_clipboard_data(vd, info, type);
}
-static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+static void vdagent_clipboard_update_info(VDAgentChardev *vd,
+ QemuClipboardInfo *info)
{
- VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update);
- QemuClipboardInfo *info = data;
QemuClipboardSelection s = info->selection;
QemuClipboardType type;
bool self_update = info->owner == &vd->cbpeer;
@@ -439,6 +466,31 @@ static void vdagent_clipboard_notify(Notifier *notifier, void *data)
}
}
+static void vdagent_clipboard_reset_serial(VDAgentChardev *vd)
+{
+ Chardev *chr = CHARDEV(vd);
+
+ /* reopen the agent connection to reset the serial state */
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+}
+
+static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+{
+ VDAgentChardev *vd =
+ container_of(notifier, VDAgentChardev, cbpeer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ vdagent_clipboard_update_info(vd, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ vdagent_clipboard_reset_serial(vd);
+ return;
+ }
+}
+
static void vdagent_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType qtype)
{
@@ -472,6 +524,24 @@ static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t
trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
info = qemu_clipboard_info_new(&vd->cbpeer, s);
+#if CHECK_SPICE_PROTOCOL_VERSION(0, 14, 1)
+ if (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)) {
+ if (size < sizeof(uint32_t)) {
+ /* this shouldn't happen! */
+ return;
+ }
+
+ info->has_serial = true;
+ info->serial = *(uint32_t *)data;
+ if (info->serial < vd->last_serial[s]) {
+ /* discard lower-ordering guest grab */
+ return;
+ }
+ vd->last_serial[s] = info->serial;
+ data += sizeof(uint32_t);
+ size -= sizeof(uint32_t);
+ }
+#endif
if (size > sizeof(uint32_t) * 10) {
/*
* spice has 6 types as of 2021. Limiting to 10 entries
@@ -648,9 +718,10 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
if (have_mouse(vd) && vd->mouse_hs) {
qemu_input_handler_activate(vd->mouse_hs);
}
- if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) {
+ if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) {
+ memset(vd->last_serial, 0, sizeof(vd->last_serial));
vd->cbpeer.name = "vdagent";
- vd->cbpeer.update.notify = vdagent_clipboard_notify;
+ vd->cbpeer.notifier.notify = vdagent_clipboard_notify;
vd->cbpeer.request = vdagent_clipboard_request;
qemu_clipboard_peer_register(&vd->cbpeer);
}
@@ -789,7 +860,7 @@ static void vdagent_disconnect(VDAgentChardev *vd)
if (vd->mouse_hs) {
qemu_input_handler_deactivate(vd->mouse_hs);
}
- if (vd->cbpeer.update.notify) {
+ if (vd->cbpeer.notifier.notify) {
qemu_clipboard_peer_unregister(&vd->cbpeer);
memset(&vd->cbpeer, 0, sizeof(vd->cbpeer));
}
@@ -797,11 +868,8 @@ static void vdagent_disconnect(VDAgentChardev *vd)
static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
{
- VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
-
if (!fe_open) {
trace_vdagent_close();
- vdagent_disconnect(vd);
return;
}
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
index 67284b556c..d48f75eb1a 100644
--- a/ui/vnc-clipboard.c
+++ b/ui/vnc-clipboard.c
@@ -189,10 +189,8 @@ static void vnc_clipboard_provide(VncState *vs,
vnc_flush(vs);
}
-static void vnc_clipboard_notify(Notifier *notifier, void *data)
+static void vnc_clipboard_update_info(VncState *vs, QemuClipboardInfo *info)
{
- VncState *vs = container_of(notifier, VncState, cbpeer.update);
- QemuClipboardInfo *info = data;
QemuClipboardType type;
bool self_update = info->owner == &vs->cbpeer;
uint32_t flags = 0;
@@ -223,6 +221,21 @@ static void vnc_clipboard_notify(Notifier *notifier, void *data)
}
}
+static void vnc_clipboard_notify(Notifier *notifier, void *data)
+{
+ VncState *vs = container_of(notifier, VncState, cbpeer.notifier);
+ QemuClipboardNotify *notify = data;
+
+ switch (notify->type) {
+ case QEMU_CLIPBOARD_UPDATE_INFO:
+ vnc_clipboard_update_info(vs, notify->info);
+ return;
+ case QEMU_CLIPBOARD_RESET_SERIAL:
+ /* ignore */
+ return;
+ }
+}
+
static void vnc_clipboard_request(QemuClipboardInfo *info,
QemuClipboardType type)
{
@@ -316,9 +329,9 @@ void vnc_server_cut_text_caps(VncState *vs)
caps[1] = 0;
vnc_clipboard_send(vs, 2, caps);
- if (!vs->cbpeer.update.notify) {
+ if (!vs->cbpeer.notifier.notify) {
vs->cbpeer.name = "vnc";
- vs->cbpeer.update.notify = vnc_clipboard_notify;
+ vs->cbpeer.notifier.notify = vnc_clipboard_notify;
vs->cbpeer.request = vnc_clipboard_request;
qemu_clipboard_peer_register(&vs->cbpeer);
}
diff --git a/ui/vnc.c b/ui/vnc.c
index af02522e84..1ed1c7efc6 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1354,7 +1354,7 @@ void vnc_disconnect_finish(VncState *vs)
/* last client gone */
vnc_update_server_surface(vs->vd);
}
- if (vs->cbpeer.update.notify) {
+ if (vs->cbpeer.notifier.notify) {
qemu_clipboard_peer_unregister(&vs->cbpeer);
}
@@ -2596,7 +2596,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
memset(&info, 0, sizeof(info));
info.width = w;
info.height = h;
- dpy_set_ui_info(vs->vd->dcl.con, &info);
+ dpy_set_ui_info(vs->vd->dcl.con, &info, false);
vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
} else {
vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);