xref: /openbmc/qemu/hw/vfio/display.c (revision ed3a06b1)
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 
39 static 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 
52 err:
53     trace_vfio_display_edid_write_error();
54 }
55 
56 static 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 
104 err:
105     trace_vfio_display_edid_write_error();
106     return;
107 }
108 
109 static 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 
126 static 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 
171 err:
172     trace_vfio_display_edid_write_error();
173     g_free(dpy->edid_regs);
174     dpy->edid_regs = NULL;
175     return;
176 }
177 
178 static 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 
189 static 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 
204 static 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 
257 static 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 
265 static 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 
281 static 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 
335 static int vfio_display_get_flags(void *opaque)
336 {
337     return GRAPHIC_FLAGS_GL | GRAPHIC_FLAGS_DMABUF;
338 }
339 
340 static 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 
346 static 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 
364 static 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 /* ---------------------------------------------------------------------- */
378 void 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 
390 static 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 
469 err:
470     vfio_region_exit(&dpy->region.buffer);
471     vfio_region_finalize(&dpy->region.buffer);
472 }
473 
474 static const GraphicHwOps vfio_display_region_ops = {
475     .gfx_update = vfio_display_region_update,
476 };
477 
478 static 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 
490 static 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 
502 int 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 
532 void 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