From: Dave Airlie <airlied@gmail.com>
To: qemu-devel@nongnu.org
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 [thread overview]
Message-ID: <1386648358-25892-7-git-send-email-airlied@gmail.com> (raw)
In-Reply-To: <1386648358-25892-1-git-send-email-airlied@gmail.com>
From: Dave Airlie <airlied@redhat.com>
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 <airlied@redhat.com>
---
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
next prev parent reply other threads:[~2013-12-10 4:06 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-12-10 4:05 [Qemu-devel] [RFC] sdl2 + virtio-gpu repost Dave Airlie
2013-12-10 4:05 ` [Qemu-devel] [PATCH 1/8] ui/sdl2 : initial port to SDL 2.0 (v2.0) Dave Airlie
2013-12-10 14:35 ` Gerd Hoffmann
2013-12-11 1:12 ` Dave Airlie
2013-12-12 12:53 ` Gerd Hoffmann
2013-12-10 4:05 ` [Qemu-devel] [PATCH 2/8] console: add state notifiers for ui<->display Dave Airlie
2013-12-10 4:05 ` [Qemu-devel] [PATCH 3/8] sdl2: add display notify change support Dave Airlie
2013-12-10 4:05 ` [Qemu-devel] [PATCH 4/8] sdl2: add UI to toggle head 1 on/off Dave Airlie
2013-12-10 4:05 ` [Qemu-devel] [PATCH 5/8] console: add ability to wrap a console Dave Airlie
2013-12-10 4:05 ` Dave Airlie [this message]
2013-12-10 4:05 ` [Qemu-devel] [PATCH 7/8] virtio-vga: v1 Dave Airlie
2013-12-10 4:05 ` [Qemu-devel] [PATCH 8/8] HACK: just to make things start easier with libvirt Dave Airlie
2013-12-10 4:58 ` [Qemu-devel] [RFC] sdl2 + virtio-gpu repost Dave Airlie
2013-12-10 5:42 ` Stefan Weil
2013-12-10 14:24 ` Gerd Hoffmann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1386648358-25892-7-git-send-email-airlied@gmail.com \
--to=airlied@gmail.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.