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 void 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;
 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
 126static void vfio_display_edid_init(VFIOPCIDevice *vdev)
 127{
 128    VFIODisplay *dpy = vdev->dpy;
 129    int fd = vdev->vbasedev.fd;
 130    int ret;
 131
 132    ret = vfio_get_dev_region_info(&vdev->vbasedev,
 133                                   VFIO_REGION_TYPE_GFX,
 134                                   VFIO_REGION_SUBTYPE_GFX_EDID,
 135                                   &dpy->edid_info);
 136    if (ret) {
 137        return;
 138    }
 139
 140    trace_vfio_display_edid_available();
 141    dpy->edid_regs = g_new0(struct vfio_region_gfx_edid, 1);
 142    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_offset)) {
 143        goto err;
 144    }
 145    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_max_size)) {
 146        goto err;
 147    }
 148    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_xres)) {
 149        goto err;
 150    }
 151    if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_yres)) {
 152        goto err;
 153    }
 154
 155    dpy->edid_blob = g_malloc0(dpy->edid_regs->edid_max_size);
 156
 157    /* if xres + yres properties are unset use the maximum resolution */
 158    if (!vdev->display_xres) {
 159        vdev->display_xres = dpy->edid_regs->max_xres;
 160    }
 161    if (!vdev->display_yres) {
 162        vdev->display_yres = dpy->edid_regs->max_yres;
 163    }
 164
 165    dpy->edid_link_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
 166                                        vfio_display_edid_link_up, vdev);
 167
 168    vfio_display_edid_update(vdev, true, 0, 0);
 169    return;
 170
 171err:
 172    trace_vfio_display_edid_write_error();
 173    g_free(dpy->edid_regs);
 174    dpy->edid_regs = NULL;
 175    return;
 176}
 177
 178static void vfio_display_edid_exit(VFIODisplay *dpy)
 179{
 180    if (!dpy->edid_regs) {
 181        return;
 182    }
 183
 184    g_free(dpy->edid_regs);
 185    g_free(dpy->edid_blob);
 186    timer_free(dpy->edid_link_timer);
 187}
 188
 189static void vfio_display_update_cursor(VFIODMABuf *dmabuf,
 190                                       struct vfio_device_gfx_plane_info *plane)
 191{
 192    if (dmabuf->pos_x != plane->x_pos || dmabuf->pos_y != plane->y_pos) {
 193        dmabuf->pos_x      = plane->x_pos;
 194        dmabuf->pos_y      = plane->y_pos;
 195        dmabuf->pos_updates++;
 196    }
 197    if (dmabuf->hot_x != plane->x_hot || dmabuf->hot_y != plane->y_hot) {
 198        dmabuf->hot_x      = plane->x_hot;
 199        dmabuf->hot_y      = plane->y_hot;
 200        dmabuf->hot_updates++;
 201    }
 202}
 203
 204static VFIODMABuf *vfio_display_get_dmabuf(VFIOPCIDevice *vdev,
 205                                           uint32_t plane_type)
 206{
 207    VFIODisplay *dpy = vdev->dpy;
 208    struct vfio_device_gfx_plane_info plane;
 209    VFIODMABuf *dmabuf;
 210    int fd, ret;
 211
 212    memset(&plane, 0, sizeof(plane));
 213    plane.argsz = sizeof(plane);
 214    plane.flags = VFIO_GFX_PLANE_TYPE_DMABUF;
 215    plane.drm_plane_type = plane_type;
 216    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
 217    if (ret < 0) {
 218        return NULL;
 219    }
 220    if (!plane.drm_format || !plane.size) {
 221        return NULL;
 222    }
 223
 224    QTAILQ_FOREACH(dmabuf, &dpy->dmabuf.bufs, next) {
 225        if (dmabuf->dmabuf_id == plane.dmabuf_id) {
 226            /* found in list, move to head, return it */
 227            QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
 228            QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
 229            if (plane_type == DRM_PLANE_TYPE_CURSOR) {
 230                vfio_display_update_cursor(dmabuf, &plane);
 231            }
 232            return dmabuf;
 233        }
 234    }
 235
 236    fd = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_GFX_DMABUF, &plane.dmabuf_id);
 237    if (fd < 0) {
 238        return NULL;
 239    }
 240
 241    dmabuf = g_new0(VFIODMABuf, 1);
 242    dmabuf->dmabuf_id  = plane.dmabuf_id;
 243    dmabuf->buf.width  = plane.width;
 244    dmabuf->buf.height = plane.height;
 245    dmabuf->buf.stride = plane.stride;
 246    dmabuf->buf.fourcc = plane.drm_format;
 247    dmabuf->buf.modifier = plane.drm_format_mod;
 248    dmabuf->buf.fd     = fd;
 249    if (plane_type == DRM_PLANE_TYPE_CURSOR) {
 250        vfio_display_update_cursor(dmabuf, &plane);
 251    }
 252
 253    QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
 254    return dmabuf;
 255}
 256
 257static void vfio_display_free_one_dmabuf(VFIODisplay *dpy, VFIODMABuf *dmabuf)
 258{
 259    QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
 260    dpy_gl_release_dmabuf(dpy->con, &dmabuf->buf);
 261    close(dmabuf->buf.fd);
 262    g_free(dmabuf);
 263}
 264
 265static void vfio_display_free_dmabufs(VFIOPCIDevice *vdev)
 266{
 267    VFIODisplay *dpy = vdev->dpy;
 268    VFIODMABuf *dmabuf, *tmp;
 269    uint32_t keep = 5;
 270
 271    QTAILQ_FOREACH_SAFE(dmabuf, &dpy->dmabuf.bufs, next, tmp) {
 272        if (keep > 0) {
 273            keep--;
 274            continue;
 275        }
 276        assert(dmabuf != dpy->dmabuf.primary);
 277        vfio_display_free_one_dmabuf(dpy, dmabuf);
 278    }
 279}
 280
 281static void vfio_display_dmabuf_update(void *opaque)
 282{
 283    VFIOPCIDevice *vdev = opaque;
 284    VFIODisplay *dpy = vdev->dpy;
 285    VFIODMABuf *primary, *cursor;
 286    bool free_bufs = false, new_cursor = false;
 287
 288    primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
 289    if (primary == NULL) {
 290        if (dpy->ramfb) {
 291            ramfb_display_update(dpy->con, dpy->ramfb);
 292        }
 293        return;
 294    }
 295
 296    if (dpy->dmabuf.primary != primary) {
 297        dpy->dmabuf.primary = primary;
 298        qemu_console_resize(dpy->con,
 299                            primary->buf.width, primary->buf.height);
 300        dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
 301        free_bufs = true;
 302    }
 303
 304    cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
 305    if (dpy->dmabuf.cursor != cursor) {
 306        dpy->dmabuf.cursor = cursor;
 307        new_cursor = true;
 308        free_bufs = true;
 309    }
 310
 311    if (cursor && (new_cursor || cursor->hot_updates)) {
 312        bool have_hot = (cursor->hot_x != 0xffffffff &&
 313                         cursor->hot_y != 0xffffffff);
 314        dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
 315                             cursor->hot_x, cursor->hot_y);
 316        cursor->hot_updates = 0;
 317    } else if (!cursor && new_cursor) {
 318        dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
 319    }
 320
 321    if (cursor && cursor->pos_updates) {
 322        dpy_gl_cursor_position(dpy->con,
 323                               cursor->pos_x,
 324                               cursor->pos_y);
 325        cursor->pos_updates = 0;
 326    }
 327
 328    dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height);
 329
 330    if (free_bufs) {
 331        vfio_display_free_dmabufs(vdev);
 332    }
 333}
 334
 335static int vfio_display_get_flags(void *opaque)
 336{
 337    return GRAPHIC_FLAGS_GL | GRAPHIC_FLAGS_DMABUF;
 338}
 339
 340static const GraphicHwOps vfio_display_dmabuf_ops = {
 341    .get_flags  = vfio_display_get_flags,
 342    .gfx_update = vfio_display_dmabuf_update,
 343    .ui_info    = vfio_display_edid_ui_info,
 344};
 345
 346static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
 347{
 348    if (!display_opengl) {
 349        error_setg(errp, "vfio-display-dmabuf: opengl not available");
 350        return -1;
 351    }
 352
 353    vdev->dpy = g_new0(VFIODisplay, 1);
 354    vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
 355                                          &vfio_display_dmabuf_ops,
 356                                          vdev);
 357    if (vdev->enable_ramfb) {
 358        vdev->dpy->ramfb = ramfb_setup(errp);
 359    }
 360    vfio_display_edid_init(vdev);
 361    return 0;
 362}
 363
 364static void vfio_display_dmabuf_exit(VFIODisplay *dpy)
 365{
 366    VFIODMABuf *dmabuf;
 367
 368    if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) {
 369        return;
 370    }
 371
 372    while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) {
 373        vfio_display_free_one_dmabuf(dpy, dmabuf);
 374    }
 375}
 376
 377/* ---------------------------------------------------------------------- */
 378void vfio_display_reset(VFIOPCIDevice *vdev)
 379{
 380    if (!vdev || !vdev->dpy || !vdev->dpy->con ||
 381        !vdev->dpy->dmabuf.primary) {
 382        return;
 383    }
 384
 385    dpy_gl_scanout_disable(vdev->dpy->con);
 386    vfio_display_dmabuf_exit(vdev->dpy);
 387    dpy_gfx_update_full(vdev->dpy->con);
 388}
 389
 390static void vfio_display_region_update(void *opaque)
 391{
 392    VFIOPCIDevice *vdev = opaque;
 393    VFIODisplay *dpy = vdev->dpy;
 394    struct vfio_device_gfx_plane_info plane = {
 395        .argsz = sizeof(plane),
 396        .flags = VFIO_GFX_PLANE_TYPE_REGION
 397    };
 398    pixman_format_code_t format;
 399    int ret;
 400
 401    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
 402    if (ret < 0) {
 403        error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
 404                     strerror(errno));
 405        return;
 406    }
 407    if (!plane.drm_format || !plane.size) {
 408        if (dpy->ramfb) {
 409            ramfb_display_update(dpy->con, dpy->ramfb);
 410            dpy->region.surface = NULL;
 411        }
 412        return;
 413    }
 414    format = qemu_drm_format_to_pixman(plane.drm_format);
 415    if (!format) {
 416        return;
 417    }
 418
 419    if (dpy->region.buffer.size &&
 420        dpy->region.buffer.nr != plane.region_index) {
 421        /* region changed */
 422        vfio_region_exit(&dpy->region.buffer);
 423        vfio_region_finalize(&dpy->region.buffer);
 424        dpy->region.surface = NULL;
 425    }
 426
 427    if (dpy->region.surface &&
 428        (surface_width(dpy->region.surface) != plane.width ||
 429         surface_height(dpy->region.surface) != plane.height ||
 430         surface_format(dpy->region.surface) != format)) {
 431        /* size changed */
 432        dpy->region.surface = NULL;
 433    }
 434
 435    if (!dpy->region.buffer.size) {
 436        /* mmap region */
 437        ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
 438                                &dpy->region.buffer,
 439                                plane.region_index,
 440                                "display");
 441        if (ret != 0) {
 442            error_report("%s: vfio_region_setup(%d): %s",
 443                         __func__, plane.region_index, strerror(-ret));
 444            goto err;
 445        }
 446        ret = vfio_region_mmap(&dpy->region.buffer);
 447        if (ret != 0) {
 448            error_report("%s: vfio_region_mmap(%d): %s", __func__,
 449                         plane.region_index, strerror(-ret));
 450            goto err;
 451        }
 452        assert(dpy->region.buffer.mmaps[0].mmap != NULL);
 453    }
 454
 455    if (dpy->region.surface == NULL) {
 456        /* create surface */
 457        dpy->region.surface = qemu_create_displaysurface_from
 458            (plane.width, plane.height, format,
 459             plane.stride, dpy->region.buffer.mmaps[0].mmap);
 460        dpy_gfx_replace_surface(dpy->con, dpy->region.surface);
 461    }
 462
 463    /* full screen update */
 464    dpy_gfx_update(dpy->con, 0, 0,
 465                   surface_width(dpy->region.surface),
 466                   surface_height(dpy->region.surface));
 467    return;
 468
 469err:
 470    vfio_region_exit(&dpy->region.buffer);
 471    vfio_region_finalize(&dpy->region.buffer);
 472}
 473
 474static const GraphicHwOps vfio_display_region_ops = {
 475    .gfx_update = vfio_display_region_update,
 476};
 477
 478static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
 479{
 480    vdev->dpy = g_new0(VFIODisplay, 1);
 481    vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
 482                                          &vfio_display_region_ops,
 483                                          vdev);
 484    if (vdev->enable_ramfb) {
 485        vdev->dpy->ramfb = ramfb_setup(errp);
 486    }
 487    return 0;
 488}
 489
 490static void vfio_display_region_exit(VFIODisplay *dpy)
 491{
 492    if (!dpy->region.buffer.size) {
 493        return;
 494    }
 495
 496    vfio_region_exit(&dpy->region.buffer);
 497    vfio_region_finalize(&dpy->region.buffer);
 498}
 499
 500/* ---------------------------------------------------------------------- */
 501
 502int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
 503{
 504    struct vfio_device_gfx_plane_info probe;
 505    int ret;
 506
 507    memset(&probe, 0, sizeof(probe));
 508    probe.argsz = sizeof(probe);
 509    probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
 510    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
 511    if (ret == 0) {
 512        return vfio_display_dmabuf_init(vdev, errp);
 513    }
 514
 515    memset(&probe, 0, sizeof(probe));
 516    probe.argsz = sizeof(probe);
 517    probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
 518    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
 519    if (ret == 0) {
 520        return vfio_display_region_init(vdev, errp);
 521    }
 522
 523    if (vdev->display == ON_OFF_AUTO_AUTO) {
 524        /* not an error in automatic mode */
 525        return 0;
 526    }
 527
 528    error_setg(errp, "vfio: device doesn't support any (known) display method");
 529    return -1;
 530}
 531
 532void vfio_display_finalize(VFIOPCIDevice *vdev)
 533{
 534    if (!vdev->dpy) {
 535        return;
 536    }
 537
 538    graphic_console_close(vdev->dpy->con);
 539    vfio_display_dmabuf_exit(vdev->dpy);
 540    vfio_display_region_exit(vdev->dpy);
 541    vfio_display_edid_exit(vdev->dpy);
 542    g_free(vdev->dpy);
 543}
 544