2 * vhost-user GPU Device
4 * Copyright Red Hat, Inc. 2018
7 * Marc-André Lureau <marcandre.lureau@redhat.com>
9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
10 * See the COPYING file in the top-level directory.
13 #include "qemu/osdep.h"
14 #include "hw/qdev-properties.h"
15 #include "hw/virtio/virtio-gpu.h"
16 #include "chardev/char-fe.h"
17 #include "qapi/error.h"
18 #include "migration/blocker.h"
20 #define VHOST_USER_GPU(obj) \
21 OBJECT_CHECK(VhostUserGPU, (obj), TYPE_VHOST_USER_GPU)
23 typedef enum VhostUserGpuRequest
{
24 VHOST_USER_GPU_NONE
= 0,
25 VHOST_USER_GPU_GET_PROTOCOL_FEATURES
,
26 VHOST_USER_GPU_SET_PROTOCOL_FEATURES
,
27 VHOST_USER_GPU_GET_DISPLAY_INFO
,
28 VHOST_USER_GPU_CURSOR_POS
,
29 VHOST_USER_GPU_CURSOR_POS_HIDE
,
30 VHOST_USER_GPU_CURSOR_UPDATE
,
31 VHOST_USER_GPU_SCANOUT
,
32 VHOST_USER_GPU_UPDATE
,
33 VHOST_USER_GPU_DMABUF_SCANOUT
,
34 VHOST_USER_GPU_DMABUF_UPDATE
,
35 } VhostUserGpuRequest
;
37 typedef struct VhostUserGpuDisplayInfoReply
{
38 struct virtio_gpu_resp_display_info info
;
39 } VhostUserGpuDisplayInfoReply
;
41 typedef struct VhostUserGpuCursorPos
{
45 } QEMU_PACKED VhostUserGpuCursorPos
;
47 typedef struct VhostUserGpuCursorUpdate
{
48 VhostUserGpuCursorPos pos
;
51 uint32_t data
[64 * 64];
52 } QEMU_PACKED VhostUserGpuCursorUpdate
;
54 typedef struct VhostUserGpuScanout
{
58 } QEMU_PACKED VhostUserGpuScanout
;
60 typedef struct VhostUserGpuUpdate
{
67 } QEMU_PACKED VhostUserGpuUpdate
;
69 typedef struct VhostUserGpuDMABUFScanout
{
80 } QEMU_PACKED VhostUserGpuDMABUFScanout
;
82 typedef struct VhostUserGpuMsg
{
83 uint32_t request
; /* VhostUserGpuRequest */
85 uint32_t size
; /* the following payload size */
87 VhostUserGpuCursorPos cursor_pos
;
88 VhostUserGpuCursorUpdate cursor_update
;
89 VhostUserGpuScanout scanout
;
90 VhostUserGpuUpdate update
;
91 VhostUserGpuDMABUFScanout dmabuf_scanout
;
92 struct virtio_gpu_resp_display_info display_info
;
95 } QEMU_PACKED VhostUserGpuMsg
;
97 static VhostUserGpuMsg m
__attribute__ ((unused
));
98 #define VHOST_USER_GPU_HDR_SIZE \
99 (sizeof(m.request) + sizeof(m.size) + sizeof(m.flags))
101 #define VHOST_USER_GPU_MSG_FLAG_REPLY 0x4
103 static void vhost_user_gpu_update_blocked(VhostUserGPU
*g
, bool blocked
);
106 vhost_user_gpu_handle_cursor(VhostUserGPU
*g
, VhostUserGpuMsg
*msg
)
108 VhostUserGpuCursorPos
*pos
= &msg
->payload
.cursor_pos
;
109 struct virtio_gpu_scanout
*s
;
111 if (pos
->scanout_id
>= g
->parent_obj
.conf
.max_outputs
) {
114 s
= &g
->parent_obj
.scanout
[pos
->scanout_id
];
116 if (msg
->request
== VHOST_USER_GPU_CURSOR_UPDATE
) {
117 VhostUserGpuCursorUpdate
*up
= &msg
->payload
.cursor_update
;
118 if (!s
->current_cursor
) {
119 s
->current_cursor
= cursor_alloc(64, 64);
122 s
->current_cursor
->hot_x
= up
->hot_x
;
123 s
->current_cursor
->hot_y
= up
->hot_y
;
125 memcpy(s
->current_cursor
->data
, up
->data
,
126 64 * 64 * sizeof(uint32_t));
128 dpy_cursor_define(s
->con
, s
->current_cursor
);
131 dpy_mouse_set(s
->con
, pos
->x
, pos
->y
,
132 msg
->request
!= VHOST_USER_GPU_CURSOR_POS_HIDE
);
136 vhost_user_gpu_send_msg(VhostUserGPU
*g
, const VhostUserGpuMsg
*msg
)
138 qemu_chr_fe_write(&g
->vhost_chr
, (uint8_t *)msg
,
139 VHOST_USER_GPU_HDR_SIZE
+ msg
->size
);
143 vhost_user_gpu_unblock(VhostUserGPU
*g
)
145 VhostUserGpuMsg msg
= {
146 .request
= VHOST_USER_GPU_DMABUF_UPDATE
,
147 .flags
= VHOST_USER_GPU_MSG_FLAG_REPLY
,
150 vhost_user_gpu_send_msg(g
, &msg
);
154 vhost_user_gpu_handle_display(VhostUserGPU
*g
, VhostUserGpuMsg
*msg
)
156 QemuConsole
*con
= NULL
;
157 struct virtio_gpu_scanout
*s
;
159 switch (msg
->request
) {
160 case VHOST_USER_GPU_GET_PROTOCOL_FEATURES
: {
161 VhostUserGpuMsg reply
= {
162 .request
= msg
->request
,
163 .flags
= VHOST_USER_GPU_MSG_FLAG_REPLY
,
164 .size
= sizeof(uint64_t),
167 vhost_user_gpu_send_msg(g
, &reply
);
170 case VHOST_USER_GPU_SET_PROTOCOL_FEATURES
: {
173 case VHOST_USER_GPU_GET_DISPLAY_INFO
: {
174 struct virtio_gpu_resp_display_info display_info
= { {} };
175 VhostUserGpuMsg reply
= {
176 .request
= msg
->request
,
177 .flags
= VHOST_USER_GPU_MSG_FLAG_REPLY
,
178 .size
= sizeof(struct virtio_gpu_resp_display_info
),
181 display_info
.hdr
.type
= VIRTIO_GPU_RESP_OK_DISPLAY_INFO
;
182 virtio_gpu_base_fill_display_info(VIRTIO_GPU_BASE(g
), &display_info
);
183 memcpy(&reply
.payload
.display_info
, &display_info
,
184 sizeof(display_info
));
185 vhost_user_gpu_send_msg(g
, &reply
);
188 case VHOST_USER_GPU_SCANOUT
: {
189 VhostUserGpuScanout
*m
= &msg
->payload
.scanout
;
191 if (m
->scanout_id
>= g
->parent_obj
.conf
.max_outputs
) {
195 g
->parent_obj
.enable
= 1;
196 s
= &g
->parent_obj
.scanout
[m
->scanout_id
];
199 if (m
->scanout_id
== 0 && m
->width
== 0) {
200 s
->ds
= qemu_create_message_surface(640, 480,
201 "Guest disabled display.");
202 dpy_gfx_replace_surface(con
, s
->ds
);
204 s
->ds
= qemu_create_displaysurface(m
->width
, m
->height
);
205 /* replace surface on next update */
210 case VHOST_USER_GPU_DMABUF_SCANOUT
: {
211 VhostUserGpuDMABUFScanout
*m
= &msg
->payload
.dmabuf_scanout
;
212 int fd
= qemu_chr_fe_get_msgfd(&g
->vhost_chr
);
215 if (m
->scanout_id
>= g
->parent_obj
.conf
.max_outputs
) {
216 error_report("invalid scanout: %d", m
->scanout_id
);
223 g
->parent_obj
.enable
= 1;
224 con
= g
->parent_obj
.scanout
[m
->scanout_id
].con
;
225 dmabuf
= &g
->dmabuf
[m
->scanout_id
];
226 if (dmabuf
->fd
>= 0) {
230 if (!console_has_gl_dmabuf(con
)) {
231 /* it would be nice to report that error earlier */
232 error_report("console doesn't support dmabuf!");
235 dpy_gl_release_dmabuf(con
, dmabuf
);
237 dpy_gl_scanout_disable(con
);
240 *dmabuf
= (QemuDmaBuf
) {
242 .width
= m
->fd_width
,
243 .height
= m
->fd_height
,
244 .stride
= m
->fd_stride
,
245 .fourcc
= m
->fd_drm_fourcc
,
246 .y0_top
= m
->fd_flags
& VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP
,
248 dpy_gl_scanout_dmabuf(con
, dmabuf
);
251 case VHOST_USER_GPU_DMABUF_UPDATE
: {
252 VhostUserGpuUpdate
*m
= &msg
->payload
.update
;
254 if (m
->scanout_id
>= g
->parent_obj
.conf
.max_outputs
||
255 !g
->parent_obj
.scanout
[m
->scanout_id
].con
) {
256 error_report("invalid scanout update: %d", m
->scanout_id
);
257 vhost_user_gpu_unblock(g
);
261 con
= g
->parent_obj
.scanout
[m
->scanout_id
].con
;
262 if (!console_has_gl(con
)) {
263 error_report("console doesn't support GL!");
264 vhost_user_gpu_unblock(g
);
267 dpy_gl_update(con
, m
->x
, m
->y
, m
->width
, m
->height
);
268 g
->backend_blocked
= true;
271 case VHOST_USER_GPU_UPDATE
: {
272 VhostUserGpuUpdate
*m
= &msg
->payload
.update
;
274 if (m
->scanout_id
>= g
->parent_obj
.conf
.max_outputs
) {
277 s
= &g
->parent_obj
.scanout
[m
->scanout_id
];
279 pixman_image_t
*image
=
280 pixman_image_create_bits(PIXMAN_x8r8g8b8
,
286 pixman_image_composite(PIXMAN_OP_SRC
,
287 image
, NULL
, s
->ds
->image
,
288 0, 0, 0, 0, m
->x
, m
->y
, m
->width
, m
->height
);
290 pixman_image_unref(image
);
291 if (qemu_console_surface(con
) != s
->ds
) {
292 dpy_gfx_replace_surface(con
, s
->ds
);
294 dpy_gfx_update(con
, m
->x
, m
->y
, m
->width
, m
->height
);
299 g_warning("unhandled message %d %d", msg
->request
, msg
->size
);
302 if (con
&& qemu_console_is_gl_blocked(con
)) {
303 vhost_user_gpu_update_blocked(g
, true);
308 vhost_user_gpu_chr_read(void *opaque
)
310 VhostUserGPU
*g
= opaque
;
311 VhostUserGpuMsg
*msg
= NULL
;
312 VhostUserGpuRequest request
;
313 uint32_t size
, flags
;
316 r
= qemu_chr_fe_read_all(&g
->vhost_chr
,
317 (uint8_t *)&request
, sizeof(uint32_t));
318 if (r
!= sizeof(uint32_t)) {
319 error_report("failed to read msg header: %d, %d", r
, errno
);
323 r
= qemu_chr_fe_read_all(&g
->vhost_chr
,
324 (uint8_t *)&flags
, sizeof(uint32_t));
325 if (r
!= sizeof(uint32_t)) {
326 error_report("failed to read msg flags");
330 r
= qemu_chr_fe_read_all(&g
->vhost_chr
,
331 (uint8_t *)&size
, sizeof(uint32_t));
332 if (r
!= sizeof(uint32_t)) {
333 error_report("failed to read msg size");
337 msg
= g_malloc(VHOST_USER_GPU_HDR_SIZE
+ size
);
338 g_return_if_fail(msg
!= NULL
);
340 r
= qemu_chr_fe_read_all(&g
->vhost_chr
,
341 (uint8_t *)&msg
->payload
, size
);
343 error_report("failed to read msg payload %d != %d", r
, size
);
347 msg
->request
= request
;
351 if (request
== VHOST_USER_GPU_CURSOR_UPDATE
||
352 request
== VHOST_USER_GPU_CURSOR_POS
||
353 request
== VHOST_USER_GPU_CURSOR_POS_HIDE
) {
354 vhost_user_gpu_handle_cursor(g
, msg
);
356 vhost_user_gpu_handle_display(g
, msg
);
364 vhost_user_gpu_update_blocked(VhostUserGPU
*g
, bool blocked
)
366 qemu_set_fd_handler(g
->vhost_gpu_fd
,
367 blocked
? NULL
: vhost_user_gpu_chr_read
, NULL
, g
);
371 vhost_user_gpu_gl_unblock(VirtIOGPUBase
*b
)
373 VhostUserGPU
*g
= VHOST_USER_GPU(b
);
375 if (g
->backend_blocked
) {
376 vhost_user_gpu_unblock(VHOST_USER_GPU(g
));
377 g
->backend_blocked
= false;
380 vhost_user_gpu_update_blocked(VHOST_USER_GPU(g
), false);
384 vhost_user_gpu_do_set_socket(VhostUserGPU
*g
, Error
**errp
)
389 if (socketpair(PF_UNIX
, SOCK_STREAM
, 0, sv
) == -1) {
390 error_setg_errno(errp
, errno
, "socketpair() failed");
394 chr
= CHARDEV(object_new(TYPE_CHARDEV_SOCKET
));
395 if (!chr
|| qemu_chr_add_client(chr
, sv
[0]) == -1) {
396 error_setg(errp
, "Failed to make socket chardev");
399 if (!qemu_chr_fe_init(&g
->vhost_chr
, chr
, errp
)) {
402 if (vhost_user_gpu_set_socket(&g
->vhost
->dev
, sv
[1]) < 0) {
403 error_setg(errp
, "Failed to set vhost-user-gpu socket");
404 qemu_chr_fe_deinit(&g
->vhost_chr
, false);
408 g
->vhost_gpu_fd
= sv
[0];
409 vhost_user_gpu_update_blocked(g
, false);
417 object_unref(OBJECT(chr
));
423 vhost_user_gpu_get_config(VirtIODevice
*vdev
, uint8_t *config_data
)
425 VhostUserGPU
*g
= VHOST_USER_GPU(vdev
);
426 VirtIOGPUBase
*b
= VIRTIO_GPU_BASE(vdev
);
427 struct virtio_gpu_config
*vgconfig
=
428 (struct virtio_gpu_config
*)config_data
;
431 memset(config_data
, 0, sizeof(struct virtio_gpu_config
));
433 ret
= vhost_dev_get_config(&g
->vhost
->dev
,
434 config_data
, sizeof(struct virtio_gpu_config
));
436 error_report("vhost-user-gpu: get device config space failed");
440 /* those fields are managed by qemu */
441 vgconfig
->num_scanouts
= b
->virtio_config
.num_scanouts
;
442 vgconfig
->events_read
= b
->virtio_config
.events_read
;
443 vgconfig
->events_clear
= b
->virtio_config
.events_clear
;
447 vhost_user_gpu_set_config(VirtIODevice
*vdev
,
448 const uint8_t *config_data
)
450 VhostUserGPU
*g
= VHOST_USER_GPU(vdev
);
451 VirtIOGPUBase
*b
= VIRTIO_GPU_BASE(vdev
);
452 const struct virtio_gpu_config
*vgconfig
=
453 (const struct virtio_gpu_config
*)config_data
;
456 if (vgconfig
->events_clear
) {
457 b
->virtio_config
.events_read
&= ~vgconfig
->events_clear
;
460 ret
= vhost_dev_set_config(&g
->vhost
->dev
, config_data
,
461 0, sizeof(struct virtio_gpu_config
),
462 VHOST_SET_CONFIG_TYPE_MASTER
);
464 error_report("vhost-user-gpu: set device config space failed");
470 vhost_user_gpu_set_status(VirtIODevice
*vdev
, uint8_t val
)
472 VhostUserGPU
*g
= VHOST_USER_GPU(vdev
);
475 if (val
& VIRTIO_CONFIG_S_DRIVER_OK
&& vdev
->vm_running
) {
476 if (!vhost_user_gpu_do_set_socket(g
, &err
)) {
477 error_report_err(err
);
480 vhost_user_backend_start(g
->vhost
);
482 /* unblock any wait and stop processing */
483 if (g
->vhost_gpu_fd
!= -1) {
484 vhost_user_gpu_update_blocked(g
, true);
485 qemu_chr_fe_deinit(&g
->vhost_chr
, true);
486 g
->vhost_gpu_fd
= -1;
488 vhost_user_backend_stop(g
->vhost
);
493 vhost_user_gpu_guest_notifier_pending(VirtIODevice
*vdev
, int idx
)
495 VhostUserGPU
*g
= VHOST_USER_GPU(vdev
);
497 return vhost_virtqueue_pending(&g
->vhost
->dev
, idx
);
501 vhost_user_gpu_guest_notifier_mask(VirtIODevice
*vdev
, int idx
, bool mask
)
503 VhostUserGPU
*g
= VHOST_USER_GPU(vdev
);
505 vhost_virtqueue_mask(&g
->vhost
->dev
, vdev
, idx
, mask
);
509 vhost_user_gpu_instance_init(Object
*obj
)
511 VhostUserGPU
*g
= VHOST_USER_GPU(obj
);
513 g
->vhost
= VHOST_USER_BACKEND(object_new(TYPE_VHOST_USER_BACKEND
));
514 object_property_add_alias(obj
, "chardev",
515 OBJECT(g
->vhost
), "chardev", &error_abort
);
519 vhost_user_gpu_instance_finalize(Object
*obj
)
521 VhostUserGPU
*g
= VHOST_USER_GPU(obj
);
523 object_unref(OBJECT(g
->vhost
));
527 vhost_user_gpu_reset(VirtIODevice
*vdev
)
529 VhostUserGPU
*g
= VHOST_USER_GPU(vdev
);
531 virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev
));
533 vhost_user_backend_stop(g
->vhost
);
537 vhost_user_gpu_config_change(struct vhost_dev
*dev
)
539 error_report("vhost-user-gpu: unhandled backend config change");
543 static const VhostDevConfigOps config_ops
= {
544 .vhost_dev_config_notifier
= vhost_user_gpu_config_change
,
548 vhost_user_gpu_device_realize(DeviceState
*qdev
, Error
**errp
)
550 VhostUserGPU
*g
= VHOST_USER_GPU(qdev
);
551 VirtIODevice
*vdev
= VIRTIO_DEVICE(g
);
553 vhost_dev_set_config_notifier(&g
->vhost
->dev
, &config_ops
);
554 if (vhost_user_backend_dev_init(g
->vhost
, vdev
, 2, errp
) < 0) {
558 if (virtio_has_feature(g
->vhost
->dev
.features
, VIRTIO_GPU_F_VIRGL
)) {
559 g
->parent_obj
.conf
.flags
|= 1 << VIRTIO_GPU_FLAG_VIRGL_ENABLED
;
562 if (!virtio_gpu_base_device_realize(qdev
, NULL
, NULL
, errp
)) {
566 g
->vhost_gpu_fd
= -1;
569 static Property vhost_user_gpu_properties
[] = {
570 VIRTIO_GPU_BASE_PROPERTIES(VhostUserGPU
, parent_obj
.conf
),
571 DEFINE_PROP_END_OF_LIST(),
575 vhost_user_gpu_class_init(ObjectClass
*klass
, void *data
)
577 DeviceClass
*dc
= DEVICE_CLASS(klass
);
578 VirtioDeviceClass
*vdc
= VIRTIO_DEVICE_CLASS(klass
);
579 VirtIOGPUBaseClass
*vgc
= VIRTIO_GPU_BASE_CLASS(klass
);
581 vgc
->gl_unblock
= vhost_user_gpu_gl_unblock
;
583 vdc
->realize
= vhost_user_gpu_device_realize
;
584 vdc
->reset
= vhost_user_gpu_reset
;
585 vdc
->set_status
= vhost_user_gpu_set_status
;
586 vdc
->guest_notifier_mask
= vhost_user_gpu_guest_notifier_mask
;
587 vdc
->guest_notifier_pending
= vhost_user_gpu_guest_notifier_pending
;
588 vdc
->get_config
= vhost_user_gpu_get_config
;
589 vdc
->set_config
= vhost_user_gpu_set_config
;
591 dc
->props
= vhost_user_gpu_properties
;
594 static const TypeInfo vhost_user_gpu_info
= {
595 .name
= TYPE_VHOST_USER_GPU
,
596 .parent
= TYPE_VIRTIO_GPU_BASE
,
597 .instance_size
= sizeof(VhostUserGPU
),
598 .instance_init
= vhost_user_gpu_instance_init
,
599 .instance_finalize
= vhost_user_gpu_instance_finalize
,
600 .class_init
= vhost_user_gpu_class_init
,
603 static void vhost_user_gpu_register_types(void)
605 type_register_static(&vhost_user_gpu_info
);
608 type_init(vhost_user_gpu_register_types
)