This reverts commit f79081b4b7
.
Patch has broken byteorder handling: RAMFBCfg fields are in bigendian
byteorder, the reset function doesn't care so native byteorder is used
instead. Given this went unnoticed so far the feature is obviously
unused, so just revert the patch.
Cc: Hou Qiming <hqm03ster@gmail.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Acked-by: Laszlo Ersek <lersek@redhat.com>
Message-id: 20200429115236.28709-2-kraxel@redhat.com
541 lines
16 KiB
C
541 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 "sysemu/sysemu.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 int vfio_display_edid_ui_info(void *opaque, uint32_t idx,
|
|
QemuUIInfo *info)
|
|
{
|
|
VFIOPCIDevice *vdev = opaque;
|
|
VFIODisplay *dpy = vdev->dpy;
|
|
|
|
if (!dpy->edid_regs) {
|
|
return 0;
|
|
}
|
|
|
|
if (info->width && info->height) {
|
|
vfio_display_edid_update(vdev, true, info->width, info->height);
|
|
} else {
|
|
vfio_display_edid_update(vdev, false, 0, 0);
|
|
}
|
|
|
|
return 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_del(dpy->edid_link_timer);
|
|
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.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 const GraphicHwOps vfio_display_dmabuf_ops = {
|
|
.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);
|
|
}
|
|
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);
|
|
}
|