qemu/hw/vfio/display.c
Dongwon Kim c0fcd6334f ui/console: Use qemu_dmabuf_new() and free() helpers instead
This commit introduces utility functions for the creation and deallocation
of QemuDmaBuf instances. Additionally, it updates all relevant sections
of the codebase to utilize these new utility functions.

v7: remove prefix, "dpy_gl_" from all helpers
    qemu_dmabuf_free() returns without doing anything if input is null
    (Daniel P. Berrangé <berrange@redhat.com>)
    call G_DEFINE_AUTOPTR_CLEANUP_FUNC for qemu_dmabuf_free()
    (Daniel P. Berrangé <berrange@redhat.com>)

v8: Introduction of helpers was removed as those were already added
    by the previous commit

v9: set dmabuf->allow_fences to 'true' when dmabuf is created in
    virtio_gpu_create_dmabuf()/virtio-gpu-udmabuf.c

    removed unnecessary spaces were accidently added in the patch,
    'ui/console: Use qemu_dmabuf_new() a...'

v11: Calling qemu_dmabuf_close was removed as closing dmabuf->fd will be
     done in qemu_dmabuf_free anyway.
     (Daniel P. Berrangé <berrange@redhat.com>)

v12: --- Calling qemu_dmabuf_close separately as qemu_dmabuf_free doesn't
         do it.

     --- 'dmabuf' is now allocated space so it should be freed at the end of
         dbus_scanout_texture

v13: --- Immediately free dmabuf after it is released to prevent possible
         leaking of the ptr
         (Marc-André Lureau <marcandre.lureau@redhat.com>)

     --- Use g_autoptr macro to define *dmabuf for auto clean up instead of
         calling qemu_dmabuf_free
         (Marc-André Lureau <marcandre.lureau@redhat.com>)

v14: --- (vhost-user-gpu) Change qemu_dmabuf_free back to g_clear_pointer
         as it was done because of some misunderstanding (v13).

     --- (vhost-user-gpu) g->dmabuf[m->scanout_id] needs to be set to NULL
         to prevent freed dmabuf to be accessed again in case if(fd==-1)break;
         happens (before new dmabuf is allocated). Otherwise, it would cause
         invalid memory access when the same function is executed. Also NULL
         check should be done before qemu_dmabuf_close (it asserts dmabuf!=NULL.).
         (Marc-André Lureau <marcandre.lureau@redhat.com>)

Suggested-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Cc: Philippe Mathieu-Daudé <philmd@linaro.org>
Cc: Daniel P. Berrangé <berrange@redhat.com>
Cc: Vivek Kasireddy <vivek.kasireddy@intel.com>
Signed-off-by: Dongwon Kim <dongwon.kim@intel.com>
Message-Id: <20240508175403.3399895-6-dongwon.kim@intel.com>
2024-05-14 17:14:12 +04:00

570 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 = qemu_dmabuf_new(plane.width, plane.height,
plane.stride, 0, 0, plane.width,
plane.height, plane.drm_format,
plane.drm_format_mod, fd, false, false);
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);
qemu_dmabuf_close(dmabuf->buf);
dpy_gl_release_dmabuf(dpy->con, dmabuf->buf);
g_clear_pointer(&dmabuf->buf, qemu_dmabuf_free);
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;
uint32_t width, height;
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;
}
width = qemu_dmabuf_get_width(primary->buf);
height = qemu_dmabuf_get_height(primary->buf);
if (dpy->dmabuf.primary != primary) {
dpy->dmabuf.primary = primary;
qemu_console_resize(dpy->con, width, 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, width, 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);
}
static bool migrate_needed(void *opaque)
{
VFIODisplay *dpy = opaque;
bool ramfb_exists = dpy->ramfb != NULL;
/* see vfio_display_migration_needed() */
assert(ramfb_exists);
return ramfb_exists;
}
const VMStateDescription vfio_display_vmstate = {
.name = "VFIODisplay",
.version_id = 1,
.minimum_version_id = 1,
.needed = migrate_needed,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT_POINTER(ramfb, VFIODisplay, ramfb_vmstate, RAMFBState),
VMSTATE_END_OF_LIST(),
}
};