From 5933e8a96ab9c59cb6b6c80c9db385364a68c959 Mon Sep 17 00:00:00 2001 From: Artyom Tarasenko Date: Mon, 2 Aug 2010 19:58:21 +0200 Subject: fix last cpu timer initialization The timer #0 is the system timer, so the timer #num_cpu is the timer of the last CPU, and it must be initialized in slavio_timer_reset. Don't mark non-existing timers as running. Signed-off-by: Artyom Tarasenko Signed-off-by: Blue Swirl --- hw/slavio_timer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/slavio_timer.c b/hw/slavio_timer.c index d7875536b..c125de4b6 100644 --- a/hw/slavio_timer.c +++ b/hw/slavio_timer.c @@ -377,12 +377,12 @@ static void slavio_timer_reset(DeviceState *d) curr_timer->limit = 0; curr_timer->count = 0; curr_timer->reached = 0; - if (i < s->num_cpus) { + if (i <= s->num_cpus) { ptimer_set_limit(curr_timer->timer, LIMIT_TO_PERIODS(TIMER_MAX_COUNT32), 1); ptimer_run(curr_timer->timer, 0); + curr_timer->running = 1; } - curr_timer->running = 1; } s->cputimer_mode = 0; } -- cgit v1.2.3 From 748a4ee311b8353292e85851034cb917906aac14 Mon Sep 17 00:00:00 2001 From: Blue Swirl Date: Tue, 3 Aug 2010 21:00:58 +0000 Subject: sparc32: use FW_CFG_CMDLINE_SIZE Add support for getting kernel command line size with FW_CFG_CMDLINE_SIZE. Signed-off-by: Blue Swirl --- hw/sun4m.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/sun4m.c b/hw/sun4m.c index e7a4cf6c9..a1373d407 100644 --- a/hw/sun4m.c +++ b/hw/sun4m.c @@ -978,8 +978,11 @@ static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size, fw_cfg_add_bytes(fw_cfg, FW_CFG_CMDLINE_DATA, (uint8_t*)strdup(kernel_cmdline), strlen(kernel_cmdline) + 1); + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, + strlen(kernel_cmdline) + 1); } else { fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 0); + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, 0); } fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, INITRD_LOAD_ADDR); fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, 0); // not used -- cgit v1.2.3 From a42a6307172d70fed5bc164f59a1596efb60447b Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 14 Jun 2010 09:54:27 +0200 Subject: add pflib: PixelFormat conversion library. --- Makefile.objs | 1 + pflib.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pflib.h | 6 ++ 3 files changed, 220 insertions(+) create mode 100644 pflib.c create mode 100644 pflib.h diff --git a/Makefile.objs b/Makefile.objs index 4a1eaa1b0..edfca87e5 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -83,6 +83,7 @@ common-obj-y += qemu-char.o savevm.o #aio.o common-obj-y += msmouse.o ps2.o common-obj-y += qdev.o qdev-properties.o common-obj-y += block-migration.o +common-obj-y += pflib.o common-obj-$(CONFIG_BRLAPI) += baum.o common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o diff --git a/pflib.c b/pflib.c new file mode 100644 index 000000000..1154d0c9a --- /dev/null +++ b/pflib.c @@ -0,0 +1,213 @@ +/* + * PixelFormat conversion library. + * + * Author: Gerd Hoffmann + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#include "qemu-common.h" +#include "console.h" +#include "pflib.h" + +typedef struct QemuPixel QemuPixel; + +typedef void (*pf_convert)(QemuPfConv *conv, + void *dst, void *src, uint32_t cnt); +typedef void (*pf_convert_from)(PixelFormat *pf, + QemuPixel *dst, void *src, uint32_t cnt); +typedef void (*pf_convert_to)(PixelFormat *pf, + void *dst, QemuPixel *src, uint32_t cnt); + +struct QemuPfConv { + pf_convert convert; + PixelFormat src; + PixelFormat dst; + + /* for copy_generic() */ + pf_convert_from conv_from; + pf_convert_to conv_to; + QemuPixel *conv_buf; + uint32_t conv_cnt; +}; + +struct QemuPixel { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t alpha; +}; + +/* ----------------------------------------------------------------------- */ +/* PixelFormat -> QemuPixel conversions */ + +static void conv_16_to_pixel(PixelFormat *pf, + QemuPixel *dst, void *src, uint32_t cnt) +{ + uint16_t *src16 = src; + + while (cnt > 0) { + dst->red = ((*src16 & pf->rmask) >> pf->rshift) << (8 - pf->rbits); + dst->green = ((*src16 & pf->gmask) >> pf->gshift) << (8 - pf->gbits); + dst->blue = ((*src16 & pf->bmask) >> pf->bshift) << (8 - pf->bbits); + dst->alpha = ((*src16 & pf->amask) >> pf->ashift) << (8 - pf->abits); + dst++, src16++, cnt--; + } +} + +/* assumes pf->{r,g,b,a}bits == 8 */ +static void conv_32_to_pixel_fast(PixelFormat *pf, + QemuPixel *dst, void *src, uint32_t cnt) +{ + uint32_t *src32 = src; + + while (cnt > 0) { + dst->red = (*src32 & pf->rmask) >> pf->rshift; + dst->green = (*src32 & pf->gmask) >> pf->gshift; + dst->blue = (*src32 & pf->bmask) >> pf->bshift; + dst->alpha = (*src32 & pf->amask) >> pf->ashift; + dst++, src32++, cnt--; + } +} + +static void conv_32_to_pixel_generic(PixelFormat *pf, + QemuPixel *dst, void *src, uint32_t cnt) +{ + uint32_t *src32 = src; + + while (cnt > 0) { + if (pf->rbits < 8) { + dst->red = ((*src32 & pf->rmask) >> pf->rshift) << (8 - pf->rbits); + } else { + dst->red = ((*src32 & pf->rmask) >> pf->rshift) >> (pf->rbits - 8); + } + if (pf->gbits < 8) { + dst->green = ((*src32 & pf->gmask) >> pf->gshift) << (8 - pf->gbits); + } else { + dst->green = ((*src32 & pf->gmask) >> pf->gshift) >> (pf->gbits - 8); + } + if (pf->bbits < 8) { + dst->blue = ((*src32 & pf->bmask) >> pf->bshift) << (8 - pf->bbits); + } else { + dst->blue = ((*src32 & pf->bmask) >> pf->bshift) >> (pf->bbits - 8); + } + if (pf->abits < 8) { + dst->alpha = ((*src32 & pf->amask) >> pf->ashift) << (8 - pf->abits); + } else { + dst->alpha = ((*src32 & pf->amask) >> pf->ashift) >> (pf->abits - 8); + } + dst++, src32++, cnt--; + } +} + +/* ----------------------------------------------------------------------- */ +/* QemuPixel -> PixelFormat conversions */ + +static void conv_pixel_to_16(PixelFormat *pf, + void *dst, QemuPixel *src, uint32_t cnt) +{ + uint16_t *dst16 = dst; + + while (cnt > 0) { + *dst16 = ((uint16_t)src->red >> (8 - pf->rbits)) << pf->rshift; + *dst16 |= ((uint16_t)src->green >> (8 - pf->gbits)) << pf->gshift; + *dst16 |= ((uint16_t)src->blue >> (8 - pf->bbits)) << pf->bshift; + *dst16 |= ((uint16_t)src->alpha >> (8 - pf->abits)) << pf->ashift; + dst16++, src++, cnt--; + } +} + +static void conv_pixel_to_32(PixelFormat *pf, + void *dst, QemuPixel *src, uint32_t cnt) +{ + uint32_t *dst32 = dst; + + while (cnt > 0) { + *dst32 = ((uint32_t)src->red >> (8 - pf->rbits)) << pf->rshift; + *dst32 |= ((uint32_t)src->green >> (8 - pf->gbits)) << pf->gshift; + *dst32 |= ((uint32_t)src->blue >> (8 - pf->bbits)) << pf->bshift; + *dst32 |= ((uint32_t)src->alpha >> (8 - pf->abits)) << pf->ashift; + dst32++, src++, cnt--; + } +} + +/* ----------------------------------------------------------------------- */ +/* PixelFormat -> PixelFormat conversions */ + +static void convert_copy(QemuPfConv *conv, void *dst, void *src, uint32_t cnt) +{ + uint32_t bytes = cnt * conv->src.bytes_per_pixel; + memcpy(dst, src, bytes); +} + +static void convert_generic(QemuPfConv *conv, void *dst, void *src, uint32_t cnt) +{ + if (conv->conv_cnt < cnt) { + conv->conv_cnt = cnt; + conv->conv_buf = qemu_realloc(conv->conv_buf, sizeof(QemuPixel) * conv->conv_cnt); + } + conv->conv_from(&conv->src, conv->conv_buf, src, cnt); + conv->conv_to(&conv->dst, dst, conv->conv_buf, cnt); +} + +/* ----------------------------------------------------------------------- */ +/* public interface */ + +QemuPfConv *qemu_pf_conv_get(PixelFormat *dst, PixelFormat *src) +{ + QemuPfConv *conv = qemu_mallocz(sizeof(QemuPfConv)); + + conv->src = *src; + conv->dst = *dst; + + if (memcmp(&conv->src, &conv->dst, sizeof(PixelFormat)) == 0) { + /* formats identical, can simply copy */ + conv->convert = convert_copy; + } else { + /* generic two-step conversion: src -> QemuPixel -> dst */ + switch (conv->src.bytes_per_pixel) { + case 2: + conv->conv_from = conv_16_to_pixel; + break; + case 4: + if (conv->src.rbits == 8 && conv->src.gbits == 8 && conv->src.bbits == 8) { + conv->conv_from = conv_32_to_pixel_fast; + } else { + conv->conv_from = conv_32_to_pixel_generic; + } + break; + default: + goto err; + } + switch (conv->dst.bytes_per_pixel) { + case 2: + conv->conv_to = conv_pixel_to_16; + break; + case 4: + conv->conv_to = conv_pixel_to_32; + break; + default: + goto err; + } + conv->convert = convert_generic; + } + return conv; + +err: + qemu_free(conv); + return NULL; +} + +void qemu_pf_conv_run(QemuPfConv *conv, void *dst, void *src, uint32_t cnt) +{ + conv->convert(conv, dst, src, cnt); +} + +void qemu_pf_conv_put(QemuPfConv *conv) +{ + if (conv) { + qemu_free(conv->conv_buf); + qemu_free(conv); + } +} diff --git a/pflib.h b/pflib.h new file mode 100644 index 000000000..8d73fdd13 --- /dev/null +++ b/pflib.h @@ -0,0 +1,6 @@ +/* public */ +typedef struct QemuPfConv QemuPfConv; + +QemuPfConv *qemu_pf_conv_get(PixelFormat *dst, PixelFormat *src); +void qemu_pf_conv_run(QemuPfConv *conv, void *dst, void *src, uint32_t cnt); +void qemu_pf_conv_put(QemuPfConv *conv); -- cgit v1.2.3 From 23243cb6764afe0a0cd2e2b4ed2116e2f27938cb Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 23 Apr 2010 13:44:10 +0200 Subject: configure: add logging Write compile commands and messages to config.log. Useful for debugging configure. --- configure | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/configure b/configure index a20371ca7..13d8be094 100755 --- a/configure +++ b/configure @@ -16,15 +16,18 @@ TMPO="${TMPDIR1}/qemu-conf-${RANDOM}-$$-${RANDOM}.o" TMPE="${TMPDIR1}/qemu-conf-${RANDOM}-$$-${RANDOM}.exe" trap "rm -f $TMPC $TMPO $TMPE ; exit" EXIT INT QUIT TERM +rm -f config.log compile_object() { - $cc $QEMU_CFLAGS -c -o $TMPO $TMPC > /dev/null 2> /dev/null + echo $cc $QEMU_CFLAGS -c -o $TMPO $TMPC >> config.log + $cc $QEMU_CFLAGS -c -o $TMPO $TMPC >> config.log 2>&1 } compile_prog() { local_cflags="$1" local_ldflags="$2" - $cc $QEMU_CFLAGS $local_cflags -o $TMPE $TMPC $LDFLAGS $local_ldflags > /dev/null 2> /dev/null + echo $cc $QEMU_CFLAGS $local_cflags -o $TMPE $TMPC $LDFLAGS $local_ldflags >> config.log + $cc $QEMU_CFLAGS $local_cflags -o $TMPE $TMPC $LDFLAGS $local_ldflags >> config.log 2>&1 } # check whether a command is available to this shell (may be either an -- cgit v1.2.3 From 08213765193f4b3a2b5d15beb6354abb18505dcd Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Wed, 24 Mar 2010 10:26:51 +0100 Subject: add spice into the configure file --- configure | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/configure b/configure index 13d8be094..56e7084f1 100755 --- a/configure +++ b/configure @@ -318,6 +318,7 @@ pkgversion="" check_utests="no" user_pie="no" zero_malloc="" +spice="" # OS specific if check_define __linux__ ; then @@ -619,6 +620,10 @@ for opt do ;; --enable-kvm) kvm="yes" ;; + --disable-spice) spice="no" + ;; + --enable-spice) spice="yes" + ;; --enable-profiler) profiler="yes" ;; --enable-cocoa) @@ -898,6 +903,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 @@ -2048,6 +2055,30 @@ if compile_prog "" ""; then gcc_attribute_warn_unused_result=yes fi +# spice probe +if test "$spice" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { spice_server_new(); return 0; } +EOF + spice_proto_ver=$($pkgconfig --modversion spice-protocol 2>/dev/null) + spice_server_ver=$($pkgconfig --modversion spice-server 2>/dev/null) + spice_cflags=$($pkgconfig --cflags spice-protocol spice-server 2>/dev/null) + spice_libs=$($pkgconfig --libs spice-protocol spice-server 2>/dev/null) + if compile_prog "$spice_cflags" "$spice_libs" ; then + spice="yes" + libs_softmmu="$libs_softmmu $spice_libs" + QEMU_CFLAGS="$QEMU_CFLAGS $spice_cflags" + else + if test "$spice" = "yes" ; then + feature_not_found "spice" + fi + spice="no" + fi +fi + +########################################## + ########################################## # check if we have fdatasync @@ -2190,6 +2221,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" @@ -2427,6 +2459,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 -- cgit v1.2.3 From 25e77134c2234d25ddcf4baf2348e6b0f71d0ace Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Wed, 4 Aug 2010 15:29:20 +0200 Subject: configure: require spice 0.5.3 --- configure | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/configure b/configure index 56e7084f1..0998e86b6 100755 --- a/configure +++ b/configure @@ -2061,11 +2061,10 @@ if test "$spice" != "no" ; then #include int main(void) { spice_server_new(); return 0; } EOF - spice_proto_ver=$($pkgconfig --modversion spice-protocol 2>/dev/null) - spice_server_ver=$($pkgconfig --modversion spice-server 2>/dev/null) spice_cflags=$($pkgconfig --cflags spice-protocol spice-server 2>/dev/null) spice_libs=$($pkgconfig --libs spice-protocol spice-server 2>/dev/null) - if compile_prog "$spice_cflags" "$spice_libs" ; then + if $pkgconfig --atleast-version=0.5.3 spice-server &&\ + compile_prog "$spice_cflags" "$spice_libs" ; then spice="yes" libs_softmmu="$libs_softmmu $spice_libs" QEMU_CFLAGS="$QEMU_CFLAGS $spice_cflags" -- cgit v1.2.3 From ae6547e0f7a611adde0cc6cbd8369ca1ca6fd93a Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 11 Mar 2010 11:13:27 -0300 Subject: spice: core bits 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.objs | 2 + qemu-config.c | 23 +++++++++ qemu-config.h | 1 + qemu-options.hx | 8 +++ qemu-spice.h | 22 +++++++++ spice.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vl.c | 15 ++++++ 7 files changed, 222 insertions(+) create mode 100644 qemu-spice.h create mode 100644 spice.c diff --git a/Makefile.objs b/Makefile.objs index edfca87e5..021067b63 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -88,6 +88,8 @@ common-obj-y += pflib.o common-obj-$(CONFIG_BRLAPI) += baum.o common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o +common-obj-$(CONFIG_SPICE) += spice.o + audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o audio-obj-$(CONFIG_SDL) += sdlaudio.o audio-obj-$(CONFIG_OSS) += ossaudio.o diff --git a/qemu-config.c b/qemu-config.c index 95abe61fa..4cf3b535d 100644 --- a/qemu-config.c +++ b/qemu-config.c @@ -342,6 +342,26 @@ QemuOptsList qemu_cpudef_opts = { }, }; +#ifdef CONFIG_SPICE +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 */ } + }, +}; +#endif + static QemuOptsList *vm_config_groups[] = { &qemu_drive_opts, &qemu_chardev_opts, @@ -352,6 +372,9 @@ static QemuOptsList *vm_config_groups[] = { &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 dca69d454..3a9021300 100644 --- a/qemu-config.h +++ b/qemu-config.h @@ -14,6 +14,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 db86feb09..c05c219fd 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -674,6 +674,14 @@ STEXI Enable SDL. ETEXI +#ifdef CONFIG_SPICE +DEF("spice", HAS_ARG, QEMU_OPTION_spice, + "-spice 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 000000000..5597576c7 --- /dev/null +++ b/qemu-spice.h @@ -0,0 +1,22 @@ +#ifndef QEMU_SPICE_H +#define QEMU_SPICE_H + +#ifdef CONFIG_SPICE + +#include + +#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 000000000..50fa5ca1f --- /dev/null +++ b/spice.c @@ -0,0 +1,151 @@ +#include +#include +#include + +#include +#include + +#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); + + watch_update_mask(watch, event_mask); + return watch; +} + +static void watch_remove(SpiceWatch *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 */ + +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_AUTO_GLZ); + + spice_server_init(spice_server, &core_interface); + using_spice = 1; +} diff --git a/vl.c b/vl.c index b3e367635..7f391c049 100644 --- a/vl.c +++ b/vl.c @@ -161,6 +161,8 @@ int main(int argc, char **argv) #include "cpus.h" #include "arch_init.h" +#include "qemu-spice.h" + //#define DEBUG_NET //#define DEBUG_SLIRP @@ -2600,6 +2602,15 @@ int main(int argc, char **argv, char **envp) } break; } +#ifdef CONFIG_SPICE + case QEMU_OPTION_spice: + opts = qemu_opts_parse(&qemu_spice_opts, optarg, 0); + if (!opts) { + fprintf(stderr, "parse error: %s\n", optarg); + exit(1); + } + break; +#endif case QEMU_OPTION_writeconfig: { FILE *fp; @@ -2868,6 +2879,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); -- cgit v1.2.3 From a546fc7699fa70bef77f334a2236c900785d1aa4 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 11 Mar 2010 11:13:28 -0300 Subject: spice: add keyboard 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.objs | 2 +- qemu-spice.h | 1 + spice-input.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ spice.c | 2 ++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spice-input.c diff --git a/Makefile.objs b/Makefile.objs index 021067b63..6ddb3736f 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -88,7 +88,7 @@ common-obj-y += pflib.o common-obj-$(CONFIG_BRLAPI) += baum.o common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o -common-obj-$(CONFIG_SPICE) += spice.o +common-obj-$(CONFIG_SPICE) += spice.o spice-input.o audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o audio-obj-$(CONFIG_SDL) += sdlaudio.o diff --git a/qemu-spice.h b/qemu-spice.h index 5597576c7..ceb3db2cb 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 000000000..e1014d78a --- /dev/null +++ b/spice-input.c @@ -0,0 +1,57 @@ +#include +#include +#include + +#include + +#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 const 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 50fa5ca1f..c763d52f7 100644 --- a/spice.c +++ b/spice.c @@ -148,4 +148,6 @@ void qemu_spice_init(void) spice_server_init(spice_server, &core_interface); using_spice = 1; + + qemu_spice_input_init(); } -- cgit v1.2.3 From 6df8a76bac423742d4cc7c228ecd842e0e230299 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 11 Mar 2010 11:13:29 -0300 Subject: spice: add mouse Open mouse channel. Now you can move the guests mouse pointer. No tablet / absolute positioning (yet) though. --- spice-input.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/spice-input.c b/spice-input.c index e1014d78a..8f3deb4aa 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 const 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); } -- cgit v1.2.3 From 4666695c13f9aedb982d558e1048c9fe35903c2a Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Wed, 24 Mar 2010 15:47:18 +0100 Subject: spice: simple display 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.objs | 2 +- qemu-spice.h | 1 + spice-display.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ spice-display.h | 52 ++++++++ vl.c | 7 +- 5 files changed, 447 insertions(+), 2 deletions(-) create mode 100644 spice-display.c create mode 100644 spice-display.h diff --git a/Makefile.objs b/Makefile.objs index 6ddb3736f..2f405296c 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -88,7 +88,7 @@ common-obj-y += pflib.o common-obj-$(CONFIG_BRLAPI) += baum.o common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o -common-obj-$(CONFIG_SPICE) += spice.o spice-input.o +common-obj-$(CONFIG_SPICE) += spice.o spice-input.o spice-display.o audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o audio-obj-$(CONFIG_SDL) += sdlaudio.o diff --git a/qemu-spice.h b/qemu-spice.h index ceb3db2cb..f06100474 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 000000000..1e6d93987 --- /dev/null +++ b/spice-display.c @@ -0,0 +1,387 @@ +#include +#include +#include +#include +#include +#include + +#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 = 0; + +int qemu_spice_rect_is_empty(const QXLRect* r) +{ + return r->top == r->bottom || r->left == r->right; +} + +void qemu_spice_rect_union(QXLRect *dest, const QXLRect *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_descriptor = 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; + image->bitmap.format = SPICE_BITMAP_FMT_32BIT; + + if (ssd->conv == NULL) { + PixelFormat dst = qemu_default_pixelformat(32); + ssd->conv = qemu_pf_conv_get(&dst, &ssd->ds->surface->pf); + assert(ssd->conv); + } + + 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++) { + qemu_pf_conv_run(ssd->conv, dst, src, bw); + src += ds_get_linesize(ssd->ds); + dst += image->bitmap.stride; + } + + 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.format = SPICE_SURFACE_FMT_32_xRGB; + 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) +{ + QXLRect 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); + qemu_pf_conv_put(ssd->conv); + ssd->conv = NULL; + + pthread_mutex_lock(&ssd->lock); + memset(&ssd->dirty, 0, sizeof(ssd->dirty)); + 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 = NUM_SURFACES; +} + +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 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_notify_update(QXLInstance *sin, uint32_t update_id) +{ + fprintf(stderr, "%s: abort()\n", __FUNCTION__); + abort(); +} + +static int interface_flush_resources(QXLInstance *sin) +{ + fprintf(stderr, "%s: abort()\n", __FUNCTION__); + abort(); + return 0; +} + +static const 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, + + .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, + .release_resource = interface_release_resource, + .get_cursor_command = interface_get_cursor_command, + .req_cursor_notification = interface_req_cursor_notification, + .notify_update = interface_notify_update, + .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 000000000..b55e7ea54 --- /dev/null +++ b/spice-display.h @@ -0,0 +1,52 @@ +#include +#include +#include + +#include "pflib.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 + +#define NUM_SURFACES 1024 + +typedef struct SimpleSpiceDisplay { + DisplayState *ds; + void *buf; + int bufsize; + QXLWorker *worker; + QXLInstance qxl; + uint32_t unique; + QemuPfConv *conv; + + pthread_mutex_t lock; + QXLRect 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 QXLRect* r); +void qemu_spice_rect_union(QXLRect *dest, const QXLRect *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 7f391c049..f68f8e840 100644 --- a/vl.c +++ b/vl.c @@ -2910,7 +2910,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 @@ -2950,6 +2950,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; -- cgit v1.2.3 From 00c551bfeb3dfc744ab1ec4201a218f484585a8c Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 13 Apr 2010 09:05:03 +0200 Subject: spice: add tablet support Add support for the spice tablet interface. The tablet interface will be registered (and then used by the spice client) as soon as a absolute pointing device is available and used by the guest, i.e. you'll have to configure your guest with '-usbdevice tablet'. --- spice-display.c | 2 +- spice-input.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/spice-display.c b/spice-display.c index 1e6d93987..fec2432f0 100644 --- a/spice-display.c +++ b/spice-display.c @@ -143,7 +143,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 8f3deb4aa..5646ff90b 100644 --- a/spice-input.c +++ b/spice-input.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -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,97 @@ static const 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); + if (height < 16) + height = 16; + if (width < 16) + width = 16; + 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 const 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); } -- cgit v1.2.3 From 873519aadc4dbb8c30ade64a603ba1b0c64a8a03 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Wed, 24 Mar 2010 11:16:54 +0100 Subject: vgabios update to 0.6c + pcibios patches. --- Makefile | 5 +++-- pc-bios/vgabios-cirrus.bin | Bin 35840 -> 35840 bytes pc-bios/vgabios-qxl.bin | Bin 0 -> 40448 bytes pc-bios/vgabios-qxldev.bin | Bin 0 -> 40448 bytes pc-bios/vgabios-stdvga.bin | Bin 0 -> 40448 bytes pc-bios/vgabios-vmware.bin | Bin 0 -> 40448 bytes pc-bios/vgabios.bin | Bin 38400 -> 40448 bytes 7 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 pc-bios/vgabios-qxl.bin create mode 100644 pc-bios/vgabios-qxldev.bin create mode 100644 pc-bios/vgabios-stdvga.bin create mode 100644 pc-bios/vgabios-vmware.bin diff --git a/Makefile b/Makefile index f95cc2fd0..1cb8fdf96 100644 --- a/Makefile +++ b/Makefile @@ -154,8 +154,9 @@ 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 \ -video.x openbios-sparc32 openbios-sparc64 openbios-ppc \ +BLOBS=bios.bin vgabios.bin vgabios-cirrus.bin \ +vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin vgabios-qxldev.bin \ +ppc_rom.bin video.x openbios-sparc32 openbios-sparc64 openbios-ppc \ gpxe-eepro100-80861209.rom \ gpxe-eepro100-80861229.rom \ pxe-e1000.bin \ diff --git a/pc-bios/vgabios-cirrus.bin b/pc-bios/vgabios-cirrus.bin index 4fa8f99f7..ffd37d8a6 100644 Binary files a/pc-bios/vgabios-cirrus.bin and b/pc-bios/vgabios-cirrus.bin differ diff --git a/pc-bios/vgabios-qxl.bin b/pc-bios/vgabios-qxl.bin new file mode 100644 index 000000000..2732d5599 Binary files /dev/null and b/pc-bios/vgabios-qxl.bin differ diff --git a/pc-bios/vgabios-qxldev.bin b/pc-bios/vgabios-qxldev.bin new file mode 100644 index 000000000..83e2161fb Binary files /dev/null and b/pc-bios/vgabios-qxldev.bin differ diff --git a/pc-bios/vgabios-stdvga.bin b/pc-bios/vgabios-stdvga.bin new file mode 100644 index 000000000..6ec49fec5 Binary files /dev/null and b/pc-bios/vgabios-stdvga.bin differ diff --git a/pc-bios/vgabios-vmware.bin b/pc-bios/vgabios-vmware.bin new file mode 100644 index 000000000..76173c2ec Binary files /dev/null and b/pc-bios/vgabios-vmware.bin differ diff --git a/pc-bios/vgabios.bin b/pc-bios/vgabios.bin index fa6f815fc..1d94417ee 100644 Binary files a/pc-bios/vgabios.bin and b/pc-bios/vgabios.bin differ -- cgit v1.2.3 From b9ecc56a3c580c85773fdc98426c7445f0f01cf9 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 6 May 2010 11:13:11 +0200 Subject: switch stdvga to pci vgabios --- hw/vga-pci.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hw/vga-pci.c b/hw/vga-pci.c index 2315f70bc..eef0e3c73 100644 --- a/hw/vga-pci.c +++ b/hw/vga-pci.c @@ -103,11 +103,10 @@ static int pci_vga_initfn(PCIDevice *dev) bios_total_size <<= 1; pci_register_bar(&d->dev, PCI_ROM_SLOT, bios_total_size, PCI_BASE_ADDRESS_MEM_PREFETCH, vga_map); + } else { + if (dev->romfile == NULL) + dev->romfile = qemu_strdup("vgabios-stdvga.bin"); } - - vga_init_vbe(s); - /* ROM BIOS */ - rom_add_vga(VGABIOS_FILENAME); return 0; } -- cgit v1.2.3 From 5e14bcd5a6c24ca65da71400c9f5d86ae3b1c5e1 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 6 May 2010 11:14:11 +0200 Subject: switch vmware_vga to pci vgabios --- hw/vmware_vga.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/hw/vmware_vga.c b/hw/vmware_vga.c index 12bff480e..682f28777 100644 --- a/hw/vmware_vga.c +++ b/hw/vmware_vga.c @@ -114,14 +114,12 @@ struct pci_vmsvga_state_s { # define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT # define SVGA_IO_MUL 1 # define SVGA_FIFO_SIZE 0x10000 -# define SVGA_MEM_BASE 0xe0000000 # define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA2 #else # define SVGA_ID SVGA_ID_1 # define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT # define SVGA_IO_MUL 4 # define SVGA_FIFO_SIZE 0x10000 -# define SVGA_MEM_BASE 0xe0000000 # define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA #endif @@ -1171,10 +1169,6 @@ static void vmsvga_init(struct vmsvga_state_s *s, int vga_ram_size) vga_init(&s->vga); vmstate_register(NULL, 0, &vmstate_vga_common, &s->vga); - vga_init_vbe(&s->vga); - - rom_add_vga(VGABIOS_FILENAME); - vmsvga_reset(s); } @@ -1272,6 +1266,7 @@ static PCIDeviceInfo vmsvga_info = { .qdev.size = sizeof(struct pci_vmsvga_state_s), .qdev.vmsd = &vmstate_vmware_vga, .init = pci_vmsvga_initfn, + .romfile = "vgabios-vmware.bin", }; static void vmsvga_register(void) -- cgit v1.2.3 From 94213a333e35e6aa5095044ef54f64792c2d0303 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 11 May 2010 22:28:44 +0200 Subject: all vga: refuse hotplugging. Try to pci hotplug a vga card, watch qemu die with hw_error(). This patch fixes it. --- hw/cirrus_vga.c | 4 ++++ hw/vga-pci.c | 4 ++++ hw/vmware_vga.c | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/hw/cirrus_vga.c b/hw/cirrus_vga.c index bbd4b082d..62296c017 100644 --- a/hw/cirrus_vga.c +++ b/hw/cirrus_vga.c @@ -3188,6 +3188,10 @@ static int pci_cirrus_vga_initfn(PCIDevice *dev) uint8_t *pci_conf = d->dev.config; int device_id = CIRRUS_ID_CLGD5446; + if (dev->qdev.hotplugged) { + return -1; + } + /* setup VGA */ vga_common_init(&s->vga, VGA_RAM_SIZE); cirrus_init_common(s, device_id, 1); diff --git a/hw/vga-pci.c b/hw/vga-pci.c index eef0e3c73..84467074e 100644 --- a/hw/vga-pci.c +++ b/hw/vga-pci.c @@ -79,6 +79,10 @@ static int pci_vga_initfn(PCIDevice *dev) VGACommonState *s = &d->vga; uint8_t *pci_conf = d->dev.config; + if (dev->qdev.hotplugged) { + return -1; + } + // vga + console init vga_common_init(s, VGA_RAM_SIZE); vga_init(s); diff --git a/hw/vmware_vga.c b/hw/vmware_vga.c index 682f28777..7ff89aa74 100644 --- a/hw/vmware_vga.c +++ b/hw/vmware_vga.c @@ -1232,6 +1232,10 @@ static int pci_vmsvga_initfn(PCIDevice *dev) struct pci_vmsvga_state_s *s = DO_UPCAST(struct pci_vmsvga_state_s, card, dev); + if (dev->qdev.hotplugged) { + return -1; + } + pci_config_set_vendor_id(s->card.config, PCI_VENDOR_ID_VMWARE); pci_config_set_device_id(s->card.config, SVGA_PCI_DEVICE_ID); pci_config_set_class(s->card.config, PCI_CLASS_DISPLAY_VGA); -- cgit v1.2.3 From 2bb36dd7a2e960e7b2000df0977ddc7c2ed84c9c Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Thu, 11 Mar 2010 11:13:32 -0300 Subject: spice: tls support 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. --- qemu-config.c | 24 ++++++++++++++++++++++ spice.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/qemu-config.c b/qemu-config.c index 4cf3b535d..41c9d3fcf 100644 --- a/qemu-config.c +++ b/qemu-config.c @@ -350,12 +350,36 @@ QemuOptsList qemu_spice_opts = { { .name = "port", .type = QEMU_OPT_NUMBER, + },{ + .name = "tls-port", + .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", + .type = QEMU_OPT_STRING, + },{ + .name = "x509-key-password", + .type = QEMU_OPT_STRING, + },{ + .name = "x509-cert-file", + .type = QEMU_OPT_STRING, + },{ + .name = "x509-cacert-file", + .type = QEMU_OPT_STRING, + },{ + .name = "x509-dh-key-file", + .type = QEMU_OPT_STRING, + },{ + .name = "tls-ciphers", + .type = QEMU_OPT_STRING, }, { /* end if list */ } }, diff --git a/spice.c b/spice.c index c763d52f7..3fe76cd1b 100644 --- a/spice.c +++ b/spice.c @@ -9,6 +9,7 @@ #include "qemu-spice.h" #include "qemu-timer.h" #include "qemu-queue.h" +#include "qemu-x509.h" #include "monitor.h" /* core bits */ @@ -126,18 +127,71 @@ static SpiceCoreInterface core_interface = { 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)) @@ -150,4 +204,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); } -- cgit v1.2.3 From 3b1940c928dd1ab6c552a376bda5849d1a85695a Mon Sep 17 00:00:00 2001 From: Yonit Halperin Date: Wed, 14 Jul 2010 13:26:34 +0300 Subject: spice: make compression configurable. --- qemu-config.c | 9 ++++++++ spice.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/qemu-config.c b/qemu-config.c index 41c9d3fcf..51ae1662a 100644 --- a/qemu-config.c +++ b/qemu-config.c @@ -380,6 +380,15 @@ QemuOptsList qemu_spice_opts = { },{ .name = "tls-ciphers", .type = QEMU_OPT_STRING, + },{ + .name = "image-compression", + .type = QEMU_OPT_STRING, + },{ + .name = "jpeg-wan-compression", + .type = QEMU_OPT_STRING, + },{ + .name = "zlib-glz-wan-compression", + .type = QEMU_OPT_STRING, }, { /* end if list */ } }, diff --git a/spice.c b/spice.c index 3fe76cd1b..f24961b98 100644 --- a/spice.c +++ b/spice.c @@ -122,6 +122,56 @@ static SpiceCoreInterface core_interface = { .watch_remove = watch_remove, }; +/* config string parsing */ + +static int name2enum(const char *string, const char *table[], int entries) +{ + int i; + + if (string) { + for (i = 0; i < entries; i++) { + if (!table[i]) + continue; + if (strcmp(string, table[i]) != 0) + continue; + return i; + } + } + return -1; +} + +static int parse_name(const char *string, const char *optname, + const char *table[], int entries) +{ + int value = name2enum(string, table, entries); + + if (value != -1) + return value; + fprintf(stderr, "spice: invalid %s: %s\n", optname, string); + exit(1); +} + +static const char *compression_names[] = { + [ SPICE_IMAGE_COMPRESS_OFF ] = "off", + [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz", + [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz", + [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic", + [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz", + [ SPICE_IMAGE_COMPRESS_LZ ] = "lz", +}; +#define parse_compression(_name) \ + parse_name(_name, "image compression", \ + compression_names, ARRAY_SIZE(compression_names)) + +static const char *wan_compression_names[] = { + [ SPICE_WAN_COMPRESSION_AUTO ] = "auto", + [ SPICE_WAN_COMPRESSION_NEVER ] = "never", + [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always", +}; +#define parse_wan_compression(_name) \ + parse_name(_name, "wan compression", \ + wan_compression_names, ARRAY_SIZE(wan_compression_names)) + /* functions for the rest of qemu */ void qemu_spice_init(void) @@ -135,6 +185,8 @@ void qemu_spice_init(void) *x509_cert_file = NULL, *x509_cacert_file = NULL; int port, tls_port, len; + spice_image_compression_t compression; + spice_wan_compression_t wan_compr; if (!opts) return; @@ -197,8 +249,26 @@ void qemu_spice_init(void) 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_AUTO_GLZ); + compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ; + str = qemu_opt_get(opts, "image-compression"); + if (str) { + compression = parse_compression(str); + } + spice_server_set_image_compression(spice_server, compression); + + wan_compr = SPICE_WAN_COMPRESSION_AUTO; + str = qemu_opt_get(opts, "jpeg-wan-compression"); + if (str) { + wan_compr = parse_wan_compression(str); + } + spice_server_set_jpeg_compression(spice_server, wan_compr); + + wan_compr = SPICE_WAN_COMPRESSION_AUTO; + str = qemu_opt_get(opts, "zlib-glz-wan-compression"); + if (str) { + wan_compr = parse_wan_compression(str); + } + spice_server_set_zlib_glz_compression(spice_server, wan_compr); spice_server_init(spice_server, &core_interface); using_spice = 1; -- cgit v1.2.3 From c05697c00ef323349389d31c33faf06bb576e774 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 27 Apr 2010 11:50:11 +0200 Subject: spice: add qxl device qxl is a paravirtual graphics card. The qxl device is the bridge between the guest and the spice server (aka libspice-server). The spice server will send the rendering commands to the spice client, which will actually render them. The spice server is also able to render locally, which is done in case the guest wants read something from video memory. Local rendering is also used to support display over vnc and sdl. qxl is activated using "-vga qxl". qxl supports multihead, additional cards can be added via '-device qxl". --- Makefile.target | 1 + hw/hw.h | 14 + hw/pc.c | 8 + hw/qxl-logger.c | 179 +++++++ hw/qxl-render.c | 207 ++++++++ hw/qxl.c | 1412 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/qxl.h | 102 ++++ hw/vga_int.h | 2 +- sysemu.h | 3 +- vl.c | 4 +- 10 files changed, 1929 insertions(+), 3 deletions(-) create mode 100644 hw/qxl-logger.c create mode 100644 hw/qxl-render.c create mode 100644 hw/qxl.c create mode 100644 hw/qxl.h diff --git a/Makefile.target b/Makefile.target index 8a9c427b5..acebac10a 100644 --- a/Makefile.target +++ b/Makefile.target @@ -198,6 +198,7 @@ obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += debugcon.o multiboot.o obj-i386-y += pc_piix.o +obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o # shared objects obj-ppc-y = ppc.o diff --git a/hw/hw.h b/hw/hw.h index e3c3db27f..205d191c1 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -524,6 +524,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), \ @@ -729,6 +740,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 58dea57f8..1c8d2ff7c 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -39,6 +39,7 @@ #include "msix.h" #include "sysbus.h" #include "sysemu.h" +#include "qemu-spice.h" /* output Bochs bios info messages */ //#define DEBUG_BIOS @@ -990,6 +991,13 @@ void pc_vga_init(PCIBus *pci_bus) 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_bus) + 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_bus) { pci_vga_init(pci_bus, 0, 0); diff --git a/hw/qxl-logger.c b/hw/qxl-logger.c new file mode 100644 index 000000000..d4a935a39 --- /dev/null +++ b/hw/qxl-logger.c @@ -0,0 +1,179 @@ +/* + * qxl command logging -- for debug purposes + */ + +#include +#include +#include +#include + +#include "qxl.h" + +static const char *qxl_type[] = { + [ QXL_CMD_NOP ] = "nop", + [ QXL_CMD_DRAW ] = "draw", + [ QXL_CMD_UPDATE ] = "update", + [ QXL_CMD_CURSOR ] = "cursor", + [ QXL_CMD_MESSAGE ] = "message", + [ QXL_CMD_SURFACE ] = "surface", +}; + +static const char *qxl_draw_type[] = { + [ QXL_DRAW_NOP ] = "nop", + [ QXL_DRAW_FILL ] = "fill", + [ QXL_DRAW_OPAQUE ] = "opaque", + [ QXL_DRAW_COPY ] = "copy", + [ QXL_COPY_BITS ] = "copy-bits", + [ QXL_DRAW_BLEND ] = "blend", + [ QXL_DRAW_BLACKNESS ] = "blackness", + [ QXL_DRAW_WHITENESS ] = "whitemess", + [ QXL_DRAW_INVERS ] = "invers", + [ QXL_DRAW_ROP3 ] = "rop3", + [ QXL_DRAW_STROKE ] = "stroke", + [ QXL_DRAW_TEXT ] = "text", + [ QXL_DRAW_TRANSPARENT ] = "transparent", + [ QXL_DRAW_ALPHA_BLEND ] = "alpha-blend", +}; + +static const char *qxl_draw_effect[] = { + [ QXL_EFFECT_BLEND ] = "blend", + [ QXL_EFFECT_OPAQUE ] = "opaque", + [ QXL_EFFECT_REVERT_ON_DUP ] = "revert-on-dup", + [ QXL_EFFECT_BLACKNESS_ON_DUP ] = "blackness-on-dup", + [ QXL_EFFECT_WHITENESS_ON_DUP ] = "whiteness-on-dup", + [ QXL_EFFECT_NOP_ON_DUP ] = "nop-on-dup", + [ QXL_EFFECT_NOP ] = "nop", + [ QXL_EFFECT_OPAQUE_BRUSH ] = "opaque-brush", +}; + +static const char *qxl_surface_cmd[] = { + [ QXL_SURFACE_CMD_CREATE ] = "create", + [ QXL_SURFACE_CMD_DESTROY ] = "destroy", +}; + +static const char *spice_surface_fmt[] = { + [ SPICE_SURFACE_FMT_INVALID ] = "invalid", + [ SPICE_SURFACE_FMT_1_A ] = "alpha/1", + [ SPICE_SURFACE_FMT_8_A ] = "alpha/8", + [ SPICE_SURFACE_FMT_16_555 ] = "555/16", + [ SPICE_SURFACE_FMT_16_565 ] = "565/16", + [ SPICE_SURFACE_FMT_32_xRGB ] = "xRGB/32", + [ SPICE_SURFACE_FMT_32_ARGB ] = "ARGB/32", +}; + +static const char *qxl_cursor_cmd[] = { + [ QXL_CURSOR_SET ] = "set", + [ QXL_CURSOR_MOVE ] = "move", + [ QXL_CURSOR_HIDE ] = "hide", + [ QXL_CURSOR_TRAIL ] = "trail", +}; + +static const char *spice_cursor_type[] = { + [ SPICE_CURSOR_TYPE_ALPHA ] = "alpha", + [ SPICE_CURSOR_TYPE_MONO ] = "mono", + [ SPICE_CURSOR_TYPE_COLOR4 ] = "color4", + [ SPICE_CURSOR_TYPE_COLOR8 ] = "color8", + [ SPICE_CURSOR_TYPE_COLOR16 ] = "color16", + [ SPICE_CURSOR_TYPE_COLOR24 ] = "color24", + [ SPICE_CURSOR_TYPE_COLOR32 ] = "color32", +}; + +static const char *qxl_v2n(const char *n[], size_t l, int v) +{ + if (v >= l || !n[v]) + return "???"; + return n[v]; +} +#define qxl_name(_list, _value) qxl_v2n(_list, ARRAY_SIZE(_list), _value) + +static void qxl_log_cmd_draw(PCIQXLDevice *qxl, QXLDrawable *draw) +{ + fprintf(stderr, ": surface_id %d type %s effect %s", + draw->surface_id, + qxl_name(qxl_draw_type, draw->type), + qxl_name(qxl_draw_effect, draw->effect)); +} + +static void qxl_log_cmd_draw_compat(PCIQXLDevice *qxl, QXLCompatDrawable *draw) +{ + fprintf(stderr, ": type %s effect %s", + qxl_name(qxl_draw_type, draw->type), + qxl_name(qxl_draw_effect, draw->effect)); +} + +static void qxl_log_cmd_surface(PCIQXLDevice *qxl, QXLSurfaceCmd *cmd) +{ + fprintf(stderr, ": %s id %d", + qxl_name(qxl_surface_cmd, cmd->type), + cmd->surface_id); + if (cmd->type == QXL_SURFACE_CMD_CREATE) { + fprintf(stderr, " size %dx%d stride %d format %s (count %d, max %d)", + cmd->u.surface_create.width, + cmd->u.surface_create.height, + cmd->u.surface_create.stride, + qxl_name(spice_surface_fmt, cmd->u.surface_create.format), + qxl->guest_surfaces.count, qxl->guest_surfaces.max); + } + if (cmd->type == QXL_SURFACE_CMD_DESTROY) { + fprintf(stderr, " (count %d)", qxl->guest_surfaces.count); + } +} + +void qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id) +{ + QXLCursor *cursor; + + fprintf(stderr, ": %s", + qxl_name(qxl_cursor_cmd, cmd->type)); + switch (cmd->type) { + case QXL_CURSOR_SET: + fprintf(stderr, " +%d+%d visible %s, shape @ 0x%" PRIx64, + cmd->u.set.position.x, + cmd->u.set.position.y, + cmd->u.set.visible ? "yes" : "no", + cmd->u.set.shape); + cursor = qxl_phys2virt(qxl, cmd->u.set.shape, group_id); + fprintf(stderr, " type %s size %dx%d hot-spot +%d+%d" + " unique 0x%" PRIx64 " data-size %d", + qxl_name(spice_cursor_type, cursor->header.type), + cursor->header.width, cursor->header.height, + cursor->header.hot_spot_x, cursor->header.hot_spot_y, + cursor->header.unique, cursor->data_size); + break; + case QXL_CURSOR_MOVE: + fprintf(stderr, " +%d+%d", cmd->u.position.x, cmd->u.position.y); + break; + } +} + +void qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext) +{ + bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT; + void *data; + + if (!qxl->cmdlog) { + return; + } + fprintf(stderr, "qxl-%d/%s:", qxl->id, ring); + fprintf(stderr, " cmd @ 0x%" PRIx64 " %s%s", ext->cmd.data, + qxl_name(qxl_type, ext->cmd.type), + compat ? "(compat)" : ""); + + data = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + switch (ext->cmd.type) { + case QXL_CMD_DRAW: + if (!compat) { + qxl_log_cmd_draw(qxl, data); + } else { + qxl_log_cmd_draw_compat(qxl, data); + } + break; + case QXL_CMD_SURFACE: + qxl_log_cmd_surface(qxl, data); + break; + case QXL_CMD_CURSOR: + qxl_log_cmd_cursor(qxl, data, ext->group_id); + break; + } + fprintf(stderr, "\n"); +} diff --git a/hw/qxl-render.c b/hw/qxl-render.c new file mode 100644 index 000000000..d9ebca409 --- /dev/null +++ b/hw/qxl-render.c @@ -0,0 +1,207 @@ +/* + * qxl local rendering (aka display on sdl/vnc) + */ +#include +#include +#include +#include + +#include "qxl.h" + +static void qxl_flip(PCIQXLDevice *qxl, QXLRect *rect) +{ + uint8_t *src = qxl->guest_primary.data; + uint8_t *dst = qxl->guest_primary.flipped; + int len, i; + + src += (qxl->guest_primary.surface.height - rect->top - 1) * + qxl->guest_primary.stride; + dst += rect->top * qxl->guest_primary.stride; + src += rect->left * qxl->guest_primary.bytes_pp; + dst += rect->left * qxl->guest_primary.bytes_pp; + len = (rect->right - rect->left) * qxl->guest_primary.bytes_pp; + + for (i = rect->top; i < rect->bottom; i++) { + memcpy(dst, src, len); + dst += qxl->guest_primary.stride; + src -= qxl->guest_primary.stride; + } +} + +void qxl_render_resize(PCIQXLDevice *qxl) +{ + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + + qxl->guest_primary.stride = sc->stride; + qxl->guest_primary.resized++; + switch (sc->format) { + case SPICE_SURFACE_FMT_16_555: + qxl->guest_primary.bytes_pp = 2; + qxl->guest_primary.bits_pp = 15; + break; + case SPICE_SURFACE_FMT_16_565: + qxl->guest_primary.bytes_pp = 2; + qxl->guest_primary.bits_pp = 16; + break; + case SPICE_SURFACE_FMT_32_xRGB: + case SPICE_SURFACE_FMT_32_ARGB: + qxl->guest_primary.bytes_pp = 4; + qxl->guest_primary.bits_pp = 32; + break; + default: + fprintf(stderr, "%s: unhandled format: %x\n", __FUNCTION__, + qxl->guest_primary.surface.format); + qxl->guest_primary.bytes_pp = 4; + qxl->guest_primary.bits_pp = 32; + break; + } +} + +void qxl_render_update(PCIQXLDevice *qxl) +{ + VGACommonState *vga = &qxl->vga; + QXLRect dirty[32], update; + void *ptr; + int i; + + if (qxl->guest_primary.resized) { + qxl->guest_primary.resized = 0; + + if (qxl->guest_primary.flipped) { + qemu_free(qxl->guest_primary.flipped); + qxl->guest_primary.flipped = NULL; + } + qemu_free_displaysurface(vga->ds); + + qxl->guest_primary.data = qemu_get_ram_ptr(qxl->vga.vram_offset); + if (qxl->guest_primary.stride < 0) { + /* spice surface is upside down -> need extra buffer to flip */ + qxl->guest_primary.stride = -qxl->guest_primary.stride; + qxl->guest_primary.flipped = qemu_malloc(qxl->guest_primary.surface.width * + qxl->guest_primary.stride); + ptr = qxl->guest_primary.flipped; + } else { + ptr = qxl->guest_primary.data; + } + fprintf(stderr, "%s: %dx%d, stride %d, bpp %d, depth %d, flip %s\n", + __FUNCTION__, + qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height, + qxl->guest_primary.stride, + qxl->guest_primary.bytes_pp, + qxl->guest_primary.bits_pp, + qxl->guest_primary.flipped ? "yes" : "no"); + vga->ds->surface = + qemu_create_displaysurface_from(qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height, + qxl->guest_primary.bits_pp, + qxl->guest_primary.stride, + ptr); + dpy_resize(vga->ds); + } + + if (!qxl->guest_primary.commands) + return; + qxl->guest_primary.commands = 0; + + update.left = 0; + update.right = qxl->guest_primary.surface.width; + update.top = 0; + update.bottom = qxl->guest_primary.surface.height; + + memset(dirty, 0, sizeof(dirty)); + qxl->ssd.worker->update_area(qxl->ssd.worker, 0, &update, + dirty, ARRAY_SIZE(dirty), 1); + + for (i = 0; i < ARRAY_SIZE(dirty); i++) { + if (qemu_spice_rect_is_empty(dirty+i)) + break; + if (qxl->guest_primary.flipped) { + qxl_flip(qxl, dirty+i); + } + dpy_update(vga->ds, + dirty[i].left, dirty[i].top, + dirty[i].right - dirty[i].left, + dirty[i].bottom - dirty[i].top); + } +} + +static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor) +{ + QEMUCursor *c; + uint8_t *image, *mask; + int size; + + c = cursor_alloc(cursor->header.width, cursor->header.height); + c->hot_x = cursor->header.hot_spot_x; + c->hot_y = cursor->header.hot_spot_y; + switch (cursor->header.type) { + case SPICE_CURSOR_TYPE_ALPHA: + size = cursor->header.width * cursor->header.height * sizeof(uint32_t); + memcpy(c->data, cursor->chunk.data, size); + if (qxl->debug > 1) + cursor_print_ascii_art(c, "qxl/alpha"); + break; + case SPICE_CURSOR_TYPE_MONO: + mask = cursor->chunk.data; + image = mask + cursor_get_mono_bpl(c) * c->width; + cursor_set_mono(c, 0xffffff, 0x000000, image, 1, mask); + if (qxl->debug > 1) + cursor_print_ascii_art(c, "qxl/mono"); + break; + default: + fprintf(stderr, "%s: not implemented: type %d\n", + __FUNCTION__, cursor->header.type); + goto fail; + } + return c; + +fail: + cursor_put(c); + return NULL; +} + + +void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext) +{ + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + QXLCursor *cursor; + QEMUCursor *c; + int x = -1, y = -1; + + if (!qxl->ssd.ds->mouse_set || + !qxl->ssd.ds->cursor_define) + return; + +#if 1 + if (cmd->type != QXL_CURSOR_MOVE) { + fprintf(stderr, "%s", __FUNCTION__); + qxl_log_cmd_cursor(qxl, cmd, ext->group_id); + fprintf(stderr, "\n"); + } +#endif + switch (cmd->type) { + case QXL_CURSOR_SET: + x = cmd->u.set.position.x; + y = cmd->u.set.position.y; + cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id); + if (cursor->chunk.data_size != cursor->data_size) { + fprintf(stderr, "%s: multiple chunks\n", __FUNCTION__); + return; + } + c = qxl_cursor(qxl, cursor); + if (c == NULL) { + c = cursor_builtin_left_ptr(); + } + qxl->ssd.ds->cursor_define(c); + cursor_put(c); + break; + case QXL_CURSOR_MOVE: + x = cmd->u.position.x; + y = cmd->u.position.y; + break; + } + if (x != -1 && y != -1) { + qxl->ssd.ds->mouse_set(x, y, 1); + } +} diff --git a/hw/qxl.c b/hw/qxl.c new file mode 100644 index 000000000..640cea9b7 --- /dev/null +++ b/hw/qxl.c @@ -0,0 +1,1412 @@ +#include +#include +#include +#include +#include +#include + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "qemu-queue.h" +#include "monitor.h" +#include "sysemu.h" + +#include "qxl.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; \ + } + +#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 +}; + +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 void qxl_reset_memslots(PCIQXLDevice *d); +static void qxl_reset_surfaces(PCIQXLDevice *d); +static void qxl_ring_set_dirty(PCIQXLDevice *qxl); + +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 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 surface0_area_size; + uint32_t num_pages; + uint32_t fb, maxfb = 0; + int i; + + memset(rom, 0, d->rom_size); + + rom->magic = cpu_to_le32(QXL_ROM_MAGIC); + rom->id = cpu_to_le32(d->id); + rom->modes_offset = cpu_to_le32(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 = cpu_to_le32(NUM_SURFACES); + + modes->n_modes = cpu_to_le32(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].id = cpu_to_le32(i); + modes->modes[i].x_res = cpu_to_le32(qxl_modes[i].x_res); + modes->modes[i].y_res = cpu_to_le32(qxl_modes[i].y_res); + modes->modes[i].bits = cpu_to_le32(qxl_modes[i].bits); + modes->modes[i].stride = cpu_to_le32(qxl_modes[i].stride); + modes->modes[i].x_mili = cpu_to_le32(qxl_modes[i].x_mili); + modes->modes[i].y_mili = cpu_to_le32(qxl_modes[i].y_mili); + modes->modes[i].orientation = cpu_to_le32(qxl_modes[i].orientation); + } + if (maxfb < VGA_RAM_SIZE && d->id == 0) + maxfb = VGA_RAM_SIZE; + + ram_header_size = ALIGN(sizeof(QXLRam), 4096); + surface0_area_size = ALIGN(maxfb, 4096); + num_pages = d->vga.vram_size; + num_pages -= ram_header_size; + num_pages -= surface0_area_size; + num_pages = num_pages / TARGET_PAGE_SIZE; + + rom->draw_area_offset = cpu_to_le32(0); + rom->surface0_area_size = cpu_to_le32(surface0_area_size); + rom->pages_offset = cpu_to_le32(surface0_area_size); + rom->num_pages = cpu_to_le32(num_pages); + rom->ram_header_offset = cpu_to_le32(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 = d->vga.vram_ptr; + d->ram = (QXLRam *)(buf + le32_to_cpu(d->shadow_rom.ram_header_offset)); + d->ram->magic = cpu_to_le32(QXL_RAM_MAGIC); + d->ram->int_pending = cpu_to_le32(0); + d->ram->int_mask = cpu_to_le32(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; + qxl_ring_set_dirty(d); +} + +static void qxl_set_dirty(ram_addr_t addr, ram_addr_t end) +{ + while (addr < end) { + cpu_physical_memory_set_dirty(addr); + addr += TARGET_PAGE_SIZE; + } +} + +static void qxl_rom_set_dirty(PCIQXLDevice *qxl) +{ + ram_addr_t addr = qxl->rom_offset; + qxl_set_dirty(addr, addr + qxl->rom_size); +} + +static void qxl_ram_set_dirty(PCIQXLDevice *qxl, void *ptr) +{ + ram_addr_t addr = qxl->vga.vram_offset; + void *base = qxl->vga.vram_ptr; + intptr_t offset; + + offset = ptr - base; + offset &= ~(TARGET_PAGE_SIZE-1); + assert(offset < qxl->vga.vram_size); + qxl_set_dirty(addr + offset, addr + offset + TARGET_PAGE_SIZE); +} + +static void qxl_ring_set_dirty(PCIQXLDevice *qxl) +{ + ram_addr_t addr = qxl->vga.vram_offset + qxl->shadow_rom.ram_header_offset; + ram_addr_t end = qxl->vga.vram_offset + qxl->vga.vram_size; + qxl_set_dirty(addr, end); +} + +/* + * keep track of some command state, for savevm/loadvm. + */ +static void qxl_track_command(PCIQXLDevice *qxl, struct QXLCommandExt *ext) +{ + switch (le32_to_cpu(ext->cmd.type)) { + case QXL_CMD_SURFACE: + { + QXLSurfaceCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + uint32_t id = le32_to_cpu(cmd->surface_id); + PANIC_ON(id >= NUM_SURFACES); + if (cmd->type == QXL_SURFACE_CMD_CREATE) { + qxl->guest_surfaces.cmds[id] = ext->cmd.data; + qxl->guest_surfaces.count++; + if (qxl->guest_surfaces.max < qxl->guest_surfaces.count) + qxl->guest_surfaces.max = qxl->guest_surfaces.count; + } + if (cmd->type == QXL_SURFACE_CMD_DESTROY) { + qxl->guest_surfaces.cmds[id] = 0; + qxl->guest_surfaces.count--; + } + break; + } + case QXL_CMD_CURSOR: + { + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + if (cmd->type == QXL_CURSOR_SET) { + qxl->guest_cursor = ext->cmd.data; + } + break; + } + } +} + +/* spice display interface callbacks */ + +static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + dprintf(qxl, 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(qxl, 1, "%s: %d\n", __FUNCTION__, level); + qxl->shadow_rom.compression_level = cpu_to_le32(level); + qxl->rom->compression_level = cpu_to_le32(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 = cpu_to_le32(mm_time); + qxl->rom->mm_clock = cpu_to_le32(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(qxl, 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 = le32_to_cpu(qxl->shadow_rom.num_pages) << TARGET_PAGE_BITS; + info->n_surfaces = NUM_SURFACES; +} + +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(qxl, 2, "%s: vga\n", __FUNCTION__); + update = qemu_spice_create_update(&qxl->ssd); + if (update == NULL) { + return false; + } + *ext = update->ext; + qxl_log_command(qxl, "vga", ext); + return true; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + dprintf(qxl, 2, "%s: %s\n", __FUNCTION__, + qxl->cmdflags ? "compat" : "native"); + 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; + ext->flags = qxl->cmdflags; + SPICE_RING_POP(ring, notify); + qxl_ring_set_dirty(qxl); + if (notify) { + qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY); + } + qxl->guest_primary.commands++; + qxl_track_command(qxl, ext); + qxl_log_command(qxl, "cmd", ext); + return true; + default: + return false; + } +} + +static int interface_req_cmd_notification(QXLInstance *sin) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int wait = 1; + + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + SPICE_RING_CONS_WAIT(&qxl->ram->cmd_ring, wait); + qxl_ring_set_dirty(qxl); + break; + default: + /* nothing */ + break; + } + return wait; +} + +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; + qxl_ring_set_dirty(d); + } +} + +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, $command.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; + qxl_ram_set_dirty(qxl, &ext.info->next); + *item = id; + qxl_ring_set_dirty(qxl); + } else { + /* append item to the list */ + qxl->last_release->next = ext.info->id; + qxl_ram_set_dirty(qxl, &qxl->last_release->next); + ext.info->next = 0; + qxl_ram_set_dirty(qxl, &ext.info->next); + } + 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; + + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + 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; + ext->flags = qxl->cmdflags; + SPICE_RING_POP(ring, notify); + qxl_ring_set_dirty(qxl); + if (notify) { + qxl_send_events(qxl, QXL_INTERRUPT_CURSOR); + } + qxl->guest_primary.commands++; + qxl_track_command(qxl, ext); + qxl_log_command(qxl, "csr", ext); + if (qxl->id == 0) { + qxl_render_cursor(qxl, ext); + } + return true; + default: + return false; + } +} + +static int interface_req_cursor_notification(QXLInstance *sin) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int wait = 1; + + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + SPICE_RING_CONS_WAIT(&qxl->ram->cursor_ring, wait); + qxl_ring_set_dirty(qxl); + break; + default: + /* nothing */ + break; + } + return wait; +} + +static void interface_notify_update(QXLInstance *sin, uint32_t update_id) +{ + 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 const 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, + + .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, + .release_resource = interface_release_resource, + .get_cursor_command = interface_get_cursor_command, + .req_cursor_notification = interface_req_cursor_notification, + .notify_update = interface_notify_update, + .flush_resources = interface_flush_resources, +}; + +static void qxl_enter_vga_mode(PCIQXLDevice *d) +{ + if (d->mode == QXL_MODE_VGA) { + return; + } + dprintf(d, 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(d, 1, "%s\n", __FUNCTION__); + qxl_destroy_primary(d); +} + +static void qxl_set_irq(PCIQXLDevice *d) +{ + uint32_t pending = le32_to_cpu(d->ram->int_pending); + uint32_t mask = le32_to_cpu(d->ram->int_mask); + int level = !!(pending & mask); + qemu_set_irq(d->pci.irq[0], level); + qxl_ring_set_dirty(d); +} + +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 = cpu_to_le32(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(d, 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, int loadvm) +{ + dprintf(d, 1, "%s: start%s\n", __FUNCTION__, + loadvm ? " (loadvm)" : ""); + + d->ssd.worker->reset_cursor(d->ssd.worker); + d->ssd.worker->reset_image_cache(d->ssd.worker); + qxl_reset_surfaces(d); + qxl_reset_memslots(d); + + /* pre loadvm reset must not touch QXLRam. This lives in + * device memory, is migrated together with RAM and thus + * already loaded at this point */ + if (!loadvm) { + qxl_reset_state(d); + } + qemu_spice_create_host_memslot(&d->ssd); + qxl_soft_reset(d); + + dprintf(d, 1, "%s: done\n", __FUNCTION__); +} + +static void qxl_reset_handler(DeviceState *dev) +{ + PCIQXLDevice *d = DO_UPCAST(PCIQXLDevice, pci.qdev, dev); + qxl_hard_reset(d, 0); +} + +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) { + dprintf(qxl, 1, "%s\n", __FUNCTION__); + 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, uint64_t delta) +{ + static const int regions[] = { + QXL_RAM_RANGE_INDEX, + QXL_VRAM_RANGE_INDEX, + }; + 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_start = le64_to_cpu(d->guest_slots[slot_id].slot.mem_start); + guest_end = le64_to_cpu(d->guest_slots[slot_id].slot.mem_end); + + dprintf(d, 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; + default: + /* should not happen */ + abort(); + } + + 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 - delta; + memslot.generation = d->rom->slot_generation = 0; // FIXME d->generation++; + qxl_rom_set_dirty(d); + + dprintf(d, 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); + d->guest_slots[slot_id].ptr = (void*)memslot.virt_start; + d->guest_slots[slot_id].size = memslot.virt_end - memslot.virt_start; + d->guest_slots[slot_id].delta = delta; + d->guest_slots[slot_id].active = 1; +} + +static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id) +{ + dprintf(d, 1, "%s: slot %d\n", __FUNCTION__, slot_id); + d->ssd.worker->del_memslot(d->ssd.worker, MEMSLOT_GROUP_HOST, slot_id); + d->guest_slots[slot_id].active = 0; +} + +static void qxl_reset_memslots(PCIQXLDevice *d) +{ + dprintf(d, 1, "%s:\n", __FUNCTION__); + d->ssd.worker->reset_memslots(d->ssd.worker); + memset(&d->guest_slots, 0, sizeof(d->guest_slots)); +} + +static void qxl_reset_surfaces(PCIQXLDevice *d) +{ + dprintf(d, 1, "%s:\n", __FUNCTION__); + d->mode = QXL_MODE_UNDEFINED; + d->ssd.worker->destroy_surfaces(d->ssd.worker); + memset(&d->guest_surfaces.cmds, 0, sizeof(d->guest_surfaces.cmds)); +} + +void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl, int group_id) +{ + uint64_t phys = le64_to_cpu(pqxl); + uint32_t slot = (phys >> (64 - 8)) & 0xff; + uint64_t offset = phys & 0xffffffffffff; + + switch (group_id) { + case MEMSLOT_GROUP_HOST: + return (void*)offset; + case MEMSLOT_GROUP_GUEST: + PANIC_ON(slot > NUM_MEMSLOTS); + PANIC_ON(!qxl->guest_slots[slot].active); + PANIC_ON(offset < qxl->guest_slots[slot].delta); + offset -= qxl->guest_slots[slot].delta; + PANIC_ON(offset > qxl->guest_slots[slot].size) + return qxl->guest_slots[slot].ptr + offset; + default: + PANIC_ON(1); + } +} + +static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm) +{ + QXLDevSurfaceCreate surface; + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + + assert(qxl->mode != QXL_MODE_NATIVE); + qxl_exit_vga_mode(qxl); + + dprintf(qxl, 1, "%s: %dx%d\n", __FUNCTION__, + le32_to_cpu(sc->width), le32_to_cpu(sc->height)); + + surface.format = le32_to_cpu(sc->format); + surface.height = le32_to_cpu(sc->height); + surface.mem = le64_to_cpu(sc->mem); + surface.position = le32_to_cpu(sc->position); + surface.stride = le32_to_cpu(sc->stride); + surface.width = le32_to_cpu(sc->width); + surface.type = le32_to_cpu(sc->type); + surface.flags = le32_to_cpu(sc->flags); + + surface.mouse_mode = true; + surface.group_id = MEMSLOT_GROUP_GUEST; + if (loadvm) { + surface.flags |= QXL_SURF_FLAG_KEEP_DATA; + } + + qxl->mode = QXL_MODE_NATIVE; + qxl->cmdflags = 0; + qxl->ssd.worker->create_primary_surface(qxl->ssd.worker, 0, &surface); + + /* for local rendering */ + qxl_render_resize(qxl); +} + +static void qxl_destroy_primary(PCIQXLDevice *d) +{ + if (d->mode == QXL_MODE_UNDEFINED) { + return; + } + + dprintf(d, 1, "%s\n", __FUNCTION__); + + d->mode = QXL_MODE_UNDEFINED; + d->ssd.worker->destroy_primary_surface(d->ssd.worker, 0); +} + +static void qxl_set_mode(PCIQXLDevice *d, int modenr) +{ + pcibus_t start = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr; + pcibus_t end = d->pci.io_regions[QXL_RAM_RANGE_INDEX].size + start; + QXLMode *mode = d->modes->modes + modenr; + uint64_t devmem = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr; + QXLMemSlot slot = { + .mem_start = start, + .mem_end = end + }; + QXLSurfaceCreate surface = { + .width = mode->x_res, + .height = mode->y_res, + .stride = -mode->x_res * 4, + .format = SPICE_SURFACE_FMT_32_xRGB, + .mouse_mode = true, + .mem = devmem, + }; + + dprintf(d, 1, "%s: mode %d [ %d x %d @ %d bpp devmem 0x%lx ]\n", __FUNCTION__, + modenr, mode->x_res, mode->y_res, mode->bits, devmem); + qxl_hard_reset(d, 0); + + d->guest_slots[0].slot = slot; + qxl_add_memslot(d, 0, devmem); + + d->guest_primary.surface = surface; + qxl_create_guest_primary(d, 0); + + d->mode = QXL_MODE_COMPAT; + d->cmdflags = QXL_COMMAND_FLAG_COMPAT; + d->shadow_rom.mode = cpu_to_le32(modenr); + d->rom->mode = cpu_to_le32(modenr); + qxl_rom_set_dirty(d); +} + +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_SET_MODE: + case QXL_IO_MEMSLOT_ADD: + case QXL_IO_MEMSLOT_DEL: + case QXL_IO_CREATE_PRIMARY: + break; + default: + if (d->mode == QXL_MODE_NATIVE || d->mode == QXL_MODE_COMPAT) + break; + dprintf(d, 1, "%s: unexpected port 0x%x in vga mode\n", __FUNCTION__, io_port); + return; + } + + switch (io_port) { + case QXL_IO_UPDATE_AREA: + { + QXLRect update = d->ram->update_area; + d->ssd.worker->update_area(d->ssd.worker, d->ram->update_surface, + &update, NULL, 0, 0); + 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_SET_MODE: + dprintf(d, 1, "QXL_SET_MODE %d\n", val); + qxl_set_mode(d, val); + break; + case QXL_IO_LOG: + dprintf(d, 1, "log %s", d->ram->log_buf); + break; + case QXL_IO_RESET: + dprintf(d, 1, "QXL_IO_RESET\n"); + qxl_hard_reset(d, 0); + break; + case QXL_IO_MEMSLOT_ADD: + PANIC_ON(val >= NUM_MEMSLOTS); + PANIC_ON(d->guest_slots[val].active); + d->guest_slots[val].slot = d->ram->mem_slot; + qxl_add_memslot(d, val, 0); + break; + case QXL_IO_MEMSLOT_DEL: + qxl_del_memslot(d, val); + break; + case QXL_IO_CREATE_PRIMARY: + PANIC_ON(val != 0); + dprintf(d, 1, "QXL_IO_CREATE_PRIMARY\n"); + d->guest_primary.surface = d->ram->create_surface; + qxl_create_guest_primary(d, 0); + break; + case QXL_IO_DESTROY_PRIMARY: + PANIC_ON(val != 0); + dprintf(d, 1, "QXL_IO_DESTROY_PRIMARY\n"); + qxl_destroy_primary(d); + break; + case QXL_IO_DESTROY_SURFACE_WAIT: + d->ssd.worker->destroy_surface_wait(d->ssd.worker, val); + break; + case QXL_IO_DESTROY_ALL_SURFACES: + d->ssd.worker->destroy_surfaces(d->ssd.worker); + break; + default: + fprintf(stderr, "%s: ioport=0x%x, abort()\n", __FUNCTION__, io_port); + abort(); + } +} + +static uint32_t ioport_read(void *opaque, uint32_t addr) +{ + PCIQXLDevice *d = opaque; + + dprintf(d, 1, "%s: unexpected\n", __FUNCTION__); + return 0xff; +} + +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(qxl, 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: + 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) +{ + uint32_t old_pending; + uint32_t le_events = cpu_to_le32(events); + + assert(d->ssd.running); + old_pending = __sync_fetch_and_or(&d->ram->int_pending, le_events); + if ((old_pending & le_events) == le_events) { + return; + } + if (pthread_self() == d->main) { + qxl_set_irq(d); + } else { + if (write(d->pipe[1], d, 1) != 1) { + dprintf(d, 1, "%s: write to pipe failed\n", __FUNCTION__); + } + } +} + +static void init_pipe_signaling(PCIQXLDevice *d) +{ + if (pipe(d->pipe) < 0) { + dprintf(d, 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); +} + +/* graphics console */ + +static void qxl_hw_update(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + switch (qxl->mode) { + case QXL_MODE_VGA: + vga->update(vga); + break; + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + break; + default: + break; + } +} + +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; + } +} + +static void qxl_vm_change_state_handler(void *opaque, int running, int reason) +{ + PCIQXLDevice *qxl = opaque; + qemu_spice_vm_change_state_handler(&qxl->ssd, running, reason); +} + +/* display change listener */ + +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); + uint32_t pci_device_id; + uint32_t pci_device_rev; + + if (device_id == 0 && dev->qdev.hotplugged) { + device_id++; + } + + qxl->id = device_id; + qxl->mode = QXL_MODE_UNDEFINED; + qxl->generation = 1; + + switch (qxl->revision) { + case 1: /* spice 0.4 -- qxl-1 */ + pci_device_id = QXL_DEVICE_ID_STABLE; + pci_device_rev = QXL_REVISION_STABLE_V04; + break; + case 2: /* spice 0.6 -- qxl-2 */ + pci_device_id = QXL_DEVICE_ID_STABLE; + pci_device_rev = QXL_REVISION_STABLE_V06; + break; + default: /* experimental */ + pci_device_id = QXL_DEVICE_ID_DEVEL; + pci_device_rev = 1; + break; + } + + 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); + + vga->ds = graphic_console_init(qxl_hw_update, qxl_hw_invalidate, + qxl_hw_screen_dump, qxl_hw_text_update, qxl); + 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) { + if (pci_device_id == 0x01ff) { + qxl->pci.romfile = qemu_strdup("vgabios-qxldev.bin"); + } else { + 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->pci.qdev, "bar0", qxl->vga.vram_size); + qxl->vga.vram_ptr = qemu_get_ram_ptr(qxl->vga.vram_offset); + + 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, pci_device_id); + pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev); + pci_set_byte(&config[PCI_INTERRUPT_PIN], 1); + + qxl->rom_size = qxl_rom_size(); + qxl->rom_offset = qemu_ram_alloc(&qxl->pci.qdev, "bar2", qxl->rom_size); + init_qxl_rom(qxl); + init_qxl_ram(qxl); + + if (qxl->vram_size < 16 * 1024 * 1024) + qxl->vram_size = 16 * 1024 * 1024; + if (qxl->revision == 1) + qxl->vram_size = 4096; + qxl->vram_size = msb_mask(qxl->vram_size * 2 - 1); + qxl->vram_offset = qemu_ram_alloc(&qxl->pci.qdev, "bar1", 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(qxl_vm_change_state_handler, qxl); + + init_pipe_signaling(qxl); + qxl_reset_state(qxl); + + device_id++; + return 0; +} + +static void qxl_pre_save(void *opaque) +{ + PCIQXLDevice* d = opaque; + uint8_t *ram_start = d->vga.vram_ptr; + + dprintf(d, 1, "%s:\n", __FUNCTION__); +#if 1 /* wanna zap this */ + if (d->last_release == NULL) { + d->last_release_offset = 0; + } else { + d->last_release_offset = (uint8_t *)d->last_release - ram_start; + } + assert(d->last_release_offset < d->vga.vram_size); +#endif +} + +static int qxl_pre_load(void *opaque) +{ + PCIQXLDevice* d = opaque; + + dprintf(d, 1, "%s: start\n", __FUNCTION__); + qxl_hard_reset(d, 1); + qxl_exit_vga_mode(d); + dprintf(d, 1, "%s: done\n", __FUNCTION__); + return 0; +} + +static int qxl_post_load(void *opaque, int version) +{ + PCIQXLDevice* d = opaque; + uint8_t *ram_start = d->vga.vram_ptr; + QXLCommandExt *cmds; + int in, out, i, newmode; + + dprintf(d, 1, "%s: start\n", __FUNCTION__); + newmode = d->mode; + d->mode = QXL_MODE_UNDEFINED; + switch (newmode) { + case QXL_MODE_UNDEFINED: + break; + case QXL_MODE_VGA: + qxl_enter_vga_mode(d); + break; + case QXL_MODE_NATIVE: + for (i = 0; i < NUM_MEMSLOTS; i++) { + if (!d->guest_slots[i].active) + continue; + qxl_add_memslot(d, i, 0); + } + qxl_create_guest_primary(d, 1); + + /* replay surface-create and cursor-set commands */ + cmds = qemu_mallocz(sizeof(QXLCommandExt) * (NUM_SURFACES + 1)); + for (in = 0, out = 0; in < NUM_SURFACES; in++) { + if (d->guest_surfaces.cmds[in] == 0) + continue; + cmds[out].cmd.data = d->guest_surfaces.cmds[in]; + cmds[out].cmd.type = QXL_CMD_SURFACE; + cmds[out].group_id = MEMSLOT_GROUP_GUEST; + out++; + } + cmds[out].cmd.data = d->guest_cursor; + cmds[out].cmd.type = QXL_CMD_CURSOR; + cmds[out].group_id = MEMSLOT_GROUP_GUEST; + out++; + d->ssd.worker->loadvm_commands(d->ssd.worker, cmds, out); + qemu_free(cmds); + + break; + case QXL_MODE_COMPAT: + qxl_set_mode(d, d->shadow_rom.mode); + break; + } + dprintf(d, 1, "%s: done\n", __FUNCTION__); + +#if 1 /* wanna zap this */ + if (d->last_release_offset >= d->vga.vram_size) { + dprintf(d, 1, "%s: invalid last_release_offset %u, ram_size %u\n", + __FUNCTION__, d->last_release_offset, d->vga.vram_size); + exit(-1); + } + + if (d->last_release_offset == 0) { + d->last_release = NULL; + } else { + d->last_release = (QXLReleaseInfo *)(ram_start + d->last_release_offset); + } +#endif + + return 0; +} + +#define QXL_VER 1 + +static VMStateDescription qxl_memslot = { + .name = "qxl-memslot", + .version_id = QXL_VER, + .minimum_version_id = QXL_VER, + .fields = (VMStateField[]) { + VMSTATE_UINT64(slot.mem_start, struct guest_slots), + VMSTATE_UINT64(slot.mem_end, struct guest_slots), + VMSTATE_UINT32(active, struct guest_slots), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription qxl_surface = { + .name = "qxl-surface", + .version_id = QXL_VER, + .minimum_version_id = QXL_VER, + .fields = (VMStateField[]) { + VMSTATE_UINT32(width, QXLSurfaceCreate), + VMSTATE_UINT32(height, QXLSurfaceCreate), + VMSTATE_INT32(stride, QXLSurfaceCreate), + VMSTATE_UINT32(format, QXLSurfaceCreate), + VMSTATE_UINT32(position, QXLSurfaceCreate), + VMSTATE_UINT32(mouse_mode, QXLSurfaceCreate), + VMSTATE_UINT32(flags, QXLSurfaceCreate), + VMSTATE_UINT32(type, QXLSurfaceCreate), + VMSTATE_UINT64(mem, QXLSurfaceCreate), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription qxl_vmstate = { + .name = "qxl", + .version_id = QXL_VER, + .minimum_version_id = QXL_VER, + .pre_save = qxl_pre_save, + .pre_load = qxl_pre_load, + .post_load = qxl_post_load, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(pci, PCIQXLDevice), + VMSTATE_STRUCT(vga, PCIQXLDevice, QXL_VER, vmstate_vga_common, VGACommonState), + VMSTATE_UINT32(shadow_rom.mode, PCIQXLDevice), +#if 1 /* wanna zap this */ + VMSTATE_UINT32(num_free_res, PCIQXLDevice), + VMSTATE_UINT32(last_release_offset, PCIQXLDevice), +#endif + VMSTATE_UINT32(mode, PCIQXLDevice), + VMSTATE_UINT32(ssd.unique, PCIQXLDevice), +#if 1 /* new stuff */ + VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, QXL_VER, + qxl_memslot, struct guest_slots), + VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, QXL_VER, + qxl_surface, QXLSurfaceCreate), + VMSTATE_ARRAY(guest_surfaces.cmds, PCIQXLDevice, NUM_SURFACES, QXL_VER, + vmstate_info_uint64, uint64_t), + VMSTATE_UINT64(guest_cursor, PCIQXLDevice), +#endif + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo qxl_info = { + .qdev.name = "qxl", + .qdev.desc = "Spice QXL GPU", + .qdev.size = sizeof(PCIQXLDevice), + .qdev.reset = qxl_reset_handler, + .qdev.vmsd = &qxl_vmstate, + .init = qxl_init, + .config_write = qxl_write_config, + .qdev.props = (Property[]) { + 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_UINT32("revision", PCIQXLDevice, revision, 2), + DEFINE_PROP_UINT32("debug", PCIQXLDevice, debug, 0), + DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void qxl_register(void) +{ + pci_qdev_register(&qxl_info); +} + +device_init(qxl_register); diff --git a/hw/qxl.h b/hw/qxl.h new file mode 100644 index 000000000..121640589 --- /dev/null +++ b/hw/qxl.h @@ -0,0 +1,102 @@ +#include "console.h" +#include "hw.h" +#include "pci.h" +#include "vga_int.h" + +#include "qemu-spice.h" +#include "spice-display.h" + +enum qxl_mode { + QXL_MODE_UNDEFINED, + QXL_MODE_VGA, + QXL_MODE_COMPAT, /* spice 0.4.x */ + QXL_MODE_NATIVE, +}; + +typedef struct PCIQXLDevice { + PCIDevice pci; + SimpleSpiceDisplay ssd; + int id; + uint32_t debug; + uint32_t cmdlog; + enum qxl_mode mode; + uint32_t cmdflags; + int generation; + uint32_t revision; + + struct guest_slots { + QXLMemSlot slot; + void *ptr; + uint64_t size; + uint64_t delta; + uint32_t active; + } guest_slots[NUM_MEMSLOTS]; + + struct guest_primary { + QXLSurfaceCreate surface; + uint32_t commands; + uint32_t resized; + int32_t stride; + uint32_t bits_pp; + uint32_t bytes_pp; + uint8_t *data, *flipped; + } guest_primary; + + struct surfaces { + QXLPHYSICAL cmds[NUM_SURFACES]; + uint32_t count; + uint32_t max; + } guest_surfaces; + QXLPHYSICAL guest_cursor; + + /* thread signaling */ + pthread_t main; + int pipe[2]; + + /* ram pci bar */ + QXLRam *ram; + VGACommonState vga; + uint32_t num_free_res; + QXLReleaseInfo *last_release; + uint32_t last_release_offset; + + /* 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; + +#define PANIC_ON(x) if ((x)) { \ + printf("%s: PANIC %s failed\n", __FUNCTION__, #x); \ + exit(-1); \ +} + +#define dprintf(_qxl, _level, _fmt, ...) \ + do { \ + if (_qxl->debug >= _level) { \ + fprintf(stderr, "qxl-%d: ", _qxl->id); \ + fprintf(stderr, _fmt, ## __VA_ARGS__); \ + } \ + } while (0) + +/* qxl.c */ +void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL phys, int group_id); + +/* qxl-logger.c */ +void qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id); +void qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext); + +/* qxl-render.c */ +void qxl_render_resize(PCIQXLDevice *qxl); +void qxl_render_update(PCIQXLDevice *qxl); +void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext); diff --git a/hw/vga_int.h b/hw/vga_int.h index 6a46a434f..beb54231e 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/sysemu.h b/sysemu.h index a1f6466ac..9404bee0c 100644 --- a/sysemu.h +++ b/sysemu.h @@ -103,7 +103,7 @@ extern int incoming_expected; 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 f68f8e840..335eeab83 100644 --- a/vl.c +++ b/vl.c @@ -1432,6 +1432,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); @@ -2951,7 +2953,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 -- cgit v1.2.3 From 59bd95c8d2f5cc1b3b278abb774df0d7e43e1b18 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 13 Apr 2010 10:34:46 +0200 Subject: spice: add audio Add support for the spice audio interface. The driver is first in the driver list, but can_be_default is set only in case spice is active. So if you are using spice the spice audio driver is the default one, otherwise whatever comes first after spice in the list. Overriding the default using QEMU_AUDIO_DRV works in any case. --- Makefile.objs | 1 + audio/audio.c | 3 + audio/audio_int.h | 1 + audio/spiceaudio.c | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu-spice.h | 1 + spice.c | 1 + 6 files changed, 319 insertions(+) create mode 100644 audio/spiceaudio.c diff --git a/Makefile.objs b/Makefile.objs index 2f405296c..cdfe7abc2 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -93,6 +93,7 @@ common-obj-$(CONFIG_SPICE) += spice.o spice-input.o spice-display.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 ad51077f3..ade342e85 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -44,6 +44,9 @@ that we generate the list. */ static struct audio_driver *drvtab[] = { +#ifdef CONFIG_SPICE + &spice_audio_driver, +#endif CONFIG_AUDIO_DRIVERS &no_audio_driver, &wav_audio_driver diff --git a/audio/audio_int.h b/audio/audio_int.h index 06e313f83..d1f6c2d6e 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 000000000..8ae749997 --- /dev/null +++ b/audio/spiceaudio.c @@ -0,0 +1,312 @@ +#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; + int64_t prev_ticks; + int active; + uint32_t *frame; + uint32_t *fpos; + uint32_t fsize; +} SpiceVoiceOut; + +typedef struct SpiceVoiceIn { + HWVoiceIn hw; + SpiceRecordInstance sin; + int64_t prev_ticks; + int active; + uint32_t samples[LINE_IN_SAMPLES]; +} SpiceVoiceIn; + +static const 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 const 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 &spice_audio_init; +} + +static void spice_audio_fini(void *opaque) +{ + /* nothing */ +} + +static int calculate_samples(struct audio_pcm_info *info, int64_t *old_ticks) +{ + int64_t now; + int64_t ticks; + int64_t bytes; + int samples; + + now = qemu_get_clock (vm_clock); + ticks = now - *old_ticks; + *old_ticks = now; + bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ()); + bytes = audio_MIN (bytes, INT_MAX); + samples = bytes >> info->shift; + return samples; +} + +/* 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; + + if (!live) { + return 0; + } + + decr = calculate_samples(&hw->info, &out->prev_ticks); + 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 = qemu_get_clock (vm_clock); + 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 delta_samp; + uint32_t *samples; + + if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in(hw))) { + return 0; + } + + delta_samp = calculate_samples(&hw->info, &in->prev_ticks); + 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 = qemu_get_clock (vm_clock); + 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, + .max_voices_out = 1, + .max_voices_in = 1, + .voice_size_out = sizeof(SpiceVoiceOut), + .voice_size_in = sizeof(SpiceVoiceIn), +}; + +void qemu_spice_audio_init(void) +{ + spice_audio_driver.can_be_default = 1; +} diff --git a/qemu-spice.h b/qemu-spice.h index f06100474..6f19ba731 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_audio_init(void); void qemu_spice_display_init(DisplayState *ds); #else /* CONFIG_SPICE */ diff --git a/spice.c b/spice.c index f24961b98..bc2f1686b 100644 --- a/spice.c +++ b/spice.c @@ -274,6 +274,7 @@ void qemu_spice_init(void) using_spice = 1; qemu_spice_input_init(); + qemu_spice_audio_init(); qemu_free(x509_key_file); qemu_free(x509_cert_file); -- cgit v1.2.3 From efb5206cc9c3b892196256b2a30b64d1f3193df7 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 20 Apr 2010 13:33:54 +0200 Subject: spice: add virtio-serial based vdi port backend. Adds the spicevmc device. This is a communication channel between the spice client and the guest. It is used to send display information and mouse events from the spice clients to the guest. --- Makefile.target | 1 + hw/spice-vmc.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 hw/spice-vmc.c diff --git a/Makefile.target b/Makefile.target index acebac10a..0fa37446d 100644 --- a/Makefile.target +++ b/Makefile.target @@ -199,6 +199,7 @@ obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += debugcon.o multiboot.o obj-i386-y += pc_piix.o obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o +obj-i386-$(CONFIG_SPICE) += spice-vmc.o # shared objects obj-ppc-y = ppc.o diff --git a/hw/spice-vmc.c b/hw/spice-vmc.c new file mode 100644 index 000000000..2ddecf406 --- /dev/null +++ b/hw/spice-vmc.c @@ -0,0 +1,223 @@ +/* + + Spice Virtual Machine Channel (VMC). + + A virtio-serial port used for spice to guest communication, over + which spice client and a daemon in the guest operating system + communicate. + + Replaces the old vdi_port PCI device. + +*/ + +#include +#include +#include +#include + +#include "virtio-serial.h" +#include "qemu-spice.h" + +#define VMC_GUEST_DEVICE_NAME "com.redhat.spice.0" +#define VMC_DEVICE_NAME "spicevmc" + +/* windows guest driver bug workaround */ +#define VMC_MAX_HOST_WRITE 2048 + +#define dprintf(_svc, _level, _fmt, ...) \ + do { \ + static unsigned __dprintf_counter = 0; \ + if (_svc->debug >= _level) { \ + fprintf(stderr, "svc: %3d: " _fmt, ++__dprintf_counter, ## __VA_ARGS__);\ + } \ + } while (0) + +typedef struct SpiceVirtualChannel { + VirtIOSerialPort port; + VMChangeStateEntry *vmstate; + SpiceVDIPortInstance sin; + bool active; + uint8_t *buffer; + uint8_t *datapos; + ssize_t bufsize, datalen; + uint32_t debug; +} SpiceVirtualChannel; + +static int vmc_write(SpiceVDIPortInstance *sin, const uint8_t *buf, int len) +{ + SpiceVirtualChannel *svc = container_of(sin, SpiceVirtualChannel, sin); + ssize_t out = 0; + ssize_t last_out; + uint8_t* p = (uint8_t*)buf; + + while (len > 0) { + last_out = virtio_serial_write(&svc->port, p, + MIN(len, VMC_MAX_HOST_WRITE)); + if (last_out > 0) { + out += last_out; + len -= last_out; + p += last_out; + } else { + break; + } + } + + dprintf(svc, 3, "%s: %lu/%zd\n", __func__, out, len + out); + return out; +} + +static int vmc_read(SpiceVDIPortInstance *sin, uint8_t *buf, int len) +{ + SpiceVirtualChannel *svc = container_of(sin, SpiceVirtualChannel, sin); + int bytes = MIN(len, svc->datalen); + + dprintf(svc, 2, "%s: %p %d/%d/%zd\n", __func__, svc->datapos, len, bytes, svc->datalen); + if (bytes > 0) { + memcpy(buf, svc->datapos, bytes); + svc->datapos += bytes; + svc->datalen -= bytes; + assert(svc->datalen >= 0); + if (svc->datalen == 0) { + svc->datapos = 0; + virtio_serial_throttle_port(&svc->port, false); + // ^^^ !!! may call vmc_have_data, so don't touch svc after it! + } + } + return bytes; +} + +static SpiceVDIPortInterface vmc_interface = { + .base.type = SPICE_INTERFACE_VDI_PORT, + .base.description = "spice virtual channel vdi port", + .base.major_version = SPICE_INTERFACE_VDI_PORT_MAJOR, + .base.minor_version = SPICE_INTERFACE_VDI_PORT_MINOR, + .write = vmc_write, + .read = vmc_read, +}; + +static void vmc_register_interface(SpiceVirtualChannel *svc) +{ + if (svc->active) { + return; + } + dprintf(svc, 1, "%s\n", __func__); + svc->sin.base.sif = &vmc_interface.base; + spice_server_add_interface(spice_server, &svc->sin.base); + svc->active = true; +} + +static void vmc_unregister_interface(SpiceVirtualChannel *svc) +{ + if (!svc->active) { + return; + } + dprintf(svc, 1, "%s\n", __func__); + spice_server_remove_interface(&svc->sin.base); + svc->active = false; +} + + +static void vmc_change_state_handler(void *opaque, int running, int reason) +{ + SpiceVirtualChannel *svc = opaque; + + if (running && svc->active) { + spice_server_vdi_port_wakeup(&svc->sin); + } +} + +/* + * virtio-serial callbacks + */ + +static void vmc_guest_open(VirtIOSerialPort *port) +{ + SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port); + + dprintf(svc, 1, "%s\n", __func__); + vmc_register_interface(svc); +} + +static void vmc_guest_close(VirtIOSerialPort *port) +{ + SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port); + + dprintf(svc, 1, "%s\n", __func__); + vmc_unregister_interface(svc); +} + +static void vmc_guest_ready(VirtIOSerialPort *port) +{ + SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port); + + dprintf(svc, 1, "%s\n", __func__); + if (svc->active) + spice_server_vdi_port_wakeup(&svc->sin); +} + +static void vmc_have_data(VirtIOSerialPort *port, const uint8_t *buf, size_t len) +{ + SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port); + + dprintf(svc, 2, "%s: %zd\n", __func__, len); + assert(svc->datalen == 0); + if (svc->bufsize < len) { + svc->bufsize = len; + svc->buffer = qemu_realloc(svc->buffer, svc->bufsize); + } + memcpy(svc->buffer, buf, len); + svc->datapos = svc->buffer; + svc->datalen = len; + virtio_serial_throttle_port(&svc->port, true); + spice_server_vdi_port_wakeup(&svc->sin); +} + +static int vmc_initfn(VirtIOSerialDevice *dev) +{ + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port); + + if (!using_spice) + return -1; + + dprintf(svc, 1, "%s\n", __func__); + port->name = qemu_strdup(VMC_GUEST_DEVICE_NAME); + svc->vmstate = qemu_add_vm_change_state_handler( + vmc_change_state_handler, svc); + virtio_serial_open(port); + return 0; +} + +static int vmc_exitfn(VirtIOSerialDevice *dev) +{ + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + SpiceVirtualChannel *svc = DO_UPCAST(SpiceVirtualChannel, port, port); + + dprintf(svc, 1, "%s\n", __func__); + vmc_unregister_interface(svc); + qemu_del_vm_change_state_handler(svc->vmstate); + virtio_serial_close(port); + return 0; +} + +static VirtIOSerialPortInfo vmc_info = { + .qdev.name = VMC_DEVICE_NAME, + .qdev.size = sizeof(SpiceVirtualChannel), + .init = vmc_initfn, + .exit = vmc_exitfn, + .guest_open = vmc_guest_open, + .guest_close = vmc_guest_close, + .guest_ready = vmc_guest_ready, + .have_data = vmc_have_data, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("nr", SpiceVirtualChannel, port.id, VIRTIO_CONSOLE_BAD_ID), + DEFINE_PROP_UINT32("debug", SpiceVirtualChannel, debug, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void vmc_register(void) +{ + virtio_serial_port_qdev_register(&vmc_info); +} +device_init(vmc_register) -- cgit v1.2.3 From da17e174fdcb0ca4e838e2664e7cc41376c8adb0 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 12 Mar 2010 16:26:18 +0100 Subject: spice: add pci vdi port backend (obsolete). This is *not* intended to be merged upstream. It is just here because the virtio-serial windows guest drivers are not ready, so you can't go with the new spice-vmc yet. --- Makefile.target | 2 +- hw/spice-vdi.c | 556 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 hw/spice-vdi.c diff --git a/Makefile.target b/Makefile.target index 0fa37446d..2caebaf38 100644 --- a/Makefile.target +++ b/Makefile.target @@ -199,7 +199,7 @@ obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += debugcon.o multiboot.o obj-i386-y += pc_piix.o obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o -obj-i386-$(CONFIG_SPICE) += spice-vmc.o +obj-i386-$(CONFIG_SPICE) += spice-vmc.o spice-vdi.o # shared objects obj-ppc-y = ppc.o diff --git a/hw/spice-vdi.c b/hw/spice-vdi.c new file mode 100644 index 000000000..23cbbe16e --- /dev/null +++ b/hw/spice-vdi.c @@ -0,0 +1,556 @@ +#include +#include + +#include "qemu-common.h" +#include "qemu-spice.h" +#include "hw/hw.h" +#include "hw/pc.h" +#include "hw/pci.h" +#include "console.h" +#include "hw/vga_int.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "console.h" +#include "pci.h" +#include "hw.h" +#include "cpu-common.h" + +#include +#include +#include +#include + +#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; \ + } + + +#undef ALIGN +#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1)) + +#define REDHAT_PCI_VENDOR_ID 0x1b36 +#define VDI_PORT_DEVICE_ID 0x0105 +#define VDI_PORT_REVISION 0x01 + +#define VDI_PORT_INTERRUPT (1 << 0) + +#define VDI_PORT_MAGIC (*(uint32_t*)"VDIP") + +#define VDI_PORT_DEV_NAME "vdi_port" +#define VDI_PORT_SAVE_VERSION 20 + +#include + +typedef struct SPICE_ATTR_PACKED VDIPortPacket { + uint32_t gen; + uint32_t size; + uint8_t data[512 - 2 * sizeof(uint32_t)]; +} VDIPortPacket; + +SPICE_RING_DECLARE(VDIPortRing, VDIPortPacket, 32); + +enum { + VDI_PORT_IO_RANGE_INDEX, + VDI_PORT_RAM_RANGE_INDEX, +}; + +enum { + VDI_PORT_IO_CONNECTION, + VDI_PORT_IO_NOTIFY = 4, + VDI_PORT_IO_UPDATE_IRQ = 8, + + VDI_PORT_IO_RANGE_SIZE = 12 +}; + +typedef struct SPICE_ATTR_PACKED VDIPortRam { + uint32_t magic; + uint32_t generation; + uint32_t int_pending; + uint32_t int_mask; + VDIPortRing input; + VDIPortRing output; + uint32_t reserv[32]; +} VDIPortRam; + +#include + +typedef struct PCIVDIPortDevice { + PCIDevice pci_dev; + uint32_t io_base; + uint64_t ram_offset; + uint32_t ram_size; + VDIPortRam *ram; + uint32_t connected; + int running; + int new_gen_on_resume; + int active_interface; + SpiceVDIPortInstance sin; + int plug_read_pos; +} PCIVDIPortDevice; + +static int debug = 1; + +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 inline uint32_t atomic_exchange(uint32_t val, uint32_t *ptr) +{ + __asm__ __volatile__("xchgl %0, %1" : "+q"(val), "+m" (*ptr) : : "memory"); + return val; +} + +static void set_dirty(void *base, ram_addr_t offset, void *start, uint32_t length) +{ + assert(start >= base); + + ram_addr_t addr = (ram_addr_t)((uint8_t*)start - (uint8_t*)base) + offset; + ram_addr_t end = ALIGN(addr + length, TARGET_PAGE_SIZE); + + do { + cpu_physical_memory_set_dirty(addr); + addr += TARGET_PAGE_SIZE; + } while ( addr < end ); +} + +static inline void vdi_port_set_dirty(PCIVDIPortDevice *d, void *start, uint32_t length) +{ + set_dirty(d->ram, d->ram_offset, start, length); +} + +static void vdi_port_new_gen(PCIVDIPortDevice *d) +{ + d->ram->generation = (d->ram->generation + 1 == 0) ? 1 : d->ram->generation + 1; + vdi_port_set_dirty(d, &d->ram->generation, sizeof(d->ram->generation)); +} + +static int vdi_port_irq_level(PCIVDIPortDevice *d) +{ + return !!(d->ram->int_pending & d->ram->int_mask); +} + +static void vdi_port_notify_guest(PCIVDIPortDevice *d) +{ + uint32_t events = VDI_PORT_INTERRUPT; + uint32_t old_pending; + + if (!d->connected) { + return; + } + old_pending = __sync_fetch_and_or(&d->ram->int_pending, events); + if ((old_pending & events) == events) { + return; + } + qemu_set_irq(d->pci_dev.irq[0], vdi_port_irq_level(d)); + vdi_port_set_dirty(d, &d->ram->int_pending, sizeof(d->ram->int_pending)); +} + +static int vdi_port_interface_write(SpiceVDIPortInstance *sin, + const uint8_t *buf, int len) +{ + PCIVDIPortDevice *d = container_of(sin, PCIVDIPortDevice, sin); + VDIPortRing *ring = &d->ram->output; + int do_notify = false; + int actual_write = 0; + int l = len; + + if (!d->running) { + return 0; + } + + while (len) { + VDIPortPacket *packet; + int notify; + int wait; + + SPICE_RING_PROD_WAIT(ring, wait); + if (wait) { + break; + } + + SPICE_RING_PROD_ITEM(ring, packet); + packet->gen = d->ram->generation; + packet->size = MIN(len, sizeof(packet->data)); + memcpy(packet->data, buf, packet->size); + vdi_port_set_dirty(d, packet, sizeof(*packet) - (sizeof(packet->data) - packet->size)); + + SPICE_RING_PUSH(ring, notify); + do_notify = do_notify || notify; + len -= packet->size; + buf += packet->size; + actual_write += packet->size; + } + vdi_port_set_dirty(d, ring, sizeof(*ring) - sizeof(ring->items)); + + if (do_notify) { + vdi_port_notify_guest(d); + } + if (debug > 1) { + fprintf(stderr, "%s: %d/%d\n", __FUNCTION__, actual_write, l); + } + return actual_write; +} + +static int vdi_port_interface_read(SpiceVDIPortInstance *sin, + uint8_t *buf, int len) +{ + PCIVDIPortDevice *d = container_of(sin, PCIVDIPortDevice, sin); + VDIPortRing *ring = &d->ram->input; + uint32_t gen = d->ram->generation; + VDIPortPacket *packet; + int do_notify = false; + int actual_read = 0; + int l = len; + + if (!d->running) { + return 0; + } + + while (!SPICE_RING_IS_EMPTY(ring)) { + int notify; + + SPICE_RING_CONS_ITEM(ring, packet); + if (packet->gen == gen) { + break; + } + SPICE_RING_POP(ring, notify); + do_notify = do_notify || notify; + } + while (len) { + VDIPortPacket *packet; + int wait; + int now; + + SPICE_RING_CONS_WAIT(ring, wait); + + if (wait) { + break; + } + + SPICE_RING_CONS_ITEM(ring, packet); + if (packet->size > sizeof(packet->data)) { + vdi_port_set_dirty(d, ring, sizeof(*ring) - sizeof(ring->items)); + printf("%s: bad packet size\n", __FUNCTION__); + return 0; + } + now = MIN(len, packet->size - d->plug_read_pos); + memcpy(buf, packet->data + d->plug_read_pos, now); + len -= now; + buf += now; + actual_read += now; + if ((d->plug_read_pos += now) == packet->size) { + int notify; + + d->plug_read_pos = 0; + SPICE_RING_POP(ring, notify); + do_notify = do_notify || notify; + } + } + vdi_port_set_dirty(d, ring, sizeof(*ring) - sizeof(ring->items)); + + if (do_notify) { + vdi_port_notify_guest(d); + } + if (debug > 1) { + fprintf(stderr, "%s: %d/%d\n", __FUNCTION__, actual_read, l); + } + return actual_read; +} + +static SpiceVDIPortInterface vdi_port_interface = { + .base.type = SPICE_INTERFACE_VDI_PORT, + .base.description = "vdi port", + .base.major_version = SPICE_INTERFACE_VDI_PORT_MAJOR, + .base.minor_version = SPICE_INTERFACE_VDI_PORT_MINOR, + + .write = vdi_port_interface_write, + .read = vdi_port_interface_read, +}; + +static void vdi_port_register_interface(PCIVDIPortDevice *d) +{ + if (d->active_interface ) { + return; + } + + if (debug) { + fprintf(stderr, "%s\n", __FUNCTION__); + } + d->sin.base.sif = &vdi_port_interface.base; + spice_server_add_interface(spice_server, &d->sin.base); + d->active_interface = true; +} + +static void vdi_port_unregister_interface(PCIVDIPortDevice *d) +{ + if (!d->active_interface ) { + return; + } + if (debug) { + fprintf(stderr, "%s\n", __FUNCTION__); + } + spice_server_remove_interface(&d->sin.base); + d->active_interface = false; +} + +static uint32_t vdi_port_dev_connect(PCIVDIPortDevice *d) +{ + if (d->connected) { + if (debug) { + fprintf(stderr, "%s: already connected\n", __FUNCTION__); + } + return 0; + } + vdi_port_new_gen(d); + d->connected = true; + vdi_port_register_interface(d); + return d->ram->generation; +} + +static void vdi_port_dev_disconnect(PCIVDIPortDevice *d) +{ + if (!d->connected) { + if (debug) { + fprintf(stderr, "%s: not connected\n", __FUNCTION__); + } + return; + } + d->connected = false; + vdi_port_unregister_interface(d); +} + +static void vdi_port_dev_notify(PCIVDIPortDevice *d) +{ + spice_server_vdi_port_wakeup(&d->sin); +} + +static void vdi_port_write_dword(void *opaque, uint32_t addr, uint32_t val) +{ + PCIVDIPortDevice *d = opaque; + uint32_t io_port = addr - d->io_base; + + if (debug > 1) { + fprintf(stderr, "%s: addr 0x%x val 0x%x\n", __FUNCTION__, addr, val); + } + switch (io_port) { + case VDI_PORT_IO_NOTIFY: + if (!d->connected) { + fprintf(stderr, "%s: not connected\n", __FUNCTION__); + return; + } + vdi_port_dev_notify(d); + break; + case VDI_PORT_IO_UPDATE_IRQ: + qemu_set_irq(d->pci_dev.irq[0], vdi_port_irq_level(d)); + break; + case VDI_PORT_IO_CONNECTION: + vdi_port_dev_disconnect(d); + break; + default: + if (debug) { + fprintf(stderr, "%s: unexpected addr 0x%x val 0x%x\n", + __FUNCTION__, addr, val); + } + }; +} + +static uint32_t vdi_port_read_dword(void *opaque, uint32_t addr) +{ + PCIVDIPortDevice *d = opaque; + uint32_t io_port = addr - d->io_base; + + if (debug > 1) { + fprintf(stderr, "%s: addr 0x%x\n", __FUNCTION__, addr); + } + if (io_port == VDI_PORT_IO_CONNECTION) { + return vdi_port_dev_connect(d); + } else { + fprintf(stderr, "%s: unexpected addr 0x%x\n", __FUNCTION__, addr); + } + return 0xffffffff; +} + +static void vdi_port_io_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIVDIPortDevice *d = DO_UPCAST(PCIVDIPortDevice, pci_dev, pci_dev); + + if (debug) { + fprintf(stderr, "%s: base 0x%lx size 0x%lx\n", __FUNCTION__, addr, size); + } + d->io_base = addr; + register_ioport_write(addr, size, 4, vdi_port_write_dword, pci_dev); + register_ioport_read(addr, size, 4, vdi_port_read_dword, pci_dev); +} + +static void vdi_port_ram_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIVDIPortDevice *d = DO_UPCAST(PCIVDIPortDevice, pci_dev, pci_dev); + + if (debug) { + fprintf(stderr, "%s: addr 0x%lx size 0x%lx\n", __FUNCTION__, addr, size); + } + + assert((addr & (size - 1)) == 0); + assert(size == d->ram_size); + + cpu_register_physical_memory(addr, size, d->ram_offset | IO_MEM_RAM); +} + +static void vdi_port_reset(PCIVDIPortDevice *d) +{ + memset(d->ram, 0, sizeof(*d->ram)); + SPICE_RING_INIT(&d->ram->input); + SPICE_RING_INIT(&d->ram->output); + d->ram->magic = VDI_PORT_MAGIC; + d->ram->generation = 0; + d->ram->int_pending = 0; + d->ram->int_mask = 0; + d->connected = false; + d->plug_read_pos = 0; + vdi_port_set_dirty(d, d->ram, sizeof(*d->ram)); +} + +static void vdi_port_reset_handler(DeviceState *dev) +{ + PCIVDIPortDevice *d = DO_UPCAST(PCIVDIPortDevice, pci_dev.qdev, dev); + + if (d->connected) { + vdi_port_dev_disconnect(d); + } + + vdi_port_reset(d); + qemu_set_irq(d->pci_dev.irq[0], vdi_port_irq_level(d)); +} + +static int vdi_port_pre_load(void* opaque) +{ + PCIVDIPortDevice* d = opaque; + + vdi_port_unregister_interface(d); + return 0; +} + +static int vdi_port_post_load(void* opaque,int version_id) +{ + PCIVDIPortDevice* d = opaque; + + if (d->connected) { + vdi_port_register_interface(d); + } + return 0; +} + +static void vdi_port_vm_change_state_handler(void *opaque, int running, int reason) +{ + PCIVDIPortDevice* d = opaque; + + if (running) { + d->running = true; + if (d->new_gen_on_resume) { + d->new_gen_on_resume = false; + vdi_port_new_gen(d); + vdi_port_notify_guest(d); + } + qemu_set_irq(d->pci_dev.irq[0], vdi_port_irq_level(d)); + vdi_port_dev_notify(d); + } else { + d->running = false; + } +} + +static int vdi_port_init(PCIDevice *dev) +{ + PCIVDIPortDevice *vdi = (PCIVDIPortDevice *)dev; + uint8_t* config = vdi->pci_dev.config; + uint32_t ram_size = msb_mask(sizeof(VDIPortRam) * 2 - 1); + + vdi->ram_offset = qemu_ram_alloc(&vdi->pci_dev.qdev, "bar1", ram_size); + vdi->ram = qemu_get_ram_ptr(vdi->ram_offset); + vdi_port_reset(vdi); + vdi->ram_size = ram_size; + vdi->new_gen_on_resume = false; + vdi->running = false; + + pci_config_set_vendor_id(config, REDHAT_PCI_VENDOR_ID); + pci_config_set_device_id(config, VDI_PORT_DEVICE_ID); + pci_config_set_class(config, PCI_CLASS_COMMUNICATION_OTHER); + pci_set_byte(&config[PCI_REVISION_ID], VDI_PORT_REVISION); + pci_set_byte(&config[PCI_INTERRUPT_PIN], 1); + + pci_register_bar(dev, VDI_PORT_IO_RANGE_INDEX, + msb_mask(VDI_PORT_IO_RANGE_SIZE * 2 - 1), + PCI_BASE_ADDRESS_SPACE_IO, vdi_port_io_map); + + pci_register_bar(dev, VDI_PORT_RAM_RANGE_INDEX, + vdi->ram_size , PCI_BASE_ADDRESS_SPACE_MEMORY, + vdi_port_ram_map); + + qemu_add_vm_change_state_handler(vdi_port_vm_change_state_handler, vdi); + + return 0; +} + +static VMStateDescription vdi_port_vmstate = { + .name = VDI_PORT_DEV_NAME, + .version_id = VDI_PORT_SAVE_VERSION, + .minimum_version_id = VDI_PORT_SAVE_VERSION, + .pre_load = vdi_port_pre_load, + .post_load = vdi_port_post_load, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(pci_dev, PCIVDIPortDevice), + VMSTATE_UINT32(connected, PCIVDIPortDevice), + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo vdi_port_info = { + .qdev.name = VDI_PORT_DEV_NAME, + .qdev.desc = "spice virtual desktop port (obsolete)", + .qdev.size = sizeof(PCIVDIPortDevice), + .qdev.vmsd = &vdi_port_vmstate, + .qdev.reset = vdi_port_reset_handler, + + .init = vdi_port_init, +}; + +static void vdi_port_register(void) +{ + pci_qdev_register(&vdi_port_info); +} + +device_init(vdi_port_register); -- cgit v1.2.3 From 1b4ac7632a8337526555b91a9148ebfa5928c88a Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 14 Jun 2010 09:53:48 +0200 Subject: [ElectricFence] use memalign instead of posix_memalign --- osdep.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osdep.c b/osdep.c index dbf872aea..14323b7ee 100644 --- a/osdep.c +++ b/osdep.c @@ -100,7 +100,12 @@ void *qemu_memalign(size_t alignment, size_t size) #if defined(_POSIX_C_SOURCE) && !defined(__sun__) int ret; void *ptr; +#if 0 ret = posix_memalign(&ptr, alignment, size); +#else + ptr = memalign(alignment, size); + ret = (ptr == NULL) ? -1 : 0; +#endif if (ret != 0) { fprintf(stderr, "Failed to allocate %zu B: %s\n", size, strerror(ret)); -- cgit v1.2.3 From 91db0417ac64255a297d88543bc91b439a48c013 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 23 Apr 2010 13:28:21 +0200 Subject: spice: live migration (wip). Handle spice client migration, i.e. inform a spice client connected about the new host and connection parameters, so it can move over the connection automatically. --- monitor.c | 1 + qemu-monitor.hx | 11 ++++++++ qemu-spice.h | 2 ++ spice.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/monitor.c b/monitor.c index 5366c3652..0c5a5e0b7 100644 --- a/monitor.c +++ b/monitor.c @@ -56,6 +56,7 @@ #include "json-parser.h" #include "osdep.h" #include "exec-all.h" +#include "qemu-spice.h" //#define DEBUG //#define DEBUG_COMPLETION diff --git a/qemu-monitor.hx b/qemu-monitor.hx index 2af3de6c2..f8c38a9f6 100644 --- a/qemu-monitor.hx +++ b/qemu-monitor.hx @@ -2497,6 +2497,17 @@ ETEXI HXCOMM DO NOT add new commands after 'info', move your addition before it! +#if defined(CONFIG_SPICE) + { + .name = "spice_migrate_info", + .args_type = "hostname:s,port:i?,tls-port:i?,cert-subject:s?", + .params = "hostname port tls-port cert-subject", + .help = "send migration info to spice client", + .user_print = monitor_user_noop, + .mhandler.cmd_new = mon_spice_migrate, + }, +#endif + STEXI @end table ETEXI diff --git a/qemu-spice.h b/qemu-spice.h index 6f19ba731..3c8e959bf 100644 --- a/qemu-spice.h +++ b/qemu-spice.h @@ -16,6 +16,8 @@ void qemu_spice_input_init(void); void qemu_spice_audio_init(void); void qemu_spice_display_init(DisplayState *ds); +int mon_spice_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data); + #else /* CONFIG_SPICE */ #define using_spice 0 diff --git a/spice.c b/spice.c index bc2f1686b..7bbb52fae 100644 --- a/spice.c +++ b/spice.c @@ -11,6 +11,7 @@ #include "qemu-queue.h" #include "qemu-x509.h" #include "monitor.h" +#include "hw/hw.h" /* core bits */ @@ -172,8 +173,90 @@ static const char *wan_compression_names[] = { parse_name(_name, "wan compression", \ wan_compression_names, ARRAY_SIZE(wan_compression_names)) +/* handle client migration */ + +static int spice_live(Monitor *mon, QEMUFile *f, int stage, void *opaque) +{ + static int last_stage; + static int migrate_client, client_connected; + int ret = 1; + + if (last_stage != stage) { + last_stage = stage; + fprintf(stderr, "%s: stage %d\n", __FUNCTION__, stage); + } else { + fprintf(stderr, "."); + } + + switch (stage) { + case 1: + migrate_client = 1; + client_connected = 0; + fprintf(stderr, "%s: start client migration\n", __FUNCTION__); + if (spice_server_migrate_start(spice_server) != 0) { + fprintf(stderr, "%s: fail -> no client migration\n", __FUNCTION__); + migrate_client = 0; + } + break; + case 2: + if (!migrate_client) + break; + switch (spice_server_migrate_client_state(spice_server)) { + case SPICE_MIGRATE_CLIENT_NONE: + fprintf(stderr, "%s: no client connected\n", __FUNCTION__); + migrate_client = 0; + break; + case SPICE_MIGRATE_CLIENT_WAITING: + ret = 0; + break; + case SPICE_MIGRATE_CLIENT_READY: + if (!client_connected) { + fprintf(stderr, "%s: client connected to target\n", __FUNCTION__); + client_connected = 1; + } + break; + } + break; + case 3: + if (migrate_client && client_connected) { + fprintf(stderr, "%s: finish client migration\n", __FUNCTION__); + spice_server_migrate_end(spice_server, 1); + } + break; + } + return ret; +} + +static void spice_save(QEMUFile *f, void *opaque) +{ + fprintf(stderr, "%s:\n", __FUNCTION__); +} + +static int spice_load(QEMUFile *f, void *opaque, int version_id) +{ + fprintf(stderr, "%s:\n", __FUNCTION__); + return 0; +} + /* functions for the rest of qemu */ +int mon_spice_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *hostname = qdict_get_str(qdict, "hostname"); + const char *subject = qdict_get_try_str(qdict, "cert-subject"); + int port = qdict_get_try_int(qdict, "port", -1); + int tls_port = qdict_get_try_int(qdict, "tls-port", -1); + + if (!spice_server) { + qerror_report(QERR_DEVICE_NOT_ACTIVE, "spice"); + return -1; + } + + /* TODO: Convert to QError */ + return spice_server_migrate_info(spice_server, hostname, + port, tls_port, subject); +} + void qemu_spice_init(void) { QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); @@ -276,6 +359,10 @@ void qemu_spice_init(void) qemu_spice_input_init(); qemu_spice_audio_init(); + register_savevm_live(NULL, "spice", -1, 1, NULL, + spice_live, spice_save, spice_load, + spice_server); + qemu_free(x509_key_file); qemu_free(x509_cert_file); qemu_free(x509_cacert_file); -- cgit v1.2.3