contrib: add vhost-user-gpu

Add a vhost-user gpu backend, based on virtio-gpu/3d device. It is
associated with a vhost-user-gpu device.

Various TODO and nice to have items:
- multi-head support
- crash & resume handling
- accelerated rendering/display that avoids the waiting round trips
- edid support

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-id: 20190524130946.31736-6-marcandre.lureau@redhat.com
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Marc-André Lureau 2019-05-24 15:09:42 +02:00 committed by Gerd Hoffmann
parent b213fee8a7
commit d52c454aad
13 changed files with 2427 additions and 2 deletions

View File

@ -1681,6 +1681,8 @@ M: Marc-André Lureau <marcandre.lureau@redhat.com>
M: Gerd Hoffmann <kraxel@redhat.com> M: Gerd Hoffmann <kraxel@redhat.com>
S: Maintained S: Maintained
F: docs/interop/vhost-user-gpu.rst F: docs/interop/vhost-user-gpu.rst
F: contrib/vhost-user-gpu
F: hw/display/vhost-user-*
Cirrus VGA Cirrus VGA
M: Gerd Hoffmann <kraxel@redhat.com> M: Gerd Hoffmann <kraxel@redhat.com>

View File

@ -314,8 +314,20 @@ $(call set-vpath, $(SRC_PATH))
LIBS+=-lz $(LIBS_TOOLS) LIBS+=-lz $(LIBS_TOOLS)
vhost-user-json-y =
HELPERS-y =
HELPERS-$(call land,$(CONFIG_SOFTMMU),$(CONFIG_LINUX)) = qemu-bridge-helper$(EXESUF) HELPERS-$(call land,$(CONFIG_SOFTMMU),$(CONFIG_LINUX)) = qemu-bridge-helper$(EXESUF)
ifdef CONFIG_LINUX
ifdef CONFIG_VIRGL
ifdef CONFIG_GBM
HELPERS-y += vhost-user-gpu$(EXESUF)
vhost-user-json-y += contrib/vhost-user-gpu/50-qemu-gpu.json
endif
endif
endif
ifdef BUILD_DOCS ifdef BUILD_DOCS
DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8 DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
DOCS+=docs/interop/qemu-qmp-ref.html docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7 DOCS+=docs/interop/qemu-qmp-ref.html docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7
@ -409,6 +421,7 @@ dummy := $(call unnest-vars,, \
vhost-user-scsi-obj-y \ vhost-user-scsi-obj-y \
vhost-user-blk-obj-y \ vhost-user-blk-obj-y \
vhost-user-input-obj-y \ vhost-user-input-obj-y \
vhost-user-gpu-obj-y \
qga-vss-dll-obj-y \ qga-vss-dll-obj-y \
block-obj-y \ block-obj-y \
block-obj-m \ block-obj-m \
@ -426,7 +439,7 @@ dummy := $(call unnest-vars,, \
include $(SRC_PATH)/tests/Makefile.include include $(SRC_PATH)/tests/Makefile.include
all: $(DOCS) $(if $(BUILD_DOCS),sphinxdocs) $(TOOLS) $(HELPERS-y) recurse-all modules all: $(DOCS) $(if $(BUILD_DOCS),sphinxdocs) $(TOOLS) $(HELPERS-y) recurse-all modules $(vhost-user-json-y)
qemu-version.h: FORCE qemu-version.h: FORCE
$(call quiet-command, \ $(call quiet-command, \
@ -619,6 +632,9 @@ rdmacm-mux$(EXESUF): LIBS += "-libumad"
rdmacm-mux$(EXESUF): $(rdmacm-mux-obj-y) $(COMMON_LDADDS) rdmacm-mux$(EXESUF): $(rdmacm-mux-obj-y) $(COMMON_LDADDS)
$(call LINK, $^) $(call LINK, $^)
vhost-user-gpu$(EXESUF): $(vhost-user-gpu-obj-y) $(libvhost-user-obj-y) libqemuutil.a libqemustub.a
$(call LINK, $^)
ifdef CONFIG_VHOST_USER_INPUT ifdef CONFIG_VHOST_USER_INPUT
ifdef CONFIG_LINUX ifdef CONFIG_LINUX
vhost-user-input$(EXESUF): $(vhost-user-input-obj-y) libvhost-user.a libqemuutil.a vhost-user-input$(EXESUF): $(vhost-user-input-obj-y) libvhost-user.a libqemuutil.a
@ -827,6 +843,12 @@ endif
ifneq ($(HELPERS-y),) ifneq ($(HELPERS-y),)
$(call install-prog,$(HELPERS-y),$(DESTDIR)$(libexecdir)) $(call install-prog,$(HELPERS-y),$(DESTDIR)$(libexecdir))
endif endif
ifneq ($(vhost-user-json-y),)
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)/vhost-user/"
for x in $(vhost-user-json-y); do \
$(INSTALL_DATA) $$x "$(DESTDIR)$(qemu_datadir)/vhost-user/"; \
done
endif
ifdef CONFIG_TRACE_SYSTEMTAP ifdef CONFIG_TRACE_SYSTEMTAP
$(INSTALL_PROG) "scripts/qemu-trace-stap" $(DESTDIR)$(bindir) $(INSTALL_PROG) "scripts/qemu-trace-stap" $(DESTDIR)$(bindir)
endif endif

View File

@ -123,6 +123,7 @@ vhost-user-scsi-obj-y = contrib/vhost-user-scsi/
vhost-user-blk-obj-y = contrib/vhost-user-blk/ vhost-user-blk-obj-y = contrib/vhost-user-blk/
rdmacm-mux-obj-y = contrib/rdmacm-mux/ rdmacm-mux-obj-y = contrib/rdmacm-mux/
vhost-user-input-obj-y = contrib/vhost-user-input/ vhost-user-input-obj-y = contrib/vhost-user-input/
vhost-user-gpu-obj-y = contrib/vhost-user-gpu/
###################################################################### ######################################################################
trace-events-subdirs = trace-events-subdirs =

17
configure vendored
View File

@ -4099,6 +4099,13 @@ libs_softmmu="$libs_softmmu $fdt_libs"
########################################## ##########################################
# opengl probe (for sdl2, gtk, milkymist-tmu2) # opengl probe (for sdl2, gtk, milkymist-tmu2)
gbm="no"
if $pkg_config gbm; then
gbm_cflags="$($pkg_config --cflags gbm)"
gbm_libs="$($pkg_config --libs gbm)"
gbm="yes"
fi
if test "$opengl" != "no" ; then if test "$opengl" != "no" ; then
opengl_pkgs="epoxy gbm" opengl_pkgs="epoxy gbm"
if $pkg_config $opengl_pkgs; then if $pkg_config $opengl_pkgs; then
@ -6964,6 +6971,13 @@ if test "$opengl" = "yes" ; then
fi fi
fi fi
if test "$gbm" = "yes" ; then
echo "CONFIG_GBM=y" >> $config_host_mak
echo "GBM_LIBS=$gbm_libs" >> $config_host_mak
echo "GBM_CFLAGS=$gbm_cflags" >> $config_host_mak
fi
if test "$malloc_trim" = "yes" ; then if test "$malloc_trim" = "yes" ; then
echo "CONFIG_MALLOC_TRIM=y" >> $config_host_mak echo "CONFIG_MALLOC_TRIM=y" >> $config_host_mak
fi fi
@ -7859,6 +7873,9 @@ echo "QEMU_CFLAGS+=$cflags" >> $config_target_mak
done # for target in $targets done # for target in $targets
echo "PIXMAN_CFLAGS=$pixman_cflags" >> $config_host_mak
echo "PIXMAN_LIBS=$pixman_libs" >> $config_host_mak
if test -n "$enabled_cross_compilers"; then if test -n "$enabled_cross_compilers"; then
echo echo
echo "NOTE: cross-compilers enabled: $enabled_cross_compilers" echo "NOTE: cross-compilers enabled: $enabled_cross_compilers"

View File

@ -0,0 +1,5 @@
{
"description": "QEMU vhost-user-gpu",
"type": "gpu",
"binary": "@libexecdir@/vhost-user-gpu",
}

View File

@ -0,0 +1,10 @@
vhost-user-gpu-obj-y = main.o virgl.o vugbm.o
main.o-cflags := $(PIXMAN_CFLAGS) $(GBM_CFLAGS)
main.o-libs := $(PIXMAN_LIBS)
virgl.o-cflags := $(VIRGL_CFLAGS) $(GBM_CFLAGS)
virgl.o-libs := $(VIRGL_LIBS)
vugbm.o-cflags := $(GBM_CFLAGS)
vugbm.o-libs := $(GBM_LIBS)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,579 @@
/*
* Virtio vhost-user GPU Device
*
* Copyright Red Hat, Inc. 2013-2018
*
* Authors:
* Dave Airlie <airlied@redhat.com>
* Gerd Hoffmann <kraxel@redhat.com>
* Marc-André Lureau <marcandre.lureau@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 <virglrenderer.h>
#include "virgl.h"
void
vg_virgl_update_cursor_data(VuGpu *g, uint32_t resource_id,
gpointer data)
{
uint32_t width, height;
uint32_t *cursor;
cursor = virgl_renderer_get_cursor_data(resource_id, &width, &height);
g_return_if_fail(cursor != NULL);
g_return_if_fail(width == 64);
g_return_if_fail(height == 64);
memcpy(data, cursor, 64 * 64 * sizeof(uint32_t));
free(cursor);
}
static void
virgl_cmd_context_create(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_ctx_create cc;
VUGPU_FILL_CMD(cc);
virgl_renderer_context_create(cc.hdr.ctx_id, cc.nlen,
cc.debug_name);
}
static void
virgl_cmd_context_destroy(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_ctx_destroy cd;
VUGPU_FILL_CMD(cd);
virgl_renderer_context_destroy(cd.hdr.ctx_id);
}
static void
virgl_cmd_create_resource_2d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_resource_create_2d c2d;
struct virgl_renderer_resource_create_args args;
VUGPU_FILL_CMD(c2d);
args.handle = c2d.resource_id;
args.target = 2;
args.format = c2d.format;
args.bind = (1 << 1);
args.width = c2d.width;
args.height = c2d.height;
args.depth = 1;
args.array_size = 1;
args.last_level = 0;
args.nr_samples = 0;
args.flags = VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP;
virgl_renderer_resource_create(&args, NULL, 0);
}
static void
virgl_cmd_create_resource_3d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_resource_create_3d c3d;
struct virgl_renderer_resource_create_args args;
VUGPU_FILL_CMD(c3d);
args.handle = c3d.resource_id;
args.target = c3d.target;
args.format = c3d.format;
args.bind = c3d.bind;
args.width = c3d.width;
args.height = c3d.height;
args.depth = c3d.depth;
args.array_size = c3d.array_size;
args.last_level = c3d.last_level;
args.nr_samples = c3d.nr_samples;
args.flags = c3d.flags;
virgl_renderer_resource_create(&args, NULL, 0);
}
static void
virgl_cmd_resource_unref(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_resource_unref unref;
VUGPU_FILL_CMD(unref);
virgl_renderer_resource_unref(unref.resource_id);
}
/* Not yet(?) defined in standard-headers, remove when possible */
#ifndef VIRTIO_GPU_CAPSET_VIRGL2
#define VIRTIO_GPU_CAPSET_VIRGL2 2
#endif
static void
virgl_cmd_get_capset_info(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_get_capset_info info;
struct virtio_gpu_resp_capset_info resp;
VUGPU_FILL_CMD(info);
if (info.capset_index == 0) {
resp.capset_id = VIRTIO_GPU_CAPSET_VIRGL;
virgl_renderer_get_cap_set(resp.capset_id,
&resp.capset_max_version,
&resp.capset_max_size);
} else if (info.capset_index == 1) {
resp.capset_id = VIRTIO_GPU_CAPSET_VIRGL2;
virgl_renderer_get_cap_set(resp.capset_id,
&resp.capset_max_version,
&resp.capset_max_size);
} else {
resp.capset_max_version = 0;
resp.capset_max_size = 0;
}
resp.hdr.type = VIRTIO_GPU_RESP_OK_CAPSET_INFO;
vg_ctrl_response(g, cmd, &resp.hdr, sizeof(resp));
}
uint32_t
vg_virgl_get_num_capsets(void)
{
uint32_t capset2_max_ver, capset2_max_size;
virgl_renderer_get_cap_set(VIRTIO_GPU_CAPSET_VIRGL2,
&capset2_max_ver,
&capset2_max_size);
return capset2_max_ver ? 2 : 1;
}
static void
virgl_cmd_get_capset(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_get_capset gc;
struct virtio_gpu_resp_capset *resp;
uint32_t max_ver, max_size;
VUGPU_FILL_CMD(gc);
virgl_renderer_get_cap_set(gc.capset_id, &max_ver,
&max_size);
resp = g_malloc0(sizeof(*resp) + max_size);
resp->hdr.type = VIRTIO_GPU_RESP_OK_CAPSET;
virgl_renderer_fill_caps(gc.capset_id,
gc.capset_version,
(void *)resp->capset_data);
vg_ctrl_response(g, cmd, &resp->hdr, sizeof(*resp) + max_size);
g_free(resp);
}
static void
virgl_cmd_submit_3d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_cmd_submit cs;
void *buf;
size_t s;
VUGPU_FILL_CMD(cs);
buf = g_malloc(cs.size);
s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
sizeof(cs), buf, cs.size);
if (s != cs.size) {
g_critical("%s: size mismatch (%zd/%d)", __func__, s, cs.size);
cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
goto out;
}
virgl_renderer_submit_cmd(buf, cs.hdr.ctx_id, cs.size / 4);
out:
g_free(buf);
}
static void
virgl_cmd_transfer_to_host_2d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_transfer_to_host_2d t2d;
struct virtio_gpu_box box;
VUGPU_FILL_CMD(t2d);
box.x = t2d.r.x;
box.y = t2d.r.y;
box.z = 0;
box.w = t2d.r.width;
box.h = t2d.r.height;
box.d = 1;
virgl_renderer_transfer_write_iov(t2d.resource_id,
0,
0,
0,
0,
(struct virgl_box *)&box,
t2d.offset, NULL, 0);
}
static void
virgl_cmd_transfer_to_host_3d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_transfer_host_3d t3d;
VUGPU_FILL_CMD(t3d);
virgl_renderer_transfer_write_iov(t3d.resource_id,
t3d.hdr.ctx_id,
t3d.level,
t3d.stride,
t3d.layer_stride,
(struct virgl_box *)&t3d.box,
t3d.offset, NULL, 0);
}
static void
virgl_cmd_transfer_from_host_3d(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_transfer_host_3d tf3d;
VUGPU_FILL_CMD(tf3d);
virgl_renderer_transfer_read_iov(tf3d.resource_id,
tf3d.hdr.ctx_id,
tf3d.level,
tf3d.stride,
tf3d.layer_stride,
(struct virgl_box *)&tf3d.box,
tf3d.offset, NULL, 0);
}
static void
virgl_resource_attach_backing(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_resource_attach_backing att_rb;
struct iovec *res_iovs;
int ret;
VUGPU_FILL_CMD(att_rb);
ret = vg_create_mapping_iov(g, &att_rb, cmd, &res_iovs);
if (ret != 0) {
cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
return;
}
virgl_renderer_resource_attach_iov(att_rb.resource_id,
res_iovs, att_rb.nr_entries);
}
static void
virgl_resource_detach_backing(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_resource_detach_backing detach_rb;
struct iovec *res_iovs = NULL;
int num_iovs = 0;
VUGPU_FILL_CMD(detach_rb);
virgl_renderer_resource_detach_iov(detach_rb.resource_id,
&res_iovs,
&num_iovs);
if (res_iovs == NULL || num_iovs == 0) {
return;
}
g_free(res_iovs);
}
static void
virgl_cmd_set_scanout(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_set_scanout ss;
struct virgl_renderer_resource_info info;
int ret;
VUGPU_FILL_CMD(ss);
if (ss.scanout_id >= VIRTIO_GPU_MAX_SCANOUTS) {
g_critical("%s: illegal scanout id specified %d",
__func__, ss.scanout_id);
cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
return;
}
memset(&info, 0, sizeof(info));
if (ss.resource_id && ss.r.width && ss.r.height) {
ret = virgl_renderer_resource_get_info(ss.resource_id, &info);
if (ret == -1) {
g_critical("%s: illegal resource specified %d\n",
__func__, ss.resource_id);
cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
return;
}
int fd = -1;
if (virgl_renderer_get_fd_for_texture(info.tex_id, &fd) < 0) {
g_critical("%s: failed to get fd for texture\n", __func__);
cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
return;
}
assert(fd >= 0);
VhostUserGpuMsg msg = {
.request = VHOST_USER_GPU_DMABUF_SCANOUT,
.size = sizeof(VhostUserGpuDMABUFScanout),
.payload.dmabuf_scanout.scanout_id = ss.scanout_id,
.payload.dmabuf_scanout.x = ss.r.x,
.payload.dmabuf_scanout.y = ss.r.y,
.payload.dmabuf_scanout.width = ss.r.width,
.payload.dmabuf_scanout.height = ss.r.height,
.payload.dmabuf_scanout.fd_width = info.width,
.payload.dmabuf_scanout.fd_height = info.height,
.payload.dmabuf_scanout.fd_stride = info.stride,
.payload.dmabuf_scanout.fd_flags = info.flags,
.payload.dmabuf_scanout.fd_drm_fourcc = info.drm_fourcc
};
vg_send_msg(g, &msg, fd);
close(fd);
} else {
VhostUserGpuMsg msg = {
.request = VHOST_USER_GPU_DMABUF_SCANOUT,
.size = sizeof(VhostUserGpuDMABUFScanout),
.payload.dmabuf_scanout.scanout_id = ss.scanout_id,
};
g_debug("disable scanout");
vg_send_msg(g, &msg, -1);
}
g->scanout[ss.scanout_id].resource_id = ss.resource_id;
}
static void
virgl_cmd_resource_flush(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_resource_flush rf;
int i;
VUGPU_FILL_CMD(rf);
if (!rf.resource_id) {
g_debug("bad resource id for flush..?");
return;
}
for (i = 0; i < VIRTIO_GPU_MAX_SCANOUTS; i++) {
if (g->scanout[i].resource_id != rf.resource_id) {
continue;
}
VhostUserGpuMsg msg = {
.request = VHOST_USER_GPU_DMABUF_UPDATE,
.size = sizeof(VhostUserGpuUpdate),
.payload.update.scanout_id = i,
.payload.update.x = rf.r.x,
.payload.update.y = rf.r.y,
.payload.update.width = rf.r.width,
.payload.update.height = rf.r.height
};
vg_send_msg(g, &msg, -1);
vg_wait_ok(g);
}
}
static void
virgl_cmd_ctx_attach_resource(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_ctx_resource att_res;
VUGPU_FILL_CMD(att_res);
virgl_renderer_ctx_attach_resource(att_res.hdr.ctx_id, att_res.resource_id);
}
static void
virgl_cmd_ctx_detach_resource(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd)
{
struct virtio_gpu_ctx_resource det_res;
VUGPU_FILL_CMD(det_res);
virgl_renderer_ctx_detach_resource(det_res.hdr.ctx_id, det_res.resource_id);
}
void vg_virgl_process_cmd(VuGpu *g, struct virtio_gpu_ctrl_command *cmd)
{
virgl_renderer_force_ctx_0();
switch (cmd->cmd_hdr.type) {
case VIRTIO_GPU_CMD_CTX_CREATE:
virgl_cmd_context_create(g, cmd);
break;
case VIRTIO_GPU_CMD_CTX_DESTROY:
virgl_cmd_context_destroy(g, cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
virgl_cmd_create_resource_2d(g, cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_CREATE_3D:
virgl_cmd_create_resource_3d(g, cmd);
break;
case VIRTIO_GPU_CMD_SUBMIT_3D:
virgl_cmd_submit_3d(g, cmd);
break;
case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D:
virgl_cmd_transfer_to_host_2d(g, cmd);
break;
case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D:
virgl_cmd_transfer_to_host_3d(g, cmd);
break;
case VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D:
virgl_cmd_transfer_from_host_3d(g, cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
virgl_resource_attach_backing(g, cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING:
virgl_resource_detach_backing(g, cmd);
break;
case VIRTIO_GPU_CMD_SET_SCANOUT:
virgl_cmd_set_scanout(g, cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_FLUSH:
virgl_cmd_resource_flush(g, cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_UNREF:
virgl_cmd_resource_unref(g, cmd);
break;
case VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE:
/* TODO add security */
virgl_cmd_ctx_attach_resource(g, cmd);
break;
case VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE:
/* TODO add security */
virgl_cmd_ctx_detach_resource(g, cmd);
break;
case VIRTIO_GPU_CMD_GET_CAPSET_INFO:
virgl_cmd_get_capset_info(g, cmd);
break;
case VIRTIO_GPU_CMD_GET_CAPSET:
virgl_cmd_get_capset(g, cmd);
break;
case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
vg_get_display_info(g, cmd);
break;
default:
g_debug("TODO handle ctrl %x\n", cmd->cmd_hdr.type);
cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
break;
}
if (cmd->finished) {
return;
}
if (cmd->error) {
g_warning("%s: ctrl 0x%x, error 0x%x\n", __func__,
cmd->cmd_hdr.type, cmd->error);
vg_ctrl_response_nodata(g, cmd, cmd->error);
return;
}
if (!(cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE)) {
vg_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
return;
}
g_debug("Creating fence id:%" PRId64 " type:%d",
cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
}
static void
virgl_write_fence(void *opaque, uint32_t fence)
{
VuGpu *g = opaque;
struct virtio_gpu_ctrl_command *cmd, *tmp;
QTAILQ_FOREACH_SAFE(cmd, &g->fenceq, next, tmp) {
/*
* the guest can end up emitting fences out of order
* so we should check all fenced cmds not just the first one.
*/
if (cmd->cmd_hdr.fence_id > fence) {
continue;
}
g_debug("FENCE %" PRIu64, cmd->cmd_hdr.fence_id);
vg_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
QTAILQ_REMOVE(&g->fenceq, cmd, next);
g_free(cmd);
g->inflight--;
}
}
#if defined(VIRGL_RENDERER_CALLBACKS_VERSION) && \
VIRGL_RENDERER_CALLBACKS_VERSION >= 2
static int
virgl_get_drm_fd(void *opaque)
{
VuGpu *g = opaque;
return g->drm_rnode_fd;
}
#endif
static struct virgl_renderer_callbacks virgl_cbs = {
#if defined(VIRGL_RENDERER_CALLBACKS_VERSION) && \
VIRGL_RENDERER_CALLBACKS_VERSION >= 2
.get_drm_fd = virgl_get_drm_fd,
.version = 2,
#else
.version = 1,
#endif
.write_fence = virgl_write_fence,
};
static void
vg_virgl_poll(VuDev *dev, int condition, void *data)
{
virgl_renderer_poll();
}
bool
vg_virgl_init(VuGpu *g)
{
int ret;
if (g->drm_rnode_fd && virgl_cbs.version == 1) {
g_warning("virgl will use the default rendernode");
}
ret = virgl_renderer_init(g,
VIRGL_RENDERER_USE_EGL |
VIRGL_RENDERER_THREAD_SYNC,
&virgl_cbs);
if (ret != 0) {
return false;
}
ret = virgl_renderer_get_poll_fd();
if (ret != -1) {
g->renderer_source =
vug_source_new(&g->dev, ret, G_IO_IN, vg_virgl_poll, g);
}
return true;
}

View File

@ -0,0 +1,25 @@
/*
* Virtio vhost-user GPU Device
*
* Copyright Red Hat, Inc. 2013-2018
*
* Authors:
* Dave Airlie <airlied@redhat.com>
* Gerd Hoffmann <kraxel@redhat.com>
* Marc-André Lureau <marcandre.lureau@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.
*/
#ifndef VUGPU_VIRGL_H_
#define VUGPU_VIRGL_H_
#include "vugpu.h"
bool vg_virgl_init(VuGpu *g);
uint32_t vg_virgl_get_num_capsets(void);
void vg_virgl_process_cmd(VuGpu *vg, struct virtio_gpu_ctrl_command *cmd);
void vg_virgl_update_cursor_data(VuGpu *g, uint32_t resource_id,
gpointer data);
#endif

View File

@ -0,0 +1,328 @@
/*
* Virtio vhost-user GPU Device
*
* DRM helpers
*
* 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 "vugbm.h"
static bool
mem_alloc_bo(struct vugbm_buffer *buf)
{
buf->mmap = g_malloc(buf->width * buf->height * 4);
buf->stride = buf->width * 4;
return true;
}
static void
mem_free_bo(struct vugbm_buffer *buf)
{
g_free(buf->mmap);
}
static bool
mem_map_bo(struct vugbm_buffer *buf)
{
return buf->mmap != NULL;
}
static void
mem_unmap_bo(struct vugbm_buffer *buf)
{
}
static void
mem_device_destroy(struct vugbm_device *dev)
{
}
#ifdef CONFIG_MEMFD
struct udmabuf_create {
uint32_t memfd;
uint32_t flags;
uint64_t offset;
uint64_t size;
};
#define UDMABUF_CREATE _IOW('u', 0x42, struct udmabuf_create)
static size_t
udmabuf_get_size(struct vugbm_buffer *buf)
{
return ROUND_UP(buf->width * buf->height * 4, getpagesize());
}
static bool
udmabuf_alloc_bo(struct vugbm_buffer *buf)
{
int ret;
buf->memfd = memfd_create("udmabuf-bo", MFD_ALLOW_SEALING);
if (buf->memfd < 0) {
return false;
}
ret = ftruncate(buf->memfd, udmabuf_get_size(buf));
if (ret < 0) {
close(buf->memfd);
return false;
}
ret = fcntl(buf->memfd, F_ADD_SEALS, F_SEAL_SHRINK);
if (ret < 0) {
close(buf->memfd);
return false;
}
buf->stride = buf->width * 4;
return true;
}
static void
udmabuf_free_bo(struct vugbm_buffer *buf)
{
close(buf->memfd);
}
static bool
udmabuf_map_bo(struct vugbm_buffer *buf)
{
buf->mmap = mmap(NULL, udmabuf_get_size(buf),
PROT_READ | PROT_WRITE, MAP_SHARED, buf->memfd, 0);
if (buf->mmap == MAP_FAILED) {
return false;
}
return true;
}
static bool
udmabuf_get_fd(struct vugbm_buffer *buf, int *fd)
{
struct udmabuf_create create = {
.memfd = buf->memfd,
.offset = 0,
.size = udmabuf_get_size(buf),
};
*fd = ioctl(buf->dev->fd, UDMABUF_CREATE, &create);
return *fd >= 0;
}
static void
udmabuf_unmap_bo(struct vugbm_buffer *buf)
{
munmap(buf->mmap, udmabuf_get_size(buf));
}
static void
udmabuf_device_destroy(struct vugbm_device *dev)
{
close(dev->fd);
}
#endif
#ifdef CONFIG_GBM
static bool
alloc_bo(struct vugbm_buffer *buf)
{
struct gbm_device *dev = buf->dev->dev;
assert(!buf->bo);
buf->bo = gbm_bo_create(dev, buf->width, buf->height,
buf->format,
GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR);
if (buf->bo) {
buf->stride = gbm_bo_get_stride(buf->bo);
return true;
}
return false;
}
static void
free_bo(struct vugbm_buffer *buf)
{
gbm_bo_destroy(buf->bo);
}
static bool
map_bo(struct vugbm_buffer *buf)
{
uint32_t stride;
buf->mmap = gbm_bo_map(buf->bo, 0, 0, buf->width, buf->height,
GBM_BO_TRANSFER_READ_WRITE, &stride,
&buf->mmap_data);
assert(stride == buf->stride);
return buf->mmap != NULL;
}
static void
unmap_bo(struct vugbm_buffer *buf)
{
gbm_bo_unmap(buf->bo, buf->mmap_data);
}
static bool
get_fd(struct vugbm_buffer *buf, int *fd)
{
*fd = gbm_bo_get_fd(buf->bo);
return *fd >= 0;
}
static void
device_destroy(struct vugbm_device *dev)
{
gbm_device_destroy(dev->dev);
}
#endif
void
vugbm_device_destroy(struct vugbm_device *dev)
{
if (!dev->inited) {
return;
}
dev->device_destroy(dev);
}
bool
vugbm_device_init(struct vugbm_device *dev, int fd)
{
dev->fd = fd;
#ifdef CONFIG_GBM
dev->dev = gbm_create_device(fd);
#endif
if (0) {
/* nothing */
}
#ifdef CONFIG_GBM
else if (dev->dev != NULL) {
dev->alloc_bo = alloc_bo;
dev->free_bo = free_bo;
dev->get_fd = get_fd;
dev->map_bo = map_bo;
dev->unmap_bo = unmap_bo;
dev->device_destroy = device_destroy;
}
#endif
#ifdef CONFIG_MEMFD
else if (g_file_test("/dev/udmabuf", G_FILE_TEST_EXISTS)) {
dev->fd = open("/dev/udmabuf", O_RDWR);
if (dev->fd < 0) {
return false;
}
g_debug("Using experimental udmabuf backend");
dev->alloc_bo = udmabuf_alloc_bo;
dev->free_bo = udmabuf_free_bo;
dev->get_fd = udmabuf_get_fd;
dev->map_bo = udmabuf_map_bo;
dev->unmap_bo = udmabuf_unmap_bo;
dev->device_destroy = udmabuf_device_destroy;
}
#endif
else {
g_debug("Using mem fallback");
dev->alloc_bo = mem_alloc_bo;
dev->free_bo = mem_free_bo;
dev->map_bo = mem_map_bo;
dev->unmap_bo = mem_unmap_bo;
dev->device_destroy = mem_device_destroy;
return false;
}
dev->inited = true;
return true;
}
static bool
vugbm_buffer_map(struct vugbm_buffer *buf)
{
struct vugbm_device *dev = buf->dev;
return dev->map_bo(buf);
}
static void
vugbm_buffer_unmap(struct vugbm_buffer *buf)
{
struct vugbm_device *dev = buf->dev;
dev->unmap_bo(buf);
}
bool
vugbm_buffer_can_get_dmabuf_fd(struct vugbm_buffer *buffer)
{
if (!buffer->dev->get_fd) {
return false;
}
return true;
}
bool
vugbm_buffer_get_dmabuf_fd(struct vugbm_buffer *buffer, int *fd)
{
if (!vugbm_buffer_can_get_dmabuf_fd(buffer) ||
!buffer->dev->get_fd(buffer, fd)) {
g_warning("Failed to get dmabuf");
return false;
}
if (*fd < 0) {
g_warning("error: dmabuf_fd < 0");
return false;
}
return true;
}
bool
vugbm_buffer_create(struct vugbm_buffer *buffer, struct vugbm_device *dev,
uint32_t width, uint32_t height)
{
buffer->dev = dev;
buffer->width = width;
buffer->height = height;
buffer->format = GBM_FORMAT_XRGB8888;
buffer->stride = 0; /* modified during alloc */
if (!dev->alloc_bo(buffer)) {
g_warning("alloc_bo failed");
return false;
}
if (!vugbm_buffer_map(buffer)) {
g_warning("map_bo failed");
goto err;
}
return true;
err:
dev->free_bo(buffer);
return false;
}
void
vugbm_buffer_destroy(struct vugbm_buffer *buffer)
{
struct vugbm_device *dev = buffer->dev;
vugbm_buffer_unmap(buffer);
dev->free_bo(buffer);
}

View File

@ -0,0 +1,67 @@
/*
* Virtio vhost-user GPU Device
*
* GBM helpers
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#ifndef VHOST_USER_GPU_GBM_H
#define VHOST_USER_GPU_GBM_H
#include "qemu/osdep.h"
#ifdef CONFIG_MEMFD
#include <sys/mman.h>
#include <sys/ioctl.h>
#endif
#ifdef CONFIG_GBM
#include <gbm.h>
#endif
struct vugbm_buffer;
struct vugbm_device {
bool inited;
int fd;
#ifdef CONFIG_GBM
struct gbm_device *dev;
#endif
bool (*alloc_bo)(struct vugbm_buffer *buf);
void (*free_bo)(struct vugbm_buffer *buf);
bool (*get_fd)(struct vugbm_buffer *buf, int *fd);
bool (*map_bo)(struct vugbm_buffer *buf);
void (*unmap_bo)(struct vugbm_buffer *buf);
void (*device_destroy)(struct vugbm_device *dev);
};
struct vugbm_buffer {
struct vugbm_device *dev;
#ifdef CONFIG_MEMFD
int memfd;
#endif
#ifdef CONFIG_GBM
struct gbm_bo *bo;
void *mmap_data;
#endif
uint8_t *mmap;
uint32_t width;
uint32_t height;
uint32_t stride;
uint32_t format;
};
bool vugbm_device_init(struct vugbm_device *dev, int fd);
void vugbm_device_destroy(struct vugbm_device *dev);
bool vugbm_buffer_create(struct vugbm_buffer *buffer, struct vugbm_device *dev,
uint32_t width, uint32_t height);
bool vugbm_buffer_can_get_dmabuf_fd(struct vugbm_buffer *buffer);
bool vugbm_buffer_get_dmabuf_fd(struct vugbm_buffer *buffer, int *fd);
void vugbm_buffer_destroy(struct vugbm_buffer *buffer);
#endif

View File

@ -0,0 +1,177 @@
/*
* Virtio vhost-user GPU Device
*
* Copyright Red Hat, Inc. 2013-2018
*
* Authors:
* Dave Airlie <airlied@redhat.com>
* Gerd Hoffmann <kraxel@redhat.com>
* Marc-André Lureau <marcandre.lureau@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.
*/
#ifndef VUGPU_H_
#define VUGPU_H_
#include "qemu/osdep.h"
#include "contrib/libvhost-user/libvhost-user-glib.h"
#include "standard-headers/linux/virtio_gpu.h"
#include "qemu/queue.h"
#include "qemu/iov.h"
#include "qemu/bswap.h"
#include "vugbm.h"
typedef enum VhostUserGpuRequest {
VHOST_USER_GPU_NONE = 0,
VHOST_USER_GPU_GET_PROTOCOL_FEATURES,
VHOST_USER_GPU_SET_PROTOCOL_FEATURES,
VHOST_USER_GPU_GET_DISPLAY_INFO,
VHOST_USER_GPU_CURSOR_POS,
VHOST_USER_GPU_CURSOR_POS_HIDE,
VHOST_USER_GPU_CURSOR_UPDATE,
VHOST_USER_GPU_SCANOUT,
VHOST_USER_GPU_UPDATE,
VHOST_USER_GPU_DMABUF_SCANOUT,
VHOST_USER_GPU_DMABUF_UPDATE,
} VhostUserGpuRequest;
typedef struct VhostUserGpuDisplayInfoReply {
struct virtio_gpu_resp_display_info info;
} VhostUserGpuDisplayInfoReply;
typedef struct VhostUserGpuCursorPos {
uint32_t scanout_id;
uint32_t x;
uint32_t y;
} QEMU_PACKED VhostUserGpuCursorPos;
typedef struct VhostUserGpuCursorUpdate {
VhostUserGpuCursorPos pos;
uint32_t hot_x;
uint32_t hot_y;
uint32_t data[64 * 64];
} QEMU_PACKED VhostUserGpuCursorUpdate;
typedef struct VhostUserGpuScanout {
uint32_t scanout_id;
uint32_t width;
uint32_t height;
} QEMU_PACKED VhostUserGpuScanout;
typedef struct VhostUserGpuUpdate {
uint32_t scanout_id;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint8_t data[];
} QEMU_PACKED VhostUserGpuUpdate;
typedef struct VhostUserGpuDMABUFScanout {
uint32_t scanout_id;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint32_t fd_width;
uint32_t fd_height;
uint32_t fd_stride;
uint32_t fd_flags;
int fd_drm_fourcc;
} QEMU_PACKED VhostUserGpuDMABUFScanout;
typedef struct VhostUserGpuMsg {
uint32_t request; /* VhostUserGpuRequest */
uint32_t flags;
uint32_t size; /* the following payload size */
union {
VhostUserGpuCursorPos cursor_pos;
VhostUserGpuCursorUpdate cursor_update;
VhostUserGpuScanout scanout;
VhostUserGpuUpdate update;
VhostUserGpuDMABUFScanout dmabuf_scanout;
struct virtio_gpu_resp_display_info display_info;
uint64_t u64;
} payload;
} QEMU_PACKED VhostUserGpuMsg;
static VhostUserGpuMsg m __attribute__ ((unused));
#define VHOST_USER_GPU_HDR_SIZE \
(sizeof(m.request) + sizeof(m.flags) + sizeof(m.size))
#define VHOST_USER_GPU_MSG_FLAG_REPLY 0x4
struct virtio_gpu_scanout {
uint32_t width, height;
int x, y;
int invalidate;
uint32_t resource_id;
};
typedef struct VuGpu {
VugDev dev;
struct virtio_gpu_config virtio_config;
struct vugbm_device gdev;
int sock_fd;
int drm_rnode_fd;
GSource *renderer_source;
guint wait_ok;
bool virgl;
bool virgl_inited;
uint32_t inflight;
struct virtio_gpu_scanout scanout[VIRTIO_GPU_MAX_SCANOUTS];
QTAILQ_HEAD(, virtio_gpu_simple_resource) reslist;
QTAILQ_HEAD(, virtio_gpu_ctrl_command) fenceq;
} VuGpu;
struct virtio_gpu_ctrl_command {
VuVirtqElement elem;
VuVirtq *vq;
struct virtio_gpu_ctrl_hdr cmd_hdr;
uint32_t error;
bool finished;
QTAILQ_ENTRY(virtio_gpu_ctrl_command) next;
};
#define VUGPU_FILL_CMD(out) do { \
size_t s; \
s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, 0, \
&out, sizeof(out)); \
if (s != sizeof(out)) { \
g_critical("%s: command size incorrect %zu vs %zu", \
__func__, s, sizeof(out)); \
return; \
} \
} while (0)
void vg_ctrl_response(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd,
struct virtio_gpu_ctrl_hdr *resp,
size_t resp_len);
void vg_ctrl_response_nodata(VuGpu *g,
struct virtio_gpu_ctrl_command *cmd,
enum virtio_gpu_ctrl_type type);
int vg_create_mapping_iov(VuGpu *g,
struct virtio_gpu_resource_attach_backing *ab,
struct virtio_gpu_ctrl_command *cmd,
struct iovec **iov);
void vg_get_display_info(VuGpu *vg, struct virtio_gpu_ctrl_command *cmd);
void vg_wait_ok(VuGpu *g);
void vg_send_msg(VuGpu *g, const VhostUserGpuMsg *msg, int fd);
bool vg_recv_msg(VuGpu *g, uint32_t expect_req, uint32_t expect_size,
gpointer payload);
#endif

View File

@ -144,7 +144,7 @@ cc-option = $(if $(shell $(CC) $1 $2 -S -o /dev/null -xc /dev/null \
cc-c-option = $(if $(shell $(CC) $1 $2 -c -o /dev/null -xc /dev/null \ cc-c-option = $(if $(shell $(CC) $1 $2 -c -o /dev/null -xc /dev/null \
>/dev/null 2>&1 && echo OK), $2, $3) >/dev/null 2>&1 && echo OK), $2, $3)
VPATH_SUFFIXES = %.c %.h %.S %.cc %.cpp %.m %.mak %.texi %.sh %.rc Kconfig% VPATH_SUFFIXES = %.c %.h %.S %.cc %.cpp %.m %.mak %.texi %.sh %.rc Kconfig% %.json.in
set-vpath = $(if $1,$(foreach PATTERN,$(VPATH_SUFFIXES),$(eval vpath $(PATTERN) $1))) set-vpath = $(if $1,$(foreach PATTERN,$(VPATH_SUFFIXES),$(eval vpath $(PATTERN) $1)))
# install-prog list, dir # install-prog list, dir
@ -392,3 +392,10 @@ TEXI2MAN = $(call quiet-command, \
$(call TEXI2MAN) $(call TEXI2MAN)
%.8: %.8:
$(call TEXI2MAN) $(call TEXI2MAN)
GEN_SUBST = $(call quiet-command, \
sed -e "s!@libexecdir@!$(libexecdir)!g" < $< > $@, \
"GEN","$@")
%.json: %.json.in
$(call GEN_SUBST)