]>
Commit | Line | Data |
---|---|---|
a9994687 GH |
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 | ||
8b818e05 GH |
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 | ||
00195ba7 GH |
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 | ||
a9994687 GH |
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) { | |
8b818e05 | 317 | return vfio_display_dmabuf_init(vdev, errp); |
a9994687 GH |
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) { | |
00195ba7 | 325 | return vfio_display_region_init(vdev, errp); |
a9994687 GH |
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 | { | |
00195ba7 GH |
339 | if (!vdev->dpy) { |
340 | return; | |
341 | } | |
342 | ||
343 | graphic_console_close(vdev->dpy->con); | |
8b818e05 | 344 | vfio_display_dmabuf_exit(vdev->dpy); |
00195ba7 GH |
345 | vfio_display_region_exit(vdev->dpy); |
346 | g_free(vdev->dpy); | |
a9994687 | 347 | } |