qemu/hw/vfio/display.c
<<
>>
Prefs
   1/*
   2 * display support for mdev based vgpu devices
   3 *
   4 * Copyright Red Hat, Inc. 2017
   5 *
   6 * Authors:
   7 *    Gerd Hoffmann
   8 *
   9 * This work is licensed under the terms of the GNU GPL, version 2.  See
  10 * the COPYING file in the top-level directory.
  11 */
  12
  13#include "qemu/osdep.h"
  14#include <linux/vfio.h>
  15#include <sys/ioctl.h>
  16
  17#include "hw/display/edid.h"
  18#include "ui/console.h"
  19#include "qapi/error.h"
  20#include "pci.h"
  21#include "trace.h"
  22
  23#ifndef DRM_PLANE_TYPE_PRIMARY
  24# define DRM_PLANE_TYPE_PRIMARY 1
  25# define DRM_PLANE_TYPE_CURSOR  2
  26#endif
  27
  28#define pread_field(_fd, _reg, _ptr, _fld)                              \
  29    (sizeof(_ptr->_fld) !=                                              \
  30     pread(_fd, &(_ptr->_fld), sizeof(_ptr->_fld),                      \
  31           _reg->offset + offsetof(typeof(*_ptr), _fld)))
  32
  33#define pwrite_field(_fd, _reg, _ptr, _fld)                             \
  34    (sizeof(_ptr->_fld) !=                                              \
  35     pwrite(_fd, &(_ptr->_fld), sizeof(_ptr->_fld),                     \
  36            _reg->offset + offsetof(typeof(*_ptr), _fld)))
  37
  38
  39static void vfio_display_edid_link_up(void *opaque)
  40{
  41    VFIOPCIDevice *vdev = opaque;
  42    VFIODisplay *dpy = vdev->dpy;
  43    int fd = vdev->vbasedev.fd;
  44
  45    dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_UP;
  46    if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) {
  47        goto err;
  48    }
  49    trace_vfio_display_edid_link_up();
  50    return;
  51
  52err:
  53    trace_vfio_display_edid_write_error();
  54}
  55
  56static void vfio_display_edid_update(VFIOPCIDevice *vdev, bool enabled,
  57                                     int prefx, int prefy)
  58{
  59    VFIODisplay *dpy = vdev->dpy;
  60    int fd = vdev->vbasedev.fd;
  61    qemu_edid_info edid = {
  62        .maxx  = dpy->edid_regs->max_xres,
  63        .maxy  = dpy->edid_regs->max_yres,
  64        .prefx = prefx ?: vdev->display_xres,
  65        .prefy = prefy ?: vdev->display_yres,
  66    };
  67
  68    timer_del(dpy->edid_link_timer);
  69    dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_DOWN;
  70    if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) {
  71        goto err;
  72    }
  73    trace_vfio_display_edid_link_down();
  74
  75    if (!enabled) {
  76        return;
  77    }
  78
  79    if (edid.maxx && edid.prefx > edid.maxx) {
  80        edid.prefx = edid.maxx;
  81    }
  82    if (edid.maxy && edid.prefy > edid.maxy) {
  83        edid.prefy = edid.maxy;
  84    }
  85    qemu_edid_generate(dpy->edid_blob,
  86                       dpy->edid_regs->edid_max_size,
  87                       &edid);
  88    trace_vfio_display_edid_update(edid.prefx, edid.prefy);
  89
  90    dpy->edid_regs->edid_size = qemu_edid_size(dpy->edid_blob);
  91    if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, edid_size)) {
  92        goto err;
  93    }
  94    if (pwrite(fd, dpy->edid_blob, dpy->edid_regs->edid_size,
  95               dpy->edid_info->offset + dpy->edid_regs->edid_offset)
  96        != dpy->edid_regs->edid_size) {
  97        goto err;
  98    }
  99
 100    timer_mod(dpy->edid_link_timer,
 101              qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 100);
 102    return;
 103
 104err:
 105    trace_vfio_display_edid_write_error();
 106    return;
 107}
 108
 109static int vfio_display_edid_ui_info(void *opaque, uint32_t idx,
 110                                     QemuUIInfo *info)
 111{
 112    VFIOPCIDevice *vdev = opaque;
 113    VFIODisplay *dpy = vdev->dpy;
 114
 115    if (!dpy->edid_regs) {
 116        return 0;
 117    }
 118
 119    if (info->width && info->height) {
 120        vfio_display_edid_update(vdev, true, info->width, info->height);
 121    } else {
 122        vfio_display_edid_update(vdev, false, 0, 0);
 123    }
 124
 125    return 0;
 126}
 127
 128static void vfio_display_edid_init(VFIOPCIDevice *vdev)
 129{
 130    VFIODisplay *dpy = vdev->dpy;
 131    int fd = vdev->vbasedev.fd;
 132    int ret;
 133
 134    ret = vfio_get_dev_region_info(&vdev->vbasedev,
 135                                   VFIO_REGION_TYPE_GFX,
 136                                   VFIO_REGION_SUBTYPE_GFX_EDID,
 137                                   &dpy->edid_info);
 138    if (ret) {
 139        return;
 140    }
 141
 142    trace_vfio_display_edid_available();
 143    dpy->edid_regs = g_new0(struct vfio_region_gfx_edid, 1);
 144    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_offset)) {
 145        goto err;
 146    }
 147    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_max_size)) {
 148        goto err;
 149    }
 150    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_xres)) {
 151        goto err;
 152    }
 153    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_yres)) {
 154        goto err;
 155    }
 156
 157    dpy->edid_blob = g_malloc0(dpy->edid_regs->edid_max_size);
 158
 159    /* if xres + yres properties are unset use the maximum resolution */
 160    if (!vdev->display_xres) {
 161        vdev->display_xres = dpy->edid_regs->max_xres;
 162    }
 163    if (!vdev->display_yres) {
 164        vdev->display_yres = dpy->edid_regs->max_yres;
 165    }
 166
 167    dpy->edid_link_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
 168                                        vfio_display_edid_link_up, vdev);
 169
 170    vfio_display_edid_update(vdev, true, 0, 0);
 171    return;
 172
 173err:
 174    trace_vfio_display_edid_write_error();
 175    g_free(dpy->edid_regs);
 176    dpy->edid_regs = NULL;
 177    return;
 178}
 179
 180static void vfio_display_edid_exit(VFIODisplay *dpy)
 181{
 182    if (!dpy->edid_regs) {
 183        return;
 184    }
 185
 186    g_free(dpy->edid_regs);
 187    g_free(dpy->edid_blob);
 188    timer_free(dpy->edid_link_timer);
 189}
 190
 191static void vfio_display_update_cursor(VFIODMABuf *dmabuf,
 192                                       struct vfio_device_gfx_plane_info *plane)
 193{
 194    if (dmabuf->pos_x != plane->x_pos || dmabuf->pos_y != plane->y_pos) {
 195        dmabuf->pos_x      = plane->x_pos;
 196        dmabuf->pos_y      = plane->y_pos;
 197        dmabuf->pos_updates++;
 198    }
 199    if (dmabuf->hot_x != plane->x_hot || dmabuf->hot_y != plane->y_hot) {
 200        dmabuf->hot_x      = plane->x_hot;
 201        dmabuf->hot_y      = plane->y_hot;
 202        dmabuf->hot_updates++;
 203    }
 204}
 205
 206static VFIODMABuf *vfio_display_get_dmabuf(VFIOPCIDevice *vdev,
 207                                           uint32_t plane_type)
 208{
 209    VFIODisplay *dpy = vdev->dpy;
 210    struct vfio_device_gfx_plane_info plane;
 211    VFIODMABuf *dmabuf;
 212    int fd, ret;
 213
 214    memset(&plane, 0, sizeof(plane));
 215    plane.argsz = sizeof(plane);
 216    plane.flags = VFIO_GFX_PLANE_TYPE_DMABUF;
 217    plane.drm_plane_type = plane_type;
 218    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
 219    if (ret < 0) {
 220        return NULL;
 221    }
 222    if (!plane.drm_format || !plane.size) {
 223        return NULL;
 224    }
 225
 226    QTAILQ_FOREACH(dmabuf, &dpy->dmabuf.bufs, next) {
 227        if (dmabuf->dmabuf_id == plane.dmabuf_id) {
 228            /* found in list, move to head, return it */
 229            QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
 230            QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
 231            if (plane_type == DRM_PLANE_TYPE_CURSOR) {
 232                vfio_display_update_cursor(dmabuf, &plane);
 233            }
 234            return dmabuf;
 235        }
 236    }
 237
 238    fd = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_GFX_DMABUF, &plane.dmabuf_id);
 239    if (fd < 0) {
 240        return NULL;
 241    }
 242
 243    dmabuf = g_new0(VFIODMABuf, 1);
 244    dmabuf->dmabuf_id  = plane.dmabuf_id;
 245    dmabuf->buf.width  = plane.width;
 246    dmabuf->buf.height = plane.height;
 247    dmabuf->buf.stride = plane.stride;
 248    dmabuf->buf.fourcc = plane.drm_format;
 249    dmabuf->buf.modifier = plane.drm_format_mod;
 250    dmabuf->buf.fd     = fd;
 251    if (plane_type == DRM_PLANE_TYPE_CURSOR) {
 252        vfio_display_update_cursor(dmabuf, &plane);
 253    }
 254
 255    QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
 256    return dmabuf;
 257}
 258
 259static void vfio_display_free_one_dmabuf(VFIODisplay *dpy, VFIODMABuf *dmabuf)
 260{
 261    QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
 262    dpy_gl_release_dmabuf(dpy->con, &dmabuf->buf);
 263    close(dmabuf->buf.fd);
 264    g_free(dmabuf);
 265}
 266
 267static void vfio_display_free_dmabufs(VFIOPCIDevice *vdev)
 268{
 269    VFIODisplay *dpy = vdev->dpy;
 270    VFIODMABuf *dmabuf, *tmp;
 271    uint32_t keep = 5;
 272
 273    QTAILQ_FOREACH_SAFE(dmabuf, &dpy->dmabuf.bufs, next, tmp) {
 274        if (keep > 0) {
 275            keep--;
 276            continue;
 277        }
 278        assert(dmabuf != dpy->dmabuf.primary);
 279        vfio_display_free_one_dmabuf(dpy, dmabuf);
 280    }
 281}
 282
 283static void vfio_display_dmabuf_update(void *opaque)
 284{
 285    VFIOPCIDevice *vdev = opaque;
 286    VFIODisplay *dpy = vdev->dpy;
 287    VFIODMABuf *primary, *cursor;
 288    bool free_bufs = false, new_cursor = false;
 289
 290    primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
 291    if (primary == NULL) {
 292        if (dpy->ramfb) {
 293            ramfb_display_update(dpy->con, dpy->ramfb);
 294        }
 295        return;
 296    }
 297
 298    if (dpy->dmabuf.primary != primary) {
 299        dpy->dmabuf.primary = primary;
 300        qemu_console_resize(dpy->con,
 301                            primary->buf.width, primary->buf.height);
 302        dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
 303        free_bufs = true;
 304    }
 305
 306    cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
 307    if (dpy->dmabuf.cursor != cursor) {
 308        dpy->dmabuf.cursor = cursor;
 309        new_cursor = true;
 310        free_bufs = true;
 311    }
 312
 313    if (cursor && (new_cursor || cursor->hot_updates)) {
 314        bool have_hot = (cursor->hot_x != 0xffffffff &&
 315                         cursor->hot_y != 0xffffffff);
 316        dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
 317                             cursor->hot_x, cursor->hot_y);
 318        cursor->hot_updates = 0;
 319    } else if (!cursor && new_cursor) {
 320        dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
 321    }
 322
 323    if (cursor && cursor->pos_updates) {
 324        dpy_gl_cursor_position(dpy->con,
 325                               cursor->pos_x,
 326                               cursor->pos_y);
 327        cursor->pos_updates = 0;
 328    }
 329
 330    dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height);
 331
 332    if (free_bufs) {
 333        vfio_display_free_dmabufs(vdev);
 334    }
 335}
 336
 337static int vfio_display_get_flags(void *opaque)
 338{
 339    return GRAPHIC_FLAGS_GL | GRAPHIC_FLAGS_DMABUF;
 340}
 341
 342static const GraphicHwOps vfio_display_dmabuf_ops = {
 343    .get_flags  = vfio_display_get_flags,
 344    .gfx_update = vfio_display_dmabuf_update,
 345    .ui_info    = vfio_display_edid_ui_info,
 346};
 347
 348static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
 349{
 350    if (!display_opengl) {
 351        error_setg(errp, "vfio-display-dmabuf: opengl not available");
 352        return -1;
 353    }
 354
 355    vdev->dpy = g_new0(VFIODisplay, 1);
 356    vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
 357                                          &vfio_display_dmabuf_ops,
 358                                          vdev);
 359    if (vdev->enable_ramfb) {
 360        vdev->dpy->ramfb = ramfb_setup(errp);
 361    }
 362    vfio_display_edid_init(vdev);
 363    return 0;
 364}
 365
 366static void vfio_display_dmabuf_exit(VFIODisplay *dpy)
 367{
 368    VFIODMABuf *dmabuf;
 369
 370    if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) {
 371        return;
 372    }
 373
 374    while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) {
 375        vfio_display_free_one_dmabuf(dpy, dmabuf);
 376    }
 377}
 378
 379/* ---------------------------------------------------------------------- */
 380void vfio_display_reset(VFIOPCIDevice *vdev)
 381{
 382    if (!vdev || !vdev->dpy || !vdev->dpy->con ||
 383        !vdev->dpy->dmabuf.primary) {
 384        return;
 385    }
 386
 387    dpy_gl_scanout_disable(vdev->dpy->con);
 388    vfio_display_dmabuf_exit(vdev->dpy);
 389    dpy_gfx_update_full(vdev->dpy->con);
 390}
 391
 392static void vfio_display_region_update(void *opaque)
 393{
 394    VFIOPCIDevice *vdev = opaque;
 395    VFIODisplay *dpy = vdev->dpy;
 396    struct vfio_device_gfx_plane_info plane = {
 397        .argsz = sizeof(plane),
 398        .flags = VFIO_GFX_PLANE_TYPE_REGION
 399    };
 400    pixman_format_code_t format;
 401    int ret;
 402
 403    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
 404    if (ret < 0) {
 405        error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
 406                     strerror(errno));
 407        return;
 408    }
 409    if (!plane.drm_format || !plane.size) {
 410        if (dpy->ramfb) {
 411            ramfb_display_update(dpy->con, dpy->ramfb);
 412            dpy->region.surface = NULL;
 413        }
 414        return;
 415    }
 416    format = qemu_drm_format_to_pixman(plane.drm_format);
 417    if (!format) {
 418        return;
 419    }
 420
 421    if (dpy->region.buffer.size &&
 422        dpy->region.buffer.nr != plane.region_index) {
 423        /* region changed */
 424        vfio_region_exit(&dpy->region.buffer);
 425        vfio_region_finalize(&dpy->region.buffer);
 426        dpy->region.surface = NULL;
 427    }
 428
 429    if (dpy->region.surface &&
 430        (surface_width(dpy->region.surface) != plane.width ||
 431         surface_height(dpy->region.surface) != plane.height ||
 432         surface_format(dpy->region.surface) != format)) {
 433        /* size changed */
 434        dpy->region.surface = NULL;
 435    }
 436
 437    if (!dpy->region.buffer.size) {
 438        /* mmap region */
 439        ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
 440                                &dpy->region.buffer,
 441                                plane.region_index,
 442                                "display");
 443        if (ret != 0) {
 444            error_report("%s: vfio_region_setup(%d): %s",
 445                         __func__, plane.region_index, strerror(-ret));
 446            goto err;
 447        }
 448        ret = vfio_region_mmap(&dpy->region.buffer);
 449        if (ret != 0) {
 450            error_report("%s: vfio_region_mmap(%d): %s", __func__,
 451                         plane.region_index, strerror(-ret));
 452            goto err;
 453        }
 454        assert(dpy->region.buffer.mmaps[0].mmap != NULL);
 455    }
 456
 457    if (dpy->region.surface == NULL) {
 458        /* create surface */
 459        dpy->region.surface = qemu_create_displaysurface_from
 460            (plane.width, plane.height, format,
 461             plane.stride, dpy->region.buffer.mmaps[0].mmap);
 462        dpy_gfx_replace_surface(dpy->con, dpy->region.surface);
 463    }
 464
 465    /* full screen update */
 466    dpy_gfx_update(dpy->con, 0, 0,
 467                   surface_width(dpy->region.surface),
 468                   surface_height(dpy->region.surface));
 469    return;
 470
 471err:
 472    vfio_region_exit(&dpy->region.buffer);
 473    vfio_region_finalize(&dpy->region.buffer);
 474}
 475
 476static const GraphicHwOps vfio_display_region_ops = {
 477    .gfx_update = vfio_display_region_update,
 478};
 479
 480static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
 481{
 482    vdev->dpy = g_new0(VFIODisplay, 1);
 483    vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
 484                                          &vfio_display_region_ops,
 485                                          vdev);
 486    if (vdev->enable_ramfb) {
 487        vdev->dpy->ramfb = ramfb_setup(errp);
 488    }
 489    return 0;
 490}
 491
 492static void vfio_display_region_exit(VFIODisplay *dpy)
 493{
 494    if (!dpy->region.buffer.size) {
 495        return;
 496    }
 497
 498    vfio_region_exit(&dpy->region.buffer);
 499    vfio_region_finalize(&dpy->region.buffer);
 500}
 501
 502/* ---------------------------------------------------------------------- */
 503
 504int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
 505{
 506    struct vfio_device_gfx_plane_info probe;
 507    int ret;
 508
 509    memset(&probe, 0, sizeof(probe));
 510    probe.argsz = sizeof(probe);
 511    probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
 512    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
 513    if (ret == 0) {
 514        return vfio_display_dmabuf_init(vdev, errp);
 515    }
 516
 517    memset(&probe, 0, sizeof(probe));
 518    probe.argsz = sizeof(probe);
 519    probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
 520    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
 521    if (ret == 0) {
 522        return vfio_display_region_init(vdev, errp);
 523    }
 524
 525    if (vdev->display == ON_OFF_AUTO_AUTO) {
 526        /* not an error in automatic mode */
 527        return 0;
 528    }
 529
 530    error_setg(errp, "vfio: device doesn't support any (known) display method");
 531    return -1;
 532}
 533
 534void vfio_display_finalize(VFIOPCIDevice *vdev)
 535{
 536    if (!vdev->dpy) {
 537        return;
 538    }
 539
 540    graphic_console_close(vdev->dpy->con);
 541    vfio_display_dmabuf_exit(vdev->dpy);
 542    vfio_display_region_exit(vdev->dpy);
 543    vfio_display_edid_exit(vdev->dpy);
 544    g_free(vdev->dpy);
 545}
 546