931150e56b
The below referenced commit renames scanout_width/height to
backing_width/height, but also promotes these fields in various portions
of the egl interface. Meanwhile vfio dmabuf support has never used the
previous scanout fields and is therefore missed in the update. This
results in a black screen when transitioning from ramfb to dmabuf display
when using Intel vGPU with these features.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1891
Link: https://lists.gnu.org/archive/html/qemu-devel/2023-08/msg02726.html
Fixes: 9ac06df8b6
("virtio-gpu-udmabuf: correct naming of QemuDmaBuf size properties")
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Tested-by: Cédric Le Goater <clg@redhat.com>
Signed-off-by: Cédric Le Goater <clg@redhat.com>
547 lines
16 KiB
C
547 lines
16 KiB
C
/*
|
|
* display support for mdev based vgpu devices
|
|
*
|
|
* Copyright Red Hat, Inc. 2017
|
|
*
|
|
* Authors:
|
|
* Gerd Hoffmann
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
* the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include <linux/vfio.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "qemu/error-report.h"
|
|
#include "hw/display/edid.h"
|
|
#include "ui/console.h"
|
|
#include "qapi/error.h"
|
|
#include "pci.h"
|
|
#include "trace.h"
|
|
|
|
#ifndef DRM_PLANE_TYPE_PRIMARY
|
|
# define DRM_PLANE_TYPE_PRIMARY 1
|
|
# define DRM_PLANE_TYPE_CURSOR 2
|
|
#endif
|
|
|
|
#define pread_field(_fd, _reg, _ptr, _fld) \
|
|
(sizeof(_ptr->_fld) != \
|
|
pread(_fd, &(_ptr->_fld), sizeof(_ptr->_fld), \
|
|
_reg->offset + offsetof(typeof(*_ptr), _fld)))
|
|
|
|
#define pwrite_field(_fd, _reg, _ptr, _fld) \
|
|
(sizeof(_ptr->_fld) != \
|
|
pwrite(_fd, &(_ptr->_fld), sizeof(_ptr->_fld), \
|
|
_reg->offset + offsetof(typeof(*_ptr), _fld)))
|
|
|
|
|
|
static void vfio_display_edid_link_up(void *opaque)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
int fd = vdev->vbasedev.fd;
|
|
|
|
dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_UP;
|
|
if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) {
|
|
goto err;
|
|
}
|
|
trace_vfio_display_edid_link_up();
|
|
return;
|
|
|
|
err:
|
|
trace_vfio_display_edid_write_error();
|
|
}
|
|
|
|
static void vfio_display_edid_update(VFIOPCIDevice *vdev, bool enabled,
|
|
int prefx, int prefy)
|
|
{
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
int fd = vdev->vbasedev.fd;
|
|
qemu_edid_info edid = {
|
|
.maxx = dpy->edid_regs->max_xres,
|
|
.maxy = dpy->edid_regs->max_yres,
|
|
.prefx = prefx ?: vdev->display_xres,
|
|
.prefy = prefy ?: vdev->display_yres,
|
|
};
|
|
|
|
timer_del(dpy->edid_link_timer);
|
|
dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_DOWN;
|
|
if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) {
|
|
goto err;
|
|
}
|
|
trace_vfio_display_edid_link_down();
|
|
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
|
|
if (edid.maxx && edid.prefx > edid.maxx) {
|
|
edid.prefx = edid.maxx;
|
|
}
|
|
if (edid.maxy && edid.prefy > edid.maxy) {
|
|
edid.prefy = edid.maxy;
|
|
}
|
|
qemu_edid_generate(dpy->edid_blob,
|
|
dpy->edid_regs->edid_max_size,
|
|
&edid);
|
|
trace_vfio_display_edid_update(edid.prefx, edid.prefy);
|
|
|
|
dpy->edid_regs->edid_size = qemu_edid_size(dpy->edid_blob);
|
|
if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, edid_size)) {
|
|
goto err;
|
|
}
|
|
if (pwrite(fd, dpy->edid_blob, dpy->edid_regs->edid_size,
|
|
dpy->edid_info->offset + dpy->edid_regs->edid_offset)
|
|
!= dpy->edid_regs->edid_size) {
|
|
goto err;
|
|
}
|
|
|
|
timer_mod(dpy->edid_link_timer,
|
|
qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 100);
|
|
return;
|
|
|
|
err:
|
|
trace_vfio_display_edid_write_error();
|
|
return;
|
|
}
|
|
|
|
static void vfio_display_edid_ui_info(void *opaque, uint32_t idx,
|
|
QemuUIInfo *info)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
|
|
if (!dpy->edid_regs) {
|
|
return;
|
|
}
|
|
|
|
if (info->width && info->height) {
|
|
vfio_display_edid_update(vdev, true, info->width, info->height);
|
|
} else {
|
|
vfio_display_edid_update(vdev, false, 0, 0);
|
|
}
|
|
}
|
|
|
|
static void vfio_display_edid_init(VFIOPCIDevice *vdev)
|
|
{
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
int fd = vdev->vbasedev.fd;
|
|
int ret;
|
|
|
|
ret = vfio_get_dev_region_info(&vdev->vbasedev,
|
|
VFIO_REGION_TYPE_GFX,
|
|
VFIO_REGION_SUBTYPE_GFX_EDID,
|
|
&dpy->edid_info);
|
|
if (ret) {
|
|
return;
|
|
}
|
|
|
|
trace_vfio_display_edid_available();
|
|
dpy->edid_regs = g_new0(struct vfio_region_gfx_edid, 1);
|
|
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_offset)) {
|
|
goto err;
|
|
}
|
|
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_max_size)) {
|
|
goto err;
|
|
}
|
|
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_xres)) {
|
|
goto err;
|
|
}
|
|
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_yres)) {
|
|
goto err;
|
|
}
|
|
|
|
dpy->edid_blob = g_malloc0(dpy->edid_regs->edid_max_size);
|
|
|
|
/* if xres + yres properties are unset use the maximum resolution */
|
|
if (!vdev->display_xres) {
|
|
vdev->display_xres = dpy->edid_regs->max_xres;
|
|
}
|
|
if (!vdev->display_yres) {
|
|
vdev->display_yres = dpy->edid_regs->max_yres;
|
|
}
|
|
|
|
dpy->edid_link_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
|
vfio_display_edid_link_up, vdev);
|
|
|
|
vfio_display_edid_update(vdev, true, 0, 0);
|
|
return;
|
|
|
|
err:
|
|
trace_vfio_display_edid_write_error();
|
|
g_free(dpy->edid_regs);
|
|
dpy->edid_regs = NULL;
|
|
return;
|
|
}
|
|
|
|
static void vfio_display_edid_exit(VFIODisplay *dpy)
|
|
{
|
|
if (!dpy->edid_regs) {
|
|
return;
|
|
}
|
|
|
|
g_free(dpy->edid_regs);
|
|
g_free(dpy->edid_blob);
|
|
timer_free(dpy->edid_link_timer);
|
|
}
|
|
|
|
static void vfio_display_update_cursor(VFIODMABuf *dmabuf,
|
|
struct vfio_device_gfx_plane_info *plane)
|
|
{
|
|
if (dmabuf->pos_x != plane->x_pos || dmabuf->pos_y != plane->y_pos) {
|
|
dmabuf->pos_x = plane->x_pos;
|
|
dmabuf->pos_y = plane->y_pos;
|
|
dmabuf->pos_updates++;
|
|
}
|
|
if (dmabuf->hot_x != plane->x_hot || dmabuf->hot_y != plane->y_hot) {
|
|
dmabuf->hot_x = plane->x_hot;
|
|
dmabuf->hot_y = plane->y_hot;
|
|
dmabuf->hot_updates++;
|
|
}
|
|
}
|
|
|
|
static VFIODMABuf *vfio_display_get_dmabuf(VFIOPCIDevice *vdev,
|
|
uint32_t plane_type)
|
|
{
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
struct vfio_device_gfx_plane_info plane;
|
|
VFIODMABuf *dmabuf;
|
|
int fd, ret;
|
|
|
|
memset(&plane, 0, sizeof(plane));
|
|
plane.argsz = sizeof(plane);
|
|
plane.flags = VFIO_GFX_PLANE_TYPE_DMABUF;
|
|
plane.drm_plane_type = plane_type;
|
|
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
|
|
if (ret < 0) {
|
|
return NULL;
|
|
}
|
|
if (!plane.drm_format || !plane.size) {
|
|
return NULL;
|
|
}
|
|
|
|
QTAILQ_FOREACH(dmabuf, &dpy->dmabuf.bufs, next) {
|
|
if (dmabuf->dmabuf_id == plane.dmabuf_id) {
|
|
/* found in list, move to head, return it */
|
|
QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
|
|
QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
|
|
if (plane_type == DRM_PLANE_TYPE_CURSOR) {
|
|
vfio_display_update_cursor(dmabuf, &plane);
|
|
}
|
|
return dmabuf;
|
|
}
|
|
}
|
|
|
|
fd = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_GFX_DMABUF, &plane.dmabuf_id);
|
|
if (fd < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
dmabuf = g_new0(VFIODMABuf, 1);
|
|
dmabuf->dmabuf_id = plane.dmabuf_id;
|
|
dmabuf->buf.width = plane.width;
|
|
dmabuf->buf.height = plane.height;
|
|
dmabuf->buf.backing_width = plane.width;
|
|
dmabuf->buf.backing_height = plane.height;
|
|
dmabuf->buf.stride = plane.stride;
|
|
dmabuf->buf.fourcc = plane.drm_format;
|
|
dmabuf->buf.modifier = plane.drm_format_mod;
|
|
dmabuf->buf.fd = fd;
|
|
if (plane_type == DRM_PLANE_TYPE_CURSOR) {
|
|
vfio_display_update_cursor(dmabuf, &plane);
|
|
}
|
|
|
|
QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
|
|
return dmabuf;
|
|
}
|
|
|
|
static void vfio_display_free_one_dmabuf(VFIODisplay *dpy, VFIODMABuf *dmabuf)
|
|
{
|
|
QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
|
|
dpy_gl_release_dmabuf(dpy->con, &dmabuf->buf);
|
|
close(dmabuf->buf.fd);
|
|
g_free(dmabuf);
|
|
}
|
|
|
|
static void vfio_display_free_dmabufs(VFIOPCIDevice *vdev)
|
|
{
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
VFIODMABuf *dmabuf, *tmp;
|
|
uint32_t keep = 5;
|
|
|
|
QTAILQ_FOREACH_SAFE(dmabuf, &dpy->dmabuf.bufs, next, tmp) {
|
|
if (keep > 0) {
|
|
keep--;
|
|
continue;
|
|
}
|
|
assert(dmabuf != dpy->dmabuf.primary);
|
|
vfio_display_free_one_dmabuf(dpy, dmabuf);
|
|
}
|
|
}
|
|
|
|
static void vfio_display_dmabuf_update(void *opaque)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
VFIODMABuf *primary, *cursor;
|
|
bool free_bufs = false, new_cursor = false;
|
|
|
|
primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
|
|
if (primary == NULL) {
|
|
if (dpy->ramfb) {
|
|
ramfb_display_update(dpy->con, dpy->ramfb);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (dpy->dmabuf.primary != primary) {
|
|
dpy->dmabuf.primary = primary;
|
|
qemu_console_resize(dpy->con,
|
|
primary->buf.width, primary->buf.height);
|
|
dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
|
|
free_bufs = true;
|
|
}
|
|
|
|
cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
|
|
if (dpy->dmabuf.cursor != cursor) {
|
|
dpy->dmabuf.cursor = cursor;
|
|
new_cursor = true;
|
|
free_bufs = true;
|
|
}
|
|
|
|
if (cursor && (new_cursor || cursor->hot_updates)) {
|
|
bool have_hot = (cursor->hot_x != 0xffffffff &&
|
|
cursor->hot_y != 0xffffffff);
|
|
dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
|
|
cursor->hot_x, cursor->hot_y);
|
|
cursor->hot_updates = 0;
|
|
} else if (!cursor && new_cursor) {
|
|
dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
|
|
}
|
|
|
|
if (cursor && cursor->pos_updates) {
|
|
dpy_gl_cursor_position(dpy->con,
|
|
cursor->pos_x,
|
|
cursor->pos_y);
|
|
cursor->pos_updates = 0;
|
|
}
|
|
|
|
dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height);
|
|
|
|
if (free_bufs) {
|
|
vfio_display_free_dmabufs(vdev);
|
|
}
|
|
}
|
|
|
|
static int vfio_display_get_flags(void *opaque)
|
|
{
|
|
return GRAPHIC_FLAGS_GL | GRAPHIC_FLAGS_DMABUF;
|
|
}
|
|
|
|
static const GraphicHwOps vfio_display_dmabuf_ops = {
|
|
.get_flags = vfio_display_get_flags,
|
|
.gfx_update = vfio_display_dmabuf_update,
|
|
.ui_info = vfio_display_edid_ui_info,
|
|
};
|
|
|
|
static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
|
|
{
|
|
if (!display_opengl) {
|
|
error_setg(errp, "vfio-display-dmabuf: opengl not available");
|
|
return -1;
|
|
}
|
|
|
|
vdev->dpy = g_new0(VFIODisplay, 1);
|
|
vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
|
|
&vfio_display_dmabuf_ops,
|
|
vdev);
|
|
if (vdev->enable_ramfb) {
|
|
vdev->dpy->ramfb = ramfb_setup(errp);
|
|
}
|
|
vfio_display_edid_init(vdev);
|
|
return 0;
|
|
}
|
|
|
|
static void vfio_display_dmabuf_exit(VFIODisplay *dpy)
|
|
{
|
|
VFIODMABuf *dmabuf;
|
|
|
|
if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) {
|
|
return;
|
|
}
|
|
|
|
while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) {
|
|
vfio_display_free_one_dmabuf(dpy, dmabuf);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
void vfio_display_reset(VFIOPCIDevice *vdev)
|
|
{
|
|
if (!vdev || !vdev->dpy || !vdev->dpy->con ||
|
|
!vdev->dpy->dmabuf.primary) {
|
|
return;
|
|
}
|
|
|
|
dpy_gl_scanout_disable(vdev->dpy->con);
|
|
vfio_display_dmabuf_exit(vdev->dpy);
|
|
dpy_gfx_update_full(vdev->dpy->con);
|
|
}
|
|
|
|
static void vfio_display_region_update(void *opaque)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
struct vfio_device_gfx_plane_info plane = {
|
|
.argsz = sizeof(plane),
|
|
.flags = VFIO_GFX_PLANE_TYPE_REGION
|
|
};
|
|
pixman_format_code_t format;
|
|
int ret;
|
|
|
|
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
|
|
if (ret < 0) {
|
|
error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
if (!plane.drm_format || !plane.size) {
|
|
if (dpy->ramfb) {
|
|
ramfb_display_update(dpy->con, dpy->ramfb);
|
|
dpy->region.surface = NULL;
|
|
}
|
|
return;
|
|
}
|
|
format = qemu_drm_format_to_pixman(plane.drm_format);
|
|
if (!format) {
|
|
return;
|
|
}
|
|
|
|
if (dpy->region.buffer.size &&
|
|
dpy->region.buffer.nr != plane.region_index) {
|
|
/* region changed */
|
|
vfio_region_exit(&dpy->region.buffer);
|
|
vfio_region_finalize(&dpy->region.buffer);
|
|
dpy->region.surface = NULL;
|
|
}
|
|
|
|
if (dpy->region.surface &&
|
|
(surface_width(dpy->region.surface) != plane.width ||
|
|
surface_height(dpy->region.surface) != plane.height ||
|
|
surface_format(dpy->region.surface) != format)) {
|
|
/* size changed */
|
|
dpy->region.surface = NULL;
|
|
}
|
|
|
|
if (!dpy->region.buffer.size) {
|
|
/* mmap region */
|
|
ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
|
|
&dpy->region.buffer,
|
|
plane.region_index,
|
|
"display");
|
|
if (ret != 0) {
|
|
error_report("%s: vfio_region_setup(%d): %s",
|
|
__func__, plane.region_index, strerror(-ret));
|
|
goto err;
|
|
}
|
|
ret = vfio_region_mmap(&dpy->region.buffer);
|
|
if (ret != 0) {
|
|
error_report("%s: vfio_region_mmap(%d): %s", __func__,
|
|
plane.region_index, strerror(-ret));
|
|
goto err;
|
|
}
|
|
assert(dpy->region.buffer.mmaps[0].mmap != NULL);
|
|
}
|
|
|
|
if (dpy->region.surface == NULL) {
|
|
/* create surface */
|
|
dpy->region.surface = qemu_create_displaysurface_from
|
|
(plane.width, plane.height, format,
|
|
plane.stride, dpy->region.buffer.mmaps[0].mmap);
|
|
dpy_gfx_replace_surface(dpy->con, dpy->region.surface);
|
|
}
|
|
|
|
/* full screen update */
|
|
dpy_gfx_update(dpy->con, 0, 0,
|
|
surface_width(dpy->region.surface),
|
|
surface_height(dpy->region.surface));
|
|
return;
|
|
|
|
err:
|
|
vfio_region_exit(&dpy->region.buffer);
|
|
vfio_region_finalize(&dpy->region.buffer);
|
|
}
|
|
|
|
static const GraphicHwOps vfio_display_region_ops = {
|
|
.gfx_update = vfio_display_region_update,
|
|
};
|
|
|
|
static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
|
|
{
|
|
vdev->dpy = g_new0(VFIODisplay, 1);
|
|
vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
|
|
&vfio_display_region_ops,
|
|
vdev);
|
|
if (vdev->enable_ramfb) {
|
|
vdev->dpy->ramfb = ramfb_setup(errp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void vfio_display_region_exit(VFIODisplay *dpy)
|
|
{
|
|
if (!dpy->region.buffer.size) {
|
|
return;
|
|
}
|
|
|
|
vfio_region_exit(&dpy->region.buffer);
|
|
vfio_region_finalize(&dpy->region.buffer);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
|
|
{
|
|
struct vfio_device_gfx_plane_info probe;
|
|
int ret;
|
|
|
|
memset(&probe, 0, sizeof(probe));
|
|
probe.argsz = sizeof(probe);
|
|
probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
|
|
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
|
|
if (ret == 0) {
|
|
return vfio_display_dmabuf_init(vdev, errp);
|
|
}
|
|
|
|
memset(&probe, 0, sizeof(probe));
|
|
probe.argsz = sizeof(probe);
|
|
probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
|
|
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
|
|
if (ret == 0) {
|
|
return vfio_display_region_init(vdev, errp);
|
|
}
|
|
|
|
if (vdev->display == ON_OFF_AUTO_AUTO) {
|
|
/* not an error in automatic mode */
|
|
return 0;
|
|
}
|
|
|
|
error_setg(errp, "vfio: device doesn't support any (known) display method");
|
|
return -1;
|
|
}
|
|
|
|
void vfio_display_finalize(VFIOPCIDevice *vdev)
|
|
{
|
|
if (!vdev->dpy) {
|
|
return;
|
|
}
|
|
|
|
graphic_console_close(vdev->dpy->con);
|
|
vfio_display_dmabuf_exit(vdev->dpy);
|
|
vfio_display_region_exit(vdev->dpy);
|
|
vfio_display_edid_exit(vdev->dpy);
|
|
g_free(vdev->dpy);
|
|
}
|