xref: /openbmc/qemu/hw/vfio/display.c (revision da054c64)
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 "sysemu/sysemu.h"
18 #include "ui/console.h"
19 #include "qapi/error.h"
20 #include "pci.h"
21 
22 #ifndef DRM_PLANE_TYPE_PRIMARY
23 # define DRM_PLANE_TYPE_PRIMARY 1
24 # define DRM_PLANE_TYPE_CURSOR  2
25 #endif
26 
27 static void vfio_display_update_cursor(VFIODMABuf *dmabuf,
28                                        struct vfio_device_gfx_plane_info *plane)
29 {
30     if (dmabuf->pos_x != plane->x_pos || dmabuf->pos_y != plane->y_pos) {
31         dmabuf->pos_x      = plane->x_pos;
32         dmabuf->pos_y      = plane->y_pos;
33         dmabuf->pos_updates++;
34     }
35     if (dmabuf->hot_x != plane->x_hot || dmabuf->hot_y != plane->y_hot) {
36         dmabuf->hot_x      = plane->x_hot;
37         dmabuf->hot_y      = plane->y_hot;
38         dmabuf->hot_updates++;
39     }
40 }
41 
42 static VFIODMABuf *vfio_display_get_dmabuf(VFIOPCIDevice *vdev,
43                                            uint32_t plane_type)
44 {
45     VFIODisplay *dpy = vdev->dpy;
46     struct vfio_device_gfx_plane_info plane;
47     VFIODMABuf *dmabuf;
48     int fd, ret;
49 
50     memset(&plane, 0, sizeof(plane));
51     plane.argsz = sizeof(plane);
52     plane.flags = VFIO_GFX_PLANE_TYPE_DMABUF;
53     plane.drm_plane_type = plane_type;
54     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
55     if (ret < 0) {
56         return NULL;
57     }
58     if (!plane.drm_format || !plane.size) {
59         return NULL;
60     }
61 
62     QTAILQ_FOREACH(dmabuf, &dpy->dmabuf.bufs, next) {
63         if (dmabuf->dmabuf_id == plane.dmabuf_id) {
64             /* found in list, move to head, return it */
65             QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
66             QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
67             if (plane_type == DRM_PLANE_TYPE_CURSOR) {
68                 vfio_display_update_cursor(dmabuf, &plane);
69             }
70             return dmabuf;
71         }
72     }
73 
74     fd = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_GFX_DMABUF, &plane.dmabuf_id);
75     if (fd < 0) {
76         return NULL;
77     }
78 
79     dmabuf = g_new0(VFIODMABuf, 1);
80     dmabuf->dmabuf_id  = plane.dmabuf_id;
81     dmabuf->buf.width  = plane.width;
82     dmabuf->buf.height = plane.height;
83     dmabuf->buf.stride = plane.stride;
84     dmabuf->buf.fourcc = plane.drm_format;
85     dmabuf->buf.fd     = fd;
86     if (plane_type == DRM_PLANE_TYPE_CURSOR) {
87         vfio_display_update_cursor(dmabuf, &plane);
88     }
89 
90     QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
91     return dmabuf;
92 }
93 
94 static void vfio_display_free_one_dmabuf(VFIODisplay *dpy, VFIODMABuf *dmabuf)
95 {
96     QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
97     dpy_gl_release_dmabuf(dpy->con, &dmabuf->buf);
98     close(dmabuf->buf.fd);
99     g_free(dmabuf);
100 }
101 
102 static void vfio_display_free_dmabufs(VFIOPCIDevice *vdev)
103 {
104     VFIODisplay *dpy = vdev->dpy;
105     VFIODMABuf *dmabuf, *tmp;
106     uint32_t keep = 5;
107 
108     QTAILQ_FOREACH_SAFE(dmabuf, &dpy->dmabuf.bufs, next, tmp) {
109         if (keep > 0) {
110             keep--;
111             continue;
112         }
113         assert(dmabuf != dpy->dmabuf.primary);
114         vfio_display_free_one_dmabuf(dpy, dmabuf);
115     }
116 }
117 
118 static void vfio_display_dmabuf_update(void *opaque)
119 {
120     VFIOPCIDevice *vdev = opaque;
121     VFIODisplay *dpy = vdev->dpy;
122     VFIODMABuf *primary, *cursor;
123     bool free_bufs = false, new_cursor = false;;
124 
125     primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
126     if (primary == NULL) {
127         if (dpy->ramfb) {
128             ramfb_display_update(dpy->con, dpy->ramfb);
129         }
130         return;
131     }
132 
133     if (dpy->dmabuf.primary != primary) {
134         dpy->dmabuf.primary = primary;
135         qemu_console_resize(dpy->con,
136                             primary->buf.width, primary->buf.height);
137         dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
138         free_bufs = true;
139     }
140 
141     cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
142     if (dpy->dmabuf.cursor != cursor) {
143         dpy->dmabuf.cursor = cursor;
144         new_cursor = true;
145         free_bufs = true;
146     }
147 
148     if (cursor && (new_cursor || cursor->hot_updates)) {
149         bool have_hot = (cursor->hot_x != 0xffffffff &&
150                          cursor->hot_y != 0xffffffff);
151         dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
152                              cursor->hot_x, cursor->hot_y);
153         cursor->hot_updates = 0;
154     } else if (!cursor && new_cursor) {
155         dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
156     }
157 
158     if (cursor && cursor->pos_updates) {
159         dpy_gl_cursor_position(dpy->con,
160                                cursor->pos_x,
161                                cursor->pos_y);
162         cursor->pos_updates = 0;
163     }
164 
165     dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height);
166 
167     if (free_bufs) {
168         vfio_display_free_dmabufs(vdev);
169     }
170 }
171 
172 static const GraphicHwOps vfio_display_dmabuf_ops = {
173     .gfx_update = vfio_display_dmabuf_update,
174 };
175 
176 static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
177 {
178     if (!display_opengl) {
179         error_setg(errp, "vfio-display-dmabuf: opengl not available");
180         return -1;
181     }
182 
183     vdev->dpy = g_new0(VFIODisplay, 1);
184     vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
185                                           &vfio_display_dmabuf_ops,
186                                           vdev);
187     if (vdev->enable_ramfb) {
188         vdev->dpy->ramfb = ramfb_setup(errp);
189     }
190     return 0;
191 }
192 
193 static void vfio_display_dmabuf_exit(VFIODisplay *dpy)
194 {
195     VFIODMABuf *dmabuf;
196 
197     if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) {
198         return;
199     }
200 
201     while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) {
202         vfio_display_free_one_dmabuf(dpy, dmabuf);
203     }
204 }
205 
206 /* ---------------------------------------------------------------------- */
207 void vfio_display_reset(VFIOPCIDevice *vdev)
208 {
209     if (!vdev || !vdev->dpy || !vdev->dpy->con ||
210         !vdev->dpy->dmabuf.primary) {
211         return;
212     }
213 
214     dpy_gl_scanout_disable(vdev->dpy->con);
215     vfio_display_dmabuf_exit(vdev->dpy);
216     dpy_gfx_update_full(vdev->dpy->con);
217 }
218 
219 static void vfio_display_region_update(void *opaque)
220 {
221     VFIOPCIDevice *vdev = opaque;
222     VFIODisplay *dpy = vdev->dpy;
223     struct vfio_device_gfx_plane_info plane = {
224         .argsz = sizeof(plane),
225         .flags = VFIO_GFX_PLANE_TYPE_REGION
226     };
227     pixman_format_code_t format;
228     int ret;
229 
230     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
231     if (ret < 0) {
232         error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
233                      strerror(errno));
234         return;
235     }
236     if (!plane.drm_format || !plane.size) {
237         if (dpy->ramfb) {
238             ramfb_display_update(dpy->con, dpy->ramfb);
239         }
240         return;
241     }
242     format = qemu_drm_format_to_pixman(plane.drm_format);
243     if (!format) {
244         return;
245     }
246 
247     if (dpy->region.buffer.size &&
248         dpy->region.buffer.nr != plane.region_index) {
249         /* region changed */
250         vfio_region_exit(&dpy->region.buffer);
251         vfio_region_finalize(&dpy->region.buffer);
252         dpy->region.surface = NULL;
253     }
254 
255     if (dpy->region.surface &&
256         (surface_width(dpy->region.surface) != plane.width ||
257          surface_height(dpy->region.surface) != plane.height ||
258          surface_format(dpy->region.surface) != format)) {
259         /* size changed */
260         dpy->region.surface = NULL;
261     }
262 
263     if (!dpy->region.buffer.size) {
264         /* mmap region */
265         ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
266                                 &dpy->region.buffer,
267                                 plane.region_index,
268                                 "display");
269         if (ret != 0) {
270             error_report("%s: vfio_region_setup(%d): %s",
271                          __func__, plane.region_index, strerror(-ret));
272             goto err;
273         }
274         ret = vfio_region_mmap(&dpy->region.buffer);
275         if (ret != 0) {
276             error_report("%s: vfio_region_mmap(%d): %s", __func__,
277                          plane.region_index, strerror(-ret));
278             goto err;
279         }
280         assert(dpy->region.buffer.mmaps[0].mmap != NULL);
281     }
282 
283     if (dpy->region.surface == NULL) {
284         /* create surface */
285         dpy->region.surface = qemu_create_displaysurface_from
286             (plane.width, plane.height, format,
287              plane.stride, dpy->region.buffer.mmaps[0].mmap);
288         dpy_gfx_replace_surface(dpy->con, dpy->region.surface);
289     }
290 
291     /* full screen update */
292     dpy_gfx_update(dpy->con, 0, 0,
293                    surface_width(dpy->region.surface),
294                    surface_height(dpy->region.surface));
295     return;
296 
297 err:
298     vfio_region_exit(&dpy->region.buffer);
299     vfio_region_finalize(&dpy->region.buffer);
300 }
301 
302 static const GraphicHwOps vfio_display_region_ops = {
303     .gfx_update = vfio_display_region_update,
304 };
305 
306 static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
307 {
308     vdev->dpy = g_new0(VFIODisplay, 1);
309     vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
310                                           &vfio_display_region_ops,
311                                           vdev);
312     if (vdev->enable_ramfb) {
313         vdev->dpy->ramfb = ramfb_setup(errp);
314     }
315     return 0;
316 }
317 
318 static void vfio_display_region_exit(VFIODisplay *dpy)
319 {
320     if (!dpy->region.buffer.size) {
321         return;
322     }
323 
324     vfio_region_exit(&dpy->region.buffer);
325     vfio_region_finalize(&dpy->region.buffer);
326 }
327 
328 /* ---------------------------------------------------------------------- */
329 
330 int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
331 {
332     struct vfio_device_gfx_plane_info probe;
333     int ret;
334 
335     memset(&probe, 0, sizeof(probe));
336     probe.argsz = sizeof(probe);
337     probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
338     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
339     if (ret == 0) {
340         return vfio_display_dmabuf_init(vdev, errp);
341     }
342 
343     memset(&probe, 0, sizeof(probe));
344     probe.argsz = sizeof(probe);
345     probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
346     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
347     if (ret == 0) {
348         return vfio_display_region_init(vdev, errp);
349     }
350 
351     if (vdev->display == ON_OFF_AUTO_AUTO) {
352         /* not an error in automatic mode */
353         return 0;
354     }
355 
356     error_setg(errp, "vfio: device doesn't support any (known) display method");
357     return -1;
358 }
359 
360 void vfio_display_finalize(VFIOPCIDevice *vdev)
361 {
362     if (!vdev->dpy) {
363         return;
364     }
365 
366     graphic_console_close(vdev->dpy->con);
367     vfio_display_dmabuf_exit(vdev->dpy);
368     vfio_display_region_exit(vdev->dpy);
369     g_free(vdev->dpy);
370 }
371