83d9019202
vfio_display_edid_init() can fail for many reasons and return silently.
It would be good to report the error.
Old mdev driver may not support vfio edid region and we allow to go
through in this case.
vfio_display_edid_update() isn't changed because it can be called at
runtime when UI changes (i.e. window resize).
Fixes: 08479114b0
("vfio/display: add edid support.")
Suggested-by: Cédric Le Goater <clg@redhat.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
580 lines
17 KiB
C
580 lines
17 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 bool vfio_display_edid_init(VFIOPCIDevice *vdev, Error **errp)
|
|
{
|
|
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) {
|
|
/* Failed to get GFX edid info, allow to go through without edid. */
|
|
return true;
|
|
}
|
|
|
|
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 true;
|
|
|
|
err:
|
|
error_setg(errp, "vfio: failed to read GFX edid field");
|
|
trace_vfio_display_edid_write_error();
|
|
g_free(dpy->edid_info);
|
|
g_free(dpy->edid_regs);
|
|
dpy->edid_info = NULL;
|
|
dpy->edid_regs = NULL;
|
|
return false;
|
|
}
|
|
|
|
static void vfio_display_edid_exit(VFIODisplay *dpy)
|
|
{
|
|
if (!dpy->edid_regs) {
|
|
return;
|
|
}
|
|
|
|
g_free(dpy->edid_info);
|
|
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 bool vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
|
|
{
|
|
if (!display_opengl) {
|
|
error_setg(errp, "vfio-display-dmabuf: opengl not available");
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
if (!vdev->dpy->ramfb) {
|
|
return false;
|
|
}
|
|
}
|
|
return vfio_display_edid_init(vdev, errp);
|
|
}
|
|
|
|
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 bool 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);
|
|
if (!vdev->dpy->ramfb) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
bool 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 true;
|
|
}
|
|
|
|
error_setg(errp, "vfio: device doesn't support any (known) display method");
|
|
return false;
|
|
}
|
|
|
|
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(),
|
|
}
|
|
};
|