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 "hw/display/edid.h" |
19 | #include "ui/console.h" |
20 | #include "qapi/error.h" |
21 | #include "pci.h" |
22 | #include "trace.h" |
23 | |
24 | #ifndef DRM_PLANE_TYPE_PRIMARY |
25 | # define DRM_PLANE_TYPE_PRIMARY 1 |
26 | # define DRM_PLANE_TYPE_CURSOR 2 |
27 | #endif |
28 | |
29 | #define pread_field(_fd, _reg, _ptr, _fld) \ |
30 | (sizeof(_ptr->_fld) != \ |
31 | pread(_fd, &(_ptr->_fld), sizeof(_ptr->_fld), \ |
32 | _reg->offset + offsetof(typeof(*_ptr), _fld))) |
33 | |
34 | #define pwrite_field(_fd, _reg, _ptr, _fld) \ |
35 | (sizeof(_ptr->_fld) != \ |
36 | pwrite(_fd, &(_ptr->_fld), sizeof(_ptr->_fld), \ |
37 | _reg->offset + offsetof(typeof(*_ptr), _fld))) |
38 | |
39 | |
40 | static void vfio_display_edid_link_up(void *opaque) |
41 | { |
42 | VFIOPCIDevice *vdev = opaque; |
43 | VFIODisplay *dpy = vdev->dpy; |
44 | int fd = vdev->vbasedev.fd; |
45 | |
46 | dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_UP; |
47 | if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) { |
48 | goto err; |
49 | } |
50 | trace_vfio_display_edid_link_up(); |
51 | return; |
52 | |
53 | err: |
54 | trace_vfio_display_edid_write_error(); |
55 | } |
56 | |
57 | static void vfio_display_edid_update(VFIOPCIDevice *vdev, bool enabled, |
58 | int prefx, int prefy) |
59 | { |
60 | VFIODisplay *dpy = vdev->dpy; |
61 | int fd = vdev->vbasedev.fd; |
62 | qemu_edid_info edid = { |
63 | .maxx = dpy->edid_regs->max_xres, |
64 | .maxy = dpy->edid_regs->max_yres, |
65 | .prefx = prefx ?: vdev->display_xres, |
66 | .prefy = prefy ?: vdev->display_yres, |
67 | }; |
68 | |
69 | timer_del(dpy->edid_link_timer); |
70 | dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_DOWN; |
71 | if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) { |
72 | goto err; |
73 | } |
74 | trace_vfio_display_edid_link_down(); |
75 | |
76 | if (!enabled) { |
77 | return; |
78 | } |
79 | |
80 | if (edid.maxx && edid.prefx > edid.maxx) { |
81 | edid.prefx = edid.maxx; |
82 | } |
83 | if (edid.maxy && edid.prefy > edid.maxy) { |
84 | edid.prefy = edid.maxy; |
85 | } |
86 | qemu_edid_generate(dpy->edid_blob, |
87 | dpy->edid_regs->edid_max_size, |
88 | &edid); |
89 | trace_vfio_display_edid_update(edid.prefx, edid.prefy); |
90 | |
91 | dpy->edid_regs->edid_size = qemu_edid_size(dpy->edid_blob); |
92 | if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, edid_size)) { |
93 | goto err; |
94 | } |
95 | if (pwrite(fd, dpy->edid_blob, dpy->edid_regs->edid_size, |
96 | dpy->edid_info->offset + dpy->edid_regs->edid_offset) |
97 | != dpy->edid_regs->edid_size) { |
98 | goto err; |
99 | } |
100 | |
101 | timer_mod(dpy->edid_link_timer, |
102 | qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 100); |
103 | return; |
104 | |
105 | err: |
106 | trace_vfio_display_edid_write_error(); |
107 | return; |
108 | } |
109 | |
110 | static int vfio_display_edid_ui_info(void *opaque, uint32_t idx, |
111 | QemuUIInfo *info) |
112 | { |
113 | VFIOPCIDevice *vdev = opaque; |
114 | VFIODisplay *dpy = vdev->dpy; |
115 | |
116 | if (!dpy->edid_regs) { |
117 | return 0; |
118 | } |
119 | |
120 | if (info->width && info->height) { |
121 | vfio_display_edid_update(vdev, true, info->width, info->height); |
122 | } else { |
123 | vfio_display_edid_update(vdev, false, 0, 0); |
124 | } |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static void vfio_display_edid_init(VFIOPCIDevice *vdev) |
130 | { |
131 | VFIODisplay *dpy = vdev->dpy; |
132 | int fd = vdev->vbasedev.fd; |
133 | int ret; |
134 | |
135 | ret = vfio_get_dev_region_info(&vdev->vbasedev, |
136 | VFIO_REGION_TYPE_GFX, |
137 | VFIO_REGION_SUBTYPE_GFX_EDID, |
138 | &dpy->edid_info); |
139 | if (ret) { |
140 | return; |
141 | } |
142 | |
143 | trace_vfio_display_edid_available(); |
144 | dpy->edid_regs = g_new0(struct vfio_region_gfx_edid, 1); |
145 | if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_offset)) { |
146 | goto err; |
147 | } |
148 | if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_max_size)) { |
149 | goto err; |
150 | } |
151 | if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_xres)) { |
152 | goto err; |
153 | } |
154 | if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_yres)) { |
155 | goto err; |
156 | } |
157 | |
158 | dpy->edid_blob = g_malloc0(dpy->edid_regs->edid_max_size); |
159 | |
160 | /* if xres + yres properties are unset use the maximum resolution */ |
161 | if (!vdev->display_xres) { |
162 | vdev->display_xres = dpy->edid_regs->max_xres; |
163 | } |
164 | if (!vdev->display_yres) { |
165 | vdev->display_yres = dpy->edid_regs->max_yres; |
166 | } |
167 | |
168 | dpy->edid_link_timer = timer_new_ms(QEMU_CLOCK_REALTIME, |
169 | vfio_display_edid_link_up, vdev); |
170 | |
171 | vfio_display_edid_update(vdev, true, 0, 0); |
172 | return; |
173 | |
174 | err: |
175 | trace_vfio_display_edid_write_error(); |
176 | g_free(dpy->edid_regs); |
177 | dpy->edid_regs = NULL; |
178 | return; |
179 | } |
180 | |
181 | static void vfio_display_edid_exit(VFIODisplay *dpy) |
182 | { |
183 | if (!dpy->edid_regs) { |
184 | return; |
185 | } |
186 | |
187 | g_free(dpy->edid_regs); |
188 | g_free(dpy->edid_blob); |
189 | timer_del(dpy->edid_link_timer); |
190 | timer_free(dpy->edid_link_timer); |
191 | } |
192 | |
193 | static void vfio_display_update_cursor(VFIODMABuf *dmabuf, |
194 | struct vfio_device_gfx_plane_info *plane) |
195 | { |
196 | if (dmabuf->pos_x != plane->x_pos || dmabuf->pos_y != plane->y_pos) { |
197 | dmabuf->pos_x = plane->x_pos; |
198 | dmabuf->pos_y = plane->y_pos; |
199 | dmabuf->pos_updates++; |
200 | } |
201 | if (dmabuf->hot_x != plane->x_hot || dmabuf->hot_y != plane->y_hot) { |
202 | dmabuf->hot_x = plane->x_hot; |
203 | dmabuf->hot_y = plane->y_hot; |
204 | dmabuf->hot_updates++; |
205 | } |
206 | } |
207 | |
208 | static VFIODMABuf *vfio_display_get_dmabuf(VFIOPCIDevice *vdev, |
209 | uint32_t plane_type) |
210 | { |
211 | VFIODisplay *dpy = vdev->dpy; |
212 | struct vfio_device_gfx_plane_info plane; |
213 | VFIODMABuf *dmabuf; |
214 | int fd, ret; |
215 | |
216 | memset(&plane, 0, sizeof(plane)); |
217 | plane.argsz = sizeof(plane); |
218 | plane.flags = VFIO_GFX_PLANE_TYPE_DMABUF; |
219 | plane.drm_plane_type = plane_type; |
220 | ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane); |
221 | if (ret < 0) { |
222 | return NULL; |
223 | } |
224 | if (!plane.drm_format || !plane.size) { |
225 | return NULL; |
226 | } |
227 | |
228 | QTAILQ_FOREACH(dmabuf, &dpy->dmabuf.bufs, next) { |
229 | if (dmabuf->dmabuf_id == plane.dmabuf_id) { |
230 | /* found in list, move to head, return it */ |
231 | QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next); |
232 | QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next); |
233 | if (plane_type == DRM_PLANE_TYPE_CURSOR) { |
234 | vfio_display_update_cursor(dmabuf, &plane); |
235 | } |
236 | return dmabuf; |
237 | } |
238 | } |
239 | |
240 | fd = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_GFX_DMABUF, &plane.dmabuf_id); |
241 | if (fd < 0) { |
242 | return NULL; |
243 | } |
244 | |
245 | dmabuf = g_new0(VFIODMABuf, 1); |
246 | dmabuf->dmabuf_id = plane.dmabuf_id; |
247 | dmabuf->buf.width = plane.width; |
248 | dmabuf->buf.height = plane.height; |
249 | dmabuf->buf.stride = plane.stride; |
250 | dmabuf->buf.fourcc = plane.drm_format; |
251 | dmabuf->buf.modifier = plane.drm_format_mod; |
252 | dmabuf->buf.fd = fd; |
253 | if (plane_type == DRM_PLANE_TYPE_CURSOR) { |
254 | vfio_display_update_cursor(dmabuf, &plane); |
255 | } |
256 | |
257 | QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next); |
258 | return dmabuf; |
259 | } |
260 | |
261 | static void vfio_display_free_one_dmabuf(VFIODisplay *dpy, VFIODMABuf *dmabuf) |
262 | { |
263 | QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next); |
264 | dpy_gl_release_dmabuf(dpy->con, &dmabuf->buf); |
265 | close(dmabuf->buf.fd); |
266 | g_free(dmabuf); |
267 | } |
268 | |
269 | static void vfio_display_free_dmabufs(VFIOPCIDevice *vdev) |
270 | { |
271 | VFIODisplay *dpy = vdev->dpy; |
272 | VFIODMABuf *dmabuf, *tmp; |
273 | uint32_t keep = 5; |
274 | |
275 | QTAILQ_FOREACH_SAFE(dmabuf, &dpy->dmabuf.bufs, next, tmp) { |
276 | if (keep > 0) { |
277 | keep--; |
278 | continue; |
279 | } |
280 | assert(dmabuf != dpy->dmabuf.primary); |
281 | vfio_display_free_one_dmabuf(dpy, dmabuf); |
282 | } |
283 | } |
284 | |
285 | static void vfio_display_dmabuf_update(void *opaque) |
286 | { |
287 | VFIOPCIDevice *vdev = opaque; |
288 | VFIODisplay *dpy = vdev->dpy; |
289 | VFIODMABuf *primary, *cursor; |
290 | bool free_bufs = false, new_cursor = false;; |
291 | |
292 | primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY); |
293 | if (primary == NULL) { |
294 | if (dpy->ramfb) { |
295 | ramfb_display_update(dpy->con, dpy->ramfb); |
296 | } |
297 | return; |
298 | } |
299 | |
300 | if (dpy->dmabuf.primary != primary) { |
301 | dpy->dmabuf.primary = primary; |
302 | qemu_console_resize(dpy->con, |
303 | primary->buf.width, primary->buf.height); |
304 | dpy_gl_scanout_dmabuf(dpy->con, &primary->buf); |
305 | free_bufs = true; |
306 | } |
307 | |
308 | cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR); |
309 | if (dpy->dmabuf.cursor != cursor) { |
310 | dpy->dmabuf.cursor = cursor; |
311 | new_cursor = true; |
312 | free_bufs = true; |
313 | } |
314 | |
315 | if (cursor && (new_cursor || cursor->hot_updates)) { |
316 | bool have_hot = (cursor->hot_x != 0xffffffff && |
317 | cursor->hot_y != 0xffffffff); |
318 | dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot, |
319 | cursor->hot_x, cursor->hot_y); |
320 | cursor->hot_updates = 0; |
321 | } else if (!cursor && new_cursor) { |
322 | dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0); |
323 | } |
324 | |
325 | if (cursor && cursor->pos_updates) { |
326 | dpy_gl_cursor_position(dpy->con, |
327 | cursor->pos_x, |
328 | cursor->pos_y); |
329 | cursor->pos_updates = 0; |
330 | } |
331 | |
332 | dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height); |
333 | |
334 | if (free_bufs) { |
335 | vfio_display_free_dmabufs(vdev); |
336 | } |
337 | } |
338 | |
339 | static const GraphicHwOps vfio_display_dmabuf_ops = { |
340 | .gfx_update = vfio_display_dmabuf_update, |
341 | .ui_info = vfio_display_edid_ui_info, |
342 | }; |
343 | |
344 | static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp) |
345 | { |
346 | if (!display_opengl) { |
347 | error_setg(errp, "vfio-display-dmabuf: opengl not available" ); |
348 | return -1; |
349 | } |
350 | |
351 | vdev->dpy = g_new0(VFIODisplay, 1); |
352 | vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0, |
353 | &vfio_display_dmabuf_ops, |
354 | vdev); |
355 | if (vdev->enable_ramfb) { |
356 | vdev->dpy->ramfb = ramfb_setup(DEVICE(vdev), errp); |
357 | } |
358 | vfio_display_edid_init(vdev); |
359 | return 0; |
360 | } |
361 | |
362 | static void vfio_display_dmabuf_exit(VFIODisplay *dpy) |
363 | { |
364 | VFIODMABuf *dmabuf; |
365 | |
366 | if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) { |
367 | return; |
368 | } |
369 | |
370 | while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) { |
371 | vfio_display_free_one_dmabuf(dpy, dmabuf); |
372 | } |
373 | } |
374 | |
375 | /* ---------------------------------------------------------------------- */ |
376 | void vfio_display_reset(VFIOPCIDevice *vdev) |
377 | { |
378 | if (!vdev || !vdev->dpy || !vdev->dpy->con || |
379 | !vdev->dpy->dmabuf.primary) { |
380 | return; |
381 | } |
382 | |
383 | dpy_gl_scanout_disable(vdev->dpy->con); |
384 | vfio_display_dmabuf_exit(vdev->dpy); |
385 | dpy_gfx_update_full(vdev->dpy->con); |
386 | } |
387 | |
388 | static void vfio_display_region_update(void *opaque) |
389 | { |
390 | VFIOPCIDevice *vdev = opaque; |
391 | VFIODisplay *dpy = vdev->dpy; |
392 | struct vfio_device_gfx_plane_info plane = { |
393 | .argsz = sizeof(plane), |
394 | .flags = VFIO_GFX_PLANE_TYPE_REGION |
395 | }; |
396 | pixman_format_code_t format; |
397 | int ret; |
398 | |
399 | ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane); |
400 | if (ret < 0) { |
401 | error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s" , |
402 | strerror(errno)); |
403 | return; |
404 | } |
405 | if (!plane.drm_format || !plane.size) { |
406 | if (dpy->ramfb) { |
407 | ramfb_display_update(dpy->con, dpy->ramfb); |
408 | } |
409 | return; |
410 | } |
411 | format = qemu_drm_format_to_pixman(plane.drm_format); |
412 | if (!format) { |
413 | return; |
414 | } |
415 | |
416 | if (dpy->region.buffer.size && |
417 | dpy->region.buffer.nr != plane.region_index) { |
418 | /* region changed */ |
419 | vfio_region_exit(&dpy->region.buffer); |
420 | vfio_region_finalize(&dpy->region.buffer); |
421 | dpy->region.surface = NULL; |
422 | } |
423 | |
424 | if (dpy->region.surface && |
425 | (surface_width(dpy->region.surface) != plane.width || |
426 | surface_height(dpy->region.surface) != plane.height || |
427 | surface_format(dpy->region.surface) != format)) { |
428 | /* size changed */ |
429 | dpy->region.surface = NULL; |
430 | } |
431 | |
432 | if (!dpy->region.buffer.size) { |
433 | /* mmap region */ |
434 | ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev, |
435 | &dpy->region.buffer, |
436 | plane.region_index, |
437 | "display" ); |
438 | if (ret != 0) { |
439 | error_report("%s: vfio_region_setup(%d): %s" , |
440 | __func__, plane.region_index, strerror(-ret)); |
441 | goto err; |
442 | } |
443 | ret = vfio_region_mmap(&dpy->region.buffer); |
444 | if (ret != 0) { |
445 | error_report("%s: vfio_region_mmap(%d): %s" , __func__, |
446 | plane.region_index, strerror(-ret)); |
447 | goto err; |
448 | } |
449 | assert(dpy->region.buffer.mmaps[0].mmap != NULL); |
450 | } |
451 | |
452 | if (dpy->region.surface == NULL) { |
453 | /* create surface */ |
454 | dpy->region.surface = qemu_create_displaysurface_from |
455 | (plane.width, plane.height, format, |
456 | plane.stride, dpy->region.buffer.mmaps[0].mmap); |
457 | dpy_gfx_replace_surface(dpy->con, dpy->region.surface); |
458 | } |
459 | |
460 | /* full screen update */ |
461 | dpy_gfx_update(dpy->con, 0, 0, |
462 | surface_width(dpy->region.surface), |
463 | surface_height(dpy->region.surface)); |
464 | return; |
465 | |
466 | err: |
467 | vfio_region_exit(&dpy->region.buffer); |
468 | vfio_region_finalize(&dpy->region.buffer); |
469 | } |
470 | |
471 | static const GraphicHwOps vfio_display_region_ops = { |
472 | .gfx_update = vfio_display_region_update, |
473 | }; |
474 | |
475 | static int vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp) |
476 | { |
477 | vdev->dpy = g_new0(VFIODisplay, 1); |
478 | vdev->dpy->con = graphic_console_init(DEVICE(vdev), 0, |
479 | &vfio_display_region_ops, |
480 | vdev); |
481 | if (vdev->enable_ramfb) { |
482 | vdev->dpy->ramfb = ramfb_setup(DEVICE(vdev), errp); |
483 | } |
484 | return 0; |
485 | } |
486 | |
487 | static void vfio_display_region_exit(VFIODisplay *dpy) |
488 | { |
489 | if (!dpy->region.buffer.size) { |
490 | return; |
491 | } |
492 | |
493 | vfio_region_exit(&dpy->region.buffer); |
494 | vfio_region_finalize(&dpy->region.buffer); |
495 | } |
496 | |
497 | /* ---------------------------------------------------------------------- */ |
498 | |
499 | int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp) |
500 | { |
501 | struct vfio_device_gfx_plane_info probe; |
502 | int ret; |
503 | |
504 | memset(&probe, 0, sizeof(probe)); |
505 | probe.argsz = sizeof(probe); |
506 | probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF; |
507 | ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe); |
508 | if (ret == 0) { |
509 | return vfio_display_dmabuf_init(vdev, errp); |
510 | } |
511 | |
512 | memset(&probe, 0, sizeof(probe)); |
513 | probe.argsz = sizeof(probe); |
514 | probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION; |
515 | ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe); |
516 | if (ret == 0) { |
517 | return vfio_display_region_init(vdev, errp); |
518 | } |
519 | |
520 | if (vdev->display == ON_OFF_AUTO_AUTO) { |
521 | /* not an error in automatic mode */ |
522 | return 0; |
523 | } |
524 | |
525 | error_setg(errp, "vfio: device doesn't support any (known) display method" ); |
526 | return -1; |
527 | } |
528 | |
529 | void vfio_display_finalize(VFIOPCIDevice *vdev) |
530 | { |
531 | if (!vdev->dpy) { |
532 | return; |
533 | } |
534 | |
535 | graphic_console_close(vdev->dpy->con); |
536 | vfio_display_dmabuf_exit(vdev->dpy); |
537 | vfio_display_region_exit(vdev->dpy); |
538 | vfio_display_edid_exit(vdev->dpy); |
539 | g_free(vdev->dpy); |
540 | } |
541 | |