xref: /openbmc/qemu/hw/vfio/display.c (revision 0dacec87)
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 
202 static void vfio_display_region_update(void *opaque)
203 {
204     VFIOPCIDevice *vdev = opaque;
205     VFIODisplay *dpy = vdev->dpy;
206     struct vfio_device_gfx_plane_info plane = {
207         .argsz = sizeof(plane),
208         .flags = VFIO_GFX_PLANE_TYPE_REGION
209     };
210     pixman_format_code_t format;
211     int ret;
212 
213     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
214     if (ret < 0) {
215         error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
216                      strerror(errno));
217         return;
218     }
219     if (!plane.drm_format || !plane.size) {
220         return;
221     }
222     format = qemu_drm_format_to_pixman(plane.drm_format);
223     if (!format) {
224         return;
225     }
226 
227     if (dpy->region.buffer.size &&
228         dpy->region.buffer.nr != plane.region_index) {
229         /* region changed */
230         vfio_region_exit(&dpy->region.buffer);
231         vfio_region_finalize(&dpy->region.buffer);
232         dpy->region.surface = NULL;
233     }
234 
235     if (dpy->region.surface &&
236         (surface_width(dpy->region.surface) != plane.width ||
237          surface_height(dpy->region.surface) != plane.height ||
238          surface_format(dpy->region.surface) != format)) {
239         /* size changed */
240         dpy->region.surface = NULL;
241     }
242 
243     if (!dpy->region.buffer.size) {
244         /* mmap region */
245         ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
246                                 &dpy->region.buffer,
247                                 plane.region_index,
248                                 "display");
249         if (ret != 0) {
250             error_report("%s: vfio_region_setup(%d): %s",
251                          __func__, plane.region_index, strerror(-ret));
252             goto err;
253         }
254         ret = vfio_region_mmap(&dpy->region.buffer);
255         if (ret != 0) {
256             error_report("%s: vfio_region_mmap(%d): %s", __func__,
257                          plane.region_index, strerror(-ret));
258             goto err;
259         }
260         assert(dpy->region.buffer.mmaps[0].mmap != NULL);
261     }
262 
263     if (dpy->region.surface == NULL) {
264         /* create surface */
265         dpy->region.surface = qemu_create_displaysurface_from
266             (plane.width, plane.height, format,
267              plane.stride, dpy->region.buffer.mmaps[0].mmap);
268         dpy_gfx_replace_surface(dpy->con, dpy->region.surface);
269     }
270 
271     /* full screen update */
272     dpy_gfx_update(dpy->con, 0, 0,
273                    surface_width(dpy->region.surface),
274                    surface_height(dpy->region.surface));
275     return;
276 
277 err:
278     vfio_region_exit(&dpy->region.buffer);
279     vfio_region_finalize(&dpy->region.buffer);
280 }
281 
282 static const GraphicHwOps vfio_display_region_ops = {
283     .gfx_update = vfio_display_region_update,
284 };
285 
286 static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
287 {
288     vdev->dpy = g_new0(VFIODisplay, 1);
289     vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0,
290                                           &vfio_display_region_ops,
291                                           vdev);
292     return 0;
293 }
294 
295 static void vfio_display_region_exit(VFIODisplay *dpy)
296 {
297     if (!dpy->region.buffer.size) {
298         return;
299     }
300 
301     vfio_region_exit(&dpy->region.buffer);
302     vfio_region_finalize(&dpy->region.buffer);
303 }
304 
305 /* ---------------------------------------------------------------------- */
306 
307 int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
308 {
309     struct vfio_device_gfx_plane_info probe;
310     int ret;
311 
312     memset(&probe, 0, sizeof(probe));
313     probe.argsz = sizeof(probe);
314     probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
315     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
316     if (ret == 0) {
317         return vfio_display_dmabuf_init(vdev, errp);
318     }
319 
320     memset(&probe, 0, sizeof(probe));
321     probe.argsz = sizeof(probe);
322     probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
323     ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
324     if (ret == 0) {
325         return vfio_display_region_init(vdev, errp);
326     }
327 
328     if (vdev->display == ON_OFF_AUTO_AUTO) {
329         /* not an error in automatic mode */
330         return 0;
331     }
332 
333     error_setg(errp, "vfio: device doesn't support any (known) display method");
334     return -1;
335 }
336 
337 void vfio_display_finalize(VFIOPCIDevice *vdev)
338 {
339     if (!vdev->dpy) {
340         return;
341     }
342 
343     graphic_console_close(vdev->dpy->con);
344     vfio_display_dmabuf_exit(vdev->dpy);
345     vfio_display_region_exit(vdev->dpy);
346     g_free(vdev->dpy);
347 }
348