xref: /openbmc/qemu/hw/vfio/display.c (revision 6598f0cd)
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