diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2021-05-30 18:33:43 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2021-05-30 18:33:43 +0100 |
commit | f9dc72de91d2915b808e82da34bf613afa5cce43 (patch) | |
tree | a956475f6d816f2ebbd95a0ed1fa919e4b0f55b9 /hw | |
parent | a648df38898ddbad1bd6d71a6680b3788f6528b2 (diff) | |
parent | bdd53f739273e97b5e5617b699d1763c42a5ea7e (diff) | |
download | qemu-f9dc72de91d2915b808e82da34bf613afa5cce43.zip qemu-f9dc72de91d2915b808e82da34bf613afa5cce43.tar.gz qemu-f9dc72de91d2915b808e82da34bf613afa5cce43.tar.bz2 |
Merge remote-tracking branch 'remotes/kraxel/tags/vga-20210527-pull-request' into staging
virtio-gpu: add blob resource support.
vhost-user-gpu: security fixes.
# gpg: Signature made Thu 27 May 2021 15:23:25 BST
# gpg: using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full]
# gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" [full]
# gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full]
# Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138
* remotes/kraxel/tags/vga-20210527-pull-request: (23 commits)
virtio-gpu: Update cursor data using blob
virtio-gpu: Add virtio_gpu_set_scanout_blob
virtio-gpu: Factor out update scanout
virtio-gpu: Add helpers to create and destroy dmabuf objects
ui/pixman: Add qemu_pixman_to_drm_format()
virtio-gpu: Add virtio_gpu_resource_create_blob
virtio-gpu: Add initial definitions for blob resources
virtio-gpu: Refactor virtio_gpu_create_mapping_iov
virtio-gpu: Refactor virtio_gpu_set_scanout
virtio-gpu: Add virtio_gpu_find_check_resource
stubs: Add stubs for udmabuf helpers
virtio-gpu: Add udmabuf helpers
headers: Add udmabuf.h
ui: Get the fd associated with udmabuf driver
hw/display/qxl: Set pci rom address aligned with page size
vhost-user-gpu: abstract vg_cleanup_mapping_iov
vhost-user-gpu: fix OOB write in 'virgl_cmd_get_capset' (CVE-2021-3546)
vhost-user-gpu: fix memory leak in 'virgl_resource_attach_backing' (CVE-2021-3544)
vhost-user-gpu: fix memory leak in 'virgl_cmd_resource_unref' (CVE-2021-3544)
vhost-user-gpu: fix memory leak while calling 'vg_resource_unref' (CVE-2021-3544)
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/display/meson.build | 1 | ||||
-rw-r--r-- | hw/display/qxl.c | 2 | ||||
-rw-r--r-- | hw/display/trace-events | 2 | ||||
-rw-r--r-- | hw/display/virtio-gpu-base.c | 3 | ||||
-rw-r--r-- | hw/display/virtio-gpu-udmabuf.c | 223 | ||||
-rw-r--r-- | hw/display/virtio-gpu-virgl.c | 3 | ||||
-rw-r--r-- | hw/display/virtio-gpu.c | 431 |
7 files changed, 567 insertions, 98 deletions
diff --git a/hw/display/meson.build b/hw/display/meson.build index aaf797c..e1f473c 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -56,6 +56,7 @@ if config_all_devices.has_key('CONFIG_VIRTIO_GPU') virtio_gpu_ss = ss.source_set() virtio_gpu_ss.add(when: 'CONFIG_VIRTIO_GPU', if_true: [files('virtio-gpu-base.c', 'virtio-gpu.c'), pixman]) + virtio_gpu_ss.add(when: 'CONFIG_LINUX', if_true: files('virtio-gpu-udmabuf.c')) virtio_gpu_ss.add(when: 'CONFIG_VHOST_USER_GPU', if_true: files('vhost-user-gpu.c')) hw_display_modules += {'virtio-gpu': virtio_gpu_ss} diff --git a/hw/display/qxl.c b/hw/display/qxl.c index 2ba7563..6e1f8ff 100644 --- a/hw/display/qxl.c +++ b/hw/display/qxl.c @@ -321,7 +321,7 @@ static ram_addr_t qxl_rom_size(void) #define QXL_ROM_SZ 8192 QEMU_BUILD_BUG_ON(QXL_REQUIRED_SZ > QXL_ROM_SZ); - return QXL_ROM_SZ; + return QEMU_ALIGN_UP(QXL_REQUIRED_SZ, qemu_real_host_page_size); } static void init_qxl_rom(PCIQXLDevice *d) diff --git a/hw/display/trace-events b/hw/display/trace-events index 9fccca1..e47264a 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -30,8 +30,10 @@ virtio_gpu_features(bool virgl) "virgl %d" virtio_gpu_cmd_get_display_info(void) "" virtio_gpu_cmd_get_edid(uint32_t scanout) "scanout %d" virtio_gpu_cmd_set_scanout(uint32_t id, uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "id %d, res 0x%x, w %d, h %d, x %d, y %d" +virtio_gpu_cmd_set_scanout_blob(uint32_t id, uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "id %d, res 0x%x, w %d, h %d, x %d, y %d" virtio_gpu_cmd_res_create_2d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h) "res 0x%x, fmt 0x%x, w %d, h %d" virtio_gpu_cmd_res_create_3d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h, uint32_t d) "res 0x%x, fmt 0x%x, w %d, h %d, d %d" +virtio_gpu_cmd_res_create_blob(uint32_t res, uint64_t size) "res 0x%x, size %" PRId64 virtio_gpu_cmd_res_unref(uint32_t res) "res 0x%x" virtio_gpu_cmd_res_back_attach(uint32_t res) "res 0x%x" virtio_gpu_cmd_res_back_detach(uint32_t res) "res 0x%x" diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c index afb3ee7..dd29427 100644 --- a/hw/display/virtio-gpu-base.c +++ b/hw/display/virtio-gpu-base.c @@ -208,6 +208,9 @@ virtio_gpu_base_get_features(VirtIODevice *vdev, uint64_t features, if (virtio_gpu_edid_enabled(g->conf)) { features |= (1 << VIRTIO_GPU_F_EDID); } + if (virtio_gpu_blob_enabled(g->conf)) { + features |= (1 << VIRTIO_GPU_F_RESOURCE_BLOB); + } return features; } diff --git a/hw/display/virtio-gpu-udmabuf.c b/hw/display/virtio-gpu-udmabuf.c new file mode 100644 index 0000000..3c01a41 --- /dev/null +++ b/hw/display/virtio-gpu-udmabuf.c @@ -0,0 +1,223 @@ +/* + * Virtio GPU Device + * + * Copyright Red Hat, Inc. 2013-2014 + * + * Authors: + * Dave Airlie <airlied@redhat.com> + * Gerd Hoffmann <kraxel@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu-common.h" +#include "qemu/iov.h" +#include "ui/console.h" +#include "hw/virtio/virtio-gpu.h" +#include "hw/virtio/virtio-gpu-pixman.h" +#include "trace.h" +#include "exec/ramblock.h" +#include "sysemu/hostmem.h" +#include <sys/ioctl.h> +#include <fcntl.h> +#include <linux/memfd.h> +#include "qemu/memfd.h" +#include "standard-headers/linux/udmabuf.h" + +static void virtio_gpu_create_udmabuf(struct virtio_gpu_simple_resource *res) +{ + struct udmabuf_create_list *list; + RAMBlock *rb; + ram_addr_t offset; + int udmabuf, i; + + udmabuf = udmabuf_fd(); + if (udmabuf < 0) { + return; + } + + list = g_malloc0(sizeof(struct udmabuf_create_list) + + sizeof(struct udmabuf_create_item) * res->iov_cnt); + + for (i = 0; i < res->iov_cnt; i++) { + rcu_read_lock(); + rb = qemu_ram_block_from_host(res->iov[i].iov_base, false, &offset); + rcu_read_unlock(); + + if (!rb || rb->fd < 0) { + g_free(list); + return; + } + + list->list[i].memfd = rb->fd; + list->list[i].offset = offset; + list->list[i].size = res->iov[i].iov_len; + } + + list->count = res->iov_cnt; + list->flags = UDMABUF_FLAGS_CLOEXEC; + + res->dmabuf_fd = ioctl(udmabuf, UDMABUF_CREATE_LIST, list); + if (res->dmabuf_fd < 0) { + warn_report("%s: UDMABUF_CREATE_LIST: %s", __func__, + strerror(errno)); + } + g_free(list); +} + +static void virtio_gpu_remap_udmabuf(struct virtio_gpu_simple_resource *res) +{ + res->remapped = mmap(NULL, res->blob_size, PROT_READ, + MAP_SHARED, res->dmabuf_fd, 0); + if (res->remapped == MAP_FAILED) { + warn_report("%s: dmabuf mmap failed: %s", __func__, + strerror(errno)); + res->remapped = NULL; + } +} + +static void virtio_gpu_destroy_udmabuf(struct virtio_gpu_simple_resource *res) +{ + if (res->remapped) { + munmap(res->remapped, res->blob_size); + res->remapped = NULL; + } + if (res->dmabuf_fd >= 0) { + close(res->dmabuf_fd); + res->dmabuf_fd = -1; + } +} + +static int find_memory_backend_type(Object *obj, void *opaque) +{ + bool *memfd_backend = opaque; + int ret; + + if (object_dynamic_cast(obj, TYPE_MEMORY_BACKEND)) { + HostMemoryBackend *backend = MEMORY_BACKEND(obj); + RAMBlock *rb = backend->mr.ram_block; + + if (rb && rb->fd > 0) { + ret = fcntl(rb->fd, F_GET_SEALS); + if (ret > 0) { + *memfd_backend = true; + } + } + } + + return 0; +} + +bool virtio_gpu_have_udmabuf(void) +{ + Object *memdev_root; + int udmabuf; + bool memfd_backend = false; + + udmabuf = udmabuf_fd(); + if (udmabuf < 0) { + return false; + } + + memdev_root = object_resolve_path("/objects", NULL); + object_child_foreach(memdev_root, find_memory_backend_type, &memfd_backend); + + return memfd_backend; +} + +void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res) +{ + void *pdata = NULL; + + res->dmabuf_fd = -1; + if (res->iov_cnt == 1) { + pdata = res->iov[0].iov_base; + } else { + virtio_gpu_create_udmabuf(res); + if (res->dmabuf_fd < 0) { + return; + } + virtio_gpu_remap_udmabuf(res); + if (!res->remapped) { + return; + } + pdata = res->remapped; + } + + res->blob = pdata; +} + +void virtio_gpu_fini_udmabuf(struct virtio_gpu_simple_resource *res) +{ + if (res->remapped) { + virtio_gpu_destroy_udmabuf(res); + } +} + +static void virtio_gpu_free_dmabuf(VirtIOGPU *g, VGPUDMABuf *dmabuf) +{ + struct virtio_gpu_scanout *scanout; + + scanout = &g->parent_obj.scanout[dmabuf->scanout_id]; + dpy_gl_release_dmabuf(scanout->con, &dmabuf->buf); + QTAILQ_REMOVE(&g->dmabuf.bufs, dmabuf, next); + g_free(dmabuf); +} + +static VGPUDMABuf +*virtio_gpu_create_dmabuf(VirtIOGPU *g, + uint32_t scanout_id, + struct virtio_gpu_simple_resource *res, + struct virtio_gpu_framebuffer *fb) +{ + VGPUDMABuf *dmabuf; + + if (res->dmabuf_fd < 0) { + return NULL; + } + + dmabuf = g_new0(VGPUDMABuf, 1); + dmabuf->buf.width = fb->width; + dmabuf->buf.height = fb->height; + dmabuf->buf.stride = fb->stride; + dmabuf->buf.fourcc = qemu_pixman_to_drm_format(fb->format); + dmabuf->buf.fd = res->dmabuf_fd; + + dmabuf->scanout_id = scanout_id; + QTAILQ_INSERT_HEAD(&g->dmabuf.bufs, dmabuf, next); + + return dmabuf; +} + +int virtio_gpu_update_dmabuf(VirtIOGPU *g, + uint32_t scanout_id, + struct virtio_gpu_simple_resource *res, + struct virtio_gpu_framebuffer *fb) +{ + struct virtio_gpu_scanout *scanout = &g->parent_obj.scanout[scanout_id]; + VGPUDMABuf *new_primary, *old_primary = NULL; + + new_primary = virtio_gpu_create_dmabuf(g, scanout_id, res, fb); + if (!new_primary) { + return -EINVAL; + } + + if (g->dmabuf.primary) { + old_primary = g->dmabuf.primary; + } + + g->dmabuf.primary = new_primary; + qemu_console_resize(scanout->con, + new_primary->buf.width, + new_primary->buf.height); + dpy_gl_scanout_dmabuf(scanout->con, &new_primary->buf); + + if (old_primary) { + virtio_gpu_free_dmabuf(g, old_primary); + } + + return 0; +} diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c index 72c14d9..092c6dc 100644 --- a/hw/display/virtio-gpu-virgl.c +++ b/hw/display/virtio-gpu-virgl.c @@ -289,7 +289,8 @@ static void virgl_resource_attach_backing(VirtIOGPU *g, VIRTIO_GPU_FILL_CMD(att_rb); trace_virtio_gpu_cmd_res_back_attach(att_rb.resource_id); - ret = virtio_gpu_create_mapping_iov(g, &att_rb, cmd, NULL, &res_iovs, &res_niov); + ret = virtio_gpu_create_mapping_iov(g, att_rb.nr_entries, sizeof(att_rb), + cmd, NULL, &res_iovs, &res_niov); if (ret != 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index db56f04..4d54937 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -35,6 +35,10 @@ static struct virtio_gpu_simple_resource* virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id); +static struct virtio_gpu_simple_resource * +virtio_gpu_find_check_resource(VirtIOGPU *g, uint32_t resource_id, + bool require_backing, + const char *caller, uint32_t *error); static void virtio_gpu_cleanup_mapping(VirtIOGPU *g, struct virtio_gpu_simple_resource *res); @@ -45,20 +49,30 @@ void virtio_gpu_update_cursor_data(VirtIOGPU *g, { struct virtio_gpu_simple_resource *res; uint32_t pixels; + void *data; - res = virtio_gpu_find_resource(g, resource_id); + res = virtio_gpu_find_check_resource(g, resource_id, false, + __func__, NULL); if (!res) { return; } - if (pixman_image_get_width(res->image) != s->current_cursor->width || - pixman_image_get_height(res->image) != s->current_cursor->height) { - return; + if (res->blob_size) { + if (res->blob_size < (s->current_cursor->width * + s->current_cursor->height * 4)) { + return; + } + data = res->blob; + } else { + if (pixman_image_get_width(res->image) != s->current_cursor->width || + pixman_image_get_height(res->image) != s->current_cursor->height) { + return; + } + data = pixman_image_get_data(res->image); } pixels = s->current_cursor->width * s->current_cursor->height; - memcpy(s->current_cursor->data, - pixman_image_get_data(res->image), + memcpy(s->current_cursor->data, data, pixels * sizeof(uint32_t)); } @@ -114,6 +128,37 @@ virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id) return NULL; } +static struct virtio_gpu_simple_resource * +virtio_gpu_find_check_resource(VirtIOGPU *g, uint32_t resource_id, + bool require_backing, + const char *caller, uint32_t *error) +{ + struct virtio_gpu_simple_resource *res; + + res = virtio_gpu_find_resource(g, resource_id); + if (!res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid resource specified %d\n", + caller, resource_id); + if (error) { + *error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + } + return NULL; + } + + if (require_backing) { + if (!res->iov || (!res->image && !res->blob)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: no backing storage %d\n", + caller, resource_id); + if (error) { + *error = VIRTIO_GPU_RESP_ERR_UNSPEC; + } + return NULL; + } + } + + return res; +} + void virtio_gpu_ctrl_response(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd, struct virtio_gpu_ctrl_hdr *resp, @@ -277,6 +322,62 @@ static void virtio_gpu_resource_create_2d(VirtIOGPU *g, g->hostmem += res->hostmem; } +static void virtio_gpu_resource_create_blob(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_resource_create_blob cblob; + int ret; + + VIRTIO_GPU_FILL_CMD(cblob); + virtio_gpu_create_blob_bswap(&cblob); + trace_virtio_gpu_cmd_res_create_blob(cblob.resource_id, cblob.size); + + if (cblob.resource_id == 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: resource id 0 is not allowed\n", + __func__); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + res = virtio_gpu_find_resource(g, cblob.resource_id); + if (res) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: resource already exists %d\n", + __func__, cblob.resource_id); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + return; + } + + res = g_new0(struct virtio_gpu_simple_resource, 1); + res->resource_id = cblob.resource_id; + res->blob_size = cblob.size; + + if (cblob.blob_mem != VIRTIO_GPU_BLOB_MEM_GUEST && + cblob.blob_flags != VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid memory type\n", + __func__); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + g_free(res); + return; + } + + if (res->iov) { + cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; + return; + } + + ret = virtio_gpu_create_mapping_iov(g, cblob.nr_entries, sizeof(cblob), + cmd, &res->addrs, &res->iov, + &res->iov_cnt); + if (ret != 0) { + cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; + return; + } + + virtio_gpu_init_udmabuf(res); + QTAILQ_INSERT_HEAD(&g->reslist, res, next); +} + static void virtio_gpu_disable_scanout(VirtIOGPU *g, int scanout_id) { struct virtio_gpu_scanout *scanout = &g->parent_obj.scanout[scanout_id]; @@ -311,7 +412,7 @@ static void virtio_gpu_resource_destroy(VirtIOGPU *g, } } - pixman_image_unref(res->image); + qemu_pixman_image_unref(res->image); virtio_gpu_cleanup_mapping(g, res); QTAILQ_REMOVE(&g->reslist, res, next); g->hostmem -= res->hostmem; @@ -352,11 +453,9 @@ static void virtio_gpu_transfer_to_host_2d(VirtIOGPU *g, virtio_gpu_t2d_bswap(&t2d); trace_virtio_gpu_cmd_res_xfer_toh_2d(t2d.resource_id); - res = virtio_gpu_find_resource(g, t2d.resource_id); - if (!res || !res->iov) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", - __func__, t2d.resource_id); - cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + res = virtio_gpu_find_check_resource(g, t2d.resource_id, true, + __func__, &cmd->error); + if (!res || res->blob) { return; } @@ -402,6 +501,7 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g, { struct virtio_gpu_simple_resource *res; struct virtio_gpu_resource_flush rf; + struct virtio_gpu_scanout *scanout; pixman_region16_t flush_region; int i; @@ -410,20 +510,31 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g, trace_virtio_gpu_cmd_res_flush(rf.resource_id, rf.r.width, rf.r.height, rf.r.x, rf.r.y); - res = virtio_gpu_find_resource(g, rf.resource_id); + res = virtio_gpu_find_check_resource(g, rf.resource_id, false, + __func__, &cmd->error); if (!res) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", - __func__, rf.resource_id); - cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; return; } - if (rf.r.x > res->width || + if (res->blob) { + for (i = 0; i < g->parent_obj.conf.max_outputs; i++) { + scanout = &g->parent_obj.scanout[i]; + if (scanout->resource_id == res->resource_id && + console_has_gl(scanout->con)) { + dpy_gl_update(scanout->con, 0, 0, scanout->width, + scanout->height); + return; + } + } + } + + if (!res->blob && + (rf.r.x > res->width || rf.r.y > res->height || rf.r.width > res->width || rf.r.height > res->height || rf.r.x + rf.r.width > res->width || - rf.r.y + rf.r.height > res->height) { + rf.r.y + rf.r.height > res->height)) { qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource" " bounds for resource %d: %d %d %d %d vs %d %d\n", __func__, rf.resource_id, rf.r.x, rf.r.y, @@ -435,7 +546,6 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g, pixman_region_init_rect(&flush_region, rf.r.x, rf.r.y, rf.r.width, rf.r.height); for (i = 0; i < g->parent_obj.conf.max_outputs; i++) { - struct virtio_gpu_scanout *scanout; pixman_region16_t region, finalregion; pixman_box16_t *extents; @@ -468,14 +578,115 @@ static void virtio_unref_resource(pixman_image_t *image, void *data) pixman_image_unref(data); } +static void virtio_gpu_update_scanout(VirtIOGPU *g, + uint32_t scanout_id, + struct virtio_gpu_simple_resource *res, + struct virtio_gpu_rect *r) +{ + struct virtio_gpu_simple_resource *ores; + struct virtio_gpu_scanout *scanout; + + scanout = &g->parent_obj.scanout[scanout_id]; + ores = virtio_gpu_find_resource(g, scanout->resource_id); + if (ores) { + ores->scanout_bitmask &= ~(1 << scanout_id); + } + + res->scanout_bitmask |= (1 << scanout_id); + scanout->resource_id = res->resource_id; + scanout->x = r->x; + scanout->y = r->y; + scanout->width = r->width; + scanout->height = r->height; +} + +static void virtio_gpu_do_set_scanout(VirtIOGPU *g, + uint32_t scanout_id, + struct virtio_gpu_framebuffer *fb, + struct virtio_gpu_simple_resource *res, + struct virtio_gpu_rect *r, + uint32_t *error) +{ + struct virtio_gpu_scanout *scanout; + uint8_t *data; + + if (scanout_id >= g->parent_obj.conf.max_outputs) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d", + __func__, scanout_id); + *error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; + return; + } + scanout = &g->parent_obj.scanout[scanout_id]; + + if (r->x > fb->width || + r->y > fb->height || + r->width < 16 || + r->height < 16 || + r->width > fb->width || + r->height > fb->height || + r->x + r->width > fb->width || + r->y + r->height > fb->height) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout %d bounds for" + " resource %d, rect (%d,%d)+%d,%d, fb %d %d\n", + __func__, scanout_id, res->resource_id, + r->x, r->y, r->width, r->height, + fb->width, fb->height); + *error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + + g->parent_obj.enable = 1; + + if (res->blob) { + if (console_has_gl(scanout->con)) { + if (!virtio_gpu_update_dmabuf(g, scanout_id, res, fb)) { + virtio_gpu_update_scanout(g, scanout_id, res, r); + return; + } + } + + data = res->blob; + } else { + data = (uint8_t *)pixman_image_get_data(res->image); + } + + /* create a surface for this scanout */ + if ((res->blob && !console_has_gl(scanout->con)) || + !scanout->ds || + surface_data(scanout->ds) != data + fb->offset || + scanout->width != r->width || + scanout->height != r->height) { + pixman_image_t *rect; + void *ptr = data + fb->offset; + rect = pixman_image_create_bits(fb->format, r->width, r->height, + ptr, fb->stride); + + if (res->image) { + pixman_image_ref(res->image); + pixman_image_set_destroy_function(rect, virtio_unref_resource, + res->image); + } + + /* realloc the surface ptr */ + scanout->ds = qemu_create_displaysurface_pixman(rect); + if (!scanout->ds) { + *error = VIRTIO_GPU_RESP_ERR_UNSPEC; + return; + } + + pixman_image_unref(rect); + dpy_gfx_replace_surface(g->parent_obj.scanout[scanout_id].con, + scanout->ds); + } + + virtio_gpu_update_scanout(g, scanout_id, res, r); +} + static void virtio_gpu_set_scanout(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) { - struct virtio_gpu_simple_resource *res, *ores; - struct virtio_gpu_scanout *scanout; - pixman_format_code_t format; - uint32_t offset; - int bpp; + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_framebuffer fb = { 0 }; struct virtio_gpu_set_scanout ss; VIRTIO_GPU_FILL_CMD(ss); @@ -483,86 +694,85 @@ static void virtio_gpu_set_scanout(VirtIOGPU *g, trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id, ss.r.width, ss.r.height, ss.r.x, ss.r.y); - if (ss.scanout_id >= g->parent_obj.conf.max_outputs) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d", - __func__, ss.scanout_id); - cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; + if (ss.resource_id == 0) { + virtio_gpu_disable_scanout(g, ss.scanout_id); return; } - g->parent_obj.enable = 1; + res = virtio_gpu_find_check_resource(g, ss.resource_id, true, + __func__, &cmd->error); + if (!res) { + return; + } + + fb.format = pixman_image_get_format(res->image); + fb.bytes_pp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(fb.format), 8); + fb.width = pixman_image_get_width(res->image); + fb.height = pixman_image_get_height(res->image); + fb.stride = pixman_image_get_stride(res->image); + fb.offset = ss.r.x * fb.bytes_pp + ss.r.y * fb.stride; + + virtio_gpu_do_set_scanout(g, ss.scanout_id, + &fb, res, &ss.r, &cmd->error); +} + +static void virtio_gpu_set_scanout_blob(VirtIOGPU *g, + struct virtio_gpu_ctrl_command *cmd) +{ + struct virtio_gpu_simple_resource *res; + struct virtio_gpu_framebuffer fb = { 0 }; + struct virtio_gpu_set_scanout_blob ss; + uint64_t fbend; + + VIRTIO_GPU_FILL_CMD(ss); + virtio_gpu_scanout_blob_bswap(&ss); + trace_virtio_gpu_cmd_set_scanout_blob(ss.scanout_id, ss.resource_id, + ss.r.width, ss.r.height, ss.r.x, + ss.r.y); + if (ss.resource_id == 0) { virtio_gpu_disable_scanout(g, ss.scanout_id); return; } - /* create a surface for this scanout */ - res = virtio_gpu_find_resource(g, ss.resource_id); + res = virtio_gpu_find_check_resource(g, ss.resource_id, true, + __func__, &cmd->error); if (!res) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", - __func__, ss.resource_id); - cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; return; } - if (ss.r.x > res->width || - ss.r.y > res->height || - ss.r.width < 16 || - ss.r.height < 16 || - ss.r.width > res->width || - ss.r.height > res->height || - ss.r.x + ss.r.width > res->width || - ss.r.y + ss.r.height > res->height) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout %d bounds for" - " resource %d, (%d,%d)+%d,%d vs %d %d\n", - __func__, ss.scanout_id, ss.resource_id, ss.r.x, ss.r.y, - ss.r.width, ss.r.height, res->width, res->height); + fb.format = virtio_gpu_get_pixman_format(ss.format); + if (!fb.format) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: host couldn't handle guest format %d\n", + __func__, ss.format); cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; return; } - scanout = &g->parent_obj.scanout[ss.scanout_id]; - - format = pixman_image_get_format(res->image); - bpp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(format), 8); - offset = (ss.r.x * bpp) + ss.r.y * pixman_image_get_stride(res->image); - if (!scanout->ds || surface_data(scanout->ds) - != ((uint8_t *)pixman_image_get_data(res->image) + offset) || - scanout->width != ss.r.width || - scanout->height != ss.r.height) { - pixman_image_t *rect; - void *ptr = (uint8_t *)pixman_image_get_data(res->image) + offset; - rect = pixman_image_create_bits(format, ss.r.width, ss.r.height, ptr, - pixman_image_get_stride(res->image)); - pixman_image_ref(res->image); - pixman_image_set_destroy_function(rect, virtio_unref_resource, - res->image); - /* realloc the surface ptr */ - scanout->ds = qemu_create_displaysurface_pixman(rect); - if (!scanout->ds) { - cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; - return; - } - pixman_image_unref(rect); - dpy_gfx_replace_surface(g->parent_obj.scanout[ss.scanout_id].con, - scanout->ds); - } + fb.bytes_pp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(fb.format), 8); + fb.width = ss.width; + fb.height = ss.height; + fb.stride = ss.strides[0]; + fb.offset = ss.offsets[0] + ss.r.x * fb.bytes_pp + ss.r.y * fb.stride; - ores = virtio_gpu_find_resource(g, scanout->resource_id); - if (ores) { - ores->scanout_bitmask &= ~(1 << ss.scanout_id); + fbend = fb.offset; + fbend += fb.stride * (ss.r.height - 1); + fbend += fb.bytes_pp * ss.r.width; + if (fbend > res->blob_size) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: fb end out of range\n", + __func__); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; } - res->scanout_bitmask |= (1 << ss.scanout_id); - scanout->resource_id = ss.resource_id; - scanout->x = ss.r.x; - scanout->y = ss.r.y; - scanout->width = ss.r.width; - scanout->height = ss.r.height; + virtio_gpu_do_set_scanout(g, ss.scanout_id, + &fb, res, &ss.r, &cmd->error); } int virtio_gpu_create_mapping_iov(VirtIOGPU *g, - struct virtio_gpu_resource_attach_backing *ab, + uint32_t nr_entries, uint32_t offset, struct virtio_gpu_ctrl_command *cmd, uint64_t **addr, struct iovec **iov, uint32_t *niov) @@ -571,17 +781,17 @@ int virtio_gpu_create_mapping_iov(VirtIOGPU *g, size_t esize, s; int e, v; - if (ab->nr_entries > 16384) { + if (nr_entries > 16384) { qemu_log_mask(LOG_GUEST_ERROR, "%s: nr_entries is too big (%d > 16384)\n", - __func__, ab->nr_entries); + __func__, nr_entries); return -1; } - esize = sizeof(*ents) * ab->nr_entries; + esize = sizeof(*ents) * nr_entries; ents = g_malloc(esize); s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, - sizeof(*ab), ents, esize); + offset, ents, esize); if (s != esize) { qemu_log_mask(LOG_GUEST_ERROR, "%s: command data size incorrect %zu vs %zu\n", @@ -594,7 +804,7 @@ int virtio_gpu_create_mapping_iov(VirtIOGPU *g, if (addr) { *addr = NULL; } - for (e = 0, v = 0; e < ab->nr_entries; e++) { + for (e = 0, v = 0; e < nr_entries; e++) { uint64_t a = le64_to_cpu(ents[e].addr); uint32_t l = le32_to_cpu(ents[e].length); hwaddr len; @@ -606,8 +816,7 @@ int virtio_gpu_create_mapping_iov(VirtIOGPU *g, a, &len, DMA_DIRECTION_TO_DEVICE); if (!map) { qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to map MMIO memory for" - " resource %d element %d\n", - __func__, ab->resource_id, e); + " element %d\n", __func__, e); virtio_gpu_cleanup_mapping_iov(g, *iov, v); g_free(ents); *iov = NULL; @@ -663,6 +872,10 @@ static void virtio_gpu_cleanup_mapping(VirtIOGPU *g, res->iov_cnt = 0; g_free(res->addrs); res->addrs = NULL; + + if (res->blob) { + virtio_gpu_fini_udmabuf(res); + } } static void @@ -690,8 +903,8 @@ virtio_gpu_resource_attach_backing(VirtIOGPU *g, return; } - ret = virtio_gpu_create_mapping_iov(g, &ab, cmd, &res->addrs, - &res->iov, &res->iov_cnt); + ret = virtio_gpu_create_mapping_iov(g, ab.nr_entries, sizeof(ab), cmd, + &res->addrs, &res->iov, &res->iov_cnt); if (ret != 0) { cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; @@ -709,11 +922,9 @@ virtio_gpu_resource_detach_backing(VirtIOGPU *g, virtio_gpu_bswap_32(&detach, sizeof(detach)); trace_virtio_gpu_cmd_res_back_detach(detach.resource_id); - res = virtio_gpu_find_resource(g, detach.resource_id); - if (!res || !res->iov) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", - __func__, detach.resource_id); - cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; + res = virtio_gpu_find_check_resource(g, detach.resource_id, true, + __func__, &cmd->error); + if (!res) { return; } virtio_gpu_cleanup_mapping(g, res); @@ -735,6 +946,13 @@ void virtio_gpu_simple_process_cmd(VirtIOGPU *g, case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: virtio_gpu_resource_create_2d(g, cmd); break; + case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB: + if (!virtio_gpu_blob_enabled(g->parent_obj.conf)) { + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + break; + } + virtio_gpu_resource_create_blob(g, cmd); + break; case VIRTIO_GPU_CMD_RESOURCE_UNREF: virtio_gpu_resource_unref(g, cmd); break; @@ -747,6 +965,13 @@ void virtio_gpu_simple_process_cmd(VirtIOGPU *g, case VIRTIO_GPU_CMD_SET_SCANOUT: virtio_gpu_set_scanout(g, cmd); break; + case VIRTIO_GPU_CMD_SET_SCANOUT_BLOB: + if (!virtio_gpu_blob_enabled(g->parent_obj.conf)) { + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + break; + } + virtio_gpu_set_scanout_blob(g, cmd); + break; case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: virtio_gpu_resource_attach_backing(g, cmd); break; @@ -1058,6 +1283,18 @@ void virtio_gpu_device_realize(DeviceState *qdev, Error **errp) VirtIODevice *vdev = VIRTIO_DEVICE(qdev); VirtIOGPU *g = VIRTIO_GPU(qdev); + if (virtio_gpu_blob_enabled(g->parent_obj.conf)) { + if (!virtio_gpu_have_udmabuf()) { + error_setg(errp, "cannot enable blob resources without udmabuf"); + return; + } + + if (virtio_gpu_virgl_enabled(g->parent_obj.conf)) { + error_setg(errp, "blobs and virgl are not compatible (yet)"); + return; + } + } + if (!virtio_gpu_base_device_realize(qdev, virtio_gpu_handle_ctrl_cb, virtio_gpu_handle_cursor_cb, @@ -1151,6 +1388,8 @@ static Property virtio_gpu_properties[] = { VIRTIO_GPU_BASE_PROPERTIES(VirtIOGPU, parent_obj.conf), DEFINE_PROP_SIZE("max_hostmem", VirtIOGPU, conf_max_hostmem, 256 * MiB), + DEFINE_PROP_BIT("blob", VirtIOGPU, parent_obj.conf.flags, + VIRTIO_GPU_FLAG_BLOB_ENABLED, false), DEFINE_PROP_END_OF_LIST(), }; |