All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu.
@ 2010-04-14  9:55 Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 01/11] vgabios update to 0.6c, add bios for qxl/unstable Gerd Hoffmann
                   ` (10 more replies)
  0 siblings, 11 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

  Hi folks,

Here is a early spice patch series for review and comments.  It is not
yet complete (save/load/migration isn't addressed at all yet) and it has
some known issues (local rendering isn't fully functional).  Also things
are in flux in upstream spice, this patch series depends on not-yet
committed libspice patches (see below for build instructions).
Nevertheless it works good enougth that you can start playing with it
and I also like to gather review comments and get merge planning
started.


Some background info:

Spice is a remote desktop protocol.  The (slightly outdated) project
website with background information is http://www.spice-space.org/  The
download section has windows guest drivers.  You need the unstable
versions for these patches.


Building spice:

The spice project runs under the freedesktop.org umbrella now, the git
repositories are hosted @ freedesktop.org.  You'll need:

 (1) spice-protocol.  http://cgit.freedesktop.org/spice/spice-protocol/
     This carries all the spice protocol structs.
 (2) celt051.  http://www.spice-space.org/yum_repo_data/f12/src/celt051-0.5.1.3-0.fc12.src.rpm
     Version 0.5.1 of the celt audio codec.  This is the only unusual
     (aka distros don't carry it) build dependency left for spice.
 (3) A bunch of devel packages.  Especially log4cpp and cegui.
     Everything else spice needs should be on your disk already if you
     are doing qemu development.
 (4) A pretty recent pixman version (0.18.0+).
 (5) spice itself.  http://cgit.freedesktop.org/~kraxel/spice/log/?h=api.v3
     This brings both libspice-server and the spice client.
 (6) This patch series.  Also available from
     http://cgit.freedesktop.org/spice/qemu/log/?h=spice.v3

If everything goes well qemu configure should autodetect that spice is
available.


Testing & using spice:

* Enable spice in qemu:
  qemu -spice port=1234,password=$secret	# password protected
  qemu -spice port=1234,disable-ticketing	# allow connects without password

* Enable qxl vga device (recommended):
  qemu -spice $options -vga qxl

* Enable multihead (in theory, just noticed its broken, to be debugged ...)
  qemu -spice $options -vga qxl -device qxl

* Enable sound:
  QEMU_AUDIO_DRV=spice qemu -spice $options -device AC97

* Adding a absolute pointing device aka tablet is strongly recommended:
  qemu -usbdevice tablet

* Start spice client:
  spicec -h localhost -p 1234 -w $secret

The spice client has two important hot keys: Shift+F11 (toggle
Fullscreen) and Shift+F12 (release pointer grab).  If you add a usb
tablet you hopefully never ever need Shift+F12 though.

cheers,
  Gerd

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 01/11] vgabios update to 0.6c, add bios for qxl/unstable
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 02/11] add spice into the configure file Gerd Hoffmann
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

---
 Makefile                   |    2 +-
 pc-bios/vgabios-cirrus.bin |  Bin 35840 -> 35840 bytes
 pc-bios/vgabios-qxl.bin    |  Bin 0 -> 40448 bytes
 pc-bios/vgabios.bin        |  Bin 38400 -> 40448 bytes
 4 files changed, 1 insertions(+), 1 deletions(-)
 create mode 100644 pc-bios/vgabios-qxl.bin

diff --git a/Makefile b/Makefile
index a404fda..73a32f4 100644
--- a/Makefile
+++ b/Makefile
@@ -170,7 +170,7 @@ ar      de     en-us  fi  fr-be  hr     it  lv  nl         pl  ru     th \
 common  de-ch  es     fo  fr-ca  hu     ja  mk  nl-be      pt  sl     tr
 
 ifdef INSTALL_BLOBS
-BLOBS=bios.bin vgabios.bin vgabios-cirrus.bin ppc_rom.bin \
+BLOBS=bios.bin vgabios.bin vgabios-cirrus.bin vgabios-qxl.bin ppc_rom.bin \
 video.x openbios-sparc32 openbios-sparc64 openbios-ppc \
 gpxe-eepro100-80861209.rom \
 gpxe-eepro100-80861229.rom \
diff --git a/pc-bios/vgabios-cirrus.bin b/pc-bios/vgabios-cirrus.bin
index 4fa8f99f7e0b4ffb492531f44703f49dc47c4837..ae3983eadd8b6cbd0dee8f253923460d03007978 100644
GIT binary patch
delta 4491
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
a%PXzNT0<<K-~SO=8ZDg<+Hz_7&Hn+dPHlhy

delta 4344
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

diff --git a/pc-bios/vgabios-qxl.bin b/pc-bios/vgabios-qxl.bin
new file mode 100644
index 0000000000000000000000000000000000000000..04a3e4432e1f549cfbb25a967be1c5dbdd0d7219
GIT binary patch
literal 40448
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
UfxFh$trgGzRUzpAj|-LkA9U@UD*ylh

literal 0
HcmV?d00001

diff --git a/pc-bios/vgabios.bin b/pc-bios/vgabios.bin
index fa6f815fcf6b115cb26aa3293ac9e9a33f8d0632..e319bf07868812c47b43f258896b05bce0098f7c 100644
GIT binary patch
delta 6698
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
T)BX10-B9rO032W(^vM4K1=5&k

delta 5151
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
N|7$zEyRV1j{67xRL1F*^

-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 02/11] add spice into the configure file
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 01/11] vgabios update to 0.6c, add bios for qxl/unstable Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 03/11] spice: core bits Gerd Hoffmann
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

---
 configure |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/configure b/configure
index 7c06719..3179952 100755
--- a/configure
+++ b/configure
@@ -299,6 +299,7 @@ pkgversion=""
 check_utests="no"
 user_pie="no"
 zero_malloc=""
+spice=""
 
 # OS specific
 if check_define __linux__ ; then
@@ -573,6 +574,10 @@ for opt do
   ;;
   --enable-kvm) kvm="yes"
   ;;
+  --disable-spice) spice="no"
+  ;;
+  --enable-spice) spice="yes"
+  ;;
   --enable-profiler) profiler="yes"
   ;;
   --enable-cocoa)
@@ -826,6 +831,8 @@ echo "  --enable-docs            enable documentation build"
 echo "  --disable-docs           disable documentation build"
 echo "  --disable-vhost-net      disable vhost-net acceleration support"
 echo "  --enable-vhost-net       enable vhost-net acceleration support"
+echo "  --disable-spice          disable spice"
+echo "  --enable-spice           enable spice"
 echo ""
 echo "NOTE: The object files are built at the place where configure is launched"
 exit 1
@@ -1891,6 +1898,30 @@ if compile_prog "" ""; then
     gcc_attribute_warn_unused_result=yes
 fi
 
+# spice probe
+if test "$spice" != "no" ; then
+  cat > $TMPC << EOF
+#include <spice.h>
+int main(void) { spice_server_new(); return 0; }
+EOF
+  spice_proto_ver=$(pkg-config --modversion spice-protocol 2>/dev/null)
+  spice_server_ver=$(pkg-config --modversion spice-server 2>/dev/null)
+  spice_cflags=$(pkg-config --cflags spice-protocol spice-server 2>/dev/null)
+  spice_libs=$(pkg-config --libs spice-protocol spice-server 2>/dev/null)
+  if compile_prog "$spice_cflags" "$spice_libs" ; then
+    spice="yes"
+    libs_softmmu="$spice_libs $libs_softmmu"
+    QEMU_CFLAGS="$spice_cflags $QEMU_CFLAGS"
+  else
+    if test "$spice" = "yes" ; then
+      feature_not_found "spice"
+    fi
+    spice="no"
+  fi
+fi
+
+##########################################
+
 ##########################################
 # check if we have fdatasync
 
@@ -2030,6 +2061,7 @@ echo "preadv support    $preadv"
 echo "fdatasync         $fdatasync"
 echo "uuid support      $uuid"
 echo "vhost-net support $vhost_net"
+echo "spice support     $spice"
 
 if test $sdl_too_old = "yes"; then
 echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -2249,6 +2281,10 @@ if test "$fdatasync" = "yes" ; then
   echo "CONFIG_FDATASYNC=y" >> $config_host_mak
 fi
 
+if test "$spice" = "yes" ; then
+  echo "CONFIG_SPICE=y" >> $config_host_mak
+fi
+
 # XXX: suppress that
 if [ "$bsd" = "yes" ] ; then
   echo "CONFIG_BSD=y" >> $config_host_mak
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 03/11] spice: core bits
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 01/11] vgabios update to 0.6c, add bios for qxl/unstable Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 02/11] add spice into the configure file Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 04/11] spice: add keyboard Gerd Hoffmann
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

Add -spice command line switch.  Has support setting passwd and port for
now.  With this patch applied the spice client can successfully connect
to qemu.  You can't do anything useful yet though.
---
 Makefile.target |    1 +
 qemu-config.c   |    3 +
 qemu-config.h   |    1 +
 qemu-options.hx |    8 +++
 qemu-spice.h    |   22 +++++++
 spice.c         |  170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vl.c            |   15 +++++
 7 files changed, 220 insertions(+), 0 deletions(-)
 create mode 100644 qemu-spice.h
 create mode 100644 spice.c

diff --git a/Makefile.target b/Makefile.target
index 95c9d23..ad3e595 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -194,6 +194,7 @@ obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o
 obj-i386-y += vmmouse.o vmport.o hpet.o
 obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
 obj-i386-y += debugcon.o multiboot.o
+obj-i386-$(CONFIG_SPICE) += spice.o
 
 # shared objects
 obj-ppc-y = ppc.o
diff --git a/qemu-config.c b/qemu-config.c
index 150157c..ed08d75 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -306,6 +306,9 @@ static QemuOptsList *lists[] = {
     &qemu_global_opts,
     &qemu_mon_opts,
     &qemu_cpudef_opts,
+#ifdef CONFIG_SPICE
+    &qemu_spice_opts,
+#endif
     NULL,
 };
 
diff --git a/qemu-config.h b/qemu-config.h
index f217c58..e743efa 100644
--- a/qemu-config.h
+++ b/qemu-config.h
@@ -10,6 +10,7 @@ extern QemuOptsList qemu_rtc_opts;
 extern QemuOptsList qemu_global_opts;
 extern QemuOptsList qemu_mon_opts;
 extern QemuOptsList qemu_cpudef_opts;
+extern QemuOptsList qemu_spice_opts;
 
 QemuOptsList *qemu_find_opts(const char *group);
 int qemu_set_option(const char *str);
diff --git a/qemu-options.hx b/qemu-options.hx
index f4b3bfe..d1624e7 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -594,6 +594,14 @@ STEXI
 Enable SDL.
 ETEXI
 
+#ifdef CONFIG_SPICE
+DEF("spice", HAS_ARG, QEMU_OPTION_spice,
+    "-spice <args>   use spice\n", QEMU_ARCH_ALL)
+STEXI
+Use Spice.
+ETEXI
+#endif
+
 DEF("portrait", 0, QEMU_OPTION_portrait,
     "-portrait       rotate graphical output 90 deg left (only PXA LCD)\n",
     QEMU_ARCH_ALL)
diff --git a/qemu-spice.h b/qemu-spice.h
new file mode 100644
index 0000000..5597576
--- /dev/null
+++ b/qemu-spice.h
@@ -0,0 +1,22 @@
+#ifndef QEMU_SPICE_H
+#define QEMU_SPICE_H
+
+#ifdef CONFIG_SPICE
+
+#include <spice.h>
+
+#include "qemu-option.h"
+#include "qemu-config.h"
+
+extern SpiceServer *spice_server;
+extern int using_spice;
+
+void qemu_spice_init(void);
+
+#else  /* CONFIG_SPICE */
+
+#define using_spice 0
+
+#endif /* CONFIG_SPICE */
+
+#endif /* QEMU_SPICE_H */
diff --git a/spice.c b/spice.c
new file mode 100644
index 0000000..5315b18
--- /dev/null
+++ b/spice.c
@@ -0,0 +1,170 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <spice.h>
+
+#include "qemu-common.h"
+#include "qemu-spice.h"
+#include "qemu-timer.h"
+#include "qemu-queue.h"
+#include "monitor.h"
+
+/* core bits */
+
+SpiceServer *spice_server;
+int using_spice = 0;
+
+struct SpiceTimer {
+    QEMUTimer *timer;
+    QTAILQ_ENTRY(SpiceTimer) next;
+};
+static QTAILQ_HEAD(, SpiceTimer) timers = QTAILQ_HEAD_INITIALIZER(timers);
+
+static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
+{
+    SpiceTimer *timer;
+
+    timer = qemu_mallocz(sizeof(*timer));
+    timer->timer = qemu_new_timer(rt_clock, func, opaque);
+    QTAILQ_INSERT_TAIL(&timers, timer, next);
+    return timer;
+}
+
+static void timer_start(SpiceTimer *timer, uint32_t ms)
+{
+    qemu_mod_timer(timer->timer, qemu_get_clock(rt_clock) + ms);
+}
+
+static void timer_cancel(SpiceTimer *timer)
+{
+    qemu_del_timer(timer->timer);
+}
+
+static void timer_remove(SpiceTimer *timer)
+{
+    qemu_del_timer(timer->timer);
+    qemu_free_timer(timer->timer);
+    QTAILQ_REMOVE(&timers, timer, next);
+    free(timer);
+}
+
+struct SpiceWatch {
+    int fd;
+    int event_mask;
+    SpiceWatchFunc func;
+    void *opaque;
+    QTAILQ_ENTRY(SpiceWatch) next;
+};
+static QTAILQ_HEAD(, SpiceWatch) watches = QTAILQ_HEAD_INITIALIZER(watches);
+
+static void watch_read(void *opaque)
+{
+    SpiceWatch *watch = opaque;
+    watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque);
+}
+
+static void watch_write(void *opaque)
+{
+    SpiceWatch *watch = opaque;
+    watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque);
+}
+
+static void watch_update_mask(SpiceWatch *watch, int event_mask)
+{
+    IOHandler *on_read = NULL;
+    IOHandler *on_write = NULL;
+
+    watch->event_mask = event_mask;
+    if (watch->event_mask & SPICE_WATCH_EVENT_READ)
+        on_read = watch_read;
+    if (watch->event_mask & SPICE_WATCH_EVENT_WRITE)
+        on_read = watch_write;
+    qemu_set_fd_handler(watch->fd, on_read, on_write, watch);
+}
+
+static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
+{
+    SpiceWatch *watch;
+
+    watch = qemu_mallocz(sizeof(*watch));
+    watch->fd     = fd;
+    watch->func   = func;
+    watch->opaque = opaque;
+    QTAILQ_INSERT_TAIL(&watches, watch, next);
+    fprintf(stderr, "%s: watch %p, fd %d\n", __FUNCTION__, watch, fd);
+
+    watch_update_mask(watch, event_mask);
+    return watch;
+}
+
+static void watch_remove(SpiceWatch *watch)
+{
+    fprintf(stderr, "%s: watch %p\n", __FUNCTION__, watch);
+    watch_update_mask(watch, 0);
+    QTAILQ_REMOVE(&watches, watch, next);
+    qemu_free(watch);
+}
+
+static SpiceCoreInterface core_interface = {
+    .base.type          = SPICE_INTERFACE_CORE,
+    .base.description   = "qemu core services",
+    .base.major_version = SPICE_INTERFACE_CORE_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_CORE_MINOR,
+
+    .timer_add          = timer_add,
+    .timer_start        = timer_start,
+    .timer_cancel       = timer_cancel,
+    .timer_remove       = timer_remove,
+
+    .watch_add          = watch_add,
+    .watch_update_mask  = watch_update_mask,
+    .watch_remove       = watch_remove,
+};
+
+/* functions for the rest of qemu */
+
+QemuOptsList qemu_spice_opts = {
+    .name = "spice",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head),
+    .desc = {
+        {
+            .name = "port",
+            .type = QEMU_OPT_NUMBER,
+        },{
+            .name = "password",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "disable-ticketing",
+            .type = QEMU_OPT_BOOL,
+        },
+        { /* end if list */ }
+    },
+};
+
+void qemu_spice_init(void)
+{
+    QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
+    const char *password;
+    int port;
+
+    if (!opts)
+        return;
+    port = qemu_opt_get_number(opts, "port", 0);
+    if (!port)
+        return;
+    password = qemu_opt_get(opts, "password");
+
+    spice_server = spice_server_new();
+    spice_server_set_port(spice_server, port);
+    if (password)
+        spice_server_set_ticket(spice_server, password, 0, 0, 0);
+    if (qemu_opt_get_bool(opts, "disable-ticketing", 0))
+        spice_server_set_noauth(spice_server);
+
+    /* TODO: make configurable via cmdline */
+    spice_server_set_image_compression(spice_server, SPICE_IMAGE_COMPRESS_GLZ);
+
+    spice_server_init(spice_server, &core_interface);
+    using_spice = 1;
+}
diff --git a/vl.c b/vl.c
index 4fb55b8..c1b12a6 100644
--- a/vl.c
+++ b/vl.c
@@ -160,6 +160,8 @@ int main(int argc, char **argv)
 #include "cpus.h"
 #include "arch_init.h"
 
+#include "qemu-spice.h"
+
 //#define DEBUG_NET
 //#define DEBUG_SLIRP
 
@@ -3348,6 +3350,15 @@ int main(int argc, char **argv, char **envp)
                     fclose(fp);
                     break;
                 }
+#ifdef CONFIG_SPICE
+            case QEMU_OPTION_spice:
+                opts = qemu_opts_parse(&qemu_spice_opts, optarg, 1);
+                if (!opts) {
+                    fprintf(stderr, "parse error: %s\n", optarg);
+                    exit(1);
+                }
+                break;
+#endif
             case QEMU_OPTION_writeconfig:
                 {
                     FILE *fp;
@@ -3660,6 +3671,10 @@ int main(int argc, char **argv, char **envp)
     }
     qemu_add_globals();
 
+#ifdef CONFIG_SPICE
+    qemu_spice_init();
+#endif
+
     machine->init(ram_size, boot_devices,
                   kernel_filename, kernel_cmdline, initrd_filename, cpu_model);
 
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 04/11] spice: add keyboard
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (2 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 03/11] spice: core bits Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 05/11] spice: add mouse Gerd Hoffmann
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

Open keyboard channel.  Now you can type into the spice client and the
keyboard events are sent to your guest.  You'll need some other display
like vnc to actually see the guest responding to them though.
---
 Makefile.target |    2 +-
 qemu-spice.h    |    1 +
 spice-input.c   |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 spice.c         |    2 +
 4 files changed, 61 insertions(+), 1 deletions(-)
 create mode 100644 spice-input.c

diff --git a/Makefile.target b/Makefile.target
index ad3e595..4da1b27 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -194,7 +194,7 @@ obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o
 obj-i386-y += vmmouse.o vmport.o hpet.o
 obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
 obj-i386-y += debugcon.o multiboot.o
-obj-i386-$(CONFIG_SPICE) += spice.o
+obj-i386-$(CONFIG_SPICE) += spice.o spice-input.o
 
 # shared objects
 obj-ppc-y = ppc.o
diff --git a/qemu-spice.h b/qemu-spice.h
index 5597576..ceb3db2 100644
--- a/qemu-spice.h
+++ b/qemu-spice.h
@@ -12,6 +12,7 @@ extern SpiceServer *spice_server;
 extern int using_spice;
 
 void qemu_spice_init(void);
+void qemu_spice_input_init(void);
 
 #else  /* CONFIG_SPICE */
 
diff --git a/spice-input.c b/spice-input.c
new file mode 100644
index 0000000..e05f300
--- /dev/null
+++ b/spice-input.c
@@ -0,0 +1,57 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <spice.h>
+
+#include "qemu-common.h"
+#include "qemu-spice.h"
+#include "console.h"
+
+/* keyboard bits */
+
+typedef struct QemuSpiceKbd {
+    SpiceKbdInstance sin;
+    int ledstate;
+} QemuSpiceKbd;
+
+static void kbd_push_key(SpiceKbdInstance *sin, uint8_t frag);
+static uint8_t kbd_get_leds(SpiceKbdInstance *sin);
+static void kbd_leds(void *opaque, int l);
+
+static SpiceKbdInterface kbd_interface = {
+    .base.type          = SPICE_INTERFACE_KEYBOARD,
+    .base.description   = "qemu keyboard",
+    .base.major_version = SPICE_INTERFACE_KEYBOARD_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_KEYBOARD_MINOR,
+    .push_scan_freg     = kbd_push_key,
+    .get_leds           = kbd_get_leds,
+};
+
+static void kbd_push_key(SpiceKbdInstance *sin, uint8_t frag)
+{
+    kbd_put_keycode(frag);
+}
+
+static uint8_t kbd_get_leds(SpiceKbdInstance *sin)
+{
+    QemuSpiceKbd *kbd = container_of(sin, QemuSpiceKbd, sin);
+    return kbd->ledstate;
+}
+
+static void kbd_leds(void *opaque, int ledstate)
+{
+    QemuSpiceKbd *kbd = opaque;
+    kbd->ledstate = ledstate;
+    spice_server_kbd_leds(&kbd->sin, ledstate);
+}
+
+void qemu_spice_input_init(void)
+{
+    QemuSpiceKbd *kbd;
+
+    kbd = qemu_mallocz(sizeof(*kbd));
+    kbd->sin.base.sif = &kbd_interface.base;
+    spice_server_add_interface(spice_server, &kbd->sin.base);
+    qemu_add_led_event_handler(kbd_leds, kbd);
+}
diff --git a/spice.c b/spice.c
index 5315b18..68d8c95 100644
--- a/spice.c
+++ b/spice.c
@@ -167,4 +167,6 @@ void qemu_spice_init(void)
 
     spice_server_init(spice_server, &core_interface);
     using_spice = 1;
+
+    qemu_spice_input_init();
 }
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 05/11] spice: add mouse
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (3 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 04/11] spice: add keyboard Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 06/11] spice: simple display Gerd Hoffmann
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

Open mouse channel.  Now you can move the guests mouse pointer.
No tablet / absolute positioning (yet) though.
---
 spice-input.c |   31 +++++++++++++++++++++++++++++++
 1 files changed, 31 insertions(+), 0 deletions(-)

diff --git a/spice-input.c b/spice-input.c
index e05f300..c22ab53 100644
--- a/spice-input.c
+++ b/spice-input.c
@@ -46,12 +46,43 @@ static void kbd_leds(void *opaque, int ledstate)
     spice_server_kbd_leds(&kbd->sin, ledstate);
 }
 
+/* mouse bits */
+
+typedef struct QemuSpiceMouse {
+    SpiceMouseInstance sin;
+} QemuSpiceMouse;
+
+static void mouse_motion(SpiceMouseInstance *sin, int dx, int dy, int dz,
+                         uint32_t buttons_state)
+{
+    kbd_mouse_event(dx, dy, dz, buttons_state);
+}
+
+static void mouse_buttons(SpiceMouseInstance *sin, uint32_t buttons_state)
+{
+    kbd_mouse_event(0, 0, 0, buttons_state);
+}
+
+static SpiceMouseInterface mouse_interface = {
+    .base.type          = SPICE_INTERFACE_MOUSE,
+    .base.description   = "mouse",
+    .base.major_version = SPICE_INTERFACE_MOUSE_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_MOUSE_MINOR,
+    .motion             = mouse_motion,
+    .buttons            = mouse_buttons,
+};
+
 void qemu_spice_input_init(void)
 {
     QemuSpiceKbd *kbd;
+    QemuSpiceMouse *mouse;
 
     kbd = qemu_mallocz(sizeof(*kbd));
     kbd->sin.base.sif = &kbd_interface.base;
     spice_server_add_interface(spice_server, &kbd->sin.base);
     qemu_add_led_event_handler(kbd_leds, kbd);
+
+    mouse = qemu_mallocz(sizeof(*mouse));
+    mouse->sin.base.sif = &mouse_interface.base;
+    spice_server_add_interface(spice_server, &mouse->sin.base);
 }
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 06/11] spice: simple display
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (4 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 05/11] spice: add mouse Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 07/11] spice: tls support Gerd Hoffmann
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

With that patch applied you'll actually see the guests screen in the
spice client.  This does *not* bring qxl and full spice support though.
This is basically the qxl vga mode made more generic, so it plays
together with any qemu-emulated gfx card.  You can display stdvga or
cirrus via spice client.  You can have both vnc and spice enabled and
clients connected at the same time.
---
 Makefile.target |    2 +-
 qemu-spice.h    |    1 +
 spice-display.c |  429 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 spice-display.h |   47 ++++++
 vl.c            |    7 +-
 5 files changed, 484 insertions(+), 2 deletions(-)
 create mode 100644 spice-display.c
 create mode 100644 spice-display.h

diff --git a/Makefile.target b/Makefile.target
index 4da1b27..842a489 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -194,7 +194,7 @@ obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o
 obj-i386-y += vmmouse.o vmport.o hpet.o
 obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
 obj-i386-y += debugcon.o multiboot.o
-obj-i386-$(CONFIG_SPICE) += spice.o spice-input.o
+obj-i386-$(CONFIG_SPICE) += spice.o spice-input.o spice-display.o
 
 # shared objects
 obj-ppc-y = ppc.o
diff --git a/qemu-spice.h b/qemu-spice.h
index ceb3db2..f061004 100644
--- a/qemu-spice.h
+++ b/qemu-spice.h
@@ -13,6 +13,7 @@ extern int using_spice;
 
 void qemu_spice_init(void);
 void qemu_spice_input_init(void);
+void qemu_spice_display_init(DisplayState *ds);
 
 #else  /* CONFIG_SPICE */
 
diff --git a/spice-display.c b/spice-display.c
new file mode 100644
index 0000000..44c259d
--- /dev/null
+++ b/spice-display.c
@@ -0,0 +1,429 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "qemu-common.h"
+#include "qemu-spice.h"
+#include "qemu-timer.h"
+#include "qemu-queue.h"
+#include "monitor.h"
+#include "console.h"
+#include "sysemu.h"
+
+#include "spice-display.h"
+
+static int debug = 1;
+
+int qemu_spice_rect_is_empty(const SpiceRect* r)
+{
+    return r->top == r->bottom || r->left == r->right;
+}
+
+void qemu_spice_rect_union(SpiceRect *dest, const SpiceRect *r)
+{
+    if (qemu_spice_rect_is_empty(r)) {
+        return;
+    }
+
+    if (qemu_spice_rect_is_empty(dest)) {
+        *dest = *r;
+        return;
+    }
+
+    dest->top = MIN(dest->top, r->top);
+    dest->left = MIN(dest->left, r->left);
+    dest->bottom = MAX(dest->bottom, r->bottom);
+    dest->right = MAX(dest->right, r->right);
+}
+
+SimpleSpiceUpdate *qemu_spice_create_update(SimpleSpiceDisplay *ssd)
+{
+    SimpleSpiceUpdate *update;
+    QXLDrawable *drawable;
+    QXLImage *image;
+    QXLCommand *cmd;
+    uint8_t *src, *dst;
+    int by, bw, bh;
+
+    if (qemu_spice_rect_is_empty(&ssd->dirty)) {
+        return NULL;
+    };
+
+    pthread_mutex_lock(&ssd->lock);
+    if (debug > 1)
+        fprintf(stderr, "%s: lr %d -> %d,  tb -> %d -> %d\n", __FUNCTION__,
+                ssd->dirty.left, ssd->dirty.right,
+                ssd->dirty.top, ssd->dirty.bottom);
+
+    update   = qemu_mallocz(sizeof(*update));
+    drawable = &update->drawable;
+    image    = &update->image;
+    cmd      = &update->ext.cmd;
+
+    bw       = ssd->dirty.right - ssd->dirty.left;
+    bh       = ssd->dirty.bottom - ssd->dirty.top;
+    update->bitmap = qemu_malloc(bw * bh * 4);
+
+    drawable->bbox            = ssd->dirty;
+    drawable->clip.type       = SPICE_CLIP_TYPE_NONE;
+    drawable->effect          = QXL_EFFECT_OPAQUE;
+    drawable->release_info.id = (intptr_t)update;
+    drawable->type            = QXL_DRAW_COPY;
+
+    drawable->u.copy.rop_decriptor   = SPICE_ROPD_OP_PUT;
+    drawable->u.copy.src_bitmap      = (intptr_t)image;
+    drawable->u.copy.src_area.right  = bw;
+    drawable->u.copy.src_area.bottom = bh;
+
+    QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, ssd->unique++);
+    image->descriptor.type   = SPICE_IMAGE_TYPE_BITMAP;
+    image->bitmap.flags      = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN;
+    image->bitmap.stride     = bw * 4;
+    image->descriptor.width  = image->bitmap.x = bw;
+    image->descriptor.height = image->bitmap.y = bh;
+    image->bitmap.data = (intptr_t)(update->bitmap);
+    image->bitmap.palette = 0;
+
+    src = ds_get_data(ssd->ds) +
+        ssd->dirty.top * ds_get_linesize(ssd->ds) +
+        ssd->dirty.left * ds_get_bytes_per_pixel(ssd->ds);
+    dst = update->bitmap;
+    for (by = 0; by < bh; by++) {
+        memcpy(dst, src, bw * ds_get_bytes_per_pixel(ssd->ds));
+        src += ds_get_linesize(ssd->ds);
+        dst += image->bitmap.stride;
+    }
+
+    switch (ds_get_bits_per_pixel(ssd->ds)) {
+    case 16:
+        image->bitmap.format = SPICE_BITMAP_FMT_16BIT;
+        break;
+    case 32:
+        image->bitmap.format = SPICE_BITMAP_FMT_32BIT;
+        break;
+    default:
+        fprintf(stderr, "%s: unhandled depth: %d bits\n", __FUNCTION__,
+                ds_get_bits_per_pixel(ssd->ds));
+        abort();
+    }
+
+    cmd->type = QXL_CMD_DRAW;
+    cmd->data = (intptr_t)drawable;
+
+    memset(&ssd->dirty, 0, sizeof(ssd->dirty));
+    pthread_mutex_unlock(&ssd->lock);
+    return update;
+}
+
+void qemu_spice_destroy_update(SimpleSpiceDisplay *sdpy, SimpleSpiceUpdate *update)
+{
+    qemu_free(update->bitmap);
+    qemu_free(update);
+}
+
+void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd)
+{
+    QXLDevMemSlot memslot;
+
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+
+    memset(&memslot, 0, sizeof(memslot));
+    memslot.slot_group_id = MEMSLOT_GROUP_HOST;
+    memslot.virt_end = ~0;
+    ssd->worker->add_memslot(ssd->worker, &memslot);
+}
+
+void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd)
+{
+    QXLDevSurfaceCreate surface;
+
+    if (debug)
+        fprintf(stderr, "%s: %dx%d\n", __FUNCTION__,
+                ds_get_width(ssd->ds), ds_get_height(ssd->ds));
+
+    surface.depth      = 32;
+    surface.width      = ds_get_width(ssd->ds);
+    surface.height     = ds_get_height(ssd->ds);
+    surface.stride     = -surface.width * 4;
+    surface.mouse_mode = 0;
+    surface.flags      = 0;
+    surface.type       = 0;
+    surface.mem        = (intptr_t)ssd->buf;
+    surface.group_id   = MEMSLOT_GROUP_HOST;
+    ssd->worker->create_primary_surface(ssd->worker, 0, &surface);
+}
+
+void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd)
+{
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+
+    ssd->worker->destroy_primary_surface(ssd->worker, 0);
+}
+
+void qemu_spice_vm_change_state_handler(void *opaque, int running, int reason)
+{
+    SimpleSpiceDisplay *ssd = opaque;
+
+    if (running) {
+        ssd->worker->start(ssd->worker);
+    } else {
+        ssd->worker->stop(ssd->worker);
+    }
+    ssd->running = running;
+}
+
+/* display listener callbacks */
+
+void qemu_spice_display_update(SimpleSpiceDisplay *ssd,
+                               int x, int y, int w, int h)
+{
+    SpiceRect update_area;
+
+    if (debug > 1)
+        fprintf(stderr, "%s: x %d y %d w %d h %d\n", __FUNCTION__, x, y, w, h);
+    update_area.left = x,
+    update_area.right = x + w;
+    update_area.top = y;
+    update_area.bottom = y + h;
+
+    pthread_mutex_lock(&ssd->lock);
+    if (qemu_spice_rect_is_empty(&ssd->dirty)) {
+        ssd->notify++;
+    }
+    qemu_spice_rect_union(&ssd->dirty, &update_area);
+    pthread_mutex_unlock(&ssd->lock);
+}
+
+void qemu_spice_display_resize(SimpleSpiceDisplay *ssd)
+{
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+
+    pthread_mutex_lock(&ssd->lock);
+    memset(&ssd->dirty, 0, sizeof(ssd->dirty));
+    pthread_mutex_unlock(&ssd->lock);
+
+    qemu_spice_destroy_host_primary(ssd);
+    qemu_spice_create_host_primary(ssd);
+
+    pthread_mutex_lock(&ssd->lock);
+    ssd->dirty.left   = 0;
+    ssd->dirty.right  = ds_get_width(ssd->ds);
+    ssd->dirty.top    = 0;
+    ssd->dirty.bottom = ds_get_height(ssd->ds);
+    ssd->notify++;
+    pthread_mutex_unlock(&ssd->lock);
+}
+
+void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd)
+{
+    if (debug > 2)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    vga_hw_update();
+    if (ssd->notify) {
+        ssd->notify = 0;
+        ssd->worker->wakeup(ssd->worker);
+        if (debug > 1)
+            fprintf(stderr, "%s: notify\n", __FUNCTION__);
+    }
+}
+
+/* spice display interface callbacks */
+
+static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
+{
+    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    ssd->worker = qxl_worker;
+}
+
+static void interface_set_compression_level(QXLInstance *sin, int level)
+{
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    /* nothing to do */
+}
+
+static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
+{
+    if (debug > 2)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    /* nothing to do */
+}
+
+static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
+{
+    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+
+    info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
+    info->memslot_id_bits  = MEMSLOT_SLOT_BITS;
+    info->num_memslots = NUM_MEMSLOTS;
+    info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
+    info->internal_groupslot_id = 0;
+    info->qxl_ram_size = ssd->bufsize;
+    info->n_surfaces = 10000;
+}
+
+static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+    SimpleSpiceUpdate *update;
+
+    if (debug > 2)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    update = qemu_spice_create_update(ssd);
+    if (update == NULL) {
+        return false;
+    }
+    *ext = update->ext;
+    return true;
+}
+
+static int interface_req_cmd_notification(QXLInstance *sin)
+{
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    return 1;
+}
+
+static int interface_has_command(QXLInstance *sin)
+{
+    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    return !qemu_spice_rect_is_empty(&ssd->dirty);
+}
+
+static void interface_release_resource(QXLInstance *sin,
+                                       struct QXLReleaseInfoExt ext)
+{
+    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+    uintptr_t id;
+
+    if (debug > 1)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    id = ext.info->id;
+    qemu_spice_destroy_update(ssd, (void*)id);
+}
+
+static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+    if (debug > 2)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    return false;
+}
+
+static int interface_req_cursor_notification(QXLInstance *sin)
+{
+    if (debug)
+        fprintf(stderr, "%s:\n", __FUNCTION__);
+    return 1;
+}
+
+static void interface_get_update_area(QXLInstance *sin, const struct SpiceRect **rect , uint32_t **surface_id)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static void interface_set_save_data(QXLInstance *sin, void *data, int size)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static void *interface_get_save_data(QXLInstance *sin)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static int interface_flush_resources(QXLInstance *sin)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+    return 0;
+}
+
+static QXLInterface dpy_interface = {
+    .base.type               = SPICE_INTERFACE_QXL,
+    .base.description        = "qemu simple display",
+    .base.major_version      = SPICE_INTERFACE_QXL_MAJOR,
+    .base.minor_version      = SPICE_INTERFACE_QXL_MINOR,
+
+    .pci_vendor              = REDHAT_PCI_VENDOR_ID,
+    .pci_id                  = QXL_DEVICE_ID,
+    .pci_revision            = QXL_REVISION,
+
+    .attache_worker          = interface_attach_worker,
+    .set_compression_level   = interface_set_compression_level,
+    .set_mm_time             = interface_set_mm_time,
+
+    .get_init_info           = interface_get_init_info,
+    .get_command             = interface_get_command,
+    .req_cmd_notification    = interface_req_cmd_notification,
+    .has_command             = interface_has_command,
+    .release_resource        = interface_release_resource,
+    .get_cursor_command      = interface_get_cursor_command,
+    .req_cursor_notification = interface_req_cursor_notification,
+    .get_update_area         = interface_get_update_area,
+    .notify_update           = interface_notify_update,
+    .set_save_data           = interface_set_save_data,
+    .get_save_data           = interface_get_save_data,
+    .flush_resources         = interface_flush_resources,
+};
+
+static SimpleSpiceDisplay sdpy;
+
+static void display_update(struct DisplayState *ds, int x, int y, int w, int h)
+{
+    qemu_spice_display_update(&sdpy, x, y, w, h);
+}
+
+static void display_resize(struct DisplayState *ds)
+{
+    qemu_spice_display_resize(&sdpy);
+}
+
+static void display_refresh(struct DisplayState *ds)
+{
+    qemu_spice_display_refresh(&sdpy);
+}
+
+static DisplayChangeListener display_listener = {
+    .dpy_update  = display_update,
+    .dpy_resize  = display_resize,
+    .dpy_refresh = display_refresh,
+};
+
+void qemu_spice_display_init(DisplayState *ds)
+{
+    assert(sdpy.ds == NULL);
+    sdpy.ds = ds;
+    sdpy.bufsize = (16 * 1024 * 1024);
+    sdpy.buf = qemu_malloc(sdpy.bufsize);
+    pthread_mutex_init(&sdpy.lock, NULL);
+    register_displaychangelistener(ds, &display_listener);
+
+    sdpy.qxl.base.sif = &dpy_interface.base;
+    spice_server_add_interface(spice_server, &sdpy.qxl.base);
+    assert(sdpy.worker);
+
+    qemu_add_vm_change_state_handler(qemu_spice_vm_change_state_handler, &sdpy);
+    qemu_spice_create_host_memslot(&sdpy);
+    qemu_spice_create_host_primary(&sdpy);
+}
diff --git a/spice-display.h b/spice-display.h
new file mode 100644
index 0000000..0626543
--- /dev/null
+++ b/spice-display.h
@@ -0,0 +1,47 @@
+#include <spice/ipc_ring.h>
+#include <spice/draw.h>
+#include <spice/qxl_dev.h>
+
+#define NUM_MEMSLOTS 8
+#define MEMSLOT_GENERATION_BITS 8
+#define MEMSLOT_SLOT_BITS 8
+
+#define MEMSLOT_GROUP_HOST  0
+#define MEMSLOT_GROUP_GUEST 1
+#define NUM_MEMSLOTS_GROUPS 2
+
+typedef struct SimpleSpiceDisplay {
+    DisplayState *ds;
+    void *buf;
+    int bufsize;
+    QXLWorker *worker;
+    QXLInstance qxl;
+    int unique;
+
+    pthread_mutex_t lock;
+    SpiceRect dirty;
+    int notify;
+    int running;
+} SimpleSpiceDisplay;
+
+typedef struct SimpleSpiceUpdate {
+    QXLDrawable drawable;
+    QXLImage image;
+    QXLCommandExt ext;
+    uint8_t *bitmap;
+} SimpleSpiceUpdate;
+
+int qemu_spice_rect_is_empty(const SpiceRect* r);
+void qemu_spice_rect_union(SpiceRect *dest, const SpiceRect *r);
+
+SimpleSpiceUpdate *qemu_spice_create_update(SimpleSpiceDisplay *sdpy);
+void qemu_spice_destroy_update(SimpleSpiceDisplay *sdpy, SimpleSpiceUpdate *update);
+void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd);
+void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd);
+void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd);
+void qemu_spice_vm_change_state_handler(void *opaque, int running, int reason);
+
+void qemu_spice_display_update(SimpleSpiceDisplay *ssd,
+                               int x, int y, int w, int h);
+void qemu_spice_display_resize(SimpleSpiceDisplay *ssd);
+void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd);
diff --git a/vl.c b/vl.c
index c1b12a6..1fab3f9 100644
--- a/vl.c
+++ b/vl.c
@@ -3704,7 +3704,7 @@ int main(int argc, char **argv, char **envp)
     /* just use the first displaystate for the moment */
     ds = get_displaystate();
 
-    if (display_type == DT_DEFAULT) {
+    if (display_type == DT_DEFAULT && !using_spice) {
 #if defined(CONFIG_SDL) || defined(CONFIG_COCOA)
         display_type = DT_SDL;
 #else
@@ -3744,6 +3744,11 @@ int main(int argc, char **argv, char **envp)
     default:
         break;
     }
+#ifdef CONFIG_SPICE
+    if (using_spice) {
+        qemu_spice_display_init(ds);
+    }
+#endif
     dpy_resize(ds);
 
     dcl = ds->listeners;
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 07/11] spice: tls support
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (5 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 06/11] spice: simple display Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device Gerd Hoffmann
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

Add options to the -spice command line switch to setup tls:

tls-port
	listening port

x509-dir
	x509 file directory.  Expects same filenames as
	-vnc $display,x509=$dir

x509-key-file
x509-key-password
x509-cert-file
x509-cacert-file
x509-dh-key-file
	x509 files can also be set individually.

tls-ciphers
	which ciphers to use.
---
 spice.c |   90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 86 insertions(+), 4 deletions(-)

diff --git a/spice.c b/spice.c
index 68d8c95..0f2595b 100644
--- a/spice.c
+++ b/spice.c
@@ -8,6 +8,7 @@
 #include "qemu-spice.h"
 #include "qemu-timer.h"
 #include "qemu-queue.h"
+#include "qemu-x509.h"
 #include "monitor.h"
 
 /* core bits */
@@ -132,11 +133,35 @@ QemuOptsList qemu_spice_opts = {
             .name = "port",
             .type = QEMU_OPT_NUMBER,
         },{
+            .name = "tls-port",           /* old: sport */
+            .type = QEMU_OPT_NUMBER,
+        },{
             .name = "password",
             .type = QEMU_OPT_STRING,
         },{
             .name = "disable-ticketing",
             .type = QEMU_OPT_BOOL,
+        },{
+            .name = "x509-dir",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "x509-key-file",      /* old: sslkey */
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "x509-key-password",  /* old: sslpassword */
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "x509-cert-file",     /* old: sslcert */
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "x509-cacert-file",   /* old: sslcafile */
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "x509-dh-key-file",   /* old: ssldhfile */
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "tls-ciphers",        /* old: sslciphersuite */
+            .type = QEMU_OPT_STRING,
         },
         { /* end if list */ }
     },
@@ -145,18 +170,71 @@ QemuOptsList qemu_spice_opts = {
 void qemu_spice_init(void)
 {
     QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
-    const char *password;
-    int port;
+    const char *password, *str, *x509_dir,
+        *x509_key_password = NULL,
+        *x509_dh_file = NULL,
+        *tls_ciphers = NULL;
+    char *x509_key_file = NULL,
+        *x509_cert_file = NULL,
+        *x509_cacert_file = NULL;
+    int port, tls_port, len;
 
     if (!opts)
         return;
     port = qemu_opt_get_number(opts, "port", 0);
-    if (!port)
+    tls_port = qemu_opt_get_number(opts, "tls-port", 0);
+    if (!port && !tls_port)
         return;
     password = qemu_opt_get(opts, "password");
 
+    if (tls_port) {
+        x509_dir = qemu_opt_get(opts, "x509-dir");
+        if (NULL == x509_dir)
+            x509_dir = ".";
+        len = strlen(x509_dir) + 32;
+
+        str = qemu_opt_get(opts, "x509-key-file");
+        if (str) {
+            x509_key_file = qemu_strdup(str);
+        } else {
+            x509_key_file = qemu_malloc(len);
+            snprintf(x509_key_file, len, "%s/%s", x509_dir, X509_SERVER_KEY_FILE);
+        }
+
+        str = qemu_opt_get(opts, "x509-cert-file");
+        if (str) {
+            x509_cert_file = qemu_strdup(str);
+        } else {
+            x509_cert_file = qemu_malloc(len);
+            snprintf(x509_cert_file, len, "%s/%s", x509_dir, X509_SERVER_CERT_FILE);
+        }
+
+        str = qemu_opt_get(opts, "x509-cacert-file");
+        if (str) {
+            x509_cacert_file = qemu_strdup(str);
+        } else {
+            x509_cacert_file = qemu_malloc(len);
+            snprintf(x509_cacert_file, len, "%s/%s", x509_dir, X509_CA_CERT_FILE);
+        }
+
+        x509_key_password = qemu_opt_get(opts, "x509-key-password");
+        x509_dh_file = qemu_opt_get(opts, "x509-dh-file");
+        tls_ciphers = qemu_opt_get(opts, "tls-ciphers");
+    }
+
     spice_server = spice_server_new();
-    spice_server_set_port(spice_server, port);
+    if (port) {
+        spice_server_set_port(spice_server, port);
+    }
+    if (tls_port) {
+        spice_server_set_tls(spice_server, tls_port,
+                             x509_cacert_file,
+                             x509_cert_file,
+                             x509_key_file,
+                             x509_key_password,
+                             x509_dh_file,
+                             tls_ciphers);
+    }
     if (password)
         spice_server_set_ticket(spice_server, password, 0, 0, 0);
     if (qemu_opt_get_bool(opts, "disable-ticketing", 0))
@@ -169,4 +247,8 @@ void qemu_spice_init(void)
     using_spice = 1;
 
     qemu_spice_input_init();
+
+    qemu_free(x509_key_file);
+    qemu_free(x509_cert_file);
+    qemu_free(x509_cacert_file);
 }
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (6 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 07/11] spice: tls support Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14 16:52   ` Blue Swirl
  2010-04-14 22:21   ` [Qemu-devel] " Alexander Graf
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 09/11] qxl: local rendering for sdl/vnc Gerd Hoffmann
                   ` (2 subsequent siblings)
  10 siblings, 2 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

---
 Makefile.target |    2 +-
 hw/hw.h         |   14 +
 hw/pc.c         |    8 +
 hw/qxl.c        | 1035 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/vga_int.h    |    2 +-
 qemu-spice.h    |    2 +
 sysemu.h        |    3 +-
 vl.c            |    4 +-
 8 files changed, 1066 insertions(+), 4 deletions(-)
 create mode 100644 hw/qxl.c

diff --git a/Makefile.target b/Makefile.target
index 842a489..57aa212 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -194,7 +194,7 @@ obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o
 obj-i386-y += vmmouse.o vmport.o hpet.o
 obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o
 obj-i386-y += debugcon.o multiboot.o
-obj-i386-$(CONFIG_SPICE) += spice.o spice-input.o spice-display.o
+obj-i386-$(CONFIG_SPICE) += spice.o spice-input.o spice-display.o qxl.o
 
 # shared objects
 obj-ppc-y = ppc.o
diff --git a/hw/hw.h b/hw/hw.h
index 328b704..894cac7 100644
--- a/hw/hw.h
+++ b/hw/hw.h
@@ -508,6 +508,17 @@ extern const VMStateInfo vmstate_info_unused_buffer;
     .start        = (_start),                                        \
 }
 
+#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \
+    .name         = (stringify(_field)),                             \
+    .version_id   = (_version),                                      \
+    .field_exists = (_test),                                         \
+    .size_offset  = vmstate_offset_value(_state, _field_size, uint32_t),\
+    .info         = &vmstate_info_buffer,                            \
+    .flags        = VMS_VBUFFER|VMS_POINTER,                         \
+    .offset       = offsetof(_state, _field),                        \
+    .start        = (_start),                                        \
+}
+
 #define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \
     .name       = (stringify(_field)),                               \
     .version_id = (_version),                                        \
@@ -713,6 +724,9 @@ extern const VMStateDescription vmstate_i2c_slave;
 #define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size)                        \
     VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size)
 
+#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size)                        \
+    VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size)
+
 #define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size)                    \
     VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size)
 
diff --git a/hw/pc.c b/hw/pc.c
index 69e597f..8cde987 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -47,6 +47,7 @@
 #include "elf.h"
 #include "multiboot.h"
 #include "kvm.h"
+#include "qemu-spice.h"
 
 /* output Bochs bios info messages */
 //#define DEBUG_BIOS
@@ -945,6 +946,13 @@ static void pc_init1(ram_addr_t ram_size,
             pci_vmsvga_init(pci_bus);
         else
             fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __FUNCTION__);
+#ifdef CONFIG_SPICE
+    } else if (qxl_enabled) {
+        if (pci_enabled)
+            pci_create_simple(pci_bus, -1, "qxl");
+        else
+            fprintf(stderr, "%s: qxl: no PCI bus\n", __FUNCTION__);
+#endif
     } else if (std_vga_enabled) {
         if (pci_enabled) {
             pci_vga_init(pci_bus, 0, 0);
diff --git a/hw/qxl.c b/hw/qxl.c
new file mode 100644
index 0000000..ed7e975
--- /dev/null
+++ b/hw/qxl.c
@@ -0,0 +1,1035 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "qemu-common.h"
+#include "qemu-barrier.h"
+#include "qemu-spice.h"
+#include "qemu-timer.h"
+#include "qemu-queue.h"
+#include "monitor.h"
+#include "console.h"
+#include "sysemu.h"
+#include "pci.h"
+#include "vga_int.h"
+
+#include "spice-display.h"
+
+#undef SPICE_RING_PROD_ITEM
+#define SPICE_RING_PROD_ITEM(r, ret) {                                  \
+        typeof(r) start = r;                                            \
+        typeof(r) end = r + 1;                                          \
+        uint32_t prod = (r)->prod & SPICE_RING_INDEX_MASK(r);           \
+        typeof(&(r)->items[prod]) m_item = &(r)->items[prod];           \
+        if (!((uint8_t*)m_item >= (uint8_t*)(start) && (uint8_t*)(m_item + 1) <= (uint8_t*)(end))) { \
+            abort();                                                    \
+        }                                                               \
+        ret = &m_item->el;                                              \
+    }
+
+#undef SPICE_RING_CONS_ITEM
+#define SPICE_RING_CONS_ITEM(r, ret) {                                  \
+        typeof(r) start = r;                                            \
+        typeof(r) end = r + 1;                                          \
+        uint32_t cons = (r)->cons & SPICE_RING_INDEX_MASK(r);           \
+        typeof(&(r)->items[cons]) m_item = &(r)->items[cons];           \
+        if (!((uint8_t*)m_item >= (uint8_t*)(start) && (uint8_t*)(m_item + 1) <= (uint8_t*)(end))) { \
+            abort();                                                    \
+        }                                                               \
+        ret = &m_item->el;                                              \
+    }
+
+
+#define PANIC_ON(x) if ((x)) {                         \
+    printf("%s: PANIC %s failed\n", __FUNCTION__, #x); \
+    exit(-1);                                          \
+}
+
+enum qxl_mode {
+    QXL_MODE_UNDEFINED,
+    QXL_MODE_VGA,
+    QXL_MODE_NATIVE,
+};
+
+typedef struct PCIQXLDevice {
+    PCIDevice          pci;
+    SimpleSpiceDisplay ssd;
+    int                id;
+    enum qxl_mode      mode;
+    int                generation;
+
+    /* thread signaling */
+    pthread_t          main;
+    int                pipe[2];
+
+    /* ram pci bar */
+    QXLRam             *ram;
+    VGACommonState     vga;
+    int                num_free_res;
+    QXLReleaseInfo     *last_release;
+
+    /* rom pci bar */
+    QXLRom             shadow_rom;
+    QXLRom             *rom;
+    QXLModes           *modes;
+    uint32_t           rom_size;
+    uint64_t           rom_offset;
+
+    /* vram pci bar */
+    uint32_t           vram_size;
+    uint64_t           vram_offset;
+
+    /* io bar */
+    uint32_t           io_base;
+
+} PCIQXLDevice;
+
+#undef ALIGN
+#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1))
+
+#define PIXEL_SIZE 0.2936875 //1280x1024 is 14.8" x 11.9" 
+
+#define QXL_MODE(_x, _y, _b, _o)                  \
+    {   .x_res = _x,                              \
+        .y_res = _y,                              \
+        .bits  = _b,                              \
+        .stride = (_x) * (_b) / 8,                \
+        .x_mili = PIXEL_SIZE * (_x),              \
+        .y_mili = PIXEL_SIZE * (_y),              \
+        .orientation = _o,                        \
+    }
+
+#define QXL_MODE_16_32(x_res, y_res, orientation) \
+    QXL_MODE(x_res, y_res, 16, orientation),      \
+    QXL_MODE(x_res, y_res, 32, orientation)
+
+#define QXL_MODE_EX(x_res, y_res)                 \
+    QXL_MODE_16_32(x_res, y_res, 0),              \
+    QXL_MODE_16_32(y_res, x_res, 1),              \
+    QXL_MODE_16_32(x_res, y_res, 2),              \
+    QXL_MODE_16_32(y_res, x_res, 3)
+
+static QXLMode qxl_modes[] = {
+    QXL_MODE_EX(640, 480),
+    QXL_MODE_EX(800, 600),
+    QXL_MODE_EX(832, 624),
+    QXL_MODE_EX(1024, 768),
+    QXL_MODE_EX(1152, 864),
+    QXL_MODE_EX(1152, 870),
+    QXL_MODE_EX(1280, 720),
+    QXL_MODE_EX(1280, 768),
+    QXL_MODE_EX(1280, 800),
+    QXL_MODE_EX(1280, 960),
+    QXL_MODE_EX(1280, 1024),
+    QXL_MODE_EX(1360, 768),
+    QXL_MODE_EX(1366, 768),
+    QXL_MODE_EX(1400, 1050),
+    QXL_MODE_EX(1440, 900),
+    QXL_MODE_EX(1600, 900),
+    QXL_MODE_EX(1600, 1200),
+    QXL_MODE_EX(1680, 1050),
+    QXL_MODE_EX(1920, 1080),
+#ifdef QXL_HIRES_MODES
+    QXL_MODE_EX(1920, 1200),
+    QXL_MODE_EX(1920, 1440),
+    QXL_MODE_EX(2048, 1536),
+    QXL_MODE_EX(2560, 1600),
+    QXL_MODE_EX(2560, 2048),
+    QXL_MODE_EX(2800, 2100),
+    QXL_MODE_EX(3200, 2400),
+#endif
+};
+
+#define dprintf(level, fmt, ...)                                         \
+    do { if (debug >= level) fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+
+static int debug = 1;
+static int device_id = 0;
+static PCIQXLDevice *qxl0;
+
+static void qxl_send_events(PCIQXLDevice *d, uint32_t events);
+static void qxl_destroy_primary(PCIQXLDevice *d);
+
+static inline uint32_t msb_mask(uint32_t val)
+{
+    uint32_t mask;
+
+    do {
+        mask = ~(val - 1) & val;
+        val &= ~mask;
+    } while (mask < val);
+
+    return mask;
+}
+
+static inline void atomic_or(uint32_t *var, uint32_t add)
+{
+   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add) : "memory");
+}
+
+static ram_addr_t qxl_rom_size(void)
+{
+    uint32_t rom_size = sizeof(QXLRom) + sizeof(QXLModes) + sizeof(qxl_modes);
+    rom_size = MAX(rom_size, TARGET_PAGE_SIZE);
+    rom_size = msb_mask(rom_size * 2 - 1);
+    return rom_size;
+}
+
+static void init_qxl_rom(PCIQXLDevice *d)
+{
+    QXLRom *rom = qemu_get_ram_ptr(d->rom_offset);
+    QXLModes *modes = (QXLModes *)(rom + 1);
+    uint32_t ram_header_size;
+    uint32_t fb, maxfb = 0;
+    int i;
+
+    memset(rom, 0, d->rom_size);
+
+    rom->magic = QXL_ROM_MAGIC;
+    rom->id = d->id;
+    rom->modes_offset = sizeof(QXLRom);
+
+    rom->slot_gen_bits = MEMSLOT_GENERATION_BITS;
+    rom->slot_id_bits = MEMSLOT_SLOT_BITS;
+    rom->slots_start = 1;
+    rom->slots_end = NUM_MEMSLOTS - 1;
+    rom->n_surfaces = 10000;
+
+    modes->n_modes = ARRAY_SIZE(qxl_modes);
+    for (i = 0; i < modes->n_modes; i++) {
+        fb = qxl_modes[i].y_res * qxl_modes[i].stride;
+        if (maxfb < fb)
+            maxfb = fb;
+        modes->modes[i] = qxl_modes[i];
+        modes->modes[i].id = i;
+    }
+    if (maxfb < VGA_RAM_SIZE && d->id == 0)
+        maxfb = VGA_RAM_SIZE;
+
+    ram_header_size = ALIGN(sizeof(QXLRam), 4096);
+    rom->surface0_area_size = ALIGN(maxfb, 4096);
+
+    rom->num_pages = d->vga.vram_size;
+    rom->num_pages -= ram_header_size;
+    rom->num_pages -= rom->surface0_area_size;
+    rom->num_pages = rom->num_pages / TARGET_PAGE_SIZE;
+
+    rom->ram_header_offset = d->vga.vram_size - ram_header_size;
+
+    d->shadow_rom = *rom;
+    d->rom = rom;
+    d->modes = modes;
+}
+
+static void init_qxl_ram(PCIQXLDevice *d)
+{
+    uint8_t *buf;
+    uint64_t *item;
+
+    buf = qemu_get_ram_ptr(d->vga.vram_offset);
+    d->ram = (QXLRam *)(buf + d->shadow_rom.ram_header_offset);
+    d->ram->magic = QXL_RAM_MAGIC;
+    d->ram->int_pending = 0;
+    d->ram->int_mask = 0;
+    SPICE_RING_INIT(&d->ram->cmd_ring);
+    SPICE_RING_INIT(&d->ram->cursor_ring);
+    SPICE_RING_INIT(&d->ram->release_ring);
+    SPICE_RING_PROD_ITEM(&d->ram->release_ring, item);
+    *item = 0;
+}
+
+static void qxl_rom_set_dirty(PCIQXLDevice *d)
+{
+    ram_addr_t addr = d->rom_offset;
+    ram_addr_t end  = addr + d->rom_size;
+
+    while (addr < end) {
+        cpu_physical_memory_set_dirty(addr);
+        addr += TARGET_PAGE_SIZE;
+    }
+}
+
+/* spice display interface callbacks */
+
+static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+    dprintf(1, "%s:\n", __FUNCTION__);
+    qxl->ssd.worker = qxl_worker;
+}
+
+static void interface_set_compression_level(QXLInstance *sin, int level)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+    dprintf(1, "%s: %d\n", __FUNCTION__, level);
+    qxl->shadow_rom.compression_level = level;
+    qxl->rom->compression_level = level;
+    qxl_rom_set_dirty(qxl);
+}
+
+static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+    qxl->shadow_rom.mm_clock = mm_time;
+    qxl->rom->mm_clock = mm_time;
+    qxl_rom_set_dirty(qxl);
+}
+
+static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+    dprintf(1, "%s:\n", __FUNCTION__);
+    info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
+    info->memslot_id_bits = MEMSLOT_SLOT_BITS;
+    info->num_memslots = NUM_MEMSLOTS;
+    info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
+    info->internal_groupslot_id = 0;
+    info->qxl_ram_size = qxl->shadow_rom.num_pages << TARGET_PAGE_BITS;
+    info->n_surfaces = 10000;
+}
+
+static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+    SimpleSpiceUpdate *update;
+    QXLCommandRing *ring;
+    QXLCommand *cmd;
+    int notify;
+
+    switch (qxl->mode) {
+    case QXL_MODE_VGA:
+        dprintf(2, "%s: vga\n", __FUNCTION__);
+        update = qemu_spice_create_update(&qxl->ssd);
+        if (update == NULL) {
+            return false;
+        }
+        *ext = update->ext;
+        return true;
+    case QXL_MODE_NATIVE:
+        dprintf(2, "%s: native\n", __FUNCTION__);
+        ring = &qxl->ram->cmd_ring;
+        if (SPICE_RING_IS_EMPTY(ring)) {
+            return false;
+        }
+        SPICE_RING_CONS_ITEM(ring, cmd);
+        ext->cmd = *cmd;
+        ext->group_id = MEMSLOT_GROUP_GUEST;
+        SPICE_RING_POP(ring, notify);
+        if (notify) {
+            qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
+        }
+        return true;
+    case QXL_MODE_UNDEFINED:
+    default:
+        return false;
+    }
+}
+
+static int interface_req_cmd_notification(QXLInstance *sin)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+    int wait = 1;
+
+    if (qxl->mode == QXL_MODE_NATIVE) {
+        SPICE_RING_CONS_WAIT(&qxl->ram->cmd_ring, wait);
+    }
+    return wait;
+}
+
+static int interface_has_command(QXLInstance *sin)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+    switch (qxl->mode) {
+    case QXL_MODE_VGA:
+        return qemu_spice_rect_is_empty(&qxl->ssd.dirty) ? false : true;
+    case QXL_MODE_NATIVE:
+        return SPICE_RING_IS_EMPTY(&qxl->ram->cmd_ring) ? false : true;
+    case QXL_MODE_UNDEFINED:
+    default:
+        return false;
+    }
+}
+
+static inline void qxl_push_free_res(PCIQXLDevice *d)
+{
+    QXLReleaseRing *ring = &d->ram->release_ring;
+    uint64_t *item;
+
+#define QXL_FREE_BUNCH_SIZE 10
+
+    if (SPICE_RING_IS_EMPTY(ring) || (d->num_free_res == QXL_FREE_BUNCH_SIZE &&
+                                      ring->prod - ring->cons + 1 != ring->num_items)) {
+        int notify;
+
+        SPICE_RING_PUSH(ring, notify);
+        if (notify) {
+            qxl_send_events(d, QXL_INTERRUPT_DISPLAY);
+        }
+        SPICE_RING_PROD_ITEM(ring, item);
+        *item = 0;
+        d->num_free_res = 0;
+        d->last_release = NULL;
+    }
+}
+
+static void interface_release_resource(QXLInstance *sin,
+                                       struct QXLReleaseInfoExt ext)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+    QXLReleaseRing *ring;
+    uint64_t *item, id;
+
+    if (ext.group_id == MEMSLOT_GROUP_HOST) {
+        /* host group -> vga mode update request */
+        qemu_spice_destroy_update(&qxl->ssd, (void*)ext.info->id);
+        return;
+    }
+
+    /*
+     * ext->info points into guest-visible memory
+     * pci bar 0, drawable.release_info
+     */
+    ring = &qxl->ram->release_ring;
+    SPICE_RING_PROD_ITEM(ring, item);
+    if (*item == 0) {
+        /* stick head into the ring */
+        id = ext.info->id;
+        ext.info->next = 0;
+        *item = id;
+    } else {
+        /* append item to the list */
+        qxl->last_release->next = ext.info->id;
+        ext.info->next = 0;
+    }
+    qxl->last_release = ext.info;
+    qxl->num_free_res++;
+    qxl_push_free_res(qxl);
+}
+
+static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+    QXLCursorRing *ring;
+    QXLCommand *cmd;
+    int notify;
+
+    if (qxl->mode == QXL_MODE_NATIVE) {
+        ring = &qxl->ram->cursor_ring;
+        if (SPICE_RING_IS_EMPTY(ring)) {
+            return false;
+        }
+        SPICE_RING_CONS_ITEM(ring, cmd);
+        ext->cmd = *cmd;
+        ext->group_id = MEMSLOT_GROUP_GUEST;
+        SPICE_RING_POP(ring, notify);
+        if (notify) {
+            qxl_send_events(qxl, QXL_INTERRUPT_CURSOR);
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static int interface_req_cursor_notification(QXLInstance *sin)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+    int wait = 1;
+
+    if (qxl->mode == QXL_MODE_NATIVE) {
+        SPICE_RING_CONS_WAIT(&qxl->ram->cursor_ring, wait);
+    }
+    return wait;
+}
+
+static void interface_get_update_area(QXLInstance *sin, const struct SpiceRect **rect , uint32_t **surface_id)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+    *rect = &qxl->ram->update_area;
+    *surface_id = &qxl->ram->update_surface;
+}
+
+static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static void interface_set_save_data(QXLInstance *sin, void *data, int size)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static void *interface_get_save_data(QXLInstance *sin)
+{
+    fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+    abort();
+}
+
+static int interface_flush_resources(QXLInstance *sin)
+{
+    PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+    int ret;
+
+    ret = qxl->num_free_res;
+    if (ret) {
+        qxl_push_free_res(qxl);
+    }
+    return ret;
+}
+
+static QXLInterface qxl_interface = {
+    .base.type               = SPICE_INTERFACE_QXL,
+    .base.description        = "qxl gpu",
+    .base.major_version      = SPICE_INTERFACE_QXL_MAJOR,
+    .base.minor_version      = SPICE_INTERFACE_QXL_MINOR,
+
+    .pci_vendor              = REDHAT_PCI_VENDOR_ID,
+    .pci_id                  = QXL_DEVICE_ID,
+    .pci_revision            = QXL_REVISION,
+
+    .attache_worker          = interface_attach_worker,
+    .set_compression_level   = interface_set_compression_level,
+    .set_mm_time             = interface_set_mm_time,
+
+    .get_init_info           = interface_get_init_info,
+    .get_command             = interface_get_command,
+    .req_cmd_notification    = interface_req_cmd_notification,
+    .has_command             = interface_has_command,
+    .release_resource        = interface_release_resource,
+    .get_cursor_command      = interface_get_cursor_command,
+    .req_cursor_notification = interface_req_cursor_notification,
+    .get_update_area         = interface_get_update_area,
+    .notify_update           = interface_notify_update,
+    .set_save_data           = interface_set_save_data,
+    .get_save_data           = interface_get_save_data,
+    .flush_resources         = interface_flush_resources,
+};
+
+static void qxl_enter_vga_mode(PCIQXLDevice *d)
+{
+    if (d->mode == QXL_MODE_VGA) {
+        return;
+    }
+    dprintf(1, "%s\n", __FUNCTION__);
+    qemu_spice_create_host_primary(&d->ssd);
+    d->mode = QXL_MODE_VGA;
+    memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty));
+}
+
+static void qxl_exit_vga_mode(PCIQXLDevice *d)
+{
+    if (d->mode != QXL_MODE_VGA) {
+        return;
+    }
+    dprintf(1, "%s\n", __FUNCTION__);
+    qxl_destroy_primary(d);
+}
+
+static void qxl_set_irq(PCIQXLDevice *d)
+{
+    int level = !!(d->ram->int_pending & d->ram->int_mask);
+    qemu_set_irq(d->pci.irq[0], level);
+}
+
+static void qxl_write_config(PCIDevice *d, uint32_t address,
+                             uint32_t val, int len)
+{
+    PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, d);
+    VGACommonState *vga = &qxl->vga;
+
+    if (qxl->id == 0) {
+        vga_dirty_log_stop(vga);
+    }
+    pci_default_write_config(d, address, val, len);
+    if (qxl->id == 0) {
+        if (vga->map_addr && qxl->pci.io_regions[0].addr == -1)
+            vga->map_addr = 0;
+        vga_dirty_log_start(vga);
+    }
+}
+
+static void qxl_check_state(PCIQXLDevice *d)
+{
+    QXLRam *ram = d->ram;
+
+    assert(SPICE_RING_IS_EMPTY(&ram->cmd_ring));
+    assert(SPICE_RING_IS_EMPTY(&ram->cursor_ring));
+}
+
+static void qxl_reset_state(PCIQXLDevice *d)
+{
+    QXLRam *ram = d->ram;
+    QXLRom *rom = d->rom;
+
+    assert(SPICE_RING_IS_EMPTY(&ram->cmd_ring));
+    assert(SPICE_RING_IS_EMPTY(&ram->cursor_ring));
+    d->shadow_rom.update_id = 0;
+    *rom = d->shadow_rom;
+    qxl_rom_set_dirty(d);
+    init_qxl_ram(d);
+    d->num_free_res = 0;
+    d->last_release = NULL;
+    memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty));
+}
+
+static void qxl_soft_reset(PCIQXLDevice *d)
+{
+    dprintf(1, "%s:\n", __FUNCTION__);
+    qxl_check_state(d);
+
+    if (d->id == 0) {
+        qxl_enter_vga_mode(d);
+    } else {
+        d->mode = QXL_MODE_UNDEFINED;
+    }
+}
+
+static void qxl_hard_reset(PCIQXLDevice *d)
+{
+    dprintf(1, "%s: start\n", __FUNCTION__);
+
+    d->mode = QXL_MODE_UNDEFINED;
+    d->ssd.worker->destroy_surfaces(d->ssd.worker);
+    d->ssd.worker->reset_cursor(d->ssd.worker);
+    d->ssd.worker->reset_image_cache(d->ssd.worker);
+    d->ssd.worker->reset_memslots(d->ssd.worker);
+
+    qxl_reset_state(d);
+    qemu_spice_create_host_memslot(&d->ssd);
+    qxl_soft_reset(d);
+
+    dprintf(1, "%s: done\n", __FUNCTION__);
+}
+
+static void qxl_reset_handler(DeviceState *dev)
+{
+    PCIQXLDevice *d = DO_UPCAST(PCIQXLDevice, pci.qdev, dev);
+    qxl_hard_reset(d);
+}
+
+static void qxl_vga_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+    VGACommonState *vga = opaque;
+    PCIQXLDevice *qxl = container_of(vga, PCIQXLDevice, vga);
+
+    if (qxl->mode != QXL_MODE_VGA) {
+        qxl_destroy_primary(qxl);
+        qxl_soft_reset(qxl);
+    }
+    vga_ioport_write(opaque, addr, val);
+}
+
+static void qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id)
+{
+    static const int regions[] = {
+        QXL_RAM_RANGE_INDEX,
+        QXL_VRAM_RANGE_INDEX,
+    };
+    QXLMemSlot *guest_slot;
+    uint64_t guest_start;
+    uint64_t guest_end;
+    int pci_region;
+    pcibus_t pci_start;
+    pcibus_t pci_end;
+    intptr_t virt_start;
+    QXLDevMemSlot memslot;
+    int i;
+
+    guest_slot  = &d->ram->mem_slot;
+    guest_start = guest_slot->mem_start;
+    guest_end   = guest_slot->mem_end;
+
+    dprintf(1, "%s: slot %d: guest phys 0x%" PRIx64 " - 0x%" PRIx64 "\n",
+            __FUNCTION__, slot_id,
+            guest_start, guest_end);
+
+    PANIC_ON(slot_id >= NUM_MEMSLOTS);
+    PANIC_ON(guest_start > guest_end);
+
+    for (i = 0; i < ARRAY_SIZE(regions); i++) {
+        pci_region = regions[i];
+        pci_start = d->pci.io_regions[pci_region].addr;
+        pci_end = pci_start + d->pci.io_regions[pci_region].size;
+        /* mapped? */
+        if (pci_start == -1) {
+            continue;
+        }
+        /* start address in range ? */
+        if (guest_start < pci_start || guest_start > pci_end) {
+            continue;
+        }
+        /* end address in range ? */
+        if (guest_end > pci_end) {
+            continue;
+        }
+        /* passed */
+        break;
+    }
+    PANIC_ON(i == ARRAY_SIZE(regions)); /* finished loop without match */
+
+    switch (pci_region) {
+    case QXL_RAM_RANGE_INDEX:
+        virt_start = (intptr_t)qemu_get_ram_ptr(d->vga.vram_offset);
+        break;
+    case QXL_VRAM_RANGE_INDEX:
+        virt_start = (intptr_t)qemu_get_ram_ptr(d->vram_offset);
+        break;
+    }
+
+    memslot.slot_id = slot_id;
+    memslot.slot_group_id = MEMSLOT_GROUP_GUEST; /* guest group */
+    memslot.virt_start = virt_start + (guest_start - pci_start);
+    memslot.virt_end   = virt_start + (guest_end   - pci_start);
+    memslot.addr_delta = memslot.virt_start;
+    memslot.generation = d->rom->slot_generation = d->generation++;
+    qxl_rom_set_dirty(d);
+
+    dprintf(1, "%s: slot %d: host virt 0x%" PRIx64 " - 0x%" PRIx64 "\n",
+            __FUNCTION__, memslot.slot_id,
+            memslot.virt_start, memslot.virt_end);
+
+    d->ssd.worker->add_memslot(d->ssd.worker, &memslot);
+}
+
+static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id)
+{
+    dprintf(1, "%s: slot %d\n", __FUNCTION__, slot_id);
+    d->ssd.worker->del_memslot(d->ssd.worker, MEMSLOT_GROUP_HOST, slot_id);
+}
+
+static void qxl_create_guest_primary(PCIQXLDevice *d)
+{
+    QXLDevSurfaceCreate surface;
+
+    assert(d->mode != QXL_MODE_NATIVE);
+    qxl_exit_vga_mode(d);
+
+    dprintf(1, "%s: %dx%d\n", __FUNCTION__,
+            d->ram->create_surface.width,
+            d->ram->create_surface.height);
+
+    surface.depth = d->ram->create_surface.depth;
+    surface.height = d->ram->create_surface.height;
+    surface.mem = d->ram->create_surface.mem;
+    surface.mouse_mode = true;
+    surface.position = d->ram->create_surface.position;
+    surface.stride = d->ram->create_surface.stride;
+    surface.width = d->ram->create_surface.width;
+    surface.type = d->ram->create_surface.type;
+    surface.flags = d->ram->create_surface.flags;
+    surface.group_id = MEMSLOT_GROUP_GUEST;
+
+    d->mode = QXL_MODE_NATIVE;
+    d->ssd.worker->create_primary_surface(d->ssd.worker, 0, &surface);
+}
+
+static void qxl_destroy_primary(PCIQXLDevice *d)
+{
+    if (d->mode == QXL_MODE_UNDEFINED) {
+        return;
+    }
+
+    dprintf(1, "%s\n", __FUNCTION__);
+
+    d->mode = QXL_MODE_UNDEFINED;
+    d->ssd.worker->destroy_primary_surface(d->ssd.worker, 0);
+}
+
+static void ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+    PCIQXLDevice *d = opaque;
+    uint32_t io_port = addr - d->io_base;
+
+    switch (io_port) {
+    case QXL_IO_RESET:
+    case QXL_IO_MEMSLOT_ADD:
+    case QXL_IO_MEMSLOT_DEL:
+    case QXL_IO_CREATE_PRIMARY:
+        break;
+    default:
+        if (d->mode == QXL_MODE_NATIVE)
+            break;
+        dprintf(1, "%s: unexpected port 0x%x in vga mode\n", __FUNCTION__, io_port);
+        return;
+    }
+
+    switch (io_port) {
+    case QXL_IO_UPDATE_AREA:
+        d->ssd.worker->update_area(d->ssd.worker);
+        break;
+    case QXL_IO_NOTIFY_CMD:
+        d->ssd.worker->wakeup(d->ssd.worker);
+        break;
+    case QXL_IO_NOTIFY_CURSOR:
+        d->ssd.worker->wakeup(d->ssd.worker);
+        break;
+    case QXL_IO_UPDATE_IRQ:
+        qxl_set_irq(d);
+        break;
+    case QXL_IO_NOTIFY_OOM:
+        if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) {
+            break;
+        }
+        pthread_yield();
+        if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) {
+            break;
+        }
+        d->ssd.worker->oom(d->ssd.worker);
+        break;
+    case QXL_IO_LOG:
+        dprintf(1, "qxl %u: log %s", d->id, d->ram->log_buf);
+        break;
+    case QXL_IO_RESET:
+        dprintf(1, "qxl %u: QXL_IO_RESET\n", d->id);
+        qxl_hard_reset(d);
+        break;
+    case QXL_IO_MEMSLOT_ADD:
+        qxl_add_memslot(d, val);
+        break;
+    case QXL_IO_MEMSLOT_DEL:
+        qxl_del_memslot(d, val);
+        break;
+    case QXL_IO_CREATE_PRIMARY:
+        PANIC_ON(val != 0);
+        qxl_create_guest_primary(d);
+        break;
+    case QXL_IO_DESTROY_PRIMARY:
+        PANIC_ON(val != 0);
+        qxl_destroy_primary(d);
+        break;
+    case QXL_IO_DESTROY_SURFACE_WAIT:
+        d->ssd.worker->destroy_surface_wait(d->ssd.worker, val);
+        break;
+    default:
+        fprintf(stderr, "%s: ioport=0x%x, abort()\n", __FUNCTION__, io_port);
+        abort();
+    }
+}
+
+static uint32_t ioport_read(void *opaque, uint32_t addr)
+{
+    dprintf(1, "%s: unexpected\n", __FUNCTION__);
+    return 0xff;
+}
+
+static void qxl_map(PCIDevice *pci, int region_num,
+                    pcibus_t addr, pcibus_t size, int type)
+{
+    PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, pci);
+
+    dprintf(1, "%s: bar %d addr 0x%lx size 0x%lx\n", __FUNCTION__,
+            region_num, addr, size);
+
+    switch (region_num) {
+    case QXL_IO_RANGE_INDEX:
+        register_ioport_write(addr, size, 1, ioport_write, pci);
+        register_ioport_read(addr, size, 1, ioport_read, pci);
+        qxl->io_base = addr;
+        break;
+    case QXL_RAM_RANGE_INDEX:
+        cpu_register_physical_memory(addr, size, qxl->vga.vram_offset | IO_MEM_RAM);
+        qxl->vga.map_addr = addr;
+        qxl->vga.map_end = addr + size;
+        if (qxl->id == 0) {
+            vga_dirty_log_start(&qxl->vga);
+        }
+        break;
+    case QXL_ROM_RANGE_INDEX:
+        cpu_register_physical_memory(addr, size, qxl->rom_offset | IO_MEM_ROM);
+        break;
+    case QXL_VRAM_RANGE_INDEX:
+        cpu_register_physical_memory(addr, size, qxl->vram_offset | IO_MEM_RAM);
+        break;
+    }
+}
+
+static void pipe_read(void *opaque)
+{
+    PCIQXLDevice *d = opaque;
+    char dummy;
+    int len;
+
+    do {
+        len = read(d->pipe[0], &dummy, sizeof(dummy));
+    } while (len == sizeof(dummy));
+    qxl_set_irq(d);
+}
+
+static void qxl_send_events(PCIQXLDevice *d, uint32_t events)
+{
+    assert(d->ssd.running);
+    smp_wmb();
+    if ((d->ram->int_pending & events) == events) {
+        return;
+    }
+    atomic_or(&d->ram->int_pending, events);
+    if (pthread_self() == d->main) {
+        qxl_set_irq(d);
+    } else {
+        if (write(d->pipe[1], d, 1) != 1) {
+            dprintf(1, "%s: write to pipe failed\n", __FUNCTION__);
+        }
+    }
+}
+
+static void init_pipe_signaling(PCIQXLDevice *d)
+{
+   if (pipe(d->pipe) < 0) {
+       dprintf(1, "%s: pipe creation failed\n", __FUNCTION__);
+       return;
+   }
+#ifdef CONFIG_IOTHREAD
+   fcntl(d->pipe[0], F_SETFL, O_NONBLOCK);
+#else
+   fcntl(d->pipe[0], F_SETFL, O_NONBLOCK /* | O_ASYNC */);
+#endif
+   fcntl(d->pipe[1], F_SETFL, O_NONBLOCK);
+   fcntl(d->pipe[0], F_SETOWN, getpid());
+
+   d->main = pthread_self();
+   qemu_set_fd_handler(d->pipe[0], pipe_read, NULL, d);
+}
+
+static void display_update(struct DisplayState *ds, int x, int y, int w, int h)
+{
+    if (qxl0->mode == QXL_MODE_VGA) {
+        qemu_spice_display_update(&qxl0->ssd, x, y, w, h);
+    }
+}
+
+static void display_resize(struct DisplayState *ds)
+{
+    if (qxl0->mode == QXL_MODE_VGA) {
+        qemu_spice_display_resize(&qxl0->ssd);
+    }
+}
+
+static void display_refresh(struct DisplayState *ds)
+{
+    if (qxl0->mode == QXL_MODE_VGA) {
+        qemu_spice_display_refresh(&qxl0->ssd);
+    }
+}
+
+static DisplayChangeListener display_listener = {
+    .dpy_update  = display_update,
+    .dpy_resize  = display_resize,
+    .dpy_refresh = display_refresh,
+};
+
+static int qxl_init(PCIDevice *dev)
+{
+    PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, dev);
+    VGACommonState *vga = &qxl->vga;
+    uint8_t* config = qxl->pci.config;
+    ram_addr_t ram_size = msb_mask(qxl->vga.vram_size * 2 - 1);
+
+    qxl->id = device_id;
+    qxl->mode = QXL_MODE_UNDEFINED;
+    qxl->generation = 1;
+
+    if (!qxl->id) {
+        if (ram_size < 32 * 1024 * 1024)
+            ram_size = 32 * 1024 * 1024;
+        vga_common_init(vga, ram_size);
+        vga_init(vga);
+        register_ioport_write(0x3c0, 16, 1, qxl_vga_ioport_write, vga);
+        register_ioport_write(0x3b4,  2, 1, qxl_vga_ioport_write, vga);
+        register_ioport_write(0x3d4,  2, 1, qxl_vga_ioport_write, vga);
+        register_ioport_write(0x3ba,  1, 1, qxl_vga_ioport_write, vga);
+        register_ioport_write(0x3da,  1, 1, qxl_vga_ioport_write, vga);
+
+        qxl0 = qxl;
+        vga->ds = graphic_console_init(vga->update, vga->invalidate,
+                                       vga->screen_dump, vga->text_update, vga);
+        qxl->ssd.ds = vga->ds;
+        qxl->ssd.bufsize = (16 * 1024 * 1024);
+        qxl->ssd.buf = qemu_malloc(qxl->ssd.bufsize);
+        pthread_mutex_init(&qxl->ssd.lock, NULL);
+        register_displaychangelistener(vga->ds, &display_listener);
+
+        if (qxl->pci.romfile == NULL)
+            qxl->pci.romfile = qemu_strdup("vgabios-qxl.bin");
+        pci_config_set_class(config, PCI_CLASS_DISPLAY_VGA);
+    } else {
+        if (ram_size < 16 * 1024 * 1024)
+            ram_size = 16 * 1024 * 1024;
+        qxl->vga.vram_size = ram_size;
+        qxl->vga.vram_offset = qemu_ram_alloc(qxl->vga.vram_size);
+
+        pci_config_set_class(config, PCI_CLASS_DISPLAY_OTHER);
+    }
+
+    pci_config_set_vendor_id(config, REDHAT_PCI_VENDOR_ID);
+    pci_config_set_device_id(config, QXL_DEVICE_ID);
+    pci_set_byte(&config[PCI_REVISION_ID], QXL_REVISION);
+    pci_set_byte(&config[PCI_INTERRUPT_PIN], 1);
+
+    qxl->rom_size = qxl_rom_size();
+    qxl->rom_offset = qemu_ram_alloc(qxl->rom_size);
+    init_qxl_rom(qxl);
+    init_qxl_ram(qxl);
+
+    if (qxl->vram_size < 16 * 1024 * 1024)
+        qxl->vram_size = 16 * 1024 * 1024;
+    qxl->vram_size = msb_mask(qxl->vram_size * 2 - 1);
+    qxl->vram_offset = qemu_ram_alloc(qxl->vram_size);
+
+    pci_register_bar(&qxl->pci, QXL_IO_RANGE_INDEX,
+                     msb_mask(QXL_IO_RANGE_SIZE * 2 - 1),
+                     PCI_BASE_ADDRESS_SPACE_IO, qxl_map);
+
+    pci_register_bar(&qxl->pci, QXL_ROM_RANGE_INDEX,
+                     qxl->rom_size, PCI_BASE_ADDRESS_SPACE_MEMORY,
+                     qxl_map);
+
+    pci_register_bar(&qxl->pci, QXL_RAM_RANGE_INDEX,
+                     qxl->vga.vram_size, PCI_BASE_ADDRESS_SPACE_MEMORY,
+                     qxl_map);
+
+    pci_register_bar(&qxl->pci, QXL_VRAM_RANGE_INDEX, qxl->vram_size,
+                     PCI_BASE_ADDRESS_SPACE_MEMORY, qxl_map);
+
+    qxl->ssd.qxl.base.sif = &qxl_interface.base;
+    qxl->ssd.qxl.id = qxl->id;
+    spice_server_add_interface(spice_server, &qxl->ssd.qxl.base);
+    qemu_add_vm_change_state_handler(qemu_spice_vm_change_state_handler, &qxl->ssd);
+
+    init_pipe_signaling(qxl);
+    qxl_reset_state(qxl);
+
+    device_id++;
+    return 0;
+}
+
+static PCIDeviceInfo qxl_info = {
+    .qdev.name    = "qxl",
+    .qdev.desc    = "Spice QXL GPU",
+    .qdev.size    = sizeof(PCIQXLDevice),
+    .qdev.reset   = qxl_reset_handler,
+    .init         = qxl_init,
+    .config_write = qxl_write_config,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, 32 * 1024 * 1024),
+        DEFINE_PROP_UINT32("vram_size", PCIQXLDevice, vram_size, 64 * 1024 * 1024),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void qxl_register(void)
+{
+    pci_qdev_register(&qxl_info);
+}
+
+device_init(qxl_register);
diff --git a/hw/vga_int.h b/hw/vga_int.h
index 6a46a43..beb5423 100644
--- a/hw/vga_int.h
+++ b/hw/vga_int.h
@@ -106,7 +106,7 @@ typedef void (* vga_update_retrace_info_fn)(struct VGACommonState *s);
 typedef struct VGACommonState {
     uint8_t *vram_ptr;
     ram_addr_t vram_offset;
-    unsigned int vram_size;
+    uint32_t vram_size;
     uint32_t lfb_addr;
     uint32_t lfb_end;
     uint32_t map_addr;
diff --git a/qemu-spice.h b/qemu-spice.h
index f061004..b835d4e 100644
--- a/qemu-spice.h
+++ b/qemu-spice.h
@@ -15,6 +15,8 @@ void qemu_spice_init(void);
 void qemu_spice_input_init(void);
 void qemu_spice_display_init(DisplayState *ds);
 
+void qxl_display_init(DisplayState *ds);
+
 #else  /* CONFIG_SPICE */
 
 #define using_spice 0
diff --git a/sysemu.h b/sysemu.h
index d0effa0..560977f 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -103,7 +103,7 @@ extern int autostart;
 extern int bios_size;
 
 typedef enum {
-    VGA_NONE, VGA_STD, VGA_CIRRUS, VGA_VMWARE, VGA_XENFB
+    VGA_NONE, VGA_STD, VGA_CIRRUS, VGA_VMWARE, VGA_XENFB, VGA_QXL,
 } VGAInterfaceType;
 
 extern int vga_interface_type;
@@ -111,6 +111,7 @@ extern int vga_interface_type;
 #define std_vga_enabled (vga_interface_type == VGA_STD)
 #define xenfb_enabled (vga_interface_type == VGA_XENFB)
 #define vmsvga_enabled (vga_interface_type == VGA_VMWARE)
+#define qxl_enabled (vga_interface_type == VGA_QXL)
 
 extern int graphic_width;
 extern int graphic_height;
diff --git a/vl.c b/vl.c
index 1fab3f9..4e87427 100644
--- a/vl.c
+++ b/vl.c
@@ -2075,6 +2075,8 @@ static void select_vgahw (const char *p)
         vga_interface_type = VGA_VMWARE;
     } else if (strstart(p, "xenfb", &opts)) {
         vga_interface_type = VGA_XENFB;
+    } else if (strstart(p, "qxl", &opts)) {
+        vga_interface_type = VGA_QXL;
     } else if (!strstart(p, "none", &opts)) {
     invalid_vga:
         fprintf(stderr, "Unknown vga type: %s\n", p);
@@ -3745,7 +3747,7 @@ int main(int argc, char **argv, char **envp)
         break;
     }
 #ifdef CONFIG_SPICE
-    if (using_spice) {
+    if (using_spice && !qxl_enabled) {
         qemu_spice_display_init(ds);
     }
 #endif
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 09/11] qxl: local rendering for sdl/vnc
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (7 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 10/11] spice: add tablet support Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 11/11] spice: add audio Gerd Hoffmann
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

Not fully functional yet.  Known issues:

 - if the guest creates a "top down" primary surface (windows does)
   the vns/sdl display will be upside down.
 - mouse pointer isn't rendered.
---
 hw/qxl.c |  121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 117 insertions(+), 4 deletions(-)

diff --git a/hw/qxl.c b/hw/qxl.c
index ed7e975..d69ad17 100644
--- a/hw/qxl.c
+++ b/hw/qxl.c
@@ -61,6 +61,12 @@ typedef struct PCIQXLDevice {
     enum qxl_mode      mode;
     int                generation;
 
+    DisplaySurface     *surface_native;
+    SpiceRect          surface_rect;
+    uint32_t           surface_id;
+    int                surface_render;
+    int                surface_commands;
+
     /* thread signaling */
     pthread_t          main;
     int                pipe[2];
@@ -325,6 +331,7 @@ static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
         if (notify) {
             qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
         }
+        qxl->surface_commands++;
         return true;
     case QXL_MODE_UNDEFINED:
     default:
@@ -433,6 +440,7 @@ static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *
         if (notify) {
             qxl_send_events(qxl, QXL_INTERRUPT_CURSOR);
         }
+        qxl->surface_commands++;
         return true;
     } else {
         return false;
@@ -454,6 +462,13 @@ static void interface_get_update_area(QXLInstance *sin, const struct SpiceRect *
 {
     PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
 
+    if (qxl->surface_render) {
+        qxl->surface_render = 0;
+        *rect = &qxl->surface_rect;
+        *surface_id = &qxl->surface_id;
+        return;
+    }
+
     *rect = &qxl->ram->update_area;
     *surface_id = &qxl->ram->update_surface;
 }
@@ -711,6 +726,7 @@ static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id)
 static void qxl_create_guest_primary(PCIQXLDevice *d)
 {
     QXLDevSurfaceCreate surface;
+    void *ptr;
 
     assert(d->mode != QXL_MODE_NATIVE);
     qxl_exit_vga_mode(d);
@@ -732,6 +748,30 @@ static void qxl_create_guest_primary(PCIQXLDevice *d)
 
     d->mode = QXL_MODE_NATIVE;
     d->ssd.worker->create_primary_surface(d->ssd.worker, 0, &surface);
+
+    fprintf(stderr, "%s: width %d height %d depth %d stride %d\n", __FUNCTION__,
+            surface.width, surface.height, surface.depth, surface.stride);
+
+    if (surface.stride < 0) {
+        /* FIXME: will display upside down */
+        surface.stride = -surface.stride;
+    }
+
+    if (d->surface_native) {
+        qemu_free(d->surface_native);
+        d->surface_native = NULL;
+    }
+
+    ptr = qemu_get_ram_ptr(d->vga.vram_offset);
+    d->surface_native =
+        qemu_create_displaysurface_from(surface.width, surface.height,
+                                        surface.depth, surface.stride, ptr);
+
+    d->surface_id          = 0;
+    d->surface_rect.top    = 0;
+    d->surface_rect.bottom = surface.height;
+    d->surface_rect.left   = 0;
+    d->surface_rect.right  = surface.width;
 }
 
 static void qxl_destroy_primary(PCIQXLDevice *d)
@@ -826,10 +866,16 @@ static uint32_t ioport_read(void *opaque, uint32_t addr)
 static void qxl_map(PCIDevice *pci, int region_num,
                     pcibus_t addr, pcibus_t size, int type)
 {
+    static const char *names[] = {
+        [ QXL_IO_RANGE_INDEX ]   = "ioports",
+        [ QXL_RAM_RANGE_INDEX ]  = "devram",
+        [ QXL_ROM_RANGE_INDEX ]  = "rom",
+        [ QXL_VRAM_RANGE_INDEX ] = "vram",
+    };
     PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, pci);
 
-    dprintf(1, "%s: bar %d addr 0x%lx size 0x%lx\n", __FUNCTION__,
-            region_num, addr, size);
+    dprintf(1, "%s: bar %d [%s] addr 0x%lx size 0x%lx\n", __FUNCTION__,
+            region_num, names[region_num], addr, size);
 
     switch (region_num) {
     case QXL_IO_RANGE_INDEX:
@@ -901,6 +947,67 @@ static void init_pipe_signaling(PCIQXLDevice *d)
    qemu_set_fd_handler(d->pipe[0], pipe_read, NULL, d);
 }
 
+/* graphics console */
+
+static void qxl_hw_update(void *opaque)
+{
+    PCIQXLDevice *qxl = opaque;
+    VGACommonState *vga = &qxl->vga;
+
+    if (qxl->mode == QXL_MODE_VGA) {
+        vga->update(vga);
+        return;
+    }
+
+    if (qxl->surface_native) {
+        qemu_free_displaysurface(vga->ds);
+        vga->ds->surface = qxl->surface_native;
+        qxl->surface_native = NULL;
+        dpy_resize(vga->ds);
+    }
+
+    if (qxl->surface_commands) {
+        qxl->surface_commands = 0;
+        qxl->surface_render = 1;
+        qxl->ssd.worker->update_area(qxl->ssd.worker);
+
+        /* FIXME: want more specific dirty region here */
+        dpy_update(vga->ds, 0, 0, ds_get_width(vga->ds), ds_get_height(vga->ds));
+    }
+}
+
+static void qxl_hw_invalidate(void *opaque)
+{
+    PCIQXLDevice *qxl = opaque;
+    VGACommonState *vga = &qxl->vga;
+
+    vga->invalidate(vga);
+}
+
+static void qxl_hw_screen_dump(void *opaque, const char *filename)
+{
+    PCIQXLDevice *qxl = opaque;
+    VGACommonState *vga = &qxl->vga;
+
+    if (qxl->mode == QXL_MODE_VGA) {
+        vga->screen_dump(vga, filename);
+        return;
+    }
+}
+
+static void qxl_hw_text_update(void *opaque, console_ch_t *chardata)
+{
+    PCIQXLDevice *qxl = opaque;
+    VGACommonState *vga = &qxl->vga;
+
+    if (qxl->mode == QXL_MODE_VGA) {
+        vga->text_update(vga, chardata);
+        return;
+    }
+}
+
+/* display change listener */
+
 static void display_update(struct DisplayState *ds, int x, int y, int w, int h)
 {
     if (qxl0->mode == QXL_MODE_VGA) {
@@ -950,13 +1057,19 @@ static int qxl_init(PCIDevice *dev)
         register_ioport_write(0x3ba,  1, 1, qxl_vga_ioport_write, vga);
         register_ioport_write(0x3da,  1, 1, qxl_vga_ioport_write, vga);
 
-        qxl0 = qxl;
+#if 0
         vga->ds = graphic_console_init(vga->update, vga->invalidate,
                                        vga->screen_dump, vga->text_update, vga);
+#else
+        vga->ds = graphic_console_init(qxl_hw_update, qxl_hw_invalidate,
+                                       qxl_hw_screen_dump, qxl_hw_text_update, qxl);
+#endif
         qxl->ssd.ds = vga->ds;
         qxl->ssd.bufsize = (16 * 1024 * 1024);
         qxl->ssd.buf = qemu_malloc(qxl->ssd.bufsize);
         pthread_mutex_init(&qxl->ssd.lock, NULL);
+
+        qxl0 = qxl;
         register_displaychangelistener(vga->ds, &display_listener);
 
         if (qxl->pci.romfile == NULL)
@@ -1021,7 +1134,7 @@ static PCIDeviceInfo qxl_info = {
     .init         = qxl_init,
     .config_write = qxl_write_config,
     .qdev.props = (Property[]) {
-        DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, 32 * 1024 * 1024),
+        DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, 64 * 1024 * 1024),
         DEFINE_PROP_UINT32("vram_size", PCIQXLDevice, vram_size, 64 * 1024 * 1024),
         DEFINE_PROP_END_OF_LIST(),
     }
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 10/11] spice: add tablet support
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (8 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 09/11] qxl: local rendering for sdl/vnc Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 11/11] spice: add audio Gerd Hoffmann
  10 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

---
 spice-display.c |    2 +-
 spice-input.c   |   95 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 89 insertions(+), 8 deletions(-)

diff --git a/spice-display.c b/spice-display.c
index 44c259d..d741d1d 100644
--- a/spice-display.c
+++ b/spice-display.c
@@ -149,7 +149,7 @@ void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd)
     surface.width      = ds_get_width(ssd->ds);
     surface.height     = ds_get_height(ssd->ds);
     surface.stride     = -surface.width * 4;
-    surface.mouse_mode = 0;
+    surface.mouse_mode = true;
     surface.flags      = 0;
     surface.type       = 0;
     surface.mem        = (intptr_t)ssd->buf;
diff --git a/spice-input.c b/spice-input.c
index c22ab53..6207ca2 100644
--- a/spice-input.c
+++ b/spice-input.c
@@ -1,5 +1,6 @@
 #include <stdlib.h>
 #include <stdio.h>
+#include <stdbool.h>
 #include <string.h>
 
 #include <spice.h>
@@ -48,9 +49,13 @@ static void kbd_leds(void *opaque, int ledstate)
 
 /* mouse bits */
 
-typedef struct QemuSpiceMouse {
-    SpiceMouseInstance sin;
-} QemuSpiceMouse;
+typedef struct QemuSpicePointer {
+    SpiceMouseInstance  mouse;
+    SpiceTabletInstance tablet;
+    int width, height, x, y;
+    Notifier mouse_mode;
+    bool absolute;
+} QemuSpicePointer;
 
 static void mouse_motion(SpiceMouseInstance *sin, int dx, int dy, int dz,
                          uint32_t buttons_state)
@@ -72,17 +77,93 @@ static SpiceMouseInterface mouse_interface = {
     .buttons            = mouse_buttons,
 };
 
+static void tablet_set_logical_size(SpiceTabletInstance* sin, int width, int height)
+{
+    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+    fprintf(stderr, "%s: %dx%d\n", __FUNCTION__, width, height);
+    pointer->width  = width;
+    pointer->height = height;
+}
+
+static void tablet_position(SpiceTabletInstance* sin, int x, int y,
+                            uint32_t buttons_state)
+{
+    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+    pointer->x = x * 0x7FFF / (pointer->width - 1);
+    pointer->y = y * 0x7FFF / (pointer->height - 1);
+    kbd_mouse_event(pointer->x, pointer->y, 0, buttons_state);
+}
+
+
+static void tablet_wheel(SpiceTabletInstance* sin, int wheel,
+                         uint32_t buttons_state)
+{
+    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+    kbd_mouse_event(pointer->x, pointer->y, wheel, buttons_state);
+}
+
+static void tablet_buttons(SpiceTabletInstance *sin,
+                           uint32_t buttons_state)
+{
+    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+    kbd_mouse_event(pointer->x, pointer->y, 0, buttons_state);
+}
+
+static SpiceTabletInterface tablet_interface = {
+    .base.type          = SPICE_INTERFACE_TABLET,
+    .base.description   = "tablet",
+    .base.major_version = SPICE_INTERFACE_TABLET_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_TABLET_MINOR,
+    .set_logical_size   = tablet_set_logical_size,
+    .position           = tablet_position,
+    .wheel              = tablet_wheel,
+    .buttons            = tablet_buttons,
+};
+
+static void mouse_mode_notifier(Notifier *notifier)
+{
+    QemuSpicePointer *pointer = container_of(notifier, QemuSpicePointer, mouse_mode);
+    bool is_absolute  = kbd_mouse_is_absolute();
+    bool has_absolute = kbd_mouse_has_absolute();
+
+    fprintf(stderr, "%s: absolute pointer: %s%s\n", __FUNCTION__,
+            has_absolute ? "present" : "not available",
+            is_absolute  ? "+active" : "");
+
+    if (pointer->absolute == is_absolute)
+        return;
+
+    if (is_absolute) {
+        fprintf(stderr, "%s: using absolute pointer (client mode)\n", __FUNCTION__);
+        spice_server_add_interface(spice_server, &pointer->tablet.base);
+    } else {
+        fprintf(stderr, "%s: using relative pointer (server mode)\n", __FUNCTION__);
+        spice_server_remove_interface(&pointer->tablet.base);
+    }
+    pointer->absolute = is_absolute;
+}
+
 void qemu_spice_input_init(void)
 {
     QemuSpiceKbd *kbd;
-    QemuSpiceMouse *mouse;
+    QemuSpicePointer *pointer;
 
     kbd = qemu_mallocz(sizeof(*kbd));
     kbd->sin.base.sif = &kbd_interface.base;
     spice_server_add_interface(spice_server, &kbd->sin.base);
     qemu_add_led_event_handler(kbd_leds, kbd);
 
-    mouse = qemu_mallocz(sizeof(*mouse));
-    mouse->sin.base.sif = &mouse_interface.base;
-    spice_server_add_interface(spice_server, &mouse->sin.base);
+    pointer = qemu_mallocz(sizeof(*pointer));
+    pointer->mouse.base.sif  = &mouse_interface.base;
+    pointer->tablet.base.sif = &tablet_interface.base;
+    spice_server_add_interface(spice_server, &pointer->mouse.base);
+
+    pointer->absolute = false;
+    pointer->mouse_mode.notify = mouse_mode_notifier;
+    qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode);
+    mouse_mode_notifier(&pointer->mouse_mode);
 }
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [Qemu-devel] [RfC PATCH 11/11] spice: add audio
  2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
                   ` (9 preceding siblings ...)
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 10/11] spice: add tablet support Gerd Hoffmann
@ 2010-04-14  9:55 ` Gerd Hoffmann
  2010-04-14 20:51   ` malc
  10 siblings, 1 reply; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-14  9:55 UTC (permalink / raw)
  To: qemu-devel; +Cc: Gerd Hoffmann

---
 Makefile.objs      |    1 +
 audio/audio.c      |    3 +
 audio/audio_int.h  |    1 +
 audio/spiceaudio.c |  315 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 320 insertions(+), 0 deletions(-)
 create mode 100644 audio/spiceaudio.c

diff --git a/Makefile.objs b/Makefile.objs
index ab1af88..b11db4c 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -84,6 +84,7 @@ common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o
 audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o
 audio-obj-$(CONFIG_SDL) += sdlaudio.o
 audio-obj-$(CONFIG_OSS) += ossaudio.o
+audio-obj-$(CONFIG_SPICE) += spiceaudio.o
 audio-obj-$(CONFIG_COREAUDIO) += coreaudio.o
 audio-obj-$(CONFIG_ALSA) += alsaaudio.o
 audio-obj-$(CONFIG_DSOUND) += dsoundaudio.o
diff --git a/audio/audio.c b/audio/audio.c
index dbf0b96..67fc1d3 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -45,6 +45,9 @@
 */
 static struct audio_driver *drvtab[] = {
     CONFIG_AUDIO_DRIVERS
+#ifdef CONFIG_SPICE
+    &spice_audio_driver,
+#endif
     &no_audio_driver,
     &wav_audio_driver
 };
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 06e313f..d1f6c2d 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -209,6 +209,7 @@ extern struct audio_driver coreaudio_audio_driver;
 extern struct audio_driver dsound_audio_driver;
 extern struct audio_driver esd_audio_driver;
 extern struct audio_driver pa_audio_driver;
+extern struct audio_driver spice_audio_driver;
 extern struct audio_driver winwave_audio_driver;
 extern struct mixeng_volume nominal_volume;
 
diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
new file mode 100644
index 0000000..0e18f2f
--- /dev/null
+++ b/audio/spiceaudio.c
@@ -0,0 +1,315 @@
+#include "hw/hw.h"
+#include "qemu-timer.h"
+#include "qemu-spice.h"
+
+#define AUDIO_CAP "spice"
+#include "audio.h"
+#include "audio_int.h"
+
+#define LINE_IN_SAMPLES 1024
+#define LINE_OUT_SAMPLES 1024
+
+typedef struct SpiceVoiceOut {
+    HWVoiceOut            hw;
+    SpicePlaybackInstance sin;
+    uint32_t              *frame;
+    uint32_t              *fpos;
+    uint32_t              fsize;
+    uint64_t              prev_ticks;
+    int                   active;
+} SpiceVoiceOut;
+
+typedef struct SpiceVoiceIn {
+    HWVoiceIn             hw;
+    SpiceRecordInstance   sin;
+    uint64_t              prev_ticks;
+    int                   active;
+    uint32_t              samples[LINE_IN_SAMPLES];
+} SpiceVoiceIn;
+
+static SpicePlaybackInterface playback_sif = {
+    .base.type          = SPICE_INTERFACE_PLAYBACK,
+    .base.description   = "playback",
+    .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR,
+};
+
+static SpiceRecordInterface record_sif = {
+    .base.type          = SPICE_INTERFACE_RECORD,
+    .base.description   = "record",
+    .base.major_version = SPICE_INTERFACE_RECORD_MAJOR,
+    .base.minor_version = SPICE_INTERFACE_RECORD_MINOR,
+};
+
+static void *spice_audio_init(void)
+{
+    if (!using_spice)
+        return NULL;
+    return qemu_malloc(42);
+}
+
+static void spice_audio_fini(void *opaque)
+{
+    qemu_free(opaque);
+}
+
+static uint64_t get_monotonic_time(void)
+{
+    struct timespec time_space;
+    clock_gettime(CLOCK_MONOTONIC, &time_space);
+    return (uint64_t)time_space.tv_sec * 1000 * 1000 * 1000 + time_space.tv_nsec;
+}
+
+/* playback */
+
+static int line_out_init(HWVoiceOut *hw, struct audsettings *as)
+{
+    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+    struct audsettings settings;
+
+    settings.freq       = SPICE_INTERFACE_PLAYBACK_FREQ;
+    settings.nchannels  = SPICE_INTERFACE_PLAYBACK_CHAN;
+    settings.fmt        = AUD_FMT_S16;
+    settings.endianness = AUDIO_HOST_ENDIANNESS;
+
+    audio_pcm_init_info(&hw->info, &settings);
+    hw->samples = LINE_OUT_SAMPLES;
+    out->active = 0;
+
+    out->sin.base.sif = &playback_sif.base;
+    spice_server_add_interface(spice_server, &out->sin.base);
+    return 0;
+}
+
+static void line_out_fini(HWVoiceOut *hw)
+{
+    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+
+    spice_server_remove_interface(&out->sin.base);
+}
+
+static int line_out_run(HWVoiceOut *hw, int live)
+{
+    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+    int rpos, decr;
+    int samples;
+    uint64_t now;
+    uint64_t ticks;
+    uint64_t bytes;
+
+    if (!live) {
+        return 0;
+    }
+
+    now = get_monotonic_time();
+    ticks = now - out->prev_ticks;
+    bytes = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000);
+    out->prev_ticks = now;
+
+    decr = (bytes > INT_MAX)
+        ? INT_MAX >> hw->info.shift
+        : (bytes + (1 << (hw->info.shift - 1))) >> hw->info.shift;
+    decr = audio_MIN(live, decr);
+
+    samples = decr;
+    rpos = hw->rpos;
+    while (samples) {
+        int left_till_end_samples = hw->samples - rpos;
+        int len = audio_MIN(samples, left_till_end_samples);
+
+        if (!out->frame) {
+            spice_server_playback_get_buffer(&out->sin, &out->frame, &out->fsize);
+            out->fpos = out->frame;
+        }
+        if (out->frame) {
+            len = audio_MIN(len, out->fsize);
+            hw->clip(out->fpos, hw->mix_buf + rpos, len);
+            out->fsize -= len;
+            out->fpos  += len;
+            if (out->fsize == 0) {
+                spice_server_playback_put_samples(&out->sin, out->frame);
+                out->frame = out->fpos = NULL;
+            }
+        }
+        rpos = (rpos + len) % hw->samples;
+        samples -= len;
+    }
+    hw->rpos = rpos;
+    return decr;
+}
+
+static int line_out_write(SWVoiceOut *sw, void *buf, int len)
+{
+    return audio_pcm_sw_write(sw, buf, len);
+}
+
+static int line_out_ctl(HWVoiceOut *hw, int cmd, ...)
+{
+    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
+
+    switch (cmd) {
+    case VOICE_ENABLE:
+        if (out->active) {
+            break;
+        }
+        out->active = 1;
+        out->prev_ticks = get_monotonic_time();
+        spice_server_playback_start(&out->sin);
+        break;
+    case VOICE_DISABLE:
+        if (!out->active) {
+            break;
+        }
+        out->active = 0;
+        if (out->frame) {
+            memset(out->fpos, 0, out->fsize << 2);
+            spice_server_playback_put_samples(&out->sin, out->frame);
+            out->frame = out->fpos = NULL;
+            spice_server_playback_stop(&out->sin);
+        }
+        break;
+    }
+    return 0;
+}
+
+/* record */
+
+static int line_in_init(HWVoiceIn *hw, struct audsettings *as)
+{
+    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
+    struct audsettings settings;
+
+    settings.freq       = SPICE_INTERFACE_RECORD_FREQ;
+    settings.nchannels  = SPICE_INTERFACE_RECORD_CHAN;
+    settings.fmt        = AUD_FMT_S16;
+    settings.endianness = AUDIO_HOST_ENDIANNESS;
+
+    audio_pcm_init_info(&hw->info, &settings);
+    hw->samples = LINE_IN_SAMPLES;
+    in->active = 0;
+
+    in->sin.base.sif = &record_sif.base;
+    spice_server_add_interface(spice_server, &in->sin.base);
+    return 0;
+}
+
+static void line_in_fini(HWVoiceIn *hw)
+{
+    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
+
+    spice_server_remove_interface(&in->sin.base);
+}
+
+static int line_in_run(HWVoiceIn *hw)
+{
+    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
+    int num_samples;
+    int ready;
+    int len[2];
+    uint64_t now;
+    uint64_t ticks;
+    uint64_t delta_samp;
+    uint32_t *samples;
+
+    if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in(hw))) {
+        return 0;
+    }
+
+    now = get_monotonic_time();
+    ticks = now - in->prev_ticks;
+    in->prev_ticks = now;
+    delta_samp = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000);
+    delta_samp = (delta_samp + (1 << (hw->info.shift - 1))) >> hw->info.shift;
+
+    num_samples = audio_MIN(num_samples, delta_samp);
+
+    ready = spice_server_record_get_samples(&in->sin, in->samples, num_samples);
+    samples = in->samples;
+    if (ready == 0) {
+        static uint32_t silence[LINE_IN_SAMPLES];
+        samples = silence;
+        ready = LINE_IN_SAMPLES;
+    }
+
+    num_samples = audio_MIN(ready, num_samples);
+
+    if (hw->wpos + num_samples > hw->samples) {
+        len[0] = hw->samples - hw->wpos;
+        len[1] = num_samples - len[0];
+    } else {
+        len[0] = num_samples;
+        len[1] = 0;
+    }
+
+    hw->conv(hw->conv_buf + hw->wpos, samples, len[0], &nominal_volume);
+
+    if (len[1]) {
+        hw->conv(hw->conv_buf, samples + len[0], len[1],
+                 &nominal_volume);
+    }
+
+    hw->wpos = (hw->wpos + num_samples) % hw->samples;
+
+    return num_samples;
+}
+
+static int line_in_read(SWVoiceIn *sw, void *buf, int size)
+{
+    return audio_pcm_sw_read(sw, buf, size);
+}
+
+static int line_in_ctl(HWVoiceIn *hw, int cmd, ...)
+{
+    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
+
+    switch (cmd) {
+    case VOICE_ENABLE:
+        if (in->active) {
+            break;
+        }
+        in->active = 1;
+        in->prev_ticks = get_monotonic_time();
+        spice_server_record_start(&in->sin);
+        break;
+    case VOICE_DISABLE:
+        if (!in->active) {
+            break;
+        }
+        in->active = 0;
+        spice_server_record_stop(&in->sin);
+        break;
+    }
+    return 0;
+}
+
+static struct audio_option audio_options[] = {
+    { /* end of list */ },
+};
+
+static struct audio_pcm_ops audio_callbacks = {
+    .init_out = line_out_init,
+    .fini_out = line_out_fini,
+    .run_out  = line_out_run,
+    .write    = line_out_write,
+    .ctl_out  = line_out_ctl,
+
+    .init_in  = line_in_init,
+    .fini_in  = line_in_fini,
+    .run_in   = line_in_run,
+    .read     = line_in_read,
+    .ctl_in   = line_in_ctl,
+};
+
+struct audio_driver spice_audio_driver = {
+    .name           = "spice",
+    .descr          = "spice audio driver",
+    .options        = audio_options,
+    .init           = spice_audio_init,
+    .fini           = spice_audio_fini,
+    .pcm_ops        = &audio_callbacks,
+    .can_be_default = 1,
+    .max_voices_out = 1,
+    .max_voices_in  = 1,
+    .voice_size_out = sizeof(SpiceVoiceOut),
+    .voice_size_in  = sizeof(SpiceVoiceIn),
+};
-- 
1.6.6.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device Gerd Hoffmann
@ 2010-04-14 16:52   ` Blue Swirl
  2010-04-14 23:08     ` [Qemu-devel] " Paolo Bonzini
  2010-04-14 22:21   ` [Qemu-devel] " Alexander Graf
  1 sibling, 1 reply; 29+ messages in thread
From: Blue Swirl @ 2010-04-14 16:52 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: qemu-devel

On 4/14/10, Gerd Hoffmann <kraxel@redhat.com> wrote:
>  +static inline void atomic_or(uint32_t *var, uint32_t add)
>  +{
>  +   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add) : "memory");
>  +}

This will break on non-x86 hosts.

>  +static QXLInterface qxl_interface = {
>  +    .base.type               = SPICE_INTERFACE_QXL,
>  +    .base.description        = "qxl gpu",
>  +    .base.major_version      = SPICE_INTERFACE_QXL_MAJOR,
>  +    .base.minor_version      = SPICE_INTERFACE_QXL_MINOR,
>  +
>  +    .pci_vendor              = REDHAT_PCI_VENDOR_ID,
>  +    .pci_id                  = QXL_DEVICE_ID,
>  +    .pci_revision            = QXL_REVISION,
>  +
>  +    .attache_worker          = interface_attach_worker,
>  +    .set_compression_level   = interface_set_compression_level,
>  +    .set_mm_time             = interface_set_mm_time,
>  +
>  +    .get_init_info           = interface_get_init_info,
>  +    .get_command             = interface_get_command,
>  +    .req_cmd_notification    = interface_req_cmd_notification,
>  +    .has_command             = interface_has_command,
>  +    .release_resource        = interface_release_resource,
>  +    .get_cursor_command      = interface_get_cursor_command,
>  +    .req_cursor_notification = interface_req_cursor_notification,
>  +    .get_update_area         = interface_get_update_area,
>  +    .notify_update           = interface_notify_update,
>  +    .set_save_data           = interface_set_save_data,
>  +    .get_save_data           = interface_get_save_data,
>  +    .flush_resources         = interface_flush_resources,
>  +};

Could this (and other similar cases) be const?

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] [RfC PATCH 11/11] spice: add audio
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 11/11] spice: add audio Gerd Hoffmann
@ 2010-04-14 20:51   ` malc
  2010-04-14 23:14     ` [Qemu-devel] " Paolo Bonzini
  2010-04-16  8:40     ` [Qemu-devel] " Gerd Hoffmann
  0 siblings, 2 replies; 29+ messages in thread
From: malc @ 2010-04-14 20:51 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: qemu-devel

On Wed, 14 Apr 2010, Gerd Hoffmann wrote:

The code does not follow neither audio(which is passable should it be 
internally consistent) nor general QEMU code style (braces missing)

> ---
>  Makefile.objs      |    1 +
>  audio/audio.c      |    3 +
>  audio/audio_int.h  |    1 +
>  audio/spiceaudio.c |  315 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 320 insertions(+), 0 deletions(-)
>  create mode 100644 audio/spiceaudio.c
> 
> diff --git a/Makefile.objs b/Makefile.objs
> index ab1af88..b11db4c 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -84,6 +84,7 @@ common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o
>  audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o
>  audio-obj-$(CONFIG_SDL) += sdlaudio.o
>  audio-obj-$(CONFIG_OSS) += ossaudio.o
> +audio-obj-$(CONFIG_SPICE) += spiceaudio.o
>  audio-obj-$(CONFIG_COREAUDIO) += coreaudio.o
>  audio-obj-$(CONFIG_ALSA) += alsaaudio.o
>  audio-obj-$(CONFIG_DSOUND) += dsoundaudio.o
> diff --git a/audio/audio.c b/audio/audio.c
> index dbf0b96..67fc1d3 100644
> --- a/audio/audio.c
> +++ b/audio/audio.c
> @@ -45,6 +45,9 @@
>  */
>  static struct audio_driver *drvtab[] = {
>      CONFIG_AUDIO_DRIVERS
> +#ifdef CONFIG_SPICE
> +    &spice_audio_driver,
> +#endif
>      &no_audio_driver,
>      &wav_audio_driver
>  };
> diff --git a/audio/audio_int.h b/audio/audio_int.h
> index 06e313f..d1f6c2d 100644
> --- a/audio/audio_int.h
> +++ b/audio/audio_int.h
> @@ -209,6 +209,7 @@ extern struct audio_driver coreaudio_audio_driver;
>  extern struct audio_driver dsound_audio_driver;
>  extern struct audio_driver esd_audio_driver;
>  extern struct audio_driver pa_audio_driver;
> +extern struct audio_driver spice_audio_driver;
>  extern struct audio_driver winwave_audio_driver;
>  extern struct mixeng_volume nominal_volume;
>  
> diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c
> new file mode 100644
> index 0000000..0e18f2f
> --- /dev/null
> +++ b/audio/spiceaudio.c
> @@ -0,0 +1,315 @@
> +#include "hw/hw.h"
> +#include "qemu-timer.h"
> +#include "qemu-spice.h"
> +
> +#define AUDIO_CAP "spice"
> +#include "audio.h"
> +#include "audio_int.h"
> +
> +#define LINE_IN_SAMPLES 1024
> +#define LINE_OUT_SAMPLES 1024
> +
> +typedef struct SpiceVoiceOut {
> +    HWVoiceOut            hw;
> +    SpicePlaybackInstance sin;
> +    uint32_t              *frame;
> +    uint32_t              *fpos;
> +    uint32_t              fsize;
> +    uint64_t              prev_ticks;
> +    int                   active;
> +} SpiceVoiceOut;
> +
> +typedef struct SpiceVoiceIn {
> +    HWVoiceIn             hw;
> +    SpiceRecordInstance   sin;
> +    uint64_t              prev_ticks;
> +    int                   active;
> +    uint32_t              samples[LINE_IN_SAMPLES];
> +} SpiceVoiceIn;
> +
> +static SpicePlaybackInterface playback_sif = {
> +    .base.type          = SPICE_INTERFACE_PLAYBACK,
> +    .base.description   = "playback",
> +    .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR,
> +    .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR,
> +};
> +
> +static SpiceRecordInterface record_sif = {
> +    .base.type          = SPICE_INTERFACE_RECORD,
> +    .base.description   = "record",
> +    .base.major_version = SPICE_INTERFACE_RECORD_MAJOR,
> +    .base.minor_version = SPICE_INTERFACE_RECORD_MINOR,
> +};
> +
> +static void *spice_audio_init(void)
> +{
> +    if (!using_spice)
> +        return NULL;
> +    return qemu_malloc(42);

Eh? The HGttG references should at least be given an explanation in
the comments.

> +}
> +
> +static void spice_audio_fini(void *opaque)
> +{
> +    qemu_free(opaque);
> +}
> +
> +static uint64_t get_monotonic_time(void)
> +{
> +    struct timespec time_space;
> +    clock_gettime(CLOCK_MONOTONIC, &time_space);

a. The presence of monotonic clock is not guranteed
b. The call can fail yet the result value is not checked
c. I have a really hard time following what rt clock (regardless
   of monotonicity is doing here at all)

> +    return (uint64_t)time_space.tv_sec * 1000 * 1000 * 1000 + time_space.tv_nsec;
> +}
> +
> +/* playback */
> +
> +static int line_out_init(HWVoiceOut *hw, struct audsettings *as)
> +{
> +    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
> +    struct audsettings settings;
> +
> +    settings.freq       = SPICE_INTERFACE_PLAYBACK_FREQ;
> +    settings.nchannels  = SPICE_INTERFACE_PLAYBACK_CHAN;
> +    settings.fmt        = AUD_FMT_S16;
> +    settings.endianness = AUDIO_HOST_ENDIANNESS;
> +
> +    audio_pcm_init_info(&hw->info, &settings);
> +    hw->samples = LINE_OUT_SAMPLES;
> +    out->active = 0;
> +
> +    out->sin.base.sif = &playback_sif.base;
> +    spice_server_add_interface(spice_server, &out->sin.base);
> +    return 0;
> +}
> +
> +static void line_out_fini(HWVoiceOut *hw)
> +{
> +    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
> +
> +    spice_server_remove_interface(&out->sin.base);
> +}
> +
> +static int line_out_run(HWVoiceOut *hw, int live)
> +{
> +    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
> +    int rpos, decr;
> +    int samples;
> +    uint64_t now;
> +    uint64_t ticks;
> +    uint64_t bytes;
> +
> +    if (!live) {
> +        return 0;
> +    }
> +
> +    now = get_monotonic_time();
> +    ticks = now - out->prev_ticks;
> +    bytes = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000);
> +    out->prev_ticks = now;
> +
> +    decr = (bytes > INT_MAX)
> +        ? INT_MAX >> hw->info.shift
> +        : (bytes + (1 << (hw->info.shift - 1))) >> hw->info.shift;
> +    decr = audio_MIN(live, decr);
> +
> +    samples = decr;
> +    rpos = hw->rpos;
> +    while (samples) {
> +        int left_till_end_samples = hw->samples - rpos;
> +        int len = audio_MIN(samples, left_till_end_samples);
> +
> +        if (!out->frame) {
> +            spice_server_playback_get_buffer(&out->sin, &out->frame, &out->fsize);
> +            out->fpos = out->frame;
> +        }
> +        if (out->frame) {
> +            len = audio_MIN(len, out->fsize);
> +            hw->clip(out->fpos, hw->mix_buf + rpos, len);
> +            out->fsize -= len;
> +            out->fpos  += len;
> +            if (out->fsize == 0) {
> +                spice_server_playback_put_samples(&out->sin, out->frame);
> +                out->frame = out->fpos = NULL;
> +            }
> +        }
> +        rpos = (rpos + len) % hw->samples;
> +        samples -= len;
> +    }
> +    hw->rpos = rpos;
> +    return decr;
> +}
> +
> +static int line_out_write(SWVoiceOut *sw, void *buf, int len)
> +{
> +    return audio_pcm_sw_write(sw, buf, len);
> +}
> +
> +static int line_out_ctl(HWVoiceOut *hw, int cmd, ...)
> +{
> +    SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
> +
> +    switch (cmd) {
> +    case VOICE_ENABLE:
> +        if (out->active) {
> +            break;
> +        }
> +        out->active = 1;
> +        out->prev_ticks = get_monotonic_time();
> +        spice_server_playback_start(&out->sin);
> +        break;
> +    case VOICE_DISABLE:
> +        if (!out->active) {
> +            break;
> +        }
> +        out->active = 0;
> +        if (out->frame) {
> +            memset(out->fpos, 0, out->fsize << 2);
> +            spice_server_playback_put_samples(&out->sin, out->frame);
> +            out->frame = out->fpos = NULL;
> +            spice_server_playback_stop(&out->sin);
> +        }
> +        break;
> +    }
> +    return 0;
> +}
> +
> +/* record */
> +
> +static int line_in_init(HWVoiceIn *hw, struct audsettings *as)
> +{
> +    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
> +    struct audsettings settings;
> +
> +    settings.freq       = SPICE_INTERFACE_RECORD_FREQ;
> +    settings.nchannels  = SPICE_INTERFACE_RECORD_CHAN;
> +    settings.fmt        = AUD_FMT_S16;
> +    settings.endianness = AUDIO_HOST_ENDIANNESS;
> +
> +    audio_pcm_init_info(&hw->info, &settings);
> +    hw->samples = LINE_IN_SAMPLES;
> +    in->active = 0;
> +
> +    in->sin.base.sif = &record_sif.base;
> +    spice_server_add_interface(spice_server, &in->sin.base);
> +    return 0;
> +}
> +
> +static void line_in_fini(HWVoiceIn *hw)
> +{
> +    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
> +
> +    spice_server_remove_interface(&in->sin.base);
> +}
> +
> +static int line_in_run(HWVoiceIn *hw)
> +{
> +    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
> +    int num_samples;
> +    int ready;
> +    int len[2];
> +    uint64_t now;
> +    uint64_t ticks;
> +    uint64_t delta_samp;
> +    uint32_t *samples;
> +
> +    if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in(hw))) {
> +        return 0;
> +    }
> +
> +    now = get_monotonic_time();
> +    ticks = now - in->prev_ticks;
> +    in->prev_ticks = now;
> +    delta_samp = (ticks * hw->info.bytes_per_second) / (1000 * 1000 * 1000);
> +    delta_samp = (delta_samp + (1 << (hw->info.shift - 1))) >> hw->info.shift;
> +
> +    num_samples = audio_MIN(num_samples, delta_samp);
> +
> +    ready = spice_server_record_get_samples(&in->sin, in->samples, num_samples);
> +    samples = in->samples;
> +    if (ready == 0) {
> +        static uint32_t silence[LINE_IN_SAMPLES];
> +        samples = silence;
> +        ready = LINE_IN_SAMPLES;
> +    }
> +
> +    num_samples = audio_MIN(ready, num_samples);
> +
> +    if (hw->wpos + num_samples > hw->samples) {
> +        len[0] = hw->samples - hw->wpos;
> +        len[1] = num_samples - len[0];
> +    } else {
> +        len[0] = num_samples;
> +        len[1] = 0;
> +    }
> +
> +    hw->conv(hw->conv_buf + hw->wpos, samples, len[0], &nominal_volume);
> +
> +    if (len[1]) {
> +        hw->conv(hw->conv_buf, samples + len[0], len[1],
> +                 &nominal_volume);
> +    }
> +
> +    hw->wpos = (hw->wpos + num_samples) % hw->samples;
> +
> +    return num_samples;
> +}
> +
> +static int line_in_read(SWVoiceIn *sw, void *buf, int size)
> +{
> +    return audio_pcm_sw_read(sw, buf, size);
> +}
> +
> +static int line_in_ctl(HWVoiceIn *hw, int cmd, ...)
> +{
> +    SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
> +
> +    switch (cmd) {
> +    case VOICE_ENABLE:
> +        if (in->active) {
> +            break;
> +        }
> +        in->active = 1;
> +        in->prev_ticks = get_monotonic_time();
> +        spice_server_record_start(&in->sin);
> +        break;
> +    case VOICE_DISABLE:
> +        if (!in->active) {
> +            break;
> +        }
> +        in->active = 0;
> +        spice_server_record_stop(&in->sin);
> +        break;
> +    }
> +    return 0;
> +}
> +
> +static struct audio_option audio_options[] = {
> +    { /* end of list */ },
> +};
> +
> +static struct audio_pcm_ops audio_callbacks = {
> +    .init_out = line_out_init,
> +    .fini_out = line_out_fini,
> +    .run_out  = line_out_run,
> +    .write    = line_out_write,
> +    .ctl_out  = line_out_ctl,
> +
> +    .init_in  = line_in_init,
> +    .fini_in  = line_in_fini,
> +    .run_in   = line_in_run,
> +    .read     = line_in_read,
> +    .ctl_in   = line_in_ctl,
> +};
> +
> +struct audio_driver spice_audio_driver = {
> +    .name           = "spice",
> +    .descr          = "spice audio driver",
> +    .options        = audio_options,
> +    .init           = spice_audio_init,
> +    .fini           = spice_audio_fini,
> +    .pcm_ops        = &audio_callbacks,
> +    .can_be_default = 1,
> +    .max_voices_out = 1,
> +    .max_voices_in  = 1,
> +    .voice_size_out = sizeof(SpiceVoiceOut),
> +    .voice_size_in  = sizeof(SpiceVoiceIn),
> +};
> 

-- 
mailto:av1474@comtv.ru

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device
  2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device Gerd Hoffmann
  2010-04-14 16:52   ` Blue Swirl
@ 2010-04-14 22:21   ` Alexander Graf
  2010-04-16  8:08     ` Gerd Hoffmann
  1 sibling, 1 reply; 29+ messages in thread
From: Alexander Graf @ 2010-04-14 22:21 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: qemu-devel


On 14.04.2010, at 11:55, Gerd Hoffmann wrote:

> 
> +static inline void atomic_or(uint32_t *var, uint32_t add)
> +{
> +   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add) : "memory");

I hope this is going away? I don't see why QXL should be coupled to x86 host and guest only.


Alex

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-14 16:52   ` Blue Swirl
@ 2010-04-14 23:08     ` Paolo Bonzini
  2010-04-15 16:47       ` Blue Swirl
  2010-04-16  8:02       ` Gerd Hoffmann
  0 siblings, 2 replies; 29+ messages in thread
From: Paolo Bonzini @ 2010-04-14 23:08 UTC (permalink / raw)
  To: Blue Swirl; +Cc: Gerd Hoffmann, qemu-devel

On 04/14/2010 06:52 PM, Blue Swirl wrote:
> On 4/14/10, Gerd Hoffmann<kraxel@redhat.com>  wrote:
>>   +static inline void atomic_or(uint32_t *var, uint32_t add)
>>   +{
>>   +   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add) : "memory");
>>   +}
>
> This will break on non-x86 hosts.

I'd just use __sync_fetch_and_or here.

Paolo

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 11/11] spice: add audio
  2010-04-14 20:51   ` malc
@ 2010-04-14 23:14     ` Paolo Bonzini
  2010-04-15  0:13       ` malc
  2010-04-16  8:40     ` [Qemu-devel] " Gerd Hoffmann
  1 sibling, 1 reply; 29+ messages in thread
From: Paolo Bonzini @ 2010-04-14 23:14 UTC (permalink / raw)
  To: malc; +Cc: Gerd Hoffmann, qemu-devel

On 04/14/2010 10:51 PM, malc wrote:
>> >  +
>> >  +static uint64_t get_monotonic_time(void)
>> >  +{
>> >  +    struct timespec time_space;
>> >  +    clock_gettime(CLOCK_MONOTONIC,&time_space);
> a. The presence of monotonic clock is not guranteed

There is qemu_get_clock_ns(rt_clock).

Paolo

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 11/11] spice: add audio
  2010-04-14 23:14     ` [Qemu-devel] " Paolo Bonzini
@ 2010-04-15  0:13       ` malc
  2010-04-15  0:26         ` Paolo Bonzini
  0 siblings, 1 reply; 29+ messages in thread
From: malc @ 2010-04-15  0:13 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Gerd Hoffmann, qemu-devel

On Thu, 15 Apr 2010, Paolo Bonzini wrote:

> On 04/14/2010 10:51 PM, malc wrote:
> > > >  +
> > > >  +static uint64_t get_monotonic_time(void)
> > > >  +{
> > > >  +    struct timespec time_space;
> > > >  +    clock_gettime(CLOCK_MONOTONIC,&time_space);
> > a. The presence of monotonic clock is not guranteed
> 
> There is qemu_get_clock_ns(rt_clock).

Sorry, but what does it have to do with anything?

> 
> Paolo
> 

-- 
mailto:av1474@comtv.ru

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 11/11] spice: add audio
  2010-04-15  0:13       ` malc
@ 2010-04-15  0:26         ` Paolo Bonzini
  2010-04-15  0:29           ` malc
  0 siblings, 1 reply; 29+ messages in thread
From: Paolo Bonzini @ 2010-04-15  0:26 UTC (permalink / raw)
  To: malc; +Cc: Gerd Hoffmann, qemu-devel

On 04/15/2010 02:13 AM, malc wrote:
>>>>> >  >  >  >    +static uint64_t get_monotonic_time(void)
>>>>> >  >  >  >    +{
>>>>> >  >  >  >    +    struct timespec time_space;
>>>>> >  >  >  >    +    clock_gettime(CLOCK_MONOTONIC,&time_space);
>>> >  >  a. The presence of monotonic clock is not guranteed
>> >
>> >  There is qemu_get_clock_ns(rt_clock).
> Sorry, but what does it have to do with anything?

It is exactly the same as Gerd's function plus a fallback if no 
monotonic clock is available.

Paolo

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 11/11] spice: add audio
  2010-04-15  0:26         ` Paolo Bonzini
@ 2010-04-15  0:29           ` malc
  0 siblings, 0 replies; 29+ messages in thread
From: malc @ 2010-04-15  0:29 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Gerd Hoffmann, qemu-devel

On Thu, 15 Apr 2010, Paolo Bonzini wrote:

> On 04/15/2010 02:13 AM, malc wrote:
> > > > > > >  >  >  >    +static uint64_t get_monotonic_time(void)
> > > > > > >  >  >  >    +{
> > > > > > >  >  >  >    +    struct timespec time_space;
> > > > > > >  >  >  >    +    clock_gettime(CLOCK_MONOTONIC,&time_space);
> > > > >  >  a. The presence of monotonic clock is not guranteed
> > > >
> > > >  There is qemu_get_clock_ns(rt_clock).
> > Sorry, but what does it have to do with anything?
> 
> It is exactly the same as Gerd's function plus a fallback if no monotonic
> clock is available.
> 

Ah get_clock which is as broken as snippet above.

-- 
mailto:av1474@comtv.ru

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-14 23:08     ` [Qemu-devel] " Paolo Bonzini
@ 2010-04-15 16:47       ` Blue Swirl
  2010-04-15 19:27         ` Richard Henderson
  2010-04-16  8:02       ` Gerd Hoffmann
  1 sibling, 1 reply; 29+ messages in thread
From: Blue Swirl @ 2010-04-15 16:47 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Gerd Hoffmann, qemu-devel

On 4/15/10, Paolo Bonzini <pbonzini@redhat.com> wrote:
> On 04/14/2010 06:52 PM, Blue Swirl wrote:
>
> > On 4/14/10, Gerd Hoffmann<kraxel@redhat.com>  wrote:
> >
> > >  +static inline void atomic_or(uint32_t *var, uint32_t add)
> > >  +{
> > >  +   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add)
> : "memory");
> > >  +}
> > >
> >
> > This will break on non-x86 hosts.
> >
>
>  I'd just use __sync_fetch_and_or here.

And on environments without __sync_fetch_and_or? Where is that available?

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-15 16:47       ` Blue Swirl
@ 2010-04-15 19:27         ` Richard Henderson
  0 siblings, 0 replies; 29+ messages in thread
From: Richard Henderson @ 2010-04-15 19:27 UTC (permalink / raw)
  To: qemu-devel; +Cc: blauwirbel, kraxel

On 04/15/2010 11:47 AM, Blue Swirl wrote:
> On 4/15/10, Paolo Bonzini<pbonzini@redhat.com>  wrote:
>> On 04/14/2010 06:52 PM, Blue Swirl wrote:
>>
>>> On 4/14/10, Gerd Hoffmann<kraxel@redhat.com>   wrote:
>>>
>>>>   +static inline void atomic_or(uint32_t *var, uint32_t add)
>>>>   +{
>>>>   +   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add)
>> : "memory");
>>>>   +}
>>>>
>>>
>>> This will break on non-x86 hosts.
>>>
>>
>>   I'd just use __sync_fetch_and_or here.
>
> And on environments without __sync_fetch_and_or? Where is that available?

GCC will provide it for capable cpus.  So sparcv9 has it via CAS.

For less capable cpus, there may be cooperation with the system in
some way.  For instance, ARM, SH, and HPPA Linux kernels all provide
various mechanisms to implement atomic sequences.  The result continues
to be provided by GCC in the form of entry points in libgcc.so.

For less capable cpus with no system support... well, the program
itself needs to figure out what an appropriate response should be.
I haven't gone back to look at the context from which this snippet
was taken to know if a locally defined mutex would be adequate.

In either case, some configure tests that detect when various forms
of atomic operations are available in the host compiler would not
be amiss.


r~

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-14 23:08     ` [Qemu-devel] " Paolo Bonzini
  2010-04-15 16:47       ` Blue Swirl
@ 2010-04-16  8:02       ` Gerd Hoffmann
  2010-04-16 10:18         ` Paolo Bonzini
  1 sibling, 1 reply; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-16  8:02 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Blue Swirl, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 529 bytes --]

On 04/15/10 01:08, Paolo Bonzini wrote:
> On 04/14/2010 06:52 PM, Blue Swirl wrote:
>> On 4/14/10, Gerd Hoffmann<kraxel@redhat.com> wrote:
>>> +static inline void atomic_or(uint32_t *var, uint32_t add)
>>> +{
>>> + __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add)
>>> : "memory");
>>> +}
>>
>> This will break on non-x86 hosts.
>
> I'd just use __sync_fetch_and_or here.

Good idea.  I think we can zap the memory barrier and fix a small race 
while being at it, see the incremental fix below.

cheers,
   Gerd

[-- Attachment #2: fix --]
[-- Type: text/plain, Size: 1138 bytes --]

diff --git a/hw/qxl.c b/hw/qxl.c
index 7ac06f6..8cbd9a3 100644
--- a/hw/qxl.c
+++ b/hw/qxl.c
@@ -6,7 +6,6 @@
 #include <pthread.h>
 
 #include "qemu-common.h"
-#include "qemu-barrier.h"
 #include "qemu-spice.h"
 #include "qemu-timer.h"
 #include "qemu-queue.h"
@@ -176,11 +175,6 @@ static inline uint32_t msb_mask(uint32_t val)
     return mask;
 }
 
-static inline void atomic_or(uint32_t *var, uint32_t add)
-{
-   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add) : "memory");
-}
-
 static ram_addr_t qxl_rom_size(void)
 {
     uint32_t rom_size = sizeof(QXLRom) + sizeof(QXLModes) + sizeof(qxl_modes);
@@ -892,12 +886,13 @@ static void pipe_read(void *opaque)
 
 static void qxl_send_events(PCIQXLDevice *d, uint32_t events)
 {
+    uint32_t old_pending;
+
     assert(d->ssd.running);
-    smp_wmb();
-    if ((d->ram->int_pending & events) == events) {
+    old_pending = __sync_fetch_and_or(&d->ram->int_pending, events);
+    if ((old_pending & events) == events) {
         return;
     }
-    atomic_or(&d->ram->int_pending, events);
     if (pthread_self() == d->main) {
         qxl_set_irq(d);
     } else {

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device
  2010-04-14 22:21   ` [Qemu-devel] " Alexander Graf
@ 2010-04-16  8:08     ` Gerd Hoffmann
  0 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-16  8:08 UTC (permalink / raw)
  To: Alexander Graf; +Cc: qemu-devel

On 04/15/10 00:21, Alexander Graf wrote:
>
> On 14.04.2010, at 11:55, Gerd Hoffmann wrote:
>
>>
>> +static inline void atomic_or(uint32_t *var, uint32_t add)
>> +{
>> +   __asm__ __volatile__ ("lock; orl %1, %0" : "+m" (*var) : "r" (add) : "memory");
>
> I hope this is going away? I don't see why QXL should be coupled to x86 host and guest only.

Spice on !x86 (especially big endian boxes) isn't going to fly at the 
moment for a number of reasons.  That is considered a bug though and we 
want fix it long-term.  There is nothing fundamental which prevents qxl 
and spice from being used on -- say -- ppc.

cheers,
   Gerd

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] [RfC PATCH 11/11] spice: add audio
  2010-04-14 20:51   ` malc
  2010-04-14 23:14     ` [Qemu-devel] " Paolo Bonzini
@ 2010-04-16  8:40     ` Gerd Hoffmann
  2010-04-16 11:13       ` Gerd Hoffmann
  1 sibling, 1 reply; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-16  8:40 UTC (permalink / raw)
  To: malc; +Cc: qemu-devel

On 04/14/10 22:51, malc wrote:
> On Wed, 14 Apr 2010, Gerd Hoffmann wrote:
>
> The code does not follow neither audio(which is passable should it be
> internally consistent) nor general QEMU code style (braces missing)

Will add the missing braces.

>> +static void *spice_audio_init(void)
>> +{
>> +    if (!using_spice)
>> +        return NULL;
>> +    return qemu_malloc(42);
>
> Eh? The HGttG references should at least be given an explanation in
> the comments.

Just need return something non-NULL here to indicate success.
Also wanted to check how carefully the reviewers are looking ;)

> c. I have a really hard time following what rt clock (regardless
>     of monotonicity is doing here at all)

Accept audio data with the correct rate.  When sending directly to the 
audio device the host hardware controls this.  Spice sends the audio 
data off to the network, so this doesn't work.  The math used by spice 
here looks like a old version of the noaudio code for rate control (/me 
inherited that code so I don't know for sure), which makes sense to me.

cheers,
   Gerd

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-16  8:02       ` Gerd Hoffmann
@ 2010-04-16 10:18         ` Paolo Bonzini
  2010-04-16 10:34           ` Gerd Hoffmann
  2010-04-16 12:53           ` Richard Henderson
  0 siblings, 2 replies; 29+ messages in thread
From: Paolo Bonzini @ 2010-04-16 10:18 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Blue Swirl, qemu-devel, rth

> > I'd just use __sync_fetch_and_or here.
> 
> Good idea.  I think we can zap the memory barrier and fix a small race 
> while being at it, see the incremental fix below.

Not sure about the memory barrier semantics of __sync_* (rth?), but
besides that the patch seems like a good idea.

Paolo

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-16 10:18         ` Paolo Bonzini
@ 2010-04-16 10:34           ` Gerd Hoffmann
  2010-04-16 12:53           ` Richard Henderson
  1 sibling, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-16 10:34 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Blue Swirl, qemu-devel, rth

On 04/16/10 12:18, Paolo Bonzini wrote:
>>> I'd just use __sync_fetch_and_or here.
>>
>> Good idea.  I think we can zap the memory barrier and fix a small race
>> while being at it, see the incremental fix below.
>
> Not sure about the memory barrier semantics of __sync_* (rth?), but
> besides that the patch seems like a good idea.

I think a memory barrier isn't needed there.  We don't touch anything 
shared beside int_pending which is only accessed using the sync op 
(after applying the patch).

cheers,
   Gerd

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [Qemu-devel] [RfC PATCH 11/11] spice: add audio
  2010-04-16  8:40     ` [Qemu-devel] " Gerd Hoffmann
@ 2010-04-16 11:13       ` Gerd Hoffmann
  0 siblings, 0 replies; 29+ messages in thread
From: Gerd Hoffmann @ 2010-04-16 11:13 UTC (permalink / raw)
  To: malc; +Cc: qemu-devel


   Hi,

>> c. I have a really hard time following what rt clock (regardless
>> of monotonicity is doing here at all)
>
> Accept audio data with the correct rate. When sending directly to the
> audio device the host hardware controls this. Spice sends the audio data
> off to the network, so this doesn't work. The math used by spice here
> looks like a old version of the noaudio code for rate control (/me
> inherited that code so I don't know for sure), which makes sense to me.

malc pointed out in irc simliar discussions came up for esd support.
Thread starts here:
   http://www.mail-archive.com/qemu-devel@nongnu.org/msg06593.html

Summary: having two clocks should better be avoided (one being vmclock 
and the other esd consuming the data, i.e. indirectly the sound hardware 
actually playing the data).  So instead of using vmtime for rate control 
the esd driver just feeds esd as fast as it can accept data.

Advantage of that approach:

   You'll avoid all clock sync issues such as audible audio blibs
   happening in case one clock is slightly faster as the other.

Problems with that approach:

   General: It adds extra latency.

   Spice: A client may or may not be connected.  In case no client is
   connected nobody consumes the sound stream data and thus there is no
   clock ...

cheers,
   Gerd

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [Qemu-devel] Re: [RfC PATCH 08/11] spice: add qxl device
  2010-04-16 10:18         ` Paolo Bonzini
  2010-04-16 10:34           ` Gerd Hoffmann
@ 2010-04-16 12:53           ` Richard Henderson
  1 sibling, 0 replies; 29+ messages in thread
From: Richard Henderson @ 2010-04-16 12:53 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: Blue Swirl, Gerd Hoffmann, qemu-devel

On 04/16/2010 05:18 AM, Paolo Bonzini wrote:
>>> I'd just use __sync_fetch_and_or here.
>>
>> Good idea.  I think we can zap the memory barrier and fix a small race
>> while being at it, see the incremental fix below.
>
> Not sure about the memory barrier semantics of __sync_* (rth?), but
> besides that the patch seems like a good idea.
>

The semantics of __sync_*, with only 2 exceptions, is full barrier.


r~

^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2010-04-16 16:47 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-04-14  9:55 [Qemu-devel] [RfC PATCH 00/11] Add spice support to qemu Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 01/11] vgabios update to 0.6c, add bios for qxl/unstable Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 02/11] add spice into the configure file Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 03/11] spice: core bits Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 04/11] spice: add keyboard Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 05/11] spice: add mouse Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 06/11] spice: simple display Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 07/11] spice: tls support Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 08/11] spice: add qxl device Gerd Hoffmann
2010-04-14 16:52   ` Blue Swirl
2010-04-14 23:08     ` [Qemu-devel] " Paolo Bonzini
2010-04-15 16:47       ` Blue Swirl
2010-04-15 19:27         ` Richard Henderson
2010-04-16  8:02       ` Gerd Hoffmann
2010-04-16 10:18         ` Paolo Bonzini
2010-04-16 10:34           ` Gerd Hoffmann
2010-04-16 12:53           ` Richard Henderson
2010-04-14 22:21   ` [Qemu-devel] " Alexander Graf
2010-04-16  8:08     ` Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 09/11] qxl: local rendering for sdl/vnc Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 10/11] spice: add tablet support Gerd Hoffmann
2010-04-14  9:55 ` [Qemu-devel] [RfC PATCH 11/11] spice: add audio Gerd Hoffmann
2010-04-14 20:51   ` malc
2010-04-14 23:14     ` [Qemu-devel] " Paolo Bonzini
2010-04-15  0:13       ` malc
2010-04-15  0:26         ` Paolo Bonzini
2010-04-15  0:29           ` malc
2010-04-16  8:40     ` [Qemu-devel] " Gerd Hoffmann
2010-04-16 11:13       ` Gerd Hoffmann

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.