[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 6/8] virtio-gpu: v0.2 of the virtio based GPU code.
From: |
Dave Airlie |
Subject: |
[Qemu-devel] [PATCH 6/8] virtio-gpu: v0.2 of the virtio based GPU code. |
Date: |
Tue, 10 Dec 2013 14:05:56 +1000 |
From: Dave Airlie <address@hidden>
This is the basic virtio-gpu which is
multi-head capable,
ARGB cursor support,
unaccelerated.
Some more info is in docs/specs/virtio-gpu.txt.
changes since v0.1:
add reset handling
fix display info response
fix cursor generation issues
drop 3d stuff that snuck in
Signed-off-by: Dave Airlie <address@hidden>
---
default-configs/x86_64-softmmu.mak | 1 +
docs/specs/virtio-gpu.txt | 89 +++++
hw/display/Makefile.objs | 2 +
hw/display/virtgpu_hw.h | 142 ++++++++
hw/display/virtio-gpu.c | 649 +++++++++++++++++++++++++++++++++++++
hw/virtio/virtio-pci.c | 49 +++
hw/virtio/virtio-pci.h | 15 +
include/hw/pci/pci.h | 1 +
include/hw/virtio/virtio-gpu.h | 89 +++++
9 files changed, 1037 insertions(+)
create mode 100644 docs/specs/virtio-gpu.txt
create mode 100644 hw/display/virtgpu_hw.h
create mode 100644 hw/display/virtio-gpu.c
create mode 100644 include/hw/virtio/virtio-gpu.h
diff --git a/default-configs/x86_64-softmmu.mak
b/default-configs/x86_64-softmmu.mak
index 31bddce..1a00b78 100644
--- a/default-configs/x86_64-softmmu.mak
+++ b/default-configs/x86_64-softmmu.mak
@@ -9,6 +9,7 @@ CONFIG_VGA_PCI=y
CONFIG_VGA_ISA=y
CONFIG_VGA_CIRRUS=y
CONFIG_VMWARE_VGA=y
+CONFIG_VIRTIO_GPU=y
CONFIG_VMMOUSE=y
CONFIG_SERIAL=y
CONFIG_PARALLEL=y
diff --git a/docs/specs/virtio-gpu.txt b/docs/specs/virtio-gpu.txt
new file mode 100644
index 0000000..987d807
--- /dev/null
+++ b/docs/specs/virtio-gpu.txt
@@ -0,0 +1,89 @@
+Initial info on virtio-gpu:
+
+virtio-gpu is a virtio based graphics adapter. The initial version provides
support for:
+
+- ARGB Hardware cursors
+- multiple outputs
+
+There are no acceleration features available in the first release of this
device, 3D acceleration features will be provided later via an interface to an
external renderer.
+
+The virtio-gpu exposes 3 vqs to the guest,
+
+1) control vq - guest->host queue for sending commands and getting responses
when required.
+2) cursor vq - guest->host queue for sending cursor position and resource
updates
+3) event vq - host->guest queue for sending async events like display
topography updates and errors (todo).
+
+How to use the virtio-gpu:
+
+The virtio-gpu is based around the concept of resources private to the host,
the guest must DMA transfer into these resources. This is a design requirement
in order to interface with future 3D rendering. In the unaccelerated there is
no support for DMA transfers from resources, just to them.
+
+Resources are initially simple 2D resources, consisting of a width, height and
format along with an identifier. The guest must then attach backing store to
the resources in order for DMA transfers to work. This is like a GART in a real
GPU.
+
+A typical guest user would create a 2D resource using
VIRTGPU_CMD_RESOURCE_CREATE_2D, attach backing store using
VIRTGPU_CMD_RESOURCE_ATTACH_BACKING, then attach the resource to a scanout
using VIRTGPU_CMD_SET_SCANOUT, then use VIRTGPU_CMD_TRANSFER_SEND_2D to send
updates to the resource, and finally VIRTGPU_CMD_RESOURCE_FLUSH to flush the
scanout buffers to screen.
+
+Command queue:
+
+VIRTGPU_CMD_GET_DISPLAY_INFO:
+Retrieve the current output configuration.
+
+This sends a response in the same queue slot. The response contains the max
number of scanouts the host can support,
+along with a list of per-scanout information. The info contains whether the
+scanout is enabled, what its preferred x, y, width and height are and some
future flags.
+
+VIRTGPU_CMD_RESOURCE_CREATE_2D:
+Create a 2D resource on the host.
+
+This creates a 2D resource on the host with the specified width, height and
format. Only a small subset of formats are support. The resource ids are
generated by the guest.
+
+VIRTGPU_CMD_RESOURCE_UNREF:
+Destroy a resource.
+
+This informs the host that a resource is no longer required by the guest.
+
+VIRTGPU_CMD_SET_SCANOUT:
+Set the scanout parameters for a single output.
+
+This sets the scanout parameters for a single scanout. The resource_id is the
+resource to be scanned out from, along with a rectangle specified by x, y,
width and height.
+
+VIRTGPU_CMD_RESOURCE_FLUSH:
+Flush a scanout resource
+
+This flushes a resource to screen, it takes a rectangle and a resource id,
+and flushes any scanouts the resource is being used on.
+
+VIRTGPU_CMD_TRANSFER_TO_HOST_2D:
+Transfer from guest memory to host resource.
+
+This takes a resource id along with an destination offset into the resource,
+and a box to transfer from the host backing for the resource.
+
+VIRTGPU_CMD_RESOURCE_ATTACH_BACKING:
+Assign backing pages to a resource.
+
+This assign an array of guest pages as the backing store for a resource. These
+pages are then used for the transfer operations for that resource from that
+point on.
+
+VIRTGPU_CMD_RESOURCE_DETACH_BACKING:
+Detach backing pages from a resource.
+
+This detaches any backing pages from a resource, to be used in case of
+guest swapping or object destruction.
+
+Cursor queue:
+The cursor queue accepts on structure per vq entry. The cursor is just a
standard 2D resource that is 64x64 sized. Transfers are used to upload the
cursor contents.
+
+For cursor movement, the cursor_x and cursor_y fields only need to be updated.
+
+For a cursor update where the guest has transferred a new cursor into the
resource, the hotspot, cursor id, should be updated, but the generation_id
should also be increased by one, so the host knows to refresh its copy of the
cursor data from the resource.
+
+Event queue:
+The only current event passed is a message to denote the host wants to update
the layout of the screens. It contains the same info as the response to
VIRTGPU_CMD_GET_DISPLAY_INFO.
+
+
+
+
+
+
+
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index 540df82..10e4066 100644
--- a/hw/display/Makefile.objs
+++ b/hw/display/Makefile.objs
@@ -32,3 +32,5 @@ obj-$(CONFIG_TCX) += tcx.o
obj-$(CONFIG_VGA) += vga.o
common-obj-$(CONFIG_QXL) += qxl.o qxl-logger.o qxl-render.o
+
+obj-$(CONFIG_VIRTIO_GPU) += virtio-gpu.o
diff --git a/hw/display/virtgpu_hw.h b/hw/display/virtgpu_hw.h
new file mode 100644
index 0000000..f915510
--- /dev/null
+++ b/hw/display/virtgpu_hw.h
@@ -0,0 +1,142 @@
+#ifndef VIRTGPU_HW_H
+#define VIRTGPU_HW_H
+
+#define VIRTGPU_CMD_HAS_RESP (1 << 31)
+
+enum virtgpu_ctrl_cmd {
+ VIRTGPU_CMD_NOP,
+ VIRTGPU_CMD_GET_DISPLAY_INFO = (1 | VIRTGPU_CMD_HAS_RESP),
+ /* reserved */
+ VIRTGPU_CMD_RESOURCE_CREATE_2D = 3,
+ VIRTGPU_CMD_RESOURCE_UNREF = 4,
+ VIRTGPU_CMD_SET_SCANOUT = 5,
+ VIRTGPU_CMD_RESOURCE_FLUSH = 6,
+ VIRTGPU_CMD_TRANSFER_TO_HOST_2D = 7,
+ VIRTGPU_CMD_RESOURCE_ATTACH_BACKING = 8,
+ VIRTGPU_CMD_RESOURCE_INVAL_BACKING = 9,
+};
+
+enum virtgpu_ctrl_event {
+ VIRTGPU_EVENT_NOP,
+ VIRTGPU_EVENT_DISPLAY_CHANGE,
+};
+
+/* data passed in the cursor vq */
+struct virtgpu_hw_cursor_page {
+ uint32_t cursor_x, cursor_y;
+ uint32_t cursor_hot_x, cursor_hot_y;
+ uint32_t cursor_id;
+ uint32_t generation_id;
+};
+
+struct virtgpu_resource_unref {
+ uint32_t resource_id;
+};
+
+/* create a simple 2d resource with a format */
+struct virtgpu_resource_create_2d {
+ uint32_t resource_id;
+ uint32_t format;
+ uint32_t width;
+ uint32_t height;
+};
+
+struct virtgpu_set_scanout {
+ uint32_t scanout_id;
+ uint32_t resource_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t x;
+ uint32_t y;
+};
+
+struct virtgpu_resource_flush {
+ uint32_t resource_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t x;
+ uint32_t y;
+};
+
+/* simple transfer to_host */
+struct virtgpu_transfer_to_host_2d {
+ uint32_t resource_id;
+ uint32_t offset;
+ uint32_t width;
+ uint32_t height;
+ uint32_t x;
+ uint32_t y;
+};
+
+struct virtgpu_mem_entry {
+ uint64_t addr;
+ uint32_t length;
+ uint32_t pad;
+};
+
+struct virtgpu_resource_attach_backing {
+ uint32_t resource_id;
+ uint32_t nr_entries;
+};
+
+struct virtgpu_resource_inval_backing {
+ uint32_t resource_id;
+};
+
+#define VIRTGPU_MAX_SCANOUTS 16
+struct virtgpu_display_info {
+ uint32_t num_scanouts;
+ struct {
+ uint32_t enabled;
+ uint32_t width;
+ uint32_t height;
+ uint32_t x;
+ uint32_t y;
+ uint32_t flags;
+ } pmodes[VIRTGPU_MAX_SCANOUTS];
+};
+
+struct virtgpu_command {
+ uint32_t type;
+ uint32_t flags;
+ uint64_t rsvd;
+ union virtgpu_cmds {
+ struct virtgpu_resource_create_2d resource_create_2d;
+ struct virtgpu_resource_unref resource_unref;
+ struct virtgpu_resource_flush resource_flush;
+ struct virtgpu_set_scanout set_scanout;
+ struct virtgpu_transfer_to_host_2d transfer_to_host_2d;
+ struct virtgpu_resource_attach_backing resource_attach_backing;
+ struct virtgpu_resource_inval_backing resource_inval_backing;
+ } u;
+};
+
+struct virtgpu_response {
+ uint32_t type;
+ uint32_t flags;
+ union virtgpu_resps {
+ struct virtgpu_display_info display_info;
+ } u;
+};
+
+struct virtgpu_event {
+ uint32_t type;
+ uint32_t err_code;
+ union virtgpu_events {
+ struct virtgpu_display_info display_info;
+ } u;
+};
+
+/* simple formats for fbcon/X use */
+enum virtgpu_formats {
+ VIRGL_FORMAT_B8G8R8A8_UNORM = 1,
+ VIRGL_FORMAT_B8G8R8X8_UNORM = 2,
+ VIRGL_FORMAT_A8R8G8B8_UNORM = 3,
+ VIRGL_FORMAT_X8R8G8B8_UNORM = 4,
+
+ VIRGL_FORMAT_B5G5R5A1_UNORM = 5,
+
+ VIRGL_FORMAT_R8_UNORM = 64,
+};
+
+#endif
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
new file mode 100644
index 0000000..7bf2fbb
--- /dev/null
+++ b/hw/display/virtio-gpu.c
@@ -0,0 +1,649 @@
+/*
+ * Virtio video device
+ *
+ * Copyright Red Hat
+ *
+ * Authors:
+ * Dave Airlie
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/iov.h"
+#include "ui/console.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "hw/virtio/virtio-bus.h"
+
+#include "virtgpu_hw.h"
+
+static struct virtgpu_simple_resource *virtgpu_find_resource(VirtIOGPU *g,
+ uint32_t resource_id);
+
+static void update_cursor_data(VirtIOGPU *g, struct virtgpu_simple_resource
*res)
+{
+ uint32_t pixels;
+ int i;
+ pixels = g->current_cursor->width * g->current_cursor->height;
+ for (i = 0; i < pixels; i++) {
+ uint32_t value = ((uint32_t *)pixman_image_get_data(res->image))[i];
+ g->current_cursor->data[i] = value;
+ }
+}
+
+static void update_cursor(VirtIOGPU *g, struct virtgpu_hw_cursor_page *cursor)
+{
+ if (g->cursor_generation_id != cursor->generation_id) {
+
+ if (!g->current_cursor)
+ g->current_cursor = cursor_alloc(64, 64);
+
+ g->current_cursor->hot_x = cursor->cursor_hot_x;
+ g->current_cursor->hot_y = cursor->cursor_hot_y;
+
+ if (cursor->cursor_id > 0) {
+ struct virtgpu_simple_resource *res;
+
+ res = virtgpu_find_resource(g, cursor->cursor_id);
+ if (res)
+ update_cursor_data(g, res);
+ }
+ g->cursor_generation_id = cursor->generation_id;
+ g->current_cursor_id = cursor->cursor_id;
+ dpy_cursor_define(g->con[0], g->current_cursor);
+ }
+
+ dpy_mouse_set(g->con[0], cursor->cursor_x, cursor->cursor_y,
cursor->cursor_id ? 1 : 0);
+}
+
+static void virtio_gpu_get_config(VirtIODevice *vdev, uint8_t *config)
+{
+
+}
+
+static void virtio_gpu_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+}
+
+static uint32_t virtio_gpu_get_features(VirtIODevice *vdev, uint32_t features)
+{
+ return features;
+}
+
+static void virtio_gpu_set_features(VirtIODevice *vdev, uint32_t features)
+{
+
+}
+
+static struct virtgpu_simple_resource *virtgpu_find_resource(VirtIOGPU *g,
+ uint32_t resource_id)
+{
+ struct virtgpu_simple_resource *res;
+
+ QLIST_FOREACH(res, &g->reslist, next) {
+ if (res->resource_id == resource_id)
+ return res;
+ }
+ return NULL;
+}
+
+static void virtgpu_fill_display_info(VirtIOGPU *g,
+ struct virtgpu_display_info *dpy_info)
+{
+ int i;
+ memset(dpy_info, 0, sizeof(*dpy_info));
+ dpy_info->num_scanouts = g->conf.max_outputs;
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ if (g->enabled_output_bitmask & (1 << i)) {
+ dpy_info->pmodes[i].enabled = 1;
+ dpy_info->pmodes[i].width = g->req_state[i].width;
+ dpy_info->pmodes[i].height = g->req_state[i].height;
+ }
+ }
+}
+
+static void virtgpu_get_display_info(VirtIOGPU *g,
+ struct iovec *iov,
+ unsigned int iov_cnt)
+{
+ struct virtgpu_response resp;
+
+ resp.type = VIRTGPU_CMD_GET_DISPLAY_INFO;
+ resp.flags = 0;
+ virtgpu_fill_display_info(g, &resp.u.display_info);
+
+ iov_from_buf(iov, iov_cnt, 0, &resp, sizeof(struct virtgpu_response));
+}
+
+static pixman_format_code_t get_pixman_format(uint32_t virtgpu_format)
+{
+ switch (virtgpu_format) {
+ case VIRGL_FORMAT_B8G8R8X8_UNORM:
+ return PIXMAN_x8r8g8b8;
+ case VIRGL_FORMAT_B8G8R8A8_UNORM:
+ return PIXMAN_a8r8g8b8;
+ default:
+ assert(0);
+ break;
+ }
+ return 0;
+}
+
+static void virtgpu_resource_create_2d(VirtIOGPU *g,
+ struct virtgpu_resource_create_2d *c2d)
+{
+ pixman_format_code_t pformat;
+ struct virtgpu_simple_resource *res;
+
+ res = calloc(1, sizeof(struct virtgpu_simple_resource));
+ if (!res)
+ return;
+
+ res->width = c2d->width;
+ res->height = c2d->height;
+ res->format = c2d->format;
+ res->resource_id = c2d->resource_id;
+
+ pformat = get_pixman_format(c2d->format);
+ res->image = pixman_image_create_bits(pformat,
+ c2d->width,
+ c2d->height,
+ NULL, 0);
+
+ QLIST_INSERT_HEAD(&g->reslist, res, next);
+}
+
+static void virtgpu_resource_destroy(struct virtgpu_simple_resource *res)
+{
+ pixman_image_unref(res->image);
+ QLIST_REMOVE(res, next);
+ free(res);
+}
+
+static void virtgpu_resource_unref(VirtIOGPU *g,
+ uint32_t resource_id)
+{
+ struct virtgpu_simple_resource *res = virtgpu_find_resource(g,
resource_id);
+
+ if (!res)
+ return;
+
+ virtgpu_resource_destroy(res);
+}
+
+static void virtgpu_transfer_to_host_2d(VirtIOGPU *g,
+ struct virtgpu_transfer_to_host_2d *t2d)
+{
+ struct virtgpu_simple_resource *res = virtgpu_find_resource(g,
t2d->resource_id);
+ int h;
+ uint32_t src_offset, dst_offset, stride;
+ int bpp;
+ pixman_format_code_t format;
+ if (!res)
+ return;
+
+ if (!res->iov)
+ return;
+
+ format = pixman_image_get_format(res->image);
+ bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8;
+ stride = pixman_image_get_stride(res->image);
+
+ if (t2d->offset || t2d->x || t2d->y || t2d->width !=
pixman_image_get_width(res->image)) {
+ for (h = 0; h < t2d->height; h++) {
+ src_offset = t2d->offset + stride * h;
+ dst_offset = (t2d->y + h) * stride + (t2d->x * bpp);
+
+ iov_to_buf(res->iov, res->iov_cnt, src_offset, (uint8_t
*)pixman_image_get_data(res->image) + dst_offset, t2d->width * bpp);
+ }
+ } else {
+ iov_to_buf(res->iov, res->iov_cnt, 0,
pixman_image_get_data(res->image), pixman_image_get_stride(res->image) *
pixman_image_get_height(res->image));
+ }
+}
+
+static void virtgpu_resource_flush(VirtIOGPU *g,
+ struct virtgpu_resource_flush *rf)
+{
+ struct virtgpu_simple_resource *res = virtgpu_find_resource(g,
rf->resource_id);
+ int i;
+ pixman_region16_t flush_region;
+
+ pixman_region_init_rect(&flush_region,
+ rf->x, rf->y, rf->width, rf->height);
+ for (i = 0; i < VIRTGPU_MAX_SCANOUT; i++) {
+ struct virtgpu_scanout *scanout;
+ pixman_region16_t region, finalregion;
+ pixman_box16_t *extents;
+
+ if (!(res->scanout_bitmask & (1 << i)))
+ continue;
+ scanout = &g->scanout[i];
+
+ pixman_region_init(&finalregion);
+ pixman_region_init_rect(®ion,
+ scanout->x, scanout->y, scanout->width,
scanout->height);
+
+ pixman_region_intersect(&finalregion, &flush_region, ®ion);
+ pixman_region_translate(&finalregion, -scanout->x, -scanout->y);
+ extents = pixman_region_extents(&finalregion);
+ /* work out the area we need to update for each console */
+ dpy_gfx_update(g->con[i], extents->x1, extents->y1, extents->x2 -
extents->x1,
+ extents->y2 - extents->y1);
+
+ pixman_region_fini(®ion);
+ pixman_region_fini(&finalregion);
+ }
+ pixman_region_fini(&flush_region);
+}
+
+static void virtgpu_set_scanout(VirtIOGPU *g,
+ struct virtgpu_set_scanout *ss)
+{
+ struct virtgpu_simple_resource *res = virtgpu_find_resource(g,
ss->resource_id);
+ uint32_t offset;
+ int bpp;
+ pixman_format_code_t format;
+
+ g->enable = 1;
+ if (ss->resource_id == 0) {
+ if (g->scanout[ss->scanout_id].resource_id) {
+ res = virtgpu_find_resource(g,
g->scanout[ss->scanout_id].resource_id);
+ if (res)
+ res->scanout_bitmask &= ~(1 << ss->scanout_id);
+ }
+ if (ss->scanout_id == 0)
+ return;
+ dpy_gfx_replace_surface(g->con[ss->scanout_id], NULL);
+ g->scanout[ss->scanout_id].ds = NULL;
+ g->scanout[ss->scanout_id].width = 0;
+ g->scanout[ss->scanout_id].height = 0;
+
+ dpy_notify_state(g->con[ss->scanout_id], 0, 0, 0, 0);
+ return;
+ }
+ /* create a surface for this scanout */
+
+ if (ss->scanout_id > 4) {
+ fprintf(stderr,"set scanout for non-0 surface %d\n", ss->scanout_id);
+ return;
+ }
+
+ format = pixman_image_get_format(res->image);
+ bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8;
+ offset = (ss->x * bpp) + ss->y * pixman_image_get_stride(res->image);
+
+ dpy_notify_state(g->con[ss->scanout_id], ss->x, ss->y, ss->width,
ss->height);
+ if (!g->scanout[ss->scanout_id].ds ||
+ surface_data(g->scanout[ss->scanout_id].ds) != ((uint8_t
*)pixman_image_get_data(res->image) + offset) ||
+ g->scanout[ss->scanout_id].width != ss->width ||
+ g->scanout[ss->scanout_id].height != ss->height) {
+ /* realloc the surface ptr */
+ g->scanout[ss->scanout_id].ds =
qemu_create_displaysurface_from(ss->width, ss->height, 32,
pixman_image_get_stride(res->image), (uint8_t
*)pixman_image_get_data(res->image) + offset, FALSE);
+ if (!g->scanout[ss->scanout_id].ds)
+ return;
+
+ dpy_gfx_replace_surface(g->con[ss->scanout_id],
g->scanout[ss->scanout_id].ds);
+ }
+
+ res->scanout_bitmask |= (1 << ss->scanout_id);
+
+
+ g->scanout[ss->scanout_id].resource_id = ss->resource_id;
+ g->scanout[ss->scanout_id].x = ss->x;
+ g->scanout[ss->scanout_id].y = ss->y;
+ g->scanout[ss->scanout_id].width = ss->width;
+ g->scanout[ss->scanout_id].height = ss->height;
+}
+
+static void virtgpu_resource_attach_backing(VirtIOGPU *g,
+ struct
virtgpu_resource_attach_backing *att_rb,
+ struct iovec *iov,
+ unsigned int iov_cnt)
+{
+ uint32_t gsize = iov_size(iov, iov_cnt);
+ struct iovec *res_iovs;
+ int i;
+ struct virtgpu_simple_resource *res = virtgpu_find_resource(g,
att_rb->resource_id);
+ void *data;
+ if (!res)
+ return;
+
+ res_iovs = malloc(att_rb->nr_entries * sizeof(struct iovec));
+ if (!res_iovs)
+ return;
+
+ if (iov_cnt > 1) {
+ data = malloc(gsize);
+ iov_to_buf(iov, iov_cnt, 0, data, gsize);
+ } else
+ data = iov[0].iov_base;
+
+ for (i = 0; i < att_rb->nr_entries; i++) {
+ struct virtgpu_mem_entry *ent = ((struct virtgpu_mem_entry *)data) + i;
+ hwaddr len;
+ res_iovs[i].iov_len = ent->length;
+
+ len = ent->length;
+ res_iovs[i].iov_base = cpu_physical_memory_map(ent->addr, &len, 1);
+ if (!res_iovs[i].iov_base || len != ent->length) {
+ fprintf(stderr, "virtgp: trying to map MMIO memory");
+ exit(1);
+ }
+ }
+
+ res->iov = res_iovs;
+ res->iov_cnt = att_rb->nr_entries;
+
+ if (iov_cnt > 1)
+ free(data);
+}
+
+static void virtgpu_resource_inval_backing(VirtIOGPU *g,
+ uint32_t resource_id)
+{
+ int i;
+ struct virtgpu_simple_resource *res = virtgpu_find_resource(g,
resource_id);
+
+ if (!res)
+ return;
+ if (!res->iov)
+ return;
+
+ for (i = 0; i < res->iov_cnt; i++) {
+ cpu_physical_memory_unmap(res->iov[i].iov_base, res->iov[i].iov_len, 1,
res->iov[i].iov_len);
+ }
+ free(res->iov);
+ res->iov_cnt = 0;
+ res->iov = NULL;
+}
+
+static void virtio_gpu_process_cmd(VirtIOGPU *g,
+ struct virtgpu_command *cmd,
+ struct iovec *iov,
+ unsigned int iov_cnt)
+{
+ switch (cmd->type) {
+ case VIRTGPU_CMD_GET_DISPLAY_INFO:
+ if (iov_cnt < 1)
+ return;
+ virtgpu_get_display_info(g, iov, iov_cnt);
+ break;
+ case VIRTGPU_CMD_RESOURCE_CREATE_2D:
+ virtgpu_resource_create_2d(g, &cmd->u.resource_create_2d);
+ break;
+ case VIRTGPU_CMD_RESOURCE_UNREF:
+ virtgpu_resource_unref(g, cmd->u.resource_unref.resource_id);
+ break;
+ case VIRTGPU_CMD_RESOURCE_FLUSH:
+ virtgpu_resource_flush(g, &cmd->u.resource_flush);
+ break;
+ case VIRTGPU_CMD_TRANSFER_TO_HOST_2D:
+ virtgpu_transfer_to_host_2d(g, &cmd->u.transfer_to_host_2d);
+ break;
+ case VIRTGPU_CMD_SET_SCANOUT:
+ virtgpu_set_scanout(g, &cmd->u.set_scanout);
+ break;
+ case VIRTGPU_CMD_RESOURCE_ATTACH_BACKING:
+ virtgpu_resource_attach_backing(g, &cmd->u.resource_attach_backing,
iov, iov_cnt);
+ break;
+ case VIRTGPU_CMD_RESOURCE_INVAL_BACKING:
+ virtgpu_resource_inval_backing(g,
cmd->u.resource_inval_backing.resource_id);
+ break;
+ case VIRTGPU_CMD_NOP:
+ break;
+ default:
+ fprintf(stderr,"got bad command from host %d\n", cmd->type);
+ break;
+ }
+}
+
+static void virtio_gpu_handle_ctrl_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ qemu_bh_schedule(g->ctrl_bh);
+}
+
+static void virtio_gpu_handle_cursor_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ qemu_bh_schedule(g->cursor_bh);
+}
+
+static void virtio_gpu_handle_event_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+
+static void virtio_gpu_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ struct iovec *iov;
+ VirtQueueElement elem;
+ struct virtgpu_command cmd;
+ size_t s;
+ unsigned int iov_cnt;
+
+ if (!virtio_queue_ready(vq))
+ return;
+ while (virtqueue_pop(vq, &elem)) {
+
+ iov = elem.out_sg;
+ iov_cnt = elem.out_num;
+
+ s = iov_to_buf(iov, iov_cnt, 0, &cmd, sizeof(cmd));
+ if (s != sizeof(cmd))
+ fprintf(stderr,"error\n");
+ else {
+ if (elem.in_num > 0)
+ virtio_gpu_process_cmd(g, &cmd, elem.in_sg, elem.in_num);
+ else
+ virtio_gpu_process_cmd(g, &cmd, &iov[1], iov_cnt - 1);
+ }
+ virtqueue_push(vq, &elem, 0);
+ virtio_notify(vdev, vq);
+ }
+}
+
+static void virtio_gpu_ctrl_bh(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+ virtio_gpu_handle_ctrl(&g->parent_obj, g->ctrl_vq);
+}
+
+static void virtio_gpu_handle_cursor(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ VirtQueueElement elem;
+ size_t s;
+ struct virtgpu_hw_cursor_page cursor_info;
+
+ if (!virtio_queue_ready(vq))
+ return;
+ while (virtqueue_pop(vq, &elem)) {
+
+ s = iov_to_buf(elem.out_sg, elem.out_num, 0, &cursor_info,
sizeof(cursor_info));
+ if (s != sizeof(cursor_info))
+ fprintf(stderr,"error\n");
+ else
+ update_cursor(g, &cursor_info);
+ virtqueue_push(vq, &elem, 0);
+ virtio_notify(vdev, vq);
+ }
+}
+
+static void virtio_gpu_cursor_bh(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+ virtio_gpu_handle_cursor(&g->parent_obj, g->cursor_vq);
+}
+
+static int virtio_gpu_send_event(VirtIOGPU *g, struct virtgpu_event *event)
+{
+ VirtQueueElement elem;
+
+ if (!virtio_queue_ready(g->event_vq))
+ return -1;
+
+ if (!virtqueue_pop(g->event_vq, &elem))
+ return -1;
+
+ iov_from_buf(elem.in_sg, elem.in_num, 0, event,
+ sizeof(struct virtgpu_event));
+
+ virtqueue_push(g->event_vq, &elem, sizeof(struct virtgpu_event));
+ virtio_notify(&g->parent_obj, g->event_vq);
+ return 0;
+}
+
+static void virtio_gpu_invalidate_display(void *opaque)
+{
+}
+
+static void virtio_gpu_update_display(void *opaque)
+{
+}
+
+static void virtio_gpu_text_update(void *opaque, console_ch_t *chardata)
+{
+}
+
+static void virtio_gpu_notify_state(void *opaque, int idx, int x, int y,
uint32_t width, uint32_t height)
+{
+ VirtIOGPU *g = opaque;
+ struct virtgpu_event event;
+
+ if (idx > g->conf.max_outputs)
+ return;
+
+ g->req_state[idx].x = x;
+ g->req_state[idx].y = y;
+ g->req_state[idx].width = width;
+ g->req_state[idx].height = height;
+
+ if (width && height)
+ g->enabled_output_bitmask |= (1 << idx);
+ else
+ g->enabled_output_bitmask &= ~(1 << idx);
+
+ /* send event to guest */
+ event.type = VIRTGPU_EVENT_DISPLAY_CHANGE;
+ event.err_code = 0;
+ virtgpu_fill_display_info(g, &event.u.display_info);
+ virtio_gpu_send_event(g, &event);
+}
+
+static const GraphicHwOps virtio_gpu_ops = {
+ .invalidate = virtio_gpu_invalidate_display,
+ .gfx_update = virtio_gpu_update_display,
+ .text_update = virtio_gpu_text_update,
+ .notify_state = virtio_gpu_notify_state,
+};
+
+static int virtio_gpu_device_init(VirtIODevice *vdev)
+{
+ DeviceState *qdev = DEVICE(vdev);
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ int i;
+
+ g->cursor_generation_id = 0xffffffff;
+ g->config_size = 0;
+ virtio_init(VIRTIO_DEVICE(g), "virtio-gpu", VIRTIO_ID_GPU,
+ g->config_size);
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ if (i == 0) {
+ g->req_state[0].width = 1024;
+ g->req_state[0].height = 768;
+ }
+ g->con[i] = graphic_console_init(DEVICE(vdev), &virtio_gpu_ops, g);
+ if (i > 0)
+ dpy_gfx_replace_surface(g->con[i], NULL);
+ }
+ g->ctrl_vq = virtio_add_queue(vdev, 64, virtio_gpu_handle_ctrl_cb);
+ g->cursor_vq = virtio_add_queue(vdev, 256, virtio_gpu_handle_cursor_cb);
+
+ g->ctrl_bh = qemu_bh_new(virtio_gpu_ctrl_bh, g);
+ g->cursor_bh = qemu_bh_new(virtio_gpu_cursor_bh, g);
+
+ g->event_vq = virtio_add_queue(vdev, 64, virtio_gpu_handle_event_cb);
+
+ g->enabled_output_bitmask = 1;
+ g->qdev = qdev;
+ return 0;
+}
+
+static void virtio_gpu_instance_init(Object *obj)
+{
+
+}
+
+static void virtio_gpu_reset(VirtIODevice *vdev)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ int i;
+ struct virtgpu_simple_resource *res, *tmp;
+
+ g->cursor_generation_id = 0xffffffff;
+ g->current_cursor_id = 0;
+ g->enable = 0;
+
+ QLIST_FOREACH_SAFE(res, &g->reslist, next, tmp) {
+ virtgpu_resource_destroy(res);
+ }
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ g->req_state[i].x = 0;
+ g->req_state[i].y = 0;
+ if (i == 0) {
+ g->req_state[0].width = 1024;
+ g->req_state[0].height = 768;
+ } else {
+ g->req_state[i].width = 0;
+ g->req_state[i].height = 0;
+ }
+ g->scanout[i].resource_id = 0;
+ g->scanout[i].width = 0;
+ g->scanout[i].height = 0;
+ g->scanout[i].x = 0;
+ g->scanout[i].y = 0;
+ g->scanout[i].ds = NULL;
+ }
+ g->enabled_output_bitmask = 1;
+}
+
+static Property virtio_gpu_properties[] = {
+ DEFINE_VIRTIO_GPU_PROPERTIES(VirtIOGPU, conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_gpu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ vdc->init = virtio_gpu_device_init;
+ vdc->get_config = virtio_gpu_get_config;
+ vdc->set_config = virtio_gpu_set_config;
+ vdc->get_features = virtio_gpu_get_features;
+ vdc->set_features = virtio_gpu_set_features;
+
+ vdc->reset = virtio_gpu_reset;
+
+ dc->props = virtio_gpu_properties;
+}
+
+static const TypeInfo virtio_gpu_info = {
+ .name = TYPE_VIRTIO_GPU,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOGPU),
+ .instance_init = virtio_gpu_instance_init,
+ .class_init = virtio_gpu_class_init,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_gpu_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index 7647be8..1f04a9e 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -1502,6 +1502,54 @@ static const TypeInfo virtio_rng_pci_info = {
.class_init = virtio_rng_pci_class_init,
};
+static Property virtio_gpu_pci_properties[] = {
+ DEFINE_VIRTIO_GPU_PCI_PROPERTIES(VirtIOPCIProxy),
+ DEFINE_VIRTIO_COMMON_FEATURES(VirtIOPCIProxy, host_features),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static int virtio_gpu_pci_init(VirtIOPCIProxy *vpci_dev)
+{
+ VirtIOGPUPCI *vgpu = VIRTIO_GPU_PCI(vpci_dev);
+ DeviceState *vdev = DEVICE(&vgpu->vdev);
+
+ qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus));
+ if (qdev_init(vdev) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static void virtio_gpu_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->props = virtio_gpu_pci_properties;
+ k->init = virtio_gpu_pci_init;
+ pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
+ pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_GPU;
+ pcidev_k->revision = VIRTIO_PCI_ABI_VERSION;
+ pcidev_k->class_id = PCI_CLASS_DISPLAY_OTHER;
+}
+
+static void virtio_gpu_initfn(Object *obj)
+{
+ VirtIOGPUPCI *dev = VIRTIO_GPU_PCI(obj);
+ object_initialize(&dev->vdev, sizeof(dev->vdev), TYPE_VIRTIO_GPU);
+ object_property_add_child(obj, "virtio-backend", OBJECT(&dev->vdev), NULL);
+}
+
+static const TypeInfo virtio_gpu_pci_info = {
+ .name = TYPE_VIRTIO_GPU_PCI,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOGPUPCI),
+ .instance_init = virtio_gpu_initfn,
+ .class_init = virtio_gpu_pci_class_init,
+};
+
/* virtio-pci-bus */
static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size,
@@ -1558,6 +1606,7 @@ static void virtio_pci_register_types(void)
#ifdef CONFIG_VHOST_SCSI
type_register_static(&vhost_scsi_pci_info);
#endif
+ type_register_static(&virtio_gpu_pci_info);
}
type_init(virtio_pci_register_types)
diff --git a/hw/virtio/virtio-pci.h b/hw/virtio/virtio-pci.h
index 917bcc5..856046d 100644
--- a/hw/virtio/virtio-pci.h
+++ b/hw/virtio/virtio-pci.h
@@ -24,6 +24,7 @@
#include "hw/virtio/virtio-balloon.h"
#include "hw/virtio/virtio-bus.h"
#include "hw/virtio/virtio-9p.h"
+#include "hw/virtio/virtio-gpu.h"
#ifdef CONFIG_VIRTFS
#include "hw/9pfs/virtio-9p.h"
#endif
@@ -39,6 +40,7 @@ typedef struct VirtIOSerialPCI VirtIOSerialPCI;
typedef struct VirtIONetPCI VirtIONetPCI;
typedef struct VHostSCSIPCI VHostSCSIPCI;
typedef struct VirtIORngPCI VirtIORngPCI;
+typedef struct VirtIOGPUPCI VirtIOGPUPCI;
/* virtio-pci-bus */
@@ -200,6 +202,19 @@ struct VirtIORngPCI {
VirtIORNG vdev;
};
+/*
+ * virtio-gpu-pci: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_GPU_PCI "virtio-gpu-pci"
+#define VIRTIO_GPU_PCI(obj) \
+ OBJECT_CHECK(VirtIOGPUPCI, (obj), TYPE_VIRTIO_GPU_PCI)
+
+struct VirtIOGPUPCI {
+ VirtIOPCIProxy parent_obj;
+ VirtIOGPU vdev;
+};
+
+
/* Virtio ABI version, if we increment this, we break the guest driver. */
#define VIRTIO_PCI_ABI_VERSION 0
diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h
index b783e68..fee5d6b 100644
--- a/include/hw/pci/pci.h
+++ b/include/hw/pci/pci.h
@@ -80,6 +80,7 @@
#define PCI_DEVICE_ID_VIRTIO_SCSI 0x1004
#define PCI_DEVICE_ID_VIRTIO_RNG 0x1005
#define PCI_DEVICE_ID_VIRTIO_9P 0x1009
+#define PCI_DEVICE_ID_VIRTIO_GPU 0x1010
#define PCI_VENDOR_ID_REDHAT 0x1b36
#define PCI_DEVICE_ID_REDHAT_BRIDGE 0x0001
diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h
new file mode 100644
index 0000000..deb649b
--- /dev/null
+++ b/include/hw/virtio/virtio-gpu.h
@@ -0,0 +1,89 @@
+#ifndef _QEMU_VIRTIO_VGA_H
+#define _QEMU_VIRTIO_VGA_H
+
+#include "qemu/queue.h"
+#include "ui/qemu-pixman.h"
+#include "ui/console.h"
+#include "hw/virtio/virtio.h"
+#include "hw/pci/pci.h"
+
+#define TYPE_VIRTIO_GPU "virtio-gpu-device"
+#define VIRTIO_GPU(obj) \
+ OBJECT_CHECK(VirtIOGPU, (obj), TYPE_VIRTIO_GPU)
+
+#define VIRTIO_ID_GPU 16
+
+#define VIRTGPU_MAX_RES 16
+
+#define VIRTGPU_MAX_SCANOUT 4
+
+struct virtgpu_simple_resource {
+ uint32_t resource_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t format;
+ struct iovec *iov;
+ unsigned int iov_cnt;
+ uint32_t scanout_bitmask;
+ pixman_image_t *image;
+ QLIST_ENTRY(virtgpu_simple_resource) next;
+};
+
+struct virtgpu_scanout {
+ DisplaySurface *ds;
+ uint32_t width, height;
+ int x, y;
+ int invalidate;
+ uint32_t resource_id;
+};
+
+struct virtgpu_requested_state {
+ uint32_t width, height;
+ int x, y;
+};
+
+struct virtio_gpu_conf {
+ uint32_t max_outputs;
+};
+
+typedef struct VirtIOGPU {
+ VirtIODevice parent_obj;
+
+ /* qemu console for this GPU */
+ QemuConsole *con[VIRTGPU_MAX_SCANOUT];
+
+ QEMUBH *ctrl_bh;
+ QEMUBH *cursor_bh;
+ VirtQueue *ctrl_vq;
+ VirtQueue *cursor_vq;
+ VirtQueue *event_vq;
+
+ int enable;
+
+ uint32_t current_cursor_id;
+ uint32_t cursor_generation_id;
+
+ int config_size;
+ DeviceState *qdev;
+
+ QLIST_HEAD(, virtgpu_simple_resource) reslist;
+
+ struct virtgpu_scanout scanout[VIRTGPU_MAX_SCANOUT];
+ struct virtgpu_requested_state req_state[VIRTGPU_MAX_SCANOUT];
+ QEMUCursor *current_cursor;
+
+ struct virtio_gpu_conf conf;
+ int enabled_output_bitmask;
+} VirtIOGPU;
+
+/* to share between PCI and VGA */
+#define DEFINE_VIRTIO_GPU_PCI_PROPERTIES(_state) \
+ DEFINE_PROP_BIT("ioeventfd", _state, flags, \
+ VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, false), \
+ DEFINE_PROP_UINT32("vectors", _state, nvectors, 4)
+
+#define DEFINE_VIRTIO_GPU_PROPERTIES(_state, _conf_field) \
+ DEFINE_PROP_UINT32("max_outputs", _state, _conf_field.max_outputs, 2)
+
+#endif
+
--
1.8.3.1
- [Qemu-devel] [RFC] sdl2 + virtio-gpu repost, Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 1/8] ui/sdl2 : initial port to SDL 2.0 (v2.0), Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 2/8] console: add state notifiers for ui<->display, Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 3/8] sdl2: add display notify change support, Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 4/8] sdl2: add UI to toggle head 1 on/off, Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 5/8] console: add ability to wrap a console., Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 6/8] virtio-gpu: v0.2 of the virtio based GPU code.,
Dave Airlie <=
- [Qemu-devel] [PATCH 7/8] virtio-vga: v1, Dave Airlie, 2013/12/09
- [Qemu-devel] [PATCH 8/8] HACK: just to make things start easier with libvirt, Dave Airlie, 2013/12/09
- Re: [Qemu-devel] [RFC] sdl2 + virtio-gpu repost, Dave Airlie, 2013/12/09
- Re: [Qemu-devel] [RFC] sdl2 + virtio-gpu repost, Stefan Weil, 2013/12/10