xref: /openbmc/qemu/hw/vfio/display.c (revision 89854803)
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         return;
128     }
129 
130     if (dpy->dmabuf.primary != primary) {
131         dpy->dmabuf.primary = primary;
132         qemu_console_resize(dpy->con,
133                             primary->buf.width, primary->buf.height);
134         dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
135         free_bufs = true;
136     }
137 
138     cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
139     if (dpy->dmabuf.cursor != cursor) {
140         dpy->dmabuf.cursor = cursor;
141         new_cursor = true;
142         free_bufs = true;
143     }
144 
145     if (cursor && (new_cursor || cursor->hot_updates)) {
146         bool have_hot = (cursor->hot_x != 0xffffffff &&
147                          cursor->hot_y != 0xffffffff);
148         dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
149                              cursor->hot_x, cursor->hot_y);
150         cursor->hot_updates = 0;
151     } else if (!cursor && new_cursor) {
152         dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
153     }
154 
155     if (cursor && cursor->pos_updates) {
156         dpy_gl_cursor_position(dpy->con,
157                                cursor->pos_x,
158                                cursor->pos_y);
159         cursor->pos_updates = 0;
160     }
161 
162     dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height);
163 
164     if (free_bufs) {
165         vfio_display_free_dmabufs(vdev);
166     }
167 }
168 
169 static const GraphicHwOps vfio_display_dmabuf_ops = {
170     .gfx_update = vfio_display_dmabuf_update,
171 };
172 
173 static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
174 {
175     if (!display_opengl) {
176         error_setg(errp, "vfio-display-dmabuf: opengl not available");
177         return -1;
178     }
179 
180     vdev->dpy = g_new0(VFIODisplay, 1);
181     vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
182                                           &vfio_display_dmabuf_ops,
183                                           vdev);
184     return 0;
185 }
186 
187 static void vfio_display_dmabuf_exit(VFIODisplay *dpy)
188 {
189     VFIODMABuf *dmabuf;
190 
191     if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) {
192         return;
193     }
194 
195     while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) {
196         vfio_display_free_one_dmabuf(dpy, dmabuf);
197     }
198 }
199 
200 /* ---------------------------------------------------------------------- */
201 void vfio_display_reset(VFIOPCIDevice *vdev)
202 {
203     if (!vdev || !vdev->dpy || !vdev->dpy->con ||
204         !vdev->dpy->dmabuf.primary) {
205         return;
206     }
207 
208     dpy_gl_scanout_disable(vdev->dpy->con);
209     vfio_display_dmabuf_exit(vdev->dpy);
210     dpy_gfx_update_full(vdev->dpy->con);
211 }
212 
213 static void vfio_display_region_update(void *opaque)
214 {
215     VFIOPCIDevice *vdev = opaque;
216     VFIODisplay *dpy = vdev->dpy;
217     struct vfio_device_gfx_plane_info plane = {
218         .argsz = sizeof(plane),
219         .flags = VFIO_GFX_PLANE_TYPE_REGION
220     };
221     pixman_format_code_t format;
222     int ret;
223 
224     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
225     if (ret < 0) {
226         error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
227                      strerror(errno));
228         return;
229     }
230     if (!plane.drm_format || !plane.size) {
231         return;
232     }
233     format = qemu_drm_format_to_pixman(plane.drm_format);
234     if (!format) {
235         return;
236     }
237 
238     if (dpy->region.buffer.size &&
239         dpy->region.buffer.nr != plane.region_index) {
240         /* region changed */
241         vfio_region_exit(&dpy->region.buffer);
242         vfio_region_finalize(&dpy->region.buffer);
243         dpy->region.surface = NULL;
244     }
245 
246     if (dpy->region.surface &&
247         (surface_width(dpy->region.surface) != plane.width ||
248          surface_height(dpy->region.surface) != plane.height ||
249          surface_format(dpy->region.surface) != format)) {
250         /* size changed */
251         dpy->region.surface = NULL;
252     }
253 
254     if (!dpy->region.buffer.size) {
255         /* mmap region */
256         ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
257                                 &dpy->region.buffer,
258                                 plane.region_index,
259                                 "display");
260         if (ret != 0) {
261             error_report("%s: vfio_region_setup(%d): %s",
262                          __func__, plane.region_index, strerror(-ret));
263             goto err;
264         }
265         ret = vfio_region_mmap(&dpy->region.buffer);
266         if (ret != 0) {
267             error_report("%s: vfio_region_mmap(%d): %s", __func__,
268                          plane.region_index, strerror(-ret));
269             goto err;
270         }
271         assert(dpy->region.buffer.mmaps[0].mmap != NULL);
272     }
273 
274     if (dpy->region.surface == NULL) {
275         /* create surface */
276         dpy->region.surface = qemu_create_displaysurface_from
277             (plane.width, plane.height, format,
278              plane.stride, dpy->region.buffer.mmaps[0].mmap);
279         dpy_gfx_replace_surface(dpy->con, dpy->region.surface);
280     }
281 
282     /* full screen update */
283     dpy_gfx_update(dpy->con, 0, 0,
284                    surface_width(dpy->region.surface),
285                    surface_height(dpy->region.surface));
286     return;
287 
288 err:
289     vfio_region_exit(&dpy->region.buffer);
290     vfio_region_finalize(&dpy->region.buffer);
291 }
292 
293 static const GraphicHwOps vfio_display_region_ops = {
294     .gfx_update = vfio_display_region_update,
295 };
296 
297 static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
298 {
299     vdev->dpy = g_new0(VFIODisplay, 1);
300     vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
301                                           &vfio_display_region_ops,
302                                           vdev);
303     return 0;
304 }
305 
306 static void vfio_display_region_exit(VFIODisplay *dpy)
307 {
308     if (!dpy->region.buffer.size) {
309         return;
310     }
311 
312     vfio_region_exit(&dpy->region.buffer);
313     vfio_region_finalize(&dpy->region.buffer);
314 }
315 
316 /* ---------------------------------------------------------------------- */
317 
318 int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
319 {
320     struct vfio_device_gfx_plane_info probe;
321     int ret;
322 
323     memset(&probe, 0, sizeof(probe));
324     probe.argsz = sizeof(probe);
325     probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
326     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
327     if (ret == 0) {
328         return vfio_display_dmabuf_init(vdev, errp);
329     }
330 
331     memset(&probe, 0, sizeof(probe));
332     probe.argsz = sizeof(probe);
333     probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
334     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
335     if (ret == 0) {
336         return vfio_display_region_init(vdev, errp);
337     }
338 
339     if (vdev->display == ON_OFF_AUTO_AUTO) {
340         /* not an error in automatic mode */
341         return 0;
342     }
343 
344     error_setg(errp, "vfio: device doesn't support any (known) display method");
345     return -1;
346 }
347 
348 void vfio_display_finalize(VFIOPCIDevice *vdev)
349 {
350     if (!vdev->dpy) {
351         return;
352     }
353 
354     graphic_console_close(vdev->dpy->con);
355     vfio_display_dmabuf_exit(vdev->dpy);
356     vfio_display_region_exit(vdev->dpy);
357     g_free(vdev->dpy);
358 }
359