From patchwork Fri Aug 24 19:14:35 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Erlon Cruz X-Patchwork-Id: 179886 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 637A22C0102 for ; Sat, 25 Aug 2012 05:16:05 +1000 (EST) Received: from localhost ([::1]:38432 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T4zMR-0005iZ-DJ for incoming@patchwork.ozlabs.org; Fri, 24 Aug 2012 15:16:03 -0400 Received: from eggs.gnu.org ([208.118.235.92]:34324) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T4zLo-0005Ac-3K for qemu-devel@nongnu.org; Fri, 24 Aug 2012 15:15:37 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1T4zLa-0002W8-Kk for qemu-devel@nongnu.org; Fri, 24 Aug 2012 15:15:22 -0400 Received: from mail-gh0-f173.google.com ([209.85.160.173]:42606) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T4zLZ-0002FT-Mu for qemu-devel@nongnu.org; Fri, 24 Aug 2012 15:15:10 -0400 Received: by mail-gh0-f173.google.com with SMTP id r17so496486ghr.4 for ; Fri, 24 Aug 2012 12:15:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:date:message-id:x-mailer:in-reply-to :references:mime-version:content-type:content-transfer-encoding; bh=z+OPZi5haxz1Gmt2zbRBUT5QyLLLVKAxpG6BVWapFaQ=; b=ibF4eEtPItn6+y6LyEOXO9g8P6jYI96JGIZwe6y9PYrumohOF5fghGDefGwrkTjjc0 9SovU2JuZcbiZTuBcYampZ1Mq/4eX6kv7OczG6UbTw+1qSW059F3xAJGK2NebiHAqLXx vwXSVFIynKYoCypI+2yjHnUIvAWy6rJI/UYCv3npgT/SUd+GlZJ5gKlVmDyjJqazERtB HrVTul2kzMSZxnmX9O8qrR2KK+B7UlXNh5+Ae1+BNa92l0aI75gulxqpIyVoAaI6t4Op kUV3e1jDN4dci0k2eB1K60f/gGt9XLhTAQOH3ICkwdkz0REj6awVSIwaKMEVd6w4PqaP ms1Q== Received: by 10.236.176.71 with SMTP id a47mr5550415yhm.43.1345835707246; Fri, 24 Aug 2012 12:15:07 -0700 (PDT) Received: from localhost.localdomain ([143.106.167.234]) by mx.google.com with ESMTPS id l1sm20244532yhm.19.2012.08.24.12.15.02 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 24 Aug 2012 12:15:06 -0700 (PDT) From: Erlon Cruz To: qemu-devel@nongnu.org Date: Fri, 24 Aug 2012 16:14:35 -0300 Message-Id: <1345835677-23371-3-git-send-email-erlon.cruz@br.flextronics.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1345835677-23371-1-git-send-email-erlon.cruz@br.flextronics.com> References: <1345835677-23371-1-git-send-email-erlon.cruz@br.flextronics.com> MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.160.173 Cc: "Erlon R. Cruz" , =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= , =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= , "Rafael F. Santos" , alevy@redhat.com Subject: [Qemu-devel] [PATCH 2/4] qxl: split qxl functions in common and pci files X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Fabiano FidĂȘncio This commit splits qxl functions into common functions (located in qxl.c) and pci-specific functions (located in qxl-pci.c). All prototypes are being kept in qxl.h, as common MACROS and inline functions. Moreover, this commit is exposing a lot of APIs, don't know if it's the correct way to do it, but it was the only way that we saw to do it. Signed-off-by: Erlon R. Cruz Signed-off-by: Fabiano FidĂȘncio Signed-off-by: Rafael F. Santos --- hw/i386/Makefile.objs | 2 +- hw/qxl-pci.c | 1607 +++++++++++++++++++++++++++++++++++++++++++++ hw/qxl.c | 1733 +------------------------------------------------ hw/qxl.h | 120 ++++ 4 files changed, 1763 insertions(+), 1699 deletions(-) create mode 100644 hw/qxl-pci.c diff --git a/hw/i386/Makefile.objs b/hw/i386/Makefile.objs index 8c764bb..1953e7f 100644 --- a/hw/i386/Makefile.objs +++ b/hw/i386/Makefile.objs @@ -10,6 +10,6 @@ obj-$(CONFIG_XEN) += xen_platform.o xen_apic.o obj-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen-host-pci-device.o obj-$(CONFIG_XEN_PCI_PASSTHROUGH) += xen_pt.o xen_pt_config_init.o xen_pt_msi.o obj-y += kvm/ -obj-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o +obj-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o qxl-pci.o obj-y := $(addprefix ../,$(obj-y)) diff --git a/hw/qxl-pci.c b/hw/qxl-pci.c new file mode 100644 index 0000000..6b4ec45 --- /dev/null +++ b/hw/qxl-pci.c @@ -0,0 +1,1607 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Yaniv Kamay, Izik Eidus, Gerd Hoffmann + * maintained by Gerd Hoffmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "qemu-queue.h" +#include "monitor.h" +#include "sysemu.h" +#include "trace.h" + +#include "qxl.h" + +static QXLDevice *qxl0; + +void qxl_set_guest_bug(QXLDevice *qxl, const char *msg, ...) +{ + qxl_send_events(qxl, QXL_INTERRUPT_ERROR); + qxl->pci.guest_bug = 1; + if (qxl->pci.guestdebug) { + va_list ap; + va_start(ap, msg); + fprintf(stderr, "qxl-%d: guest bug: ", qxl->id); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + va_end(ap); + } +} + +static void qxl_clear_guest_bug(QXLDevice *qxl) +{ + qxl->pci.guest_bug = 0; +} + +static void qxl_spice_destroy_surface_wait_complete(QXLDevice *qxl, + uint32_t id) +{ + trace_qxl_spice_destroy_surface_wait_complete(qxl->id, id); + qemu_mutex_lock(&qxl->track_lock); + qxl->guest_surfaces.cmds[id] = 0; + qxl->guest_surfaces.count--; + qemu_mutex_unlock(&qxl->track_lock); +} + +static void qxl_spice_destroy_surface_wait(QXLDevice *qxl, uint32_t id, + qxl_async_io async) +{ + QXLCookie *cookie; + + trace_qxl_spice_destroy_surface_wait(qxl->id, id, async); + if (async) { + cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_DESTROY_SURFACE_ASYNC); + cookie->u.surface_id = id; + spice_qxl_destroy_surface_async(&qxl->ssd.qxl, id, (uintptr_t)cookie); + } else { + qxl->ssd.worker->destroy_surface_wait(qxl->ssd.worker, id); + } +} + +static void qxl_spice_flush_surfaces_async(QXLDevice *qxl) +{ + trace_qxl_spice_flush_surfaces_async(qxl->id, qxl->guest_surfaces.count, + qxl->num_free_res); + spice_qxl_flush_surfaces_async(&qxl->ssd.qxl, + (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_FLUSH_SURFACES_ASYNC)); +} + +static void qxl_spice_destroy_surfaces_complete(QXLDevice *qxl) +{ + trace_qxl_spice_destroy_surfaces_complete(qxl->id); + qemu_mutex_lock(&qxl->track_lock); + memset(&qxl->guest_surfaces.cmds, 0, sizeof(qxl->guest_surfaces.cmds)); + qxl->guest_surfaces.count = 0; + qemu_mutex_unlock(&qxl->track_lock); +} + +static void qxl_spice_destroy_surfaces(QXLDevice *qxl, qxl_async_io async) +{ + trace_qxl_spice_destroy_surfaces(qxl->id, async); + if (async) { + spice_qxl_destroy_surfaces_async(&qxl->ssd.qxl, + (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_DESTROY_ALL_SURFACES_ASYNC)); + } else { + qxl->ssd.worker->destroy_surfaces(qxl->ssd.worker); + qxl_spice_destroy_surfaces_complete(qxl); + } +} + +/* can be called from spice server thread context */ +static void qxl_set_dirty(MemoryRegion *mr, ram_addr_t addr, ram_addr_t end) +{ + memory_region_set_dirty(mr, addr, end - addr); +} + +void qxl_rom_set_dirty(QXLDevice *qxl) +{ + qxl_set_dirty(&qxl->pci.rom_bar, 0, qxl->pci.rom_size); +} + +/* called from spice server thread context only */ +void qxl_ram_set_dirty(QXLDevice *qxl, void *ptr) +{ + void *base = qxl->pci.vga.vram_ptr; + intptr_t offset; + + offset = ptr - base; + offset &= ~(TARGET_PAGE_SIZE-1); + assert(offset < qxl->pci.vga.vram_size); + qxl_set_dirty(&qxl->pci.vga.vram, offset, offset + TARGET_PAGE_SIZE); +} + +/* can be called from spice server thread context */ +void qxl_ring_set_dirty(QXLDevice *qxl) +{ + ram_addr_t addr = qxl->shadow_rom.ram_header_offset; + ram_addr_t end = qxl->pci.vga.vram_size; + qxl_set_dirty(&qxl->pci.vga.vram, addr, end); +} + +/* + * keep track of some command state, for savevm/loadvm. + * called from spice server thread context only + */ +static int qxl_track_command(QXLDevice *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); + + if (!cmd) { + return 1; + } + uint32_t id = le32_to_cpu(cmd->surface_id); + + if (id >= NUM_SURFACES) { + qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE id %d >= %d", id, + NUM_SURFACES); + return 1; + } + qemu_mutex_lock(&qxl->track_lock); + 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--; + } + qemu_mutex_unlock(&qxl->track_lock); + break; + } + case QXL_CMD_CURSOR: + { + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + + if (!cmd) { + return 1; + } + if (cmd->type == QXL_CURSOR_SET) { + qemu_mutex_lock(&qxl->track_lock); + qxl->guest_cursor = ext->cmd.data; + qemu_mutex_unlock(&qxl->track_lock); + } + break; + } + } + return 0; +} + +/* spice display interface callbacks */ + +const char *qxl_mode_to_string(int mode) +{ + switch (mode) { + case QXL_MODE_COMPAT: + return "compat"; + case QXL_MODE_NATIVE: + return "native"; + case QXL_MODE_UNDEFINED: + return "undefined"; + case QXL_MODE_VGA: + return "vga"; + } + return "INVALID"; +} + +static const char *io_port_to_string(uint32_t io_port) +{ + if (io_port >= QXL_IO_RANGE_SIZE) { + return "out of range"; + } + static const char *io_port_to_string[QXL_IO_RANGE_SIZE + 1] = { + [QXL_IO_NOTIFY_CMD] = "QXL_IO_NOTIFY_CMD", + [QXL_IO_NOTIFY_CURSOR] = "QXL_IO_NOTIFY_CURSOR", + [QXL_IO_UPDATE_AREA] = "QXL_IO_UPDATE_AREA", + [QXL_IO_UPDATE_IRQ] = "QXL_IO_UPDATE_IRQ", + [QXL_IO_NOTIFY_OOM] = "QXL_IO_NOTIFY_OOM", + [QXL_IO_RESET] = "QXL_IO_RESET", + [QXL_IO_SET_MODE] = "QXL_IO_SET_MODE", + [QXL_IO_LOG] = "QXL_IO_LOG", + [QXL_IO_MEMSLOT_ADD] = "QXL_IO_MEMSLOT_ADD", + [QXL_IO_MEMSLOT_DEL] = "QXL_IO_MEMSLOT_DEL", + [QXL_IO_DETACH_PRIMARY] = "QXL_IO_DETACH_PRIMARY", + [QXL_IO_ATTACH_PRIMARY] = "QXL_IO_ATTACH_PRIMARY", + [QXL_IO_CREATE_PRIMARY] = "QXL_IO_CREATE_PRIMARY", + [QXL_IO_DESTROY_PRIMARY] = "QXL_IO_DESTROY_PRIMARY", + [QXL_IO_DESTROY_SURFACE_WAIT] = "QXL_IO_DESTROY_SURFACE_WAIT", + [QXL_IO_DESTROY_ALL_SURFACES] = "QXL_IO_DESTROY_ALL_SURFACES", + [QXL_IO_UPDATE_AREA_ASYNC] = "QXL_IO_UPDATE_AREA_ASYNC", + [QXL_IO_MEMSLOT_ADD_ASYNC] = "QXL_IO_MEMSLOT_ADD_ASYNC", + [QXL_IO_CREATE_PRIMARY_ASYNC] = "QXL_IO_CREATE_PRIMARY_ASYNC", + [QXL_IO_DESTROY_PRIMARY_ASYNC] = "QXL_IO_DESTROY_PRIMARY_ASYNC", + [QXL_IO_DESTROY_SURFACE_ASYNC] = "QXL_IO_DESTROY_SURFACE_ASYNC", + [QXL_IO_DESTROY_ALL_SURFACES_ASYNC] + = "QXL_IO_DESTROY_ALL_SURFACES_ASYNC", + [QXL_IO_FLUSH_SURFACES_ASYNC] = "QXL_IO_FLUSH_SURFACES_ASYNC", + [QXL_IO_FLUSH_RELEASE] = "QXL_IO_FLUSH_RELEASE", + }; + return io_port_to_string[io_port]; +} + +static void qxl_create_guest_primary_complete(QXLDevice *d); + +/* called from spice server thread context only */ +int interface_get_command_pci(QXLDevice *qxl, QXLCommandExt *ext) +{ + SimpleSpiceUpdate *update; + QXLCommandRing *ring; + QXLCommand *cmd; + int notify, ret; + + trace_qxl_ring_command_check(qxl->id, qxl_mode_to_string(qxl->pci.mode)); + + switch (qxl->pci.mode) { + case QXL_MODE_VGA: + ret = false; + qemu_mutex_lock(&qxl->ssd.lock); + if (qxl->ssd.update != NULL) { + update = qxl->ssd.update; + qxl->ssd.update = NULL; + *ext = update->ext; + ret = true; + } + qemu_mutex_unlock(&qxl->ssd.lock); + if (ret) { + trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->pci.mode)); + qxl_log_command(qxl, "vga", ext); + } + return ret; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + ring = &qxl->ram->cmd_ring; + if (qxl->pci.guest_bug || SPICE_RING_IS_EMPTY(ring)) { + return false; + } + SPICE_RING_CONS_ITEM(qxl, ring, cmd); + if (!cmd) { + return false; + } + 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); + trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->pci.mode)); + return true; + default: + return false; + } +} + +int interface_get_cursor_command_pci(QXLDevice *qxl, struct QXLCommandExt *ext) +{ + QXLCursorRing *ring; + QXLCommand *cmd; + int notify; + + trace_qxl_ring_cursor_check(qxl->id, qxl_mode_to_string(qxl->pci.mode)); + + switch (qxl->pci.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(qxl, ring, cmd); + if (!cmd) { + return false; + } + 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); + } + trace_qxl_ring_cursor_get(qxl->id, qxl_mode_to_string(qxl->pci.mode)); + return true; + default: + return false; + } +} + +void interface_async_complete_io(QXLDevice *qxl, QXLCookie *cookie) +{ + uint32_t current_async; + + qemu_mutex_lock(&qxl->async_lock); + current_async = qxl->current_async; + qxl->current_async = QXL_UNDEFINED_IO; + qemu_mutex_unlock(&qxl->async_lock); + + trace_qxl_interface_async_complete_io(qxl->id, current_async, cookie); + if (!cookie) { + fprintf(stderr, "qxl: %s: error, cookie is NULL\n", __func__); + return; + } + if (cookie && current_async != cookie->io) { + fprintf(stderr, + "qxl: %s: error: current_async = %d != %" + PRId64 " = cookie->io\n", __func__, current_async, cookie->io); + } + switch (current_async) { + case QXL_IO_MEMSLOT_ADD_ASYNC: + case QXL_IO_DESTROY_PRIMARY_ASYNC: + case QXL_IO_UPDATE_AREA_ASYNC: + case QXL_IO_FLUSH_SURFACES_ASYNC: + break; + case QXL_IO_CREATE_PRIMARY_ASYNC: + qxl_create_guest_primary_complete(qxl); + break; + case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: + qxl_spice_destroy_surfaces_complete(qxl); + break; + case QXL_IO_DESTROY_SURFACE_ASYNC: + qxl_spice_destroy_surface_wait_complete(qxl, cookie->u.surface_id); + break; + default: + fprintf(stderr, "qxl: %s: unexpected current_async %d\n", __func__, + current_async); + } + qxl_send_events(qxl, QXL_INTERRUPT_IO_CMD); +} + +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, + + /* the callbacks below are called from spice server thread context */ + .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, + .async_complete = interface_async_complete, + .update_area_complete = interface_update_area_complete, +}; + +static void qxl_enter_vga_mode(QXLDevice *d) +{ + if (d->pci.mode == QXL_MODE_VGA) { + return; + } + trace_qxl_enter_vga_mode(d->id); + qemu_spice_create_host_primary(&d->ssd); + d->pci.mode = QXL_MODE_VGA; + memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty)); + vga_dirty_log_start(&d->pci.vga); +} + +static void qxl_exit_vga_mode(QXLDevice *d) +{ + if (d->pci.mode != QXL_MODE_VGA) { + return; + } + trace_qxl_exit_vga_mode(d->id); + vga_dirty_log_stop(&d->pci.vga); + qxl_destroy_primary(d, QXL_SYNC); +} + +static void qxl_update_irq(QXLDevice *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.pci.irq[0], level); + qxl_ring_set_dirty(d); +} + +static void qxl_check_state(QXLDevice *d) +{ + QXLRam *ram = d->ram; + + assert(!d->ssd.running || SPICE_RING_IS_EMPTY(&ram->cmd_ring)); + assert(!d->ssd.running || SPICE_RING_IS_EMPTY(&ram->cursor_ring)); +} + +static void qxl_reset_state(QXLDevice *d) +{ + QXLRom *rom = d->rom; + + qxl_check_state(d); + 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(QXLDevice *d) +{ + trace_qxl_soft_reset(d->id); + qxl_check_state(d); + qxl_clear_guest_bug(d); + d->current_async = QXL_UNDEFINED_IO; + + if (d->id == 0) { + qxl_enter_vga_mode(d); + } else { + d->pci.mode = QXL_MODE_UNDEFINED; + } +} + +static void qxl_hard_reset(QXLDevice *d, int loadvm) +{ + trace_qxl_hard_reset(d->id, loadvm); + + qxl_spice_reset_cursor(d); + qxl_spice_reset_image_cache(d); + 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); +} + +static void qxl_reset_handler(DeviceState *dev) +{ + QXLDevice *d = (QXLDevice *) 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; + QXLDevice *qxl = (QXLDevice *) container_of(vga, PCIQXLDevice, vga); + + trace_qxl_io_write_vga(qxl->id, qxl_mode_to_string(qxl->pci.mode), addr, val); + if (qxl->pci.mode != QXL_MODE_VGA) { + qxl_destroy_primary(qxl, QXL_SYNC); + qxl_soft_reset(qxl); + } + vga_ioport_write(opaque, addr, val); +} + +static const MemoryRegionPortio qxl_vga_portio_list[] = { + { 0x04, 2, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3b4 */ + { 0x0a, 1, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3ba */ + { 0x10, 16, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3c0 */ + { 0x24, 2, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3d4 */ + { 0x2a, 1, 1, .read = vga_ioport_read, + .write = qxl_vga_ioport_write }, /* 3da */ + PORTIO_END_OF_LIST(), +}; + +static int qxl_add_memslot(QXLDevice *d, uint32_t slot_id, uint64_t delta, + qxl_async_io async) +{ + static const int regions[] = { + QXL_RAM_RANGE_INDEX, + QXL_VRAM_RANGE_INDEX, + QXL_VRAM64_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); + + trace_qxl_memslot_add_guest(d->id, slot_id, guest_start, guest_end); + + if (slot_id >= NUM_MEMSLOTS) { + qxl_set_guest_bug(d, "%s: slot_id >= NUM_MEMSLOTS %d >= %d", __func__, + slot_id, NUM_MEMSLOTS); + return 1; + } + if (guest_start > guest_end) { + qxl_set_guest_bug(d, "%s: guest_start > guest_end 0x%" PRIx64 + " > 0x%" PRIx64, __func__, guest_start, guest_end); + return 1; + } + + for (i = 0; i < ARRAY_SIZE(regions); i++) { + pci_region = regions[i]; + pci_start = d->pci.pci.io_regions[pci_region].addr; + pci_end = pci_start + d->pci.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; + } + if (i == ARRAY_SIZE(regions)) { + qxl_set_guest_bug(d, "%s: finished loop without match", __func__); + return 1; + } + + switch (pci_region) { + case QXL_RAM_RANGE_INDEX: + virt_start = (intptr_t)memory_region_get_ram_ptr(&d->pci.vga.vram); + break; + case QXL_VRAM_RANGE_INDEX: + case 4 /* vram 64bit */: + virt_start = (intptr_t)memory_region_get_ram_ptr(&d->pci.vram_bar); + break; + default: + /* should not happen */ + qxl_set_guest_bug(d, "%s: pci_region = %d", __func__, pci_region); + return 1; + } + + 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; + qxl_rom_set_dirty(d); + + qemu_spice_add_memslot(&d->ssd, &memslot, async); + 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; + return 0; +} + +static void qxl_del_memslot(QXLDevice *d, uint32_t slot_id) +{ + qemu_spice_del_memslot(&d->ssd, MEMSLOT_GROUP_HOST, slot_id); + d->guest_slots[slot_id].active = 0; +} + +void qxl_reset_memslots(QXLDevice *d) +{ + qxl_spice_reset_memslots(d); + memset(&d->guest_slots, 0, sizeof(d->guest_slots)); +} + +void qxl_reset_surfaces(QXLDevice *d) +{ + trace_qxl_reset_surfaces(d->id); + d->pci.mode = QXL_MODE_UNDEFINED; + qxl_spice_destroy_surfaces(d, QXL_SYNC); +} + +/* can be also called from spice server thread context */ +void *qxl_phys2virt(QXLDevice *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 *)(intptr_t)offset; + case MEMSLOT_GROUP_GUEST: + if (slot >= NUM_MEMSLOTS) { + qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot, + NUM_MEMSLOTS); + return NULL; + } + if (!qxl->guest_slots[slot].active) { + qxl_set_guest_bug(qxl, "inactive slot %d\n", slot); + return NULL; + } + if (offset < qxl->guest_slots[slot].delta) { + qxl_set_guest_bug(qxl, + "slot %d offset %"PRIu64" < delta %"PRIu64"\n", + slot, offset, qxl->guest_slots[slot].delta); + return NULL; + } + offset -= qxl->guest_slots[slot].delta; + if (offset > qxl->guest_slots[slot].size) { + qxl_set_guest_bug(qxl, + "slot %d offset %"PRIu64" > size %"PRIu64"\n", + slot, offset, qxl->guest_slots[slot].size); + return NULL; + } + return qxl->guest_slots[slot].ptr + offset; + } + return NULL; +} + +static void qxl_create_guest_primary_complete(QXLDevice *qxl) +{ + /* for local rendering */ + qxl_render_resize(qxl); +} + +static void qxl_create_guest_primary(QXLDevice *qxl, int loadvm, + qxl_async_io async) +{ + QXLDevSurfaceCreate surface; + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + int size; + int requested_height = le32_to_cpu(sc->height); + int requested_stride = le32_to_cpu(sc->stride); + + size = abs(requested_stride) * requested_height; + if (size > qxl->pci.vgamem_size) { + qxl_set_guest_bug(qxl, "%s: requested primary larger then framebuffer" + " size", __func__); + return; + } + + if (qxl->pci.mode == QXL_MODE_NATIVE) { + qxl_set_guest_bug(qxl, "%s: nop since already in QXL_MODE_NATIVE", + __func__); + } + qxl_exit_vga_mode(qxl); + + 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); + trace_qxl_create_guest_primary(qxl->id, sc->width, sc->height, sc->mem, + sc->format, sc->position); + trace_qxl_create_guest_primary_rest(qxl->id, sc->stride, sc->type, + sc->flags); + + surface.mouse_mode = true; + surface.group_id = MEMSLOT_GROUP_GUEST; + if (loadvm) { + surface.flags |= QXL_SURF_FLAG_KEEP_DATA; + } + + qxl->pci.mode = QXL_MODE_NATIVE; + qxl->cmdflags = 0; + qemu_spice_create_primary_surface(&qxl->ssd, 0, &surface, async); + + if (async == QXL_SYNC) { + qxl_create_guest_primary_complete(qxl); + } +} + +/* return 1 if surface destoy was initiated (in QXL_ASYNC case) or + * done (in QXL_SYNC case), 0 otherwise. */ +int qxl_destroy_primary(QXLDevice *d, qxl_async_io async) +{ + if (d->pci.mode == QXL_MODE_UNDEFINED) { + return 0; + } + trace_qxl_destroy_primary(d->id); + d->pci.mode = QXL_MODE_UNDEFINED; + qemu_spice_destroy_primary_surface(&d->ssd, 0, async); + qxl_spice_reset_cursor(d); + return 1; +} + +static void qxl_set_mode(QXLDevice *d, int modenr, int loadvm) +{ + pcibus_t start = d->pci.pci.io_regions[QXL_RAM_RANGE_INDEX].addr; + pcibus_t end = d->pci.pci.io_regions[QXL_RAM_RANGE_INDEX].size + start; + QXLMode *mode = d->modes->modes + modenr; + uint64_t devmem = d->pci.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, + .flags = loadvm ? QXL_SURF_FLAG_KEEP_DATA : 0, + .mouse_mode = true, + .mem = devmem + d->shadow_rom.draw_area_offset, + }; + + trace_qxl_set_mode(d->id, modenr, mode->x_res, mode->y_res, mode->bits, + devmem); + if (!loadvm) { + qxl_hard_reset(d, 0); + } + + d->guest_slots[0].slot = slot; + assert(qxl_add_memslot(d, 0, devmem, QXL_SYNC) == 0); + + d->guest_primary.surface = surface; + qxl_create_guest_primary(d, 0, QXL_SYNC); + + d->pci.mode = QXL_MODE_COMPAT; + d->cmdflags = QXL_COMMAND_FLAG_COMPAT; +#ifdef QXL_COMMAND_FLAG_COMPAT_16BPP /* new in spice 0.6.1 */ + if (mode->bits == 16) { + d->cmdflags |= QXL_COMMAND_FLAG_COMPAT_16BPP; + } +#endif + 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, target_phys_addr_t addr, + uint64_t val, unsigned size) +{ + QXLDevice *d = opaque; + uint32_t io_port = addr; + qxl_async_io async = QXL_SYNC; + uint32_t orig_io_port = io_port; + + if (d->pci.guest_bug && !io_port == QXL_IO_RESET) { + return; + } + + 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: + case QXL_IO_UPDATE_IRQ: + case QXL_IO_LOG: + case QXL_IO_MEMSLOT_ADD_ASYNC: + case QXL_IO_CREATE_PRIMARY_ASYNC: + break; + default: + if (d->pci.mode != QXL_MODE_VGA) { + break; + } + trace_qxl_io_unexpected_vga_mode(d->id, + io_port, io_port_to_string(io_port)); + /* be nice to buggy guest drivers */ + if (io_port >= QXL_IO_UPDATE_AREA_ASYNC && + io_port <= QXL_IO_DESTROY_ALL_SURFACES_ASYNC) { + qxl_send_events(d, QXL_INTERRUPT_IO_CMD); + } + return; + } + + /* we change the io_port to avoid ifdeffery in the main switch */ + orig_io_port = io_port; + switch (io_port) { + case QXL_IO_UPDATE_AREA_ASYNC: + io_port = QXL_IO_UPDATE_AREA; + goto async_common; + case QXL_IO_MEMSLOT_ADD_ASYNC: + io_port = QXL_IO_MEMSLOT_ADD; + goto async_common; + case QXL_IO_CREATE_PRIMARY_ASYNC: + io_port = QXL_IO_CREATE_PRIMARY; + goto async_common; + case QXL_IO_DESTROY_PRIMARY_ASYNC: + io_port = QXL_IO_DESTROY_PRIMARY; + goto async_common; + case QXL_IO_DESTROY_SURFACE_ASYNC: + io_port = QXL_IO_DESTROY_SURFACE_WAIT; + goto async_common; + case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: + io_port = QXL_IO_DESTROY_ALL_SURFACES; + goto async_common; + case QXL_IO_FLUSH_SURFACES_ASYNC: +async_common: + async = QXL_ASYNC; + qemu_mutex_lock(&d->async_lock); + if (d->current_async != QXL_UNDEFINED_IO) { + qxl_set_guest_bug(d, "%d async started before last (%d) complete", + io_port, d->current_async); + qemu_mutex_unlock(&d->async_lock); + return; + } + d->current_async = orig_io_port; + qemu_mutex_unlock(&d->async_lock); + break; + default: + break; + } + trace_qxl_io_write(d->id, qxl_mode_to_string(d->pci.mode), addr, val, size, + async); + + switch (io_port) { + case QXL_IO_UPDATE_AREA: + { + QXLCookie *cookie = NULL; + QXLRect update = d->ram->update_area; + + if (async == QXL_ASYNC) { + cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, + QXL_IO_UPDATE_AREA_ASYNC); + cookie->u.area = update; + } + qxl_spice_update_area(d, d->ram->update_surface, + cookie ? &cookie->u.area : &update, + NULL, 0, 0, async, cookie); + break; + } + case QXL_IO_NOTIFY_CMD: + qemu_spice_wakeup(&d->ssd); + break; + case QXL_IO_NOTIFY_CURSOR: + qemu_spice_wakeup(&d->ssd); + break; + case QXL_IO_UPDATE_IRQ: + qxl_update_irq(d); + break; + case QXL_IO_NOTIFY_OOM: + if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) { + break; + } + d->oom_running = 1; + qxl_spice_oom(d); + d->oom_running = 0; + break; + case QXL_IO_SET_MODE: + qxl_set_mode(d, val, 0); + break; + case QXL_IO_LOG: + if (d->pci.guestdebug) { + fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id, + qemu_get_clock_ns(vm_clock), d->ram->log_buf); + } + break; + case QXL_IO_RESET: + qxl_hard_reset(d, 0); + break; + case QXL_IO_MEMSLOT_ADD: + if (val >= NUM_MEMSLOTS) { + qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: val out of range"); + break; + } + if (d->guest_slots[val].active) { + qxl_set_guest_bug(d, + "QXL_IO_MEMSLOT_ADD: memory slot already active"); + break; + } + d->guest_slots[val].slot = d->ram->mem_slot; + qxl_add_memslot(d, val, 0, async); + break; + case QXL_IO_MEMSLOT_DEL: + if (val >= NUM_MEMSLOTS) { + qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_DEL: val out of range"); + break; + } + qxl_del_memslot(d, val); + break; + case QXL_IO_CREATE_PRIMARY: + if (val != 0) { + qxl_set_guest_bug(d, "QXL_IO_CREATE_PRIMARY (async=%d): val != 0", + async); + goto cancel_async; + } + d->guest_primary.surface = d->ram->create_surface; + qxl_create_guest_primary(d, 0, async); + break; + case QXL_IO_DESTROY_PRIMARY: + if (val != 0) { + qxl_set_guest_bug(d, "QXL_IO_DESTROY_PRIMARY (async=%d): val != 0", + async); + goto cancel_async; + } + if (!qxl_destroy_primary(d, async)) { + trace_qxl_io_destroy_primary_ignored(d->id, + qxl_mode_to_string(d->pci.mode)); + goto cancel_async; + } + break; + case QXL_IO_DESTROY_SURFACE_WAIT: + if (val >= NUM_SURFACES) { + qxl_set_guest_bug(d, "QXL_IO_DESTROY_SURFACE (async=%d):" + "%" PRIu64 " >= NUM_SURFACES", async, val); + goto cancel_async; + } + qxl_spice_destroy_surface_wait(d, val, async); + break; + case QXL_IO_FLUSH_RELEASE: { + QXLReleaseRing *ring = &d->ram->release_ring; + if (ring->prod - ring->cons + 1 == ring->num_items) { + fprintf(stderr, + "ERROR: no flush, full release ring [p%d,%dc]\n", + ring->prod, ring->cons); + } + qxl_push_free_res(d, 1 /* flush */); + break; + } + case QXL_IO_FLUSH_SURFACES_ASYNC: + qxl_spice_flush_surfaces_async(d); + break; + case QXL_IO_DESTROY_ALL_SURFACES: + d->pci.mode = QXL_MODE_UNDEFINED; + qxl_spice_destroy_surfaces(d, async); + break; + default: + qxl_set_guest_bug(d, "%s: unexpected ioport=0x%x\n", __func__, io_port); + } + return; +cancel_async: + if (async) { + qxl_send_events(d, QXL_INTERRUPT_IO_CMD); + qemu_mutex_lock(&d->async_lock); + d->current_async = QXL_UNDEFINED_IO; + qemu_mutex_unlock(&d->async_lock); + } +} + +static uint64_t ioport_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + QXLDevice *d = opaque; + + trace_qxl_io_read_unexpected(d->id); + return 0xff; +} + +static const MemoryRegionOps qxl_io_ops = { + .read = ioport_read, + .write = ioport_write, + .valid = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static void pipe_read(void *opaque) +{ + QXLDevice *d = opaque; + char dummy; + int len; + + do { + len = read(d->pipe[0], &dummy, sizeof(dummy)); + } while (len == sizeof(dummy)); + qxl_update_irq(d); +} + +void qxl_send_events(QXLDevice *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 (qemu_thread_is_self(&d->main)) { + qxl_update_irq(d); + } else { + if (write(d->pipe[1], d, 1) != 1) { + dprint(d, 1, "%s: write to pipe failed\n", __func__); + } + } +} + +static void init_pipe_signaling(QXLDevice *d) +{ + if (pipe(d->pipe) < 0) { + fprintf(stderr, "%s:%s: qxl pipe creation failed\n", + __FILE__, __func__); + exit(1); + } + fcntl(d->pipe[0], F_SETFL, O_NONBLOCK); + fcntl(d->pipe[1], F_SETFL, O_NONBLOCK); + fcntl(d->pipe[0], F_SETOWN, getpid()); + + qemu_thread_get_self(&d->main); + qemu_set_fd_handler(d->pipe[0], pipe_read, NULL, d); +} + +/* graphics console */ + +static void qxl_hw_update(void *opaque) +{ + QXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->pci.vga; + + switch (qxl->pci.mode) { + case QXL_MODE_VGA: + vga->update(vga); + break; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + break; + default: + break; + } +} + +static void qxl_hw_invalidate(void *opaque) +{ + QXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->pci.vga; + + vga->invalidate(vga); +} + +static void qxl_hw_screen_dump(void *opaque, const char *filename, bool cswitch) +{ + QXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->pci.vga; + + switch (qxl->pci.mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + ppm_save(filename, qxl->ssd.ds->surface); + break; + case QXL_MODE_VGA: + vga->screen_dump(vga, filename, cswitch); + break; + default: + break; + } +} + +static void qxl_hw_text_update(void *opaque, console_ch_t *chardata) +{ + QXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->pci.vga; + + if (qxl->pci.mode == QXL_MODE_VGA) { + vga->text_update(vga, chardata); + return; + } +} + +static void qxl_dirty_surfaces(QXLDevice *qxl) +{ + intptr_t vram_start; + int i; + + if (qxl->pci.mode != QXL_MODE_NATIVE && qxl->pci.mode != QXL_MODE_COMPAT) { + return; + } + + /* dirty the primary surface */ + qxl_set_dirty(&qxl->pci.vga.vram, qxl->shadow_rom.draw_area_offset, + qxl->shadow_rom.surface0_area_size); + + vram_start = (intptr_t)memory_region_get_ram_ptr(&qxl->pci.vram_bar); + + /* dirty the off-screen surfaces */ + for (i = 0; i < NUM_SURFACES; i++) { + QXLSurfaceCmd *cmd; + intptr_t surface_offset; + int surface_size; + + if (qxl->guest_surfaces.cmds[i] == 0) { + continue; + } + + cmd = qxl_phys2virt(qxl, qxl->guest_surfaces.cmds[i], + MEMSLOT_GROUP_GUEST); + assert(cmd); + assert(cmd->type == QXL_SURFACE_CMD_CREATE); + surface_offset = (intptr_t)qxl_phys2virt(qxl, + cmd->u.surface_create.data, + MEMSLOT_GROUP_GUEST); + assert(surface_offset); + surface_offset -= vram_start; + surface_size = cmd->u.surface_create.height * + abs(cmd->u.surface_create.stride); + trace_qxl_surfaces_dirty(qxl->id, i, (int)surface_offset, surface_size); + qxl_set_dirty(&qxl->pci.vram_bar, surface_offset, surface_size); + } +} + +static void qxl_vm_change_state_handler(void *opaque, int running, + RunState state) +{ + QXLDevice *qxl = opaque; + qemu_spice_vm_change_state_handler(&qxl->ssd, running, state); + + if (running) { + /* + * if qxl_send_events was called from spice server context before + * migration ended, qxl_update_irq for these events might not have been + * called + */ + qxl_update_irq(qxl); + } else { + /* make sure surfaces are saved before migration */ + qxl_dirty_surfaces(qxl); + } +} + +/* display change listener */ + +static void display_update(struct DisplayState *ds, int x, int y, int w, int h) +{ + if (qxl0->pci.mode == QXL_MODE_VGA) { + qemu_spice_display_update(&qxl0->ssd, x, y, w, h); + } +} + +static void display_resize(struct DisplayState *ds) +{ + if (qxl0->pci.mode == QXL_MODE_VGA) { + qemu_spice_display_resize(&qxl0->ssd); + } +} + +static void display_refresh(struct DisplayState *ds) +{ + if (qxl0->pci.mode == QXL_MODE_VGA) { + qemu_spice_display_refresh(&qxl0->ssd); + } else { + qemu_mutex_lock(&qxl0->ssd.lock); + qemu_spice_cursor_refresh_unlocked(&qxl0->ssd); + qemu_mutex_unlock(&qxl0->ssd.lock); + } +} + +static DisplayChangeListener display_listener = { + .dpy_update = display_update, + .dpy_resize = display_resize, + .dpy_refresh = display_refresh, +}; + +static void qxl_init_ramsize(QXLDevice *qxl) +{ + /* vga mode framebuffer / primary surface (bar 0, first part) */ + if (qxl->pci.vgamem_size_mb < 8) { + qxl->pci.vgamem_size_mb = 8; + } + qxl->pci.vgamem_size = qxl->pci.vgamem_size_mb * 1024 * 1024; + + /* vga ram (bar 0, total) */ + if (qxl->ram_size_mb != -1) { + qxl->pci.vga.vram_size = qxl->ram_size_mb * 1024 * 1024; + } + if (qxl->pci.vga.vram_size < qxl->pci.vgamem_size * 2) { + qxl->pci.vga.vram_size = qxl->pci.vgamem_size * 2; + } + + /* vram32 (surfaces, 32bit, bar 1) */ + if (qxl->vram32_size_mb != -1) { + qxl->vram32_size = qxl->vram32_size_mb * 1024 * 1024; + } + if (qxl->vram32_size < 4096) { + qxl->vram32_size = 4096; + } + + /* vram (surfaces, 64bit, bar 4+5) */ + if (qxl->vram_size_mb != -1) { + qxl->vram_size = qxl->vram_size_mb * 1024 * 1024; + } + if (qxl->vram_size < qxl->vram32_size) { + qxl->vram_size = qxl->vram32_size; + } + + if (qxl->pci.revision == 1) { + qxl->vram32_size = 4096; + qxl->vram_size = 4096; + } + qxl->pci.vgamem_size = msb_mask(qxl->pci.vgamem_size * 2 - 1); + qxl->pci.vga.vram_size = msb_mask(qxl->pci.vga.vram_size * 2 - 1); + qxl->vram32_size = msb_mask(qxl->vram32_size * 2 - 1); + qxl->vram_size = msb_mask(qxl->vram_size * 2 - 1); +} + +static int qxl_init_common(QXLDevice *qxl) +{ + uint8_t* config = qxl->pci.pci.config; + uint32_t pci_device_rev; + uint32_t io_size; + + qxl->pci.mode = QXL_MODE_UNDEFINED; + qxl->pci.generation = 1; + qxl->num_memslots = NUM_MEMSLOTS; + qxl->num_surfaces = NUM_SURFACES; + qemu_mutex_init(&qxl->track_lock); + qemu_mutex_init(&qxl->async_lock); + qxl->current_async = QXL_UNDEFINED_IO; + qxl->pci.guest_bug = 0; + + switch (qxl->pci.revision) { + case 1: /* spice 0.4 -- qxl-1 */ + pci_device_rev = QXL_REVISION_STABLE_V04; + io_size = 8; + break; + case 2: /* spice 0.6 -- qxl-2 */ + pci_device_rev = QXL_REVISION_STABLE_V06; + io_size = 16; + break; + case 3: /* qxl-3 */ + default: + pci_device_rev = QXL_DEFAULT_REVISION; + io_size = msb_mask(QXL_IO_RANGE_SIZE * 2 - 1); + break; + } + + pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev); + pci_set_byte(&config[PCI_INTERRUPT_PIN], 1); + + qxl->pci.rom_size = qxl_rom_size(); + memory_region_init_ram(&qxl->pci.rom_bar,"qxl-pci.vrom", + qxl->pci.rom_size); + vmstate_register_ram(&qxl->pci.rom_bar, &qxl->pci.pci.qdev); + init_qxl_rom(qxl); + init_qxl_ram(qxl); + + memory_region_init_ram(&qxl->pci.vram_bar, "qxl-pci.vram", + qxl->vram_size); + vmstate_register_ram(&qxl->pci.vram_bar, &qxl->pci.pci.qdev); + memory_region_init_alias(&qxl->pci.vram32_bar, "qxl-pci.vram32", + &qxl->pci.vram_bar, 0, qxl->vram32_size); + + memory_region_init_io(&qxl->pci.io_bar, &qxl_io_ops, qxl, + "qxl-pci.ioports", io_size); + if (qxl->id == 0) { + vga_dirty_log_start(&qxl->pci.vga); + } + + + pci_register_bar(&qxl->pci.pci, QXL_IO_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_IO, &qxl->pci.io_bar); + + pci_register_bar(&qxl->pci.pci, QXL_ROM_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->pci.rom_bar); + + pci_register_bar(&qxl->pci.pci, QXL_RAM_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->pci.vga.vram); + + pci_register_bar(&qxl->pci.pci, QXL_VRAM_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->pci.vram32_bar); + + if (qxl->vram32_size < qxl->vram_size) { + /* + * Make the 64bit vram bar show up only in case it is + * configured to be larger than the 32bit vram bar. + */ + pci_register_bar(&qxl->pci.pci, QXL_VRAM64_RANGE_INDEX, + PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_64 | + PCI_BASE_ADDRESS_MEM_PREFETCH, + &qxl->pci.vram_bar); + } + + /* print pci bar details */ + dprint(qxl, 1, "ram/%s: %d MB [region 0]\n", + qxl->id == 0 ? "pri" : "sec", + qxl->pci.vga.vram_size / (1024*1024)); + dprint(qxl, 1, "vram/32: %d MB [region 1]\n", + qxl->vram32_size / (1024*1024)); + dprint(qxl, 1, "vram/64: %d MB %s\n", + qxl->vram_size / (1024*1024), + qxl->vram32_size < qxl->vram_size ? "[region 4]" : "[unmapped]"); + + qxl->ssd.qxl.base.sif = &qxl_interface.base; + qxl->ssd.qxl.id = qxl->id; + qemu_spice_add_interface(&qxl->ssd.qxl.base); + qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl); + + init_pipe_signaling(qxl); + qxl_reset_state(qxl); + + qxl->update_area_bh = qemu_bh_new(qxl_render_update_area_bh, qxl); + + return 0; +} + +static int qxl_init_primary(PCIDevice *dev) +{ + QXLDevice *qxl = (QXLDevice *) DO_UPCAST(PCIQXLDevice, pci, dev); + VGACommonState *vga = &qxl->pci.vga; + PortioList *qxl_vga_port_list = g_new(PortioList, 1); + + qxl->id = 0; + qxl_init_ramsize(qxl); + vga->vram_size_mb = qxl->pci.vga.vram_size >> 20; + vga_common_init(vga); + vga_init(vga, pci_address_space(dev), pci_address_space_io(dev), false); + portio_list_init(qxl_vga_port_list, qxl_vga_portio_list, vga, "vga"); + portio_list_add(qxl_vga_port_list, pci_address_space_io(dev), 0x3b0); + + vga->ds = graphic_console_init(qxl_hw_update, qxl_hw_invalidate, + qxl_hw_screen_dump, qxl_hw_text_update, qxl); + qemu_spice_display_init_common(&qxl->ssd, vga->ds); + + qxl0 = qxl; + register_displaychangelistener(vga->ds, &display_listener); + + return qxl_init_common(qxl); +} + +static int qxl_init_secondary(PCIDevice *dev) +{ + static int device_id = 1; + QXLDevice *qxl = (QXLDevice *) DO_UPCAST(PCIQXLDevice, pci, dev); + + qxl->id = device_id++; + qxl_init_ramsize(qxl); + memory_region_init_ram(&qxl->pci.vga.vram, "qxl-pci.vgavram", qxl->pci.vga.vram_size); + vmstate_register_ram(&qxl->pci.vga.vram, &qxl->pci.pci.qdev); + qxl->pci.vga.vram_ptr = memory_region_get_ram_ptr(&qxl->pci.vga.vram); + + return qxl_init_common(qxl); +} + +static void qxl_pre_save(void *opaque) +{ + QXLDevice* d = opaque; + uint8_t *ram_start = d->pci.vga.vram_ptr; + + trace_qxl_pre_save(d->id); + 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->pci.vga.vram_size); +} + +static int qxl_pre_load(void *opaque) +{ + QXLDevice* d = opaque; + + trace_qxl_pre_load(d->id); + qxl_hard_reset(d, 1); + qxl_exit_vga_mode(d); + return 0; +} + +static void qxl_create_memslots(QXLDevice *d) +{ + int i; + + for (i = 0; i < NUM_MEMSLOTS; i++) { + if (!d->guest_slots[i].active) { + continue; + } + qxl_add_memslot(d, i, 0, QXL_SYNC); + } +} + +static int qxl_post_load(void *opaque, int version) +{ + QXLDevice *d = opaque; + uint8_t *ram_start = d->pci.vga.vram_ptr; + QXLCommandExt *cmds; + int in, out, newmode; + + assert(d->last_release_offset < d->pci.vga.vram_size); + if (d->last_release_offset == 0) { + d->last_release = NULL; + } else { + d->last_release = (QXLReleaseInfo *)(ram_start + d->last_release_offset); + } + + d->modes = (QXLModes*)((uint8_t*)d->rom + d->rom->modes_offset); + + trace_qxl_post_load(d->id, qxl_mode_to_string(d->pci.mode)); + newmode = d->pci.mode; + d->pci.mode = QXL_MODE_UNDEFINED; + + switch (newmode) { + case QXL_MODE_UNDEFINED: + break; + case QXL_MODE_VGA: + qxl_create_memslots(d); + qxl_enter_vga_mode(d); + break; + case QXL_MODE_NATIVE: + qxl_create_memslots(d); + qxl_create_guest_primary(d, 1, QXL_SYNC); + + /* replay surface-create and cursor-set commands */ + cmds = g_malloc0(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++; + } + if (d->guest_cursor) { + cmds[out].cmd.data = d->guest_cursor; + cmds[out].cmd.type = QXL_CMD_CURSOR; + cmds[out].group_id = MEMSLOT_GROUP_GUEST; + out++; + } + qxl_spice_loadvm_commands(d, cmds, out); + g_free(cmds); + + break; + case QXL_MODE_COMPAT: + /* note: no need to call qxl_create_memslots, qxl_set_mode + * creates the mem slot. */ + qxl_set_mode(d, d->shadow_rom.mode, 1); + break; + } + return 0; +} + +#define QXL_SAVE_VERSION 21 + +static VMStateDescription qxl_memslot = { + .name = "qxl-memslot", + .version_id = QXL_SAVE_VERSION, + .minimum_version_id = QXL_SAVE_VERSION, + .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_SAVE_VERSION, + .minimum_version_id = QXL_SAVE_VERSION, + .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_SAVE_VERSION, + .minimum_version_id = QXL_SAVE_VERSION, + .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, 0, vmstate_vga_common, VGACommonState), + VMSTATE_UINT32(shadow_rom.mode, QXLDevice), + VMSTATE_UINT32(num_free_res, QXLDevice), + VMSTATE_UINT32(last_release_offset, QXLDevice), + VMSTATE_UINT32(mode, PCIQXLDevice), + VMSTATE_UINT32(ssd.unique, QXLDevice), + VMSTATE_INT32_EQUAL(num_memslots, QXLDevice), + VMSTATE_STRUCT_ARRAY(guest_slots, QXLDevice, NUM_MEMSLOTS, 0, + qxl_memslot, struct guest_slots), + VMSTATE_STRUCT(guest_primary.surface, QXLDevice, 0, + qxl_surface, QXLSurfaceCreate), + VMSTATE_INT32_EQUAL(num_surfaces, QXLDevice), + VMSTATE_ARRAY(guest_surfaces.cmds, QXLDevice, NUM_SURFACES, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_UINT64(guest_cursor, QXLDevice), + VMSTATE_END_OF_LIST() + }, +}; + +static Property qxl_properties[] = { + DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, + 64 * 1024 * 1024), + DEFINE_PROP_UINT32("vram_size", QXLDevice, vram32_size, + 64 * 1024 * 1024), + DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision, + QXL_DEFAULT_REVISION), + DEFINE_PROP_UINT32("debug", QXLDevice, debug, 0), + DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0), + DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0), + DEFINE_PROP_UINT32("ram_size_mb", QXLDevice, ram_size_mb, -1), + DEFINE_PROP_UINT32("vram_size_mb", QXLDevice, vram32_size_mb, -1), + DEFINE_PROP_UINT32("vram64_size_mb", QXLDevice, vram_size_mb, -1), + DEFINE_PROP_UINT32("vgamem_mb", PCIQXLDevice, vgamem_size_mb, 16), + DEFINE_PROP_END_OF_LIST(), +}; + +static void qxl_primary_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->no_hotplug = 1; + k->init = qxl_init_primary; + k->romfile = "vgabios-qxl.bin"; + k->vendor_id = REDHAT_PCI_VENDOR_ID; + k->device_id = QXL_DEVICE_ID_STABLE; + k->class_id = PCI_CLASS_DISPLAY_VGA; + dc->desc = "Spice QXL GPU (primary, vga compatible)"; + dc->reset = qxl_reset_handler; + dc->vmsd = &qxl_vmstate; + dc->props = qxl_properties; +} + +static TypeInfo qxl_primary_info = { + .name = "qxl-vga", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(QXLDevice), + .class_init = qxl_primary_class_init, +}; + +static void qxl_secondary_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = qxl_init_secondary; + k->vendor_id = REDHAT_PCI_VENDOR_ID; + k->device_id = QXL_DEVICE_ID_STABLE; + k->class_id = PCI_CLASS_DISPLAY_OTHER; + dc->desc = "Spice QXL GPU (secondary)"; + dc->reset = qxl_reset_handler; + dc->vmsd = &qxl_vmstate; + dc->props = qxl_properties; +} + +static TypeInfo qxl_secondary_info = { + .name = "qxl", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(QXLDevice), + .class_init = qxl_secondary_class_init, +}; + +static void qxl_register_types(void) +{ + type_register_static(&qxl_primary_info); + type_register_static(&qxl_secondary_info); +} + +type_init(qxl_register_types) diff --git a/hw/qxl.c b/hw/qxl.c index c8a36aa..c9a63e2 100644 --- a/hw/qxl.c +++ b/hw/qxl.c @@ -27,44 +27,6 @@ #include "qxl.h" -/* - * NOTE: SPICE_RING_PROD_ITEM accesses memory on the pci bar and as - * such can be changed by the guest, so to avoid a guest trigerrable - * abort we just qxl_set_guest_bug and set the return to NULL. Still - * it may happen as a result of emulator bug as well. - */ -#undef SPICE_RING_PROD_ITEM -#define SPICE_RING_PROD_ITEM(qxl, 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))) { \ - qxl_set_guest_bug(qxl, "SPICE_RING_PROD_ITEM indices mismatch " \ - "! %p <= %p < %p", (uint8_t *)start, \ - (uint8_t *)m_item, (uint8_t *)end); \ - ret = NULL; \ - } else { \ - ret = &m_item->el; \ - } \ - } - -#undef SPICE_RING_CONS_ITEM -#define SPICE_RING_CONS_ITEM(qxl, 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))) { \ - qxl_set_guest_bug(qxl, "SPICE_RING_CONS_ITEM indices mismatch " \ - "! %p <= %p < %p", (uint8_t *)start, \ - (uint8_t *)m_item, (uint8_t *)end); \ - ret = NULL; \ - } else { \ - ret = &m_item->el; \ - } \ - } - #undef ALIGN #define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1)) @@ -126,88 +88,6 @@ static QXLMode qxl_modes[] = { QXL_MODE_EX(3200, 2400), }; -static QXLDevice *qxl0; - -static void qxl_send_events(QXLDevice *d, uint32_t events); -static int qxl_destroy_primary(QXLDevice *d, qxl_async_io async); -static void qxl_reset_memslots(QXLDevice *d); -static void qxl_reset_surfaces(QXLDevice *d); -static void qxl_ring_set_dirty(QXLDevice *qxl); - -void qxl_set_guest_bug(QXLDevice *qxl, const char *msg, ...) -{ - qxl_send_events(qxl, QXL_INTERRUPT_ERROR); - qxl->pci.guest_bug = 1; - if (qxl->pci.guestdebug) { - va_list ap; - va_start(ap, msg); - fprintf(stderr, "qxl-%d: guest bug: ", qxl->id); - vfprintf(stderr, msg, ap); - fprintf(stderr, "\n"); - va_end(ap); - } -} - -static void qxl_clear_guest_bug(QXLDevice *qxl) -{ - qxl->pci.guest_bug = 0; -} - -void qxl_spice_update_area(QXLDevice *qxl, uint32_t surface_id, - struct QXLRect *area, struct QXLRect *dirty_rects, - uint32_t num_dirty_rects, - uint32_t clear_dirty_region, - qxl_async_io async, struct QXLCookie *cookie) -{ - trace_qxl_spice_update_area(qxl->id, surface_id, area->left, area->right, - area->top, area->bottom); - trace_qxl_spice_update_area_rest(qxl->id, num_dirty_rects, - clear_dirty_region); - if (async == QXL_SYNC) { - qxl->ssd.worker->update_area(qxl->ssd.worker, surface_id, area, - dirty_rects, num_dirty_rects, clear_dirty_region); - } else { - assert(cookie != NULL); - spice_qxl_update_area_async(&qxl->ssd.qxl, surface_id, area, - clear_dirty_region, (uintptr_t)cookie); - } -} - -static void qxl_spice_destroy_surface_wait_complete(QXLDevice *qxl, - uint32_t id) -{ - trace_qxl_spice_destroy_surface_wait_complete(qxl->id, id); - qemu_mutex_lock(&qxl->track_lock); - qxl->guest_surfaces.cmds[id] = 0; - qxl->guest_surfaces.count--; - qemu_mutex_unlock(&qxl->track_lock); -} - -static void qxl_spice_destroy_surface_wait(QXLDevice *qxl, uint32_t id, - qxl_async_io async) -{ - QXLCookie *cookie; - - trace_qxl_spice_destroy_surface_wait(qxl->id, id, async); - if (async) { - cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, - QXL_IO_DESTROY_SURFACE_ASYNC); - cookie->u.surface_id = id; - spice_qxl_destroy_surface_async(&qxl->ssd.qxl, id, (uintptr_t)cookie); - } else { - qxl->ssd.worker->destroy_surface_wait(qxl->ssd.worker, id); - } -} - -static void qxl_spice_flush_surfaces_async(QXLDevice *qxl) -{ - trace_qxl_spice_flush_surfaces_async(qxl->id, qxl->guest_surfaces.count, - qxl->num_free_res); - spice_qxl_flush_surfaces_async(&qxl->ssd.qxl, - (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, - QXL_IO_FLUSH_SURFACES_ASYNC)); -} - void qxl_spice_loadvm_commands(QXLDevice *qxl, struct QXLCommandExt *ext, uint32_t count) { @@ -227,28 +107,6 @@ void qxl_spice_reset_memslots(QXLDevice *qxl) qxl->ssd.worker->reset_memslots(qxl->ssd.worker); } -static void qxl_spice_destroy_surfaces_complete(QXLDevice *qxl) -{ - trace_qxl_spice_destroy_surfaces_complete(qxl->id); - qemu_mutex_lock(&qxl->track_lock); - memset(&qxl->guest_surfaces.cmds, 0, sizeof(qxl->guest_surfaces.cmds)); - qxl->guest_surfaces.count = 0; - qemu_mutex_unlock(&qxl->track_lock); -} - -static void qxl_spice_destroy_surfaces(QXLDevice *qxl, qxl_async_io async) -{ - trace_qxl_spice_destroy_surfaces(qxl->id, async); - if (async) { - spice_qxl_destroy_surfaces_async(&qxl->ssd.qxl, - (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, - QXL_IO_DESTROY_ALL_SURFACES_ASYNC)); - } else { - qxl->ssd.worker->destroy_surfaces(qxl->ssd.worker); - qxl_spice_destroy_surfaces_complete(qxl); - } -} - void qxl_spice_reset_image_cache(QXLDevice *qxl) { trace_qxl_spice_reset_image_cache(qxl->id); @@ -264,20 +122,27 @@ void qxl_spice_reset_cursor(QXLDevice *qxl) qemu_mutex_unlock(&qxl->track_lock); } - -static inline uint32_t msb_mask(uint32_t val) +void qxl_spice_update_area(QXLDevice *qxl, uint32_t surface_id, + struct QXLRect *area, struct QXLRect *dirty_rects, + uint32_t num_dirty_rects, + uint32_t clear_dirty_region, + qxl_async_io async, struct QXLCookie *cookie) { - uint32_t mask; - - do { - mask = ~(val - 1) & val; - val &= ~mask; - } while (mask < val); - - return mask; + trace_qxl_spice_update_area(qxl->id, surface_id, area->left, area->right, + area->top, area->bottom); + trace_qxl_spice_update_area_rest(qxl->id, num_dirty_rects, + clear_dirty_region); + if (async == QXL_SYNC) { + qxl->ssd.worker->update_area(qxl->ssd.worker, surface_id, area, + dirty_rects, num_dirty_rects, clear_dirty_region); + } else { + assert(cookie != NULL); + spice_qxl_update_area_async(&qxl->ssd.qxl, surface_id, area, + clear_dirty_region, (uintptr_t)cookie); + } } -static ram_addr_t qxl_rom_size(void) +ram_addr_t qxl_rom_size(void) { uint32_t rom_size = sizeof(QXLRom) + sizeof(QXLModes) + sizeof(qxl_modes); @@ -286,7 +151,7 @@ static ram_addr_t qxl_rom_size(void) return rom_size; } -static void init_qxl_rom(QXLDevice *d) +void init_qxl_rom(QXLDevice *d) { QXLRom *rom; rom = memory_region_get_ram_ptr(&d->pci.rom_bar); @@ -345,7 +210,7 @@ static void init_qxl_rom(QXLDevice *d) d->modes = modes; } -static void init_qxl_ram(QXLDevice *d) +void init_qxl_ram(QXLDevice *d) { uint8_t *buf; uint64_t *item; @@ -365,93 +230,9 @@ static void init_qxl_ram(QXLDevice *d) qxl_ring_set_dirty(d); } -/* can be called from spice server thread context */ -static void qxl_set_dirty(MemoryRegion *mr, ram_addr_t addr, ram_addr_t end) -{ - memory_region_set_dirty(mr, addr, end - addr); -} - -static void qxl_rom_set_dirty(QXLDevice *qxl) -{ - qxl_set_dirty(&qxl->pci.rom_bar, 0, qxl->pci.rom_size); -} - -/* called from spice server thread context only */ -static void qxl_ram_set_dirty(QXLDevice *qxl, void *ptr) -{ - void *base = qxl->pci.vga.vram_ptr; - intptr_t offset; - - offset = ptr - base; - offset &= ~(TARGET_PAGE_SIZE-1); - assert(offset < qxl->pci.vga.vram_size); - qxl_set_dirty(&qxl->pci.vga.vram, offset, offset + TARGET_PAGE_SIZE); -} - -/* can be called from spice server thread context */ -static void qxl_ring_set_dirty(QXLDevice *qxl) -{ - ram_addr_t addr = qxl->shadow_rom.ram_header_offset; - ram_addr_t end = qxl->pci.vga.vram_size; - qxl_set_dirty(&qxl->pci.vga.vram, addr, end); -} - -/* - * keep track of some command state, for savevm/loadvm. - * called from spice server thread context only - */ -static int qxl_track_command(QXLDevice *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); - - if (!cmd) { - return 1; - } - uint32_t id = le32_to_cpu(cmd->surface_id); - - if (id >= NUM_SURFACES) { - qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE id %d >= %d", id, - NUM_SURFACES); - return 1; - } - qemu_mutex_lock(&qxl->track_lock); - 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--; - } - qemu_mutex_unlock(&qxl->track_lock); - break; - } - case QXL_CMD_CURSOR: - { - QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); - - if (!cmd) { - return 1; - } - if (cmd->type == QXL_CURSOR_SET) { - qemu_mutex_lock(&qxl->track_lock); - qxl->guest_cursor = ext->cmd.data; - qemu_mutex_unlock(&qxl->track_lock); - } - break; - } - } - return 0; -} - /* spice display interface callbacks */ -static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) +void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); @@ -459,7 +240,7 @@ static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) trace_qxl_interface_attach_worker(qxl->id); } -static void interface_set_compression_level(QXLInstance *sin, int level) +void interface_set_compression_level(QXLInstance *sin, int level) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); @@ -469,7 +250,7 @@ static void interface_set_compression_level(QXLInstance *sin, int level) qxl->rom->compression_level = cpu_to_le32(level); } -static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time) +void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); @@ -479,7 +260,7 @@ static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time) qxl->rom->mm_clock = cpu_to_le32(mm_time); } -static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info) +void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); @@ -493,113 +274,15 @@ static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info) info->n_surfaces = NUM_SURFACES; } -static const char *qxl_mode_to_string(int mode) -{ - switch (mode) { - case QXL_MODE_COMPAT: - return "compat"; - case QXL_MODE_NATIVE: - return "native"; - case QXL_MODE_UNDEFINED: - return "undefined"; - case QXL_MODE_VGA: - return "vga"; - } - return "INVALID"; -} - -static const char *io_port_to_string(uint32_t io_port) -{ - if (io_port >= QXL_IO_RANGE_SIZE) { - return "out of range"; - } - static const char *io_port_to_string[QXL_IO_RANGE_SIZE + 1] = { - [QXL_IO_NOTIFY_CMD] = "QXL_IO_NOTIFY_CMD", - [QXL_IO_NOTIFY_CURSOR] = "QXL_IO_NOTIFY_CURSOR", - [QXL_IO_UPDATE_AREA] = "QXL_IO_UPDATE_AREA", - [QXL_IO_UPDATE_IRQ] = "QXL_IO_UPDATE_IRQ", - [QXL_IO_NOTIFY_OOM] = "QXL_IO_NOTIFY_OOM", - [QXL_IO_RESET] = "QXL_IO_RESET", - [QXL_IO_SET_MODE] = "QXL_IO_SET_MODE", - [QXL_IO_LOG] = "QXL_IO_LOG", - [QXL_IO_MEMSLOT_ADD] = "QXL_IO_MEMSLOT_ADD", - [QXL_IO_MEMSLOT_DEL] = "QXL_IO_MEMSLOT_DEL", - [QXL_IO_DETACH_PRIMARY] = "QXL_IO_DETACH_PRIMARY", - [QXL_IO_ATTACH_PRIMARY] = "QXL_IO_ATTACH_PRIMARY", - [QXL_IO_CREATE_PRIMARY] = "QXL_IO_CREATE_PRIMARY", - [QXL_IO_DESTROY_PRIMARY] = "QXL_IO_DESTROY_PRIMARY", - [QXL_IO_DESTROY_SURFACE_WAIT] = "QXL_IO_DESTROY_SURFACE_WAIT", - [QXL_IO_DESTROY_ALL_SURFACES] = "QXL_IO_DESTROY_ALL_SURFACES", - [QXL_IO_UPDATE_AREA_ASYNC] = "QXL_IO_UPDATE_AREA_ASYNC", - [QXL_IO_MEMSLOT_ADD_ASYNC] = "QXL_IO_MEMSLOT_ADD_ASYNC", - [QXL_IO_CREATE_PRIMARY_ASYNC] = "QXL_IO_CREATE_PRIMARY_ASYNC", - [QXL_IO_DESTROY_PRIMARY_ASYNC] = "QXL_IO_DESTROY_PRIMARY_ASYNC", - [QXL_IO_DESTROY_SURFACE_ASYNC] = "QXL_IO_DESTROY_SURFACE_ASYNC", - [QXL_IO_DESTROY_ALL_SURFACES_ASYNC] - = "QXL_IO_DESTROY_ALL_SURFACES_ASYNC", - [QXL_IO_FLUSH_SURFACES_ASYNC] = "QXL_IO_FLUSH_SURFACES_ASYNC", - [QXL_IO_FLUSH_RELEASE] = "QXL_IO_FLUSH_RELEASE", - }; - return io_port_to_string[io_port]; -} - -static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext) +int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); - SimpleSpiceUpdate *update; - QXLCommandRing *ring; - QXLCommand *cmd; - int notify, ret; - - trace_qxl_ring_command_check(qxl->id, qxl_mode_to_string(qxl->pci.mode)); - switch (qxl->pci.mode) { - case QXL_MODE_VGA: - ret = false; - qemu_mutex_lock(&qxl->ssd.lock); - if (qxl->ssd.update != NULL) { - update = qxl->ssd.update; - qxl->ssd.update = NULL; - *ext = update->ext; - ret = true; - } - qemu_mutex_unlock(&qxl->ssd.lock); - if (ret) { - trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->pci.mode)); - qxl_log_command(qxl, "vga", ext); - } - return ret; - case QXL_MODE_COMPAT: - case QXL_MODE_NATIVE: - case QXL_MODE_UNDEFINED: - ring = &qxl->ram->cmd_ring; - if (qxl->pci.guest_bug || SPICE_RING_IS_EMPTY(ring)) { - return false; - } - SPICE_RING_CONS_ITEM(qxl, ring, cmd); - if (!cmd) { - return false; - } - 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); - trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->pci.mode)); - return true; - default: - return false; - } + return interface_get_command_pci(qxl, ext); } /* called from spice server thread context only */ -static int interface_req_cmd_notification(QXLInstance *sin) +int interface_req_cmd_notification(QXLInstance *sin) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); int wait = 1; @@ -620,48 +303,7 @@ static int interface_req_cmd_notification(QXLInstance *sin) } /* called from spice server thread context only */ -static inline void qxl_push_free_res(QXLDevice *d, int flush) -{ - QXLReleaseRing *ring = &d->ram->release_ring; - uint64_t *item; - int notify; - -#define QXL_FREE_BUNCH_SIZE 32 - - if (ring->prod - ring->cons + 1 == ring->num_items) { - /* ring full -- can't push */ - return; - } - if (!flush && d->oom_running) { - /* collect everything from oom handler before pushing */ - return; - } - if (!flush && d->num_free_res < QXL_FREE_BUNCH_SIZE) { - /* collect a bit more before pushing */ - return; - } - - SPICE_RING_PUSH(ring, notify); - trace_qxl_ring_res_push(d->id, qxl_mode_to_string(d->pci.mode), - d->guest_surfaces.count, d->num_free_res, - d->last_release, notify ? "yes" : "no"); - trace_qxl_ring_res_push_rest(d->id, ring->prod - ring->cons, - ring->num_items, ring->prod, ring->cons); - if (notify) { - qxl_send_events(d, QXL_INTERRUPT_DISPLAY); - } - SPICE_RING_PROD_ITEM(d, ring, item); - if (!item) { - return; - } - *item = 0; - d->num_free_res = 0; - d->last_release = NULL; - qxl_ring_set_dirty(d); -} - -/* called from spice server thread context only */ -static void interface_release_resource(QXLInstance *sin, +void interface_release_resource(QXLInstance *sin, struct QXLReleaseInfoExt ext) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); @@ -704,50 +346,15 @@ static void interface_release_resource(QXLInstance *sin, } /* called from spice server thread context only */ -static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext) +int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); - QXLCursorRing *ring; - QXLCommand *cmd; - int notify; - - trace_qxl_ring_cursor_check(qxl->id, qxl_mode_to_string(qxl->pci.mode)); - switch (qxl->pci.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(qxl, ring, cmd); - if (!cmd) { - return false; - } - 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); - } - trace_qxl_ring_cursor_get(qxl->id, qxl_mode_to_string(qxl->pci.mode)); - return true; - default: - return false; - } + return interface_get_cursor_command_pci(qxl, ext); } /* called from spice server thread context only */ -static int interface_req_cursor_notification(QXLInstance *sin) +int interface_req_cursor_notification(QXLInstance *sin) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); int wait = 1; @@ -768,7 +375,7 @@ static int interface_req_cursor_notification(QXLInstance *sin) } /* called from spice server thread context */ -static void interface_notify_update(QXLInstance *sin, uint32_t update_id) +void interface_notify_update(QXLInstance *sin, uint32_t update_id) { /* * Called by spice-server as a result of a QXL_CMD_UPDATE which is not in @@ -780,7 +387,7 @@ static void interface_notify_update(QXLInstance *sin, uint32_t update_id) } /* called from spice server thread context only */ -static int interface_flush_resources(QXLInstance *sin) +int interface_flush_resources(QXLInstance *sin) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); int ret; @@ -792,52 +399,8 @@ static int interface_flush_resources(QXLInstance *sin) return ret; } -static void qxl_create_guest_primary_complete(QXLDevice *d); - -/* called from spice server thread context only */ -static void interface_async_complete_io(QXLDevice *qxl, QXLCookie *cookie) -{ - uint32_t current_async; - - qemu_mutex_lock(&qxl->async_lock); - current_async = qxl->current_async; - qxl->current_async = QXL_UNDEFINED_IO; - qemu_mutex_unlock(&qxl->async_lock); - - trace_qxl_interface_async_complete_io(qxl->id, current_async, cookie); - if (!cookie) { - fprintf(stderr, "qxl: %s: error, cookie is NULL\n", __func__); - return; - } - if (cookie && current_async != cookie->io) { - fprintf(stderr, - "qxl: %s: error: current_async = %d != %" - PRId64 " = cookie->io\n", __func__, current_async, cookie->io); - } - switch (current_async) { - case QXL_IO_MEMSLOT_ADD_ASYNC: - case QXL_IO_DESTROY_PRIMARY_ASYNC: - case QXL_IO_UPDATE_AREA_ASYNC: - case QXL_IO_FLUSH_SURFACES_ASYNC: - break; - case QXL_IO_CREATE_PRIMARY_ASYNC: - qxl_create_guest_primary_complete(qxl); - break; - case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: - qxl_spice_destroy_surfaces_complete(qxl); - break; - case QXL_IO_DESTROY_SURFACE_ASYNC: - qxl_spice_destroy_surface_wait_complete(qxl, cookie->u.surface_id); - break; - default: - fprintf(stderr, "qxl: %s: unexpected current_async %d\n", __func__, - current_async); - } - qxl_send_events(qxl, QXL_INTERRUPT_IO_CMD); -} - /* called from spice server thread context only */ -static void interface_update_area_complete(QXLInstance *sin, +void interface_update_area_complete(QXLInstance *sin, uint32_t surface_id, QXLRect *dirty, uint32_t num_updated_rects) { @@ -881,7 +444,7 @@ static void interface_update_area_complete(QXLInstance *sin, } /* called from spice server thread context only */ -static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token) +void interface_async_complete(QXLInstance *sin, uint64_t cookie_token) { QXLDevice *qxl = container_of(sin, QXLDevice, ssd.qxl); QXLCookie *cookie = (QXLCookie *)(uintptr_t)cookie_token; @@ -900,1229 +463,3 @@ static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token) g_free(cookie); } } - -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, - - /* the callbacks below are called from spice server thread context */ - .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, - .async_complete = interface_async_complete, - .update_area_complete = interface_update_area_complete, -}; - -static void qxl_enter_vga_mode(QXLDevice *d) -{ - if (d->pci.mode == QXL_MODE_VGA) { - return; - } - trace_qxl_enter_vga_mode(d->id); - qemu_spice_create_host_primary(&d->ssd); - d->pci.mode = QXL_MODE_VGA; - memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty)); - vga_dirty_log_start(&d->pci.vga); -} - -static void qxl_exit_vga_mode(QXLDevice *d) -{ - if (d->pci.mode != QXL_MODE_VGA) { - return; - } - trace_qxl_exit_vga_mode(d->id); - vga_dirty_log_stop(&d->pci.vga); - qxl_destroy_primary(d, QXL_SYNC); -} - -static void qxl_update_irq(QXLDevice *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.pci.irq[0], level); - qxl_ring_set_dirty(d); -} - -static void qxl_check_state(QXLDevice *d) -{ - QXLRam *ram = d->ram; - - assert(!d->ssd.running || SPICE_RING_IS_EMPTY(&ram->cmd_ring)); - assert(!d->ssd.running || SPICE_RING_IS_EMPTY(&ram->cursor_ring)); -} - -static void qxl_reset_state(QXLDevice *d) -{ - QXLRom *rom = d->rom; - - qxl_check_state(d); - 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(QXLDevice *d) -{ - trace_qxl_soft_reset(d->id); - qxl_check_state(d); - qxl_clear_guest_bug(d); - d->current_async = QXL_UNDEFINED_IO; - - if (d->id == 0) { - qxl_enter_vga_mode(d); - } else { - d->pci.mode = QXL_MODE_UNDEFINED; - } -} - -static void qxl_hard_reset(QXLDevice *d, int loadvm) -{ - trace_qxl_hard_reset(d->id, loadvm); - - qxl_spice_reset_cursor(d); - qxl_spice_reset_image_cache(d); - 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); -} - -static void qxl_reset_handler(DeviceState *dev) -{ - QXLDevice *d = (QXLDevice *) 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; - QXLDevice *qxl = (QXLDevice *) container_of(vga, PCIQXLDevice, vga); - - trace_qxl_io_write_vga(qxl->id, qxl_mode_to_string(qxl->pci.mode), addr, val); - if (qxl->pci.mode != QXL_MODE_VGA) { - qxl_destroy_primary(qxl, QXL_SYNC); - qxl_soft_reset(qxl); - } - vga_ioport_write(opaque, addr, val); -} - -static const MemoryRegionPortio qxl_vga_portio_list[] = { - { 0x04, 2, 1, .read = vga_ioport_read, - .write = qxl_vga_ioport_write }, /* 3b4 */ - { 0x0a, 1, 1, .read = vga_ioport_read, - .write = qxl_vga_ioport_write }, /* 3ba */ - { 0x10, 16, 1, .read = vga_ioport_read, - .write = qxl_vga_ioport_write }, /* 3c0 */ - { 0x24, 2, 1, .read = vga_ioport_read, - .write = qxl_vga_ioport_write }, /* 3d4 */ - { 0x2a, 1, 1, .read = vga_ioport_read, - .write = qxl_vga_ioport_write }, /* 3da */ - PORTIO_END_OF_LIST(), -}; - -static int qxl_add_memslot(QXLDevice *d, uint32_t slot_id, uint64_t delta, - qxl_async_io async) -{ - static const int regions[] = { - QXL_RAM_RANGE_INDEX, - QXL_VRAM_RANGE_INDEX, - QXL_VRAM64_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); - - trace_qxl_memslot_add_guest(d->id, slot_id, guest_start, guest_end); - - if (slot_id >= NUM_MEMSLOTS) { - qxl_set_guest_bug(d, "%s: slot_id >= NUM_MEMSLOTS %d >= %d", __func__, - slot_id, NUM_MEMSLOTS); - return 1; - } - if (guest_start > guest_end) { - qxl_set_guest_bug(d, "%s: guest_start > guest_end 0x%" PRIx64 - " > 0x%" PRIx64, __func__, guest_start, guest_end); - return 1; - } - - for (i = 0; i < ARRAY_SIZE(regions); i++) { - pci_region = regions[i]; - pci_start = d->pci.pci.io_regions[pci_region].addr; - pci_end = pci_start + d->pci.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; - } - if (i == ARRAY_SIZE(regions)) { - qxl_set_guest_bug(d, "%s: finished loop without match", __func__); - return 1; - } - - switch (pci_region) { - case QXL_RAM_RANGE_INDEX: - virt_start = (intptr_t)memory_region_get_ram_ptr(&d->pci.vga.vram); - break; - case QXL_VRAM_RANGE_INDEX: - case 4 /* vram 64bit */: - virt_start = (intptr_t)memory_region_get_ram_ptr(&d->pci.vram_bar); - break; - default: - /* should not happen */ - qxl_set_guest_bug(d, "%s: pci_region = %d", __func__, pci_region); - return 1; - } - - 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; - qxl_rom_set_dirty(d); - - qemu_spice_add_memslot(&d->ssd, &memslot, async); - 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; - return 0; -} - -static void qxl_del_memslot(QXLDevice *d, uint32_t slot_id) -{ - qemu_spice_del_memslot(&d->ssd, MEMSLOT_GROUP_HOST, slot_id); - d->guest_slots[slot_id].active = 0; -} - -static void qxl_reset_memslots(QXLDevice *d) -{ - qxl_spice_reset_memslots(d); - memset(&d->guest_slots, 0, sizeof(d->guest_slots)); -} - -static void qxl_reset_surfaces(QXLDevice *d) -{ - trace_qxl_reset_surfaces(d->id); - d->pci.mode = QXL_MODE_UNDEFINED; - qxl_spice_destroy_surfaces(d, QXL_SYNC); -} - -/* can be also called from spice server thread context */ -void *qxl_phys2virt(QXLDevice *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 *)(intptr_t)offset; - case MEMSLOT_GROUP_GUEST: - if (slot >= NUM_MEMSLOTS) { - qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot, - NUM_MEMSLOTS); - return NULL; - } - if (!qxl->guest_slots[slot].active) { - qxl_set_guest_bug(qxl, "inactive slot %d\n", slot); - return NULL; - } - if (offset < qxl->guest_slots[slot].delta) { - qxl_set_guest_bug(qxl, - "slot %d offset %"PRIu64" < delta %"PRIu64"\n", - slot, offset, qxl->guest_slots[slot].delta); - return NULL; - } - offset -= qxl->guest_slots[slot].delta; - if (offset > qxl->guest_slots[slot].size) { - qxl_set_guest_bug(qxl, - "slot %d offset %"PRIu64" > size %"PRIu64"\n", - slot, offset, qxl->guest_slots[slot].size); - return NULL; - } - return qxl->guest_slots[slot].ptr + offset; - } - return NULL; -} - -static void qxl_create_guest_primary_complete(QXLDevice *qxl) -{ - /* for local rendering */ - qxl_render_resize(qxl); -} - -static void qxl_create_guest_primary(QXLDevice *qxl, int loadvm, - qxl_async_io async) -{ - QXLDevSurfaceCreate surface; - QXLSurfaceCreate *sc = &qxl->guest_primary.surface; - int size; - int requested_height = le32_to_cpu(sc->height); - int requested_stride = le32_to_cpu(sc->stride); - - size = abs(requested_stride) * requested_height; - if (size > qxl->pci.vgamem_size) { - qxl_set_guest_bug(qxl, "%s: requested primary larger then framebuffer" - " size", __func__); - return; - } - - if (qxl->pci.mode == QXL_MODE_NATIVE) { - qxl_set_guest_bug(qxl, "%s: nop since already in QXL_MODE_NATIVE", - __func__); - } - qxl_exit_vga_mode(qxl); - - 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); - trace_qxl_create_guest_primary(qxl->id, sc->width, sc->height, sc->mem, - sc->format, sc->position); - trace_qxl_create_guest_primary_rest(qxl->id, sc->stride, sc->type, - sc->flags); - - surface.mouse_mode = true; - surface.group_id = MEMSLOT_GROUP_GUEST; - if (loadvm) { - surface.flags |= QXL_SURF_FLAG_KEEP_DATA; - } - - qxl->pci.mode = QXL_MODE_NATIVE; - qxl->cmdflags = 0; - qemu_spice_create_primary_surface(&qxl->ssd, 0, &surface, async); - - if (async == QXL_SYNC) { - qxl_create_guest_primary_complete(qxl); - } -} - -/* return 1 if surface destoy was initiated (in QXL_ASYNC case) or - * done (in QXL_SYNC case), 0 otherwise. */ -static int qxl_destroy_primary(QXLDevice *d, qxl_async_io async) -{ - if (d->pci.mode == QXL_MODE_UNDEFINED) { - return 0; - } - trace_qxl_destroy_primary(d->id); - d->pci.mode = QXL_MODE_UNDEFINED; - qemu_spice_destroy_primary_surface(&d->ssd, 0, async); - qxl_spice_reset_cursor(d); - return 1; -} - -static void qxl_set_mode(QXLDevice *d, int modenr, int loadvm) -{ - pcibus_t start = d->pci.pci.io_regions[QXL_RAM_RANGE_INDEX].addr; - pcibus_t end = d->pci.pci.io_regions[QXL_RAM_RANGE_INDEX].size + start; - QXLMode *mode = d->modes->modes + modenr; - uint64_t devmem = d->pci.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, - .flags = loadvm ? QXL_SURF_FLAG_KEEP_DATA : 0, - .mouse_mode = true, - .mem = devmem + d->shadow_rom.draw_area_offset, - }; - - trace_qxl_set_mode(d->id, modenr, mode->x_res, mode->y_res, mode->bits, - devmem); - if (!loadvm) { - qxl_hard_reset(d, 0); - } - - d->guest_slots[0].slot = slot; - assert(qxl_add_memslot(d, 0, devmem, QXL_SYNC) == 0); - - d->guest_primary.surface = surface; - qxl_create_guest_primary(d, 0, QXL_SYNC); - - d->pci.mode = QXL_MODE_COMPAT; - d->cmdflags = QXL_COMMAND_FLAG_COMPAT; -#ifdef QXL_COMMAND_FLAG_COMPAT_16BPP /* new in spice 0.6.1 */ - if (mode->bits == 16) { - d->cmdflags |= QXL_COMMAND_FLAG_COMPAT_16BPP; - } -#endif - 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, target_phys_addr_t addr, - uint64_t val, unsigned size) -{ - QXLDevice *d = opaque; - uint32_t io_port = addr; - qxl_async_io async = QXL_SYNC; - uint32_t orig_io_port = io_port; - - if (d->pci.guest_bug && !io_port == QXL_IO_RESET) { - return; - } - - 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: - case QXL_IO_UPDATE_IRQ: - case QXL_IO_LOG: - case QXL_IO_MEMSLOT_ADD_ASYNC: - case QXL_IO_CREATE_PRIMARY_ASYNC: - break; - default: - if (d->pci.mode != QXL_MODE_VGA) { - break; - } - trace_qxl_io_unexpected_vga_mode(d->id, - io_port, io_port_to_string(io_port)); - /* be nice to buggy guest drivers */ - if (io_port >= QXL_IO_UPDATE_AREA_ASYNC && - io_port <= QXL_IO_DESTROY_ALL_SURFACES_ASYNC) { - qxl_send_events(d, QXL_INTERRUPT_IO_CMD); - } - return; - } - - /* we change the io_port to avoid ifdeffery in the main switch */ - orig_io_port = io_port; - switch (io_port) { - case QXL_IO_UPDATE_AREA_ASYNC: - io_port = QXL_IO_UPDATE_AREA; - goto async_common; - case QXL_IO_MEMSLOT_ADD_ASYNC: - io_port = QXL_IO_MEMSLOT_ADD; - goto async_common; - case QXL_IO_CREATE_PRIMARY_ASYNC: - io_port = QXL_IO_CREATE_PRIMARY; - goto async_common; - case QXL_IO_DESTROY_PRIMARY_ASYNC: - io_port = QXL_IO_DESTROY_PRIMARY; - goto async_common; - case QXL_IO_DESTROY_SURFACE_ASYNC: - io_port = QXL_IO_DESTROY_SURFACE_WAIT; - goto async_common; - case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: - io_port = QXL_IO_DESTROY_ALL_SURFACES; - goto async_common; - case QXL_IO_FLUSH_SURFACES_ASYNC: -async_common: - async = QXL_ASYNC; - qemu_mutex_lock(&d->async_lock); - if (d->current_async != QXL_UNDEFINED_IO) { - qxl_set_guest_bug(d, "%d async started before last (%d) complete", - io_port, d->current_async); - qemu_mutex_unlock(&d->async_lock); - return; - } - d->current_async = orig_io_port; - qemu_mutex_unlock(&d->async_lock); - break; - default: - break; - } - trace_qxl_io_write(d->id, qxl_mode_to_string(d->pci.mode), addr, val, size, - async); - - switch (io_port) { - case QXL_IO_UPDATE_AREA: - { - QXLCookie *cookie = NULL; - QXLRect update = d->ram->update_area; - - if (async == QXL_ASYNC) { - cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, - QXL_IO_UPDATE_AREA_ASYNC); - cookie->u.area = update; - } - qxl_spice_update_area(d, d->ram->update_surface, - cookie ? &cookie->u.area : &update, - NULL, 0, 0, async, cookie); - break; - } - case QXL_IO_NOTIFY_CMD: - qemu_spice_wakeup(&d->ssd); - break; - case QXL_IO_NOTIFY_CURSOR: - qemu_spice_wakeup(&d->ssd); - break; - case QXL_IO_UPDATE_IRQ: - qxl_update_irq(d); - break; - case QXL_IO_NOTIFY_OOM: - if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) { - break; - } - d->oom_running = 1; - qxl_spice_oom(d); - d->oom_running = 0; - break; - case QXL_IO_SET_MODE: - qxl_set_mode(d, val, 0); - break; - case QXL_IO_LOG: - if (d->pci.guestdebug) { - fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id, - qemu_get_clock_ns(vm_clock), d->ram->log_buf); - } - break; - case QXL_IO_RESET: - qxl_hard_reset(d, 0); - break; - case QXL_IO_MEMSLOT_ADD: - if (val >= NUM_MEMSLOTS) { - qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: val out of range"); - break; - } - if (d->guest_slots[val].active) { - qxl_set_guest_bug(d, - "QXL_IO_MEMSLOT_ADD: memory slot already active"); - break; - } - d->guest_slots[val].slot = d->ram->mem_slot; - qxl_add_memslot(d, val, 0, async); - break; - case QXL_IO_MEMSLOT_DEL: - if (val >= NUM_MEMSLOTS) { - qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_DEL: val out of range"); - break; - } - qxl_del_memslot(d, val); - break; - case QXL_IO_CREATE_PRIMARY: - if (val != 0) { - qxl_set_guest_bug(d, "QXL_IO_CREATE_PRIMARY (async=%d): val != 0", - async); - goto cancel_async; - } - d->guest_primary.surface = d->ram->create_surface; - qxl_create_guest_primary(d, 0, async); - break; - case QXL_IO_DESTROY_PRIMARY: - if (val != 0) { - qxl_set_guest_bug(d, "QXL_IO_DESTROY_PRIMARY (async=%d): val != 0", - async); - goto cancel_async; - } - if (!qxl_destroy_primary(d, async)) { - trace_qxl_io_destroy_primary_ignored(d->id, - qxl_mode_to_string(d->pci.mode)); - goto cancel_async; - } - break; - case QXL_IO_DESTROY_SURFACE_WAIT: - if (val >= NUM_SURFACES) { - qxl_set_guest_bug(d, "QXL_IO_DESTROY_SURFACE (async=%d):" - "%" PRIu64 " >= NUM_SURFACES", async, val); - goto cancel_async; - } - qxl_spice_destroy_surface_wait(d, val, async); - break; - case QXL_IO_FLUSH_RELEASE: { - QXLReleaseRing *ring = &d->ram->release_ring; - if (ring->prod - ring->cons + 1 == ring->num_items) { - fprintf(stderr, - "ERROR: no flush, full release ring [p%d,%dc]\n", - ring->prod, ring->cons); - } - qxl_push_free_res(d, 1 /* flush */); - break; - } - case QXL_IO_FLUSH_SURFACES_ASYNC: - qxl_spice_flush_surfaces_async(d); - break; - case QXL_IO_DESTROY_ALL_SURFACES: - d->pci.mode = QXL_MODE_UNDEFINED; - qxl_spice_destroy_surfaces(d, async); - break; - default: - qxl_set_guest_bug(d, "%s: unexpected ioport=0x%x\n", __func__, io_port); - } - return; -cancel_async: - if (async) { - qxl_send_events(d, QXL_INTERRUPT_IO_CMD); - qemu_mutex_lock(&d->async_lock); - d->current_async = QXL_UNDEFINED_IO; - qemu_mutex_unlock(&d->async_lock); - } -} - -static uint64_t ioport_read(void *opaque, target_phys_addr_t addr, - unsigned size) -{ - QXLDevice *d = opaque; - - trace_qxl_io_read_unexpected(d->id); - return 0xff; -} - -static const MemoryRegionOps qxl_io_ops = { - .read = ioport_read, - .write = ioport_write, - .valid = { - .min_access_size = 1, - .max_access_size = 1, - }, -}; - -static void pipe_read(void *opaque) -{ - QXLDevice *d = opaque; - char dummy; - int len; - - do { - len = read(d->pipe[0], &dummy, sizeof(dummy)); - } while (len == sizeof(dummy)); - qxl_update_irq(d); -} - -static void qxl_send_events(QXLDevice *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 (qemu_thread_is_self(&d->main)) { - qxl_update_irq(d); - } else { - if (write(d->pipe[1], d, 1) != 1) { - dprint(d, 1, "%s: write to pipe failed\n", __func__); - } - } -} - -static void init_pipe_signaling(QXLDevice *d) -{ - if (pipe(d->pipe) < 0) { - fprintf(stderr, "%s:%s: qxl pipe creation failed\n", - __FILE__, __func__); - exit(1); - } - fcntl(d->pipe[0], F_SETFL, O_NONBLOCK); - fcntl(d->pipe[1], F_SETFL, O_NONBLOCK); - fcntl(d->pipe[0], F_SETOWN, getpid()); - - qemu_thread_get_self(&d->main); - qemu_set_fd_handler(d->pipe[0], pipe_read, NULL, d); -} - -/* graphics console */ - -static void qxl_hw_update(void *opaque) -{ - QXLDevice *qxl = opaque; - VGACommonState *vga = &qxl->pci.vga; - - switch (qxl->pci.mode) { - case QXL_MODE_VGA: - vga->update(vga); - break; - case QXL_MODE_COMPAT: - case QXL_MODE_NATIVE: - qxl_render_update(qxl); - break; - default: - break; - } -} - -static void qxl_hw_invalidate(void *opaque) -{ - QXLDevice *qxl = opaque; - VGACommonState *vga = &qxl->pci.vga; - - vga->invalidate(vga); -} - -static void qxl_hw_screen_dump(void *opaque, const char *filename, bool cswitch) -{ - QXLDevice *qxl = opaque; - VGACommonState *vga = &qxl->pci.vga; - - switch (qxl->pci.mode) { - case QXL_MODE_COMPAT: - case QXL_MODE_NATIVE: - qxl_render_update(qxl); - ppm_save(filename, qxl->ssd.ds->surface); - break; - case QXL_MODE_VGA: - vga->screen_dump(vga, filename, cswitch); - break; - default: - break; - } -} - -static void qxl_hw_text_update(void *opaque, console_ch_t *chardata) -{ - QXLDevice *qxl = opaque; - VGACommonState *vga = &qxl->pci.vga; - - if (qxl->pci.mode == QXL_MODE_VGA) { - vga->text_update(vga, chardata); - return; - } -} - -static void qxl_dirty_surfaces(QXLDevice *qxl) -{ - intptr_t vram_start; - int i; - - if (qxl->pci.mode != QXL_MODE_NATIVE && qxl->pci.mode != QXL_MODE_COMPAT) { - return; - } - - /* dirty the primary surface */ - qxl_set_dirty(&qxl->pci.vga.vram, qxl->shadow_rom.draw_area_offset, - qxl->shadow_rom.surface0_area_size); - - vram_start = (intptr_t)memory_region_get_ram_ptr(&qxl->pci.vram_bar); - - /* dirty the off-screen surfaces */ - for (i = 0; i < NUM_SURFACES; i++) { - QXLSurfaceCmd *cmd; - intptr_t surface_offset; - int surface_size; - - if (qxl->guest_surfaces.cmds[i] == 0) { - continue; - } - - cmd = qxl_phys2virt(qxl, qxl->guest_surfaces.cmds[i], - MEMSLOT_GROUP_GUEST); - assert(cmd); - assert(cmd->type == QXL_SURFACE_CMD_CREATE); - surface_offset = (intptr_t)qxl_phys2virt(qxl, - cmd->u.surface_create.data, - MEMSLOT_GROUP_GUEST); - assert(surface_offset); - surface_offset -= vram_start; - surface_size = cmd->u.surface_create.height * - abs(cmd->u.surface_create.stride); - trace_qxl_surfaces_dirty(qxl->id, i, (int)surface_offset, surface_size); - qxl_set_dirty(&qxl->pci.vram_bar, surface_offset, surface_size); - } -} - -static void qxl_vm_change_state_handler(void *opaque, int running, - RunState state) -{ - QXLDevice *qxl = opaque; - qemu_spice_vm_change_state_handler(&qxl->ssd, running, state); - - if (running) { - /* - * if qxl_send_events was called from spice server context before - * migration ended, qxl_update_irq for these events might not have been - * called - */ - qxl_update_irq(qxl); - } else { - /* make sure surfaces are saved before migration */ - qxl_dirty_surfaces(qxl); - } -} - -/* display change listener */ - -static void display_update(struct DisplayState *ds, int x, int y, int w, int h) -{ - if (qxl0->pci.mode == QXL_MODE_VGA) { - qemu_spice_display_update(&qxl0->ssd, x, y, w, h); - } -} - -static void display_resize(struct DisplayState *ds) -{ - if (qxl0->pci.mode == QXL_MODE_VGA) { - qemu_spice_display_resize(&qxl0->ssd); - } -} - -static void display_refresh(struct DisplayState *ds) -{ - if (qxl0->pci.mode == QXL_MODE_VGA) { - qemu_spice_display_refresh(&qxl0->ssd); - } else { - qemu_mutex_lock(&qxl0->ssd.lock); - qemu_spice_cursor_refresh_unlocked(&qxl0->ssd); - qemu_mutex_unlock(&qxl0->ssd.lock); - } -} - -static DisplayChangeListener display_listener = { - .dpy_update = display_update, - .dpy_resize = display_resize, - .dpy_refresh = display_refresh, -}; - -static void qxl_init_ramsize(QXLDevice *qxl) -{ - /* vga mode framebuffer / primary surface (bar 0, first part) */ - if (qxl->pci.vgamem_size_mb < 8) { - qxl->pci.vgamem_size_mb = 8; - } - qxl->pci.vgamem_size = qxl->pci.vgamem_size_mb * 1024 * 1024; - - /* vga ram (bar 0, total) */ - if (qxl->ram_size_mb != -1) { - qxl->pci.vga.vram_size = qxl->ram_size_mb * 1024 * 1024; - } - if (qxl->pci.vga.vram_size < qxl->pci.vgamem_size * 2) { - qxl->pci.vga.vram_size = qxl->pci.vgamem_size * 2; - } - - /* vram32 (surfaces, 32bit, bar 1) */ - if (qxl->vram32_size_mb != -1) { - qxl->vram32_size = qxl->vram32_size_mb * 1024 * 1024; - } - if (qxl->vram32_size < 4096) { - qxl->vram32_size = 4096; - } - - /* vram (surfaces, 64bit, bar 4+5) */ - if (qxl->vram_size_mb != -1) { - qxl->vram_size = qxl->vram_size_mb * 1024 * 1024; - } - if (qxl->vram_size < qxl->vram32_size) { - qxl->vram_size = qxl->vram32_size; - } - - if (qxl->pci.revision == 1) { - qxl->vram32_size = 4096; - qxl->vram_size = 4096; - } - qxl->pci.vgamem_size = msb_mask(qxl->pci.vgamem_size * 2 - 1); - qxl->pci.vga.vram_size = msb_mask(qxl->pci.vga.vram_size * 2 - 1); - qxl->vram32_size = msb_mask(qxl->vram32_size * 2 - 1); - qxl->vram_size = msb_mask(qxl->vram_size * 2 - 1); -} - -static int qxl_init_common(QXLDevice *qxl) -{ - uint8_t* config = qxl->pci.pci.config; - uint32_t pci_device_rev; - uint32_t io_size; - - qxl->pci.mode = QXL_MODE_UNDEFINED; - qxl->pci.generation = 1; - qxl->num_memslots = NUM_MEMSLOTS; - qxl->num_surfaces = NUM_SURFACES; - qemu_mutex_init(&qxl->track_lock); - qemu_mutex_init(&qxl->async_lock); - qxl->current_async = QXL_UNDEFINED_IO; - qxl->pci.guest_bug = 0; - - switch (qxl->pci.revision) { - case 1: /* spice 0.4 -- qxl-1 */ - pci_device_rev = QXL_REVISION_STABLE_V04; - io_size = 8; - break; - case 2: /* spice 0.6 -- qxl-2 */ - pci_device_rev = QXL_REVISION_STABLE_V06; - io_size = 16; - break; - case 3: /* qxl-3 */ - default: - pci_device_rev = QXL_DEFAULT_REVISION; - io_size = msb_mask(QXL_IO_RANGE_SIZE * 2 - 1); - break; - } - - pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev); - pci_set_byte(&config[PCI_INTERRUPT_PIN], 1); - - qxl->pci.rom_size = qxl_rom_size(); - memory_region_init_ram(&qxl->pci.rom_bar,"qxl-pci.vrom", - qxl->pci.rom_size); - vmstate_register_ram(&qxl->pci.rom_bar, &qxl->pci.pci.qdev); - init_qxl_rom(qxl); - init_qxl_ram(qxl); - - memory_region_init_ram(&qxl->pci.vram_bar, "qxl-pci.vram", - qxl->vram_size); - vmstate_register_ram(&qxl->pci.vram_bar, &qxl->pci.pci.qdev); - memory_region_init_alias(&qxl->pci.vram32_bar, "qxl-pci.vram32", - &qxl->pci.vram_bar, 0, qxl->vram32_size); - - memory_region_init_io(&qxl->pci.io_bar, &qxl_io_ops, qxl, - "qxl-pci.ioports", io_size); - if (qxl->id == 0) { - vga_dirty_log_start(&qxl->pci.vga); - } - - - pci_register_bar(&qxl->pci.pci, QXL_IO_RANGE_INDEX, - PCI_BASE_ADDRESS_SPACE_IO, &qxl->pci.io_bar); - - pci_register_bar(&qxl->pci.pci, QXL_ROM_RANGE_INDEX, - PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->pci.rom_bar); - - pci_register_bar(&qxl->pci.pci, QXL_RAM_RANGE_INDEX, - PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->pci.vga.vram); - - pci_register_bar(&qxl->pci.pci, QXL_VRAM_RANGE_INDEX, - PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->pci.vram32_bar); - - if (qxl->vram32_size < qxl->vram_size) { - /* - * Make the 64bit vram bar show up only in case it is - * configured to be larger than the 32bit vram bar. - */ - pci_register_bar(&qxl->pci.pci, QXL_VRAM64_RANGE_INDEX, - PCI_BASE_ADDRESS_SPACE_MEMORY | - PCI_BASE_ADDRESS_MEM_TYPE_64 | - PCI_BASE_ADDRESS_MEM_PREFETCH, - &qxl->pci.vram_bar); - } - - /* print pci bar details */ - dprint(qxl, 1, "ram/%s: %d MB [region 0]\n", - qxl->id == 0 ? "pri" : "sec", - qxl->pci.vga.vram_size / (1024*1024)); - dprint(qxl, 1, "vram/32: %d MB [region 1]\n", - qxl->vram32_size / (1024*1024)); - dprint(qxl, 1, "vram/64: %d MB %s\n", - qxl->vram_size / (1024*1024), - qxl->vram32_size < qxl->vram_size ? "[region 4]" : "[unmapped]"); - - qxl->ssd.qxl.base.sif = &qxl_interface.base; - qxl->ssd.qxl.id = qxl->id; - qemu_spice_add_interface(&qxl->ssd.qxl.base); - qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl); - - init_pipe_signaling(qxl); - qxl_reset_state(qxl); - - qxl->update_area_bh = qemu_bh_new(qxl_render_update_area_bh, qxl); - - return 0; -} - -static int qxl_init_primary(PCIDevice *dev) -{ - QXLDevice *qxl = (QXLDevice *) DO_UPCAST(PCIQXLDevice, pci, dev); - VGACommonState *vga = &qxl->pci.vga; - PortioList *qxl_vga_port_list = g_new(PortioList, 1); - - qxl->id = 0; - qxl_init_ramsize(qxl); - vga->vram_size_mb = qxl->pci.vga.vram_size >> 20; - vga_common_init(vga); - vga_init(vga, pci_address_space(dev), pci_address_space_io(dev), false); - portio_list_init(qxl_vga_port_list, qxl_vga_portio_list, vga, "vga"); - portio_list_add(qxl_vga_port_list, pci_address_space_io(dev), 0x3b0); - - vga->ds = graphic_console_init(qxl_hw_update, qxl_hw_invalidate, - qxl_hw_screen_dump, qxl_hw_text_update, qxl); - qemu_spice_display_init_common(&qxl->ssd, vga->ds); - - qxl0 = qxl; - register_displaychangelistener(vga->ds, &display_listener); - - return qxl_init_common(qxl); -} - -static int qxl_init_secondary(PCIDevice *dev) -{ - static int device_id = 1; - QXLDevice *qxl = (QXLDevice *) DO_UPCAST(PCIQXLDevice, pci, dev); - - qxl->id = device_id++; - qxl_init_ramsize(qxl); - memory_region_init_ram(&qxl->pci.vga.vram, "qxl-pci.vgavram", qxl->pci.vga.vram_size); - vmstate_register_ram(&qxl->pci.vga.vram, &qxl->pci.pci.qdev); - qxl->pci.vga.vram_ptr = memory_region_get_ram_ptr(&qxl->pci.vga.vram); - - return qxl_init_common(qxl); -} - -static void qxl_pre_save(void *opaque) -{ - QXLDevice* d = opaque; - uint8_t *ram_start = d->pci.vga.vram_ptr; - - trace_qxl_pre_save(d->id); - 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->pci.vga.vram_size); -} - -static int qxl_pre_load(void *opaque) -{ - QXLDevice* d = opaque; - - trace_qxl_pre_load(d->id); - qxl_hard_reset(d, 1); - qxl_exit_vga_mode(d); - return 0; -} - -static void qxl_create_memslots(QXLDevice *d) -{ - int i; - - for (i = 0; i < NUM_MEMSLOTS; i++) { - if (!d->guest_slots[i].active) { - continue; - } - qxl_add_memslot(d, i, 0, QXL_SYNC); - } -} - -static int qxl_post_load(void *opaque, int version) -{ - QXLDevice *d = opaque; - uint8_t *ram_start = d->pci.vga.vram_ptr; - QXLCommandExt *cmds; - int in, out, newmode; - - assert(d->last_release_offset < d->pci.vga.vram_size); - if (d->last_release_offset == 0) { - d->last_release = NULL; - } else { - d->last_release = (QXLReleaseInfo *)(ram_start + d->last_release_offset); - } - - d->modes = (QXLModes*)((uint8_t*)d->rom + d->rom->modes_offset); - - trace_qxl_post_load(d->id, qxl_mode_to_string(d->pci.mode)); - newmode = d->pci.mode; - d->pci.mode = QXL_MODE_UNDEFINED; - - switch (newmode) { - case QXL_MODE_UNDEFINED: - break; - case QXL_MODE_VGA: - qxl_create_memslots(d); - qxl_enter_vga_mode(d); - break; - case QXL_MODE_NATIVE: - qxl_create_memslots(d); - qxl_create_guest_primary(d, 1, QXL_SYNC); - - /* replay surface-create and cursor-set commands */ - cmds = g_malloc0(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++; - } - if (d->guest_cursor) { - cmds[out].cmd.data = d->guest_cursor; - cmds[out].cmd.type = QXL_CMD_CURSOR; - cmds[out].group_id = MEMSLOT_GROUP_GUEST; - out++; - } - qxl_spice_loadvm_commands(d, cmds, out); - g_free(cmds); - - break; - case QXL_MODE_COMPAT: - /* note: no need to call qxl_create_memslots, qxl_set_mode - * creates the mem slot. */ - qxl_set_mode(d, d->shadow_rom.mode, 1); - break; - } - return 0; -} - -#define QXL_SAVE_VERSION 21 - -static VMStateDescription qxl_memslot = { - .name = "qxl-memslot", - .version_id = QXL_SAVE_VERSION, - .minimum_version_id = QXL_SAVE_VERSION, - .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_SAVE_VERSION, - .minimum_version_id = QXL_SAVE_VERSION, - .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_SAVE_VERSION, - .minimum_version_id = QXL_SAVE_VERSION, - .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, 0, vmstate_vga_common, VGACommonState), - VMSTATE_UINT32(shadow_rom.mode, QXLDevice), - VMSTATE_UINT32(num_free_res, QXLDevice), - VMSTATE_UINT32(last_release_offset, QXLDevice), - VMSTATE_UINT32(mode, PCIQXLDevice), - VMSTATE_UINT32(ssd.unique, QXLDevice), - VMSTATE_INT32_EQUAL(num_memslots, QXLDevice), - VMSTATE_STRUCT_ARRAY(guest_slots, QXLDevice, NUM_MEMSLOTS, 0, - qxl_memslot, struct guest_slots), - VMSTATE_STRUCT(guest_primary.surface, QXLDevice, 0, - qxl_surface, QXLSurfaceCreate), - VMSTATE_INT32_EQUAL(num_surfaces, QXLDevice), - VMSTATE_ARRAY(guest_surfaces.cmds, QXLDevice, NUM_SURFACES, 0, - vmstate_info_uint64, uint64_t), - VMSTATE_UINT64(guest_cursor, QXLDevice), - VMSTATE_END_OF_LIST() - }, -}; - -static Property qxl_properties[] = { - DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, - 64 * 1024 * 1024), - DEFINE_PROP_UINT32("vram_size", QXLDevice, vram32_size, - 64 * 1024 * 1024), - DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision, - QXL_DEFAULT_REVISION), - DEFINE_PROP_UINT32("debug", QXLDevice, debug, 0), - DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0), - DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0), - DEFINE_PROP_UINT32("ram_size_mb", QXLDevice, ram_size_mb, -1), - DEFINE_PROP_UINT32("vram_size_mb", QXLDevice, vram32_size_mb, -1), - DEFINE_PROP_UINT32("vram64_size_mb", QXLDevice, vram_size_mb, -1), - DEFINE_PROP_UINT32("vgamem_mb", PCIQXLDevice, vgamem_size_mb, 16), - DEFINE_PROP_END_OF_LIST(), -}; - -static void qxl_primary_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->no_hotplug = 1; - k->init = qxl_init_primary; - k->romfile = "vgabios-qxl.bin"; - k->vendor_id = REDHAT_PCI_VENDOR_ID; - k->device_id = QXL_DEVICE_ID_STABLE; - k->class_id = PCI_CLASS_DISPLAY_VGA; - dc->desc = "Spice QXL GPU (primary, vga compatible)"; - dc->reset = qxl_reset_handler; - dc->vmsd = &qxl_vmstate; - dc->props = qxl_properties; -} - -static TypeInfo qxl_primary_info = { - .name = "qxl-vga", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(QXLDevice), - .class_init = qxl_primary_class_init, -}; - -static void qxl_secondary_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); - - k->init = qxl_init_secondary; - k->vendor_id = REDHAT_PCI_VENDOR_ID; - k->device_id = QXL_DEVICE_ID_STABLE; - k->class_id = PCI_CLASS_DISPLAY_OTHER; - dc->desc = "Spice QXL GPU (secondary)"; - dc->reset = qxl_reset_handler; - dc->vmsd = &qxl_vmstate; - dc->props = qxl_properties; -} - -static TypeInfo qxl_secondary_info = { - .name = "qxl", - .parent = TYPE_PCI_DEVICE, - .instance_size = sizeof(QXLDevice), - .class_init = qxl_secondary_class_init, -}; - -static void qxl_register_types(void) -{ - type_register_static(&qxl_primary_info); - type_register_static(&qxl_secondary_info); -} - -type_init(qxl_register_types) diff --git a/hw/qxl.h b/hw/qxl.h index f25e341..516e7da 100644 --- a/hw/qxl.h +++ b/hw/qxl.h @@ -143,6 +143,44 @@ typedef struct QXLDevice { } \ } while (0) +/* + * NOTE: SPICE_RING_PROD_ITEM accesses memory on the pci bar and as + * such can be changed by the guest, so to avoid a guest trigerrable + * abort we just qxl_set_guest_bug and set the return to NULL. Still + * it may happen as a result of emulator bug as well. + */ +#undef SPICE_RING_PROD_ITEM +#define SPICE_RING_PROD_ITEM(qxl, 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))) { \ + qxl_set_guest_bug(qxl, "SPICE_RING_PROD_ITEM indices mismatch " \ + "! %p <= %p < %p", (uint8_t *)start, \ + (uint8_t *)m_item, (uint8_t *)end); \ + ret = NULL; \ + } else { \ + ret = &m_item->el; \ + } \ + } + +#undef SPICE_RING_CONS_ITEM +#define SPICE_RING_CONS_ITEM(qxl, 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))) { \ + qxl_set_guest_bug(qxl, "SPICE_RING_CONS_ITEM indices mismatch " \ + "! %p <= %p < %p", (uint8_t *)start, \ + (uint8_t *)m_item, (uint8_t *)end); \ + ret = NULL; \ + } else { \ + ret = &m_item->el; \ + } \ + } + /* qxl.c */ void *qxl_phys2virt(QXLDevice *qxl, QXLPHYSICAL phys, int group_id); void qxl_set_guest_bug(QXLDevice *qxl, const char *msg, ...) @@ -158,6 +196,22 @@ void qxl_spice_oom(QXLDevice *qxl); void qxl_spice_reset_memslots(QXLDevice *qxl); void qxl_spice_reset_image_cache(QXLDevice *qxl); void qxl_spice_reset_cursor(QXLDevice *qxl); +void init_qxl_rom(QXLDevice *d); +void init_qxl_ram(QXLDevice *d); +void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker); +void interface_set_compression_level(QXLInstance *sin, int level); +void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time); +void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info); +int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext); +int interface_req_cmd_notification(QXLInstance *sin); +void interface_release_resource(QXLInstance *sin, struct QXLReleaseInfoExt ext); +int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext); +int interface_req_cursor_notification(QXLInstance *sin); +void interface_notify_update(QXLInstance *sin, uint32_t update_id); +int interface_flush_resources(QXLInstance *sin); +void interface_update_area_complete(QXLInstance *sin, uint32_t surface_id, QXLRect *dirty, uint32_t num_updated_rects); +void interface_async_complete(QXLInstance *sin, uint64_t cookie_token); +ram_addr_t qxl_rom_size(void); /* qxl-logger.c */ int qxl_log_cmd_cursor(QXLDevice *qxl, QXLCursorCmd *cmd, int group_id); @@ -169,3 +223,69 @@ void qxl_render_update(QXLDevice *qxl); int qxl_render_cursor(QXLDevice *qxl, QXLCommandExt *ext); void qxl_render_update_area_done(QXLDevice *qxl, QXLCookie *cookie); void qxl_render_update_area_bh(void *opaque); + +/* qxl-pci.c */ +void qxl_send_events(QXLDevice *d, uint32_t events); +int qxl_destroy_primary(QXLDevice *d, qxl_async_io async); +void qxl_reset_memslots(QXLDevice *d); +void qxl_reset_surfaces(QXLDevice *d); +void qxl_ring_set_dirty(QXLDevice *qxl); +void qxl_rom_set_dirty(QXLDevice *qxl); +void qxl_ram_set_dirty(QXLDevice *qxl, void *ptr); +void interface_async_complete_io(QXLDevice *qxl, QXLCookie *cookie); +int interface_get_command_pci(QXLDevice *qxl, struct QXLCommandExt *ext); +int interface_get_cursor_command_pci(QXLDevice *qxl, struct QXLCommandExt *ext); +const char *qxl_mode_to_string(int mode); + +/* called from spice server thread context only */ +static inline void qxl_push_free_res(QXLDevice *d, int flush) +{ + QXLReleaseRing *ring = &d->ram->release_ring; + uint64_t *item; + int notify; + +#define QXL_FREE_BUNCH_SIZE 32 + + if (ring->prod - ring->cons + 1 == ring->num_items) { + /* ring full -- can't push */ + return; + } + if (!flush && d->oom_running) { + /* collect everything from oom handler before pushing */ + return; + } + if (!flush && d->num_free_res < QXL_FREE_BUNCH_SIZE) { + /* collect a bit more before pushing */ + return; + } + + SPICE_RING_PUSH(ring, notify); + trace_qxl_ring_res_push(d->id, qxl_mode_to_string(d->pci.mode), + d->guest_surfaces.count, d->num_free_res, + d->last_release, notify ? "yes" : "no"); + trace_qxl_ring_res_push_rest(d->id, ring->prod - ring->cons, + ring->num_items, ring->prod, ring->cons); + if (notify) { + qxl_send_events(d, QXL_INTERRUPT_DISPLAY); + } + SPICE_RING_PROD_ITEM(d, ring, item); + if (!item) { + return; + } + *item = 0; + d->num_free_res = 0; + d->last_release = NULL; + qxl_ring_set_dirty(d); +} + +static inline uint32_t msb_mask(uint32_t val) +{ + uint32_t mask; + + do { + mask = ~(val - 1) & val; + val &= ~mask; + } while (mask < val); + + return mask; +}