1 /* 2 * vhost-user GPU Device 3 * 4 * Copyright Red Hat, Inc. 2018 5 * 6 * Authors: 7 * Marc-André Lureau <marcandre.lureau@redhat.com> 8 * 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. 11 */ 12 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" 19 20 #define VHOST_USER_GPU(obj) \ 21 OBJECT_CHECK(VhostUserGPU, (obj), TYPE_VHOST_USER_GPU) 22 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; 36 37 typedef struct VhostUserGpuDisplayInfoReply { 38 struct virtio_gpu_resp_display_info info; 39 } VhostUserGpuDisplayInfoReply; 40 41 typedef struct VhostUserGpuCursorPos { 42 uint32_t scanout_id; 43 uint32_t x; 44 uint32_t y; 45 } QEMU_PACKED VhostUserGpuCursorPos; 46 47 typedef struct VhostUserGpuCursorUpdate { 48 VhostUserGpuCursorPos pos; 49 uint32_t hot_x; 50 uint32_t hot_y; 51 uint32_t data[64 * 64]; 52 } QEMU_PACKED VhostUserGpuCursorUpdate; 53 54 typedef struct VhostUserGpuScanout { 55 uint32_t scanout_id; 56 uint32_t width; 57 uint32_t height; 58 } QEMU_PACKED VhostUserGpuScanout; 59 60 typedef struct VhostUserGpuUpdate { 61 uint32_t scanout_id; 62 uint32_t x; 63 uint32_t y; 64 uint32_t width; 65 uint32_t height; 66 uint8_t data[]; 67 } QEMU_PACKED VhostUserGpuUpdate; 68 69 typedef struct VhostUserGpuDMABUFScanout { 70 uint32_t scanout_id; 71 uint32_t x; 72 uint32_t y; 73 uint32_t width; 74 uint32_t height; 75 uint32_t fd_width; 76 uint32_t fd_height; 77 uint32_t fd_stride; 78 uint32_t fd_flags; 79 int fd_drm_fourcc; 80 } QEMU_PACKED VhostUserGpuDMABUFScanout; 81 82 typedef struct VhostUserGpuMsg { 83 uint32_t request; /* VhostUserGpuRequest */ 84 uint32_t flags; 85 uint32_t size; /* the following payload size */ 86 union { 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; 93 uint64_t u64; 94 } payload; 95 } QEMU_PACKED VhostUserGpuMsg; 96 97 static VhostUserGpuMsg m __attribute__ ((unused)); 98 #define VHOST_USER_GPU_HDR_SIZE \ 99 (sizeof(m.request) + sizeof(m.size) + sizeof(m.flags)) 100 101 #define VHOST_USER_GPU_MSG_FLAG_REPLY 0x4 102 103 static void vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked); 104 105 static void 106 vhost_user_gpu_handle_cursor(VhostUserGPU *g, VhostUserGpuMsg *msg) 107 { 108 VhostUserGpuCursorPos *pos = &msg->payload.cursor_pos; 109 struct virtio_gpu_scanout *s; 110 111 if (pos->scanout_id >= g->parent_obj.conf.max_outputs) { 112 return; 113 } 114 s = &g->parent_obj.scanout[pos->scanout_id]; 115 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); 120 } 121 122 s->current_cursor->hot_x = up->hot_x; 123 s->current_cursor->hot_y = up->hot_y; 124 125 memcpy(s->current_cursor->data, up->data, 126 64 * 64 * sizeof(uint32_t)); 127 128 dpy_cursor_define(s->con, s->current_cursor); 129 } 130 131 dpy_mouse_set(s->con, pos->x, pos->y, 132 msg->request != VHOST_USER_GPU_CURSOR_POS_HIDE); 133 } 134 135 static void 136 vhost_user_gpu_send_msg(VhostUserGPU *g, const VhostUserGpuMsg *msg) 137 { 138 qemu_chr_fe_write(&g->vhost_chr, (uint8_t *)msg, 139 VHOST_USER_GPU_HDR_SIZE + msg->size); 140 } 141 142 static void 143 vhost_user_gpu_unblock(VhostUserGPU *g) 144 { 145 VhostUserGpuMsg msg = { 146 .request = VHOST_USER_GPU_DMABUF_UPDATE, 147 .flags = VHOST_USER_GPU_MSG_FLAG_REPLY, 148 }; 149 150 vhost_user_gpu_send_msg(g, &msg); 151 } 152 153 static void 154 vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg) 155 { 156 QemuConsole *con = NULL; 157 struct virtio_gpu_scanout *s; 158 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), 165 }; 166 167 vhost_user_gpu_send_msg(g, &reply); 168 break; 169 } 170 case VHOST_USER_GPU_SET_PROTOCOL_FEATURES: { 171 break; 172 } 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), 179 }; 180 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); 186 break; 187 } 188 case VHOST_USER_GPU_SCANOUT: { 189 VhostUserGpuScanout *m = &msg->payload.scanout; 190 191 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 192 return; 193 } 194 195 g->parent_obj.enable = 1; 196 s = &g->parent_obj.scanout[m->scanout_id]; 197 con = s->con; 198 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); 203 } else { 204 s->ds = qemu_create_displaysurface(m->width, m->height); 205 /* replace surface on next update */ 206 } 207 208 break; 209 } 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); 213 QemuDmaBuf *dmabuf; 214 215 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 216 error_report("invalid scanout: %d", m->scanout_id); 217 if (fd >= 0) { 218 close(fd); 219 } 220 break; 221 } 222 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) { 227 close(dmabuf->fd); 228 dmabuf->fd = -1; 229 } 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!"); 233 break; 234 } 235 dpy_gl_release_dmabuf(con, dmabuf); 236 if (fd == -1) { 237 dpy_gl_scanout_disable(con); 238 break; 239 } 240 *dmabuf = (QemuDmaBuf) { 241 .fd = fd, 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, 247 }; 248 dpy_gl_scanout_dmabuf(con, dmabuf); 249 break; 250 } 251 case VHOST_USER_GPU_DMABUF_UPDATE: { 252 VhostUserGpuUpdate *m = &msg->payload.update; 253 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); 258 break; 259 } 260 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); 265 break; 266 } 267 dpy_gl_update(con, m->x, m->y, m->width, m->height); 268 g->backend_blocked = true; 269 break; 270 } 271 case VHOST_USER_GPU_UPDATE: { 272 VhostUserGpuUpdate *m = &msg->payload.update; 273 274 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 275 break; 276 } 277 s = &g->parent_obj.scanout[m->scanout_id]; 278 con = s->con; 279 pixman_image_t *image = 280 pixman_image_create_bits(PIXMAN_x8r8g8b8, 281 m->width, 282 m->height, 283 (uint32_t *)m->data, 284 m->width * 4); 285 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); 289 290 pixman_image_unref(image); 291 if (qemu_console_surface(con) != s->ds) { 292 dpy_gfx_replace_surface(con, s->ds); 293 } else { 294 dpy_gfx_update(con, m->x, m->y, m->width, m->height); 295 } 296 break; 297 } 298 default: 299 g_warning("unhandled message %d %d", msg->request, msg->size); 300 } 301 302 if (con && qemu_console_is_gl_blocked(con)) { 303 vhost_user_gpu_update_blocked(g, true); 304 } 305 } 306 307 static void 308 vhost_user_gpu_chr_read(void *opaque) 309 { 310 VhostUserGPU *g = opaque; 311 VhostUserGpuMsg *msg = NULL; 312 VhostUserGpuRequest request; 313 uint32_t size, flags; 314 int r; 315 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); 320 goto end; 321 } 322 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"); 327 goto end; 328 } 329 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"); 334 goto end; 335 } 336 337 msg = g_malloc(VHOST_USER_GPU_HDR_SIZE + size); 338 g_return_if_fail(msg != NULL); 339 340 r = qemu_chr_fe_read_all(&g->vhost_chr, 341 (uint8_t *)&msg->payload, size); 342 if (r != size) { 343 error_report("failed to read msg payload %d != %d", r, size); 344 goto end; 345 } 346 347 msg->request = request; 348 msg->flags = size; 349 msg->size = size; 350 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); 355 } else { 356 vhost_user_gpu_handle_display(g, msg); 357 } 358 359 end: 360 g_free(msg); 361 } 362 363 static void 364 vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked) 365 { 366 qemu_set_fd_handler(g->vhost_gpu_fd, 367 blocked ? NULL : vhost_user_gpu_chr_read, NULL, g); 368 } 369 370 static void 371 vhost_user_gpu_gl_unblock(VirtIOGPUBase *b) 372 { 373 VhostUserGPU *g = VHOST_USER_GPU(b); 374 375 if (g->backend_blocked) { 376 vhost_user_gpu_unblock(VHOST_USER_GPU(g)); 377 g->backend_blocked = false; 378 } 379 380 vhost_user_gpu_update_blocked(VHOST_USER_GPU(g), false); 381 } 382 383 static bool 384 vhost_user_gpu_do_set_socket(VhostUserGPU *g, Error **errp) 385 { 386 Chardev *chr; 387 int sv[2]; 388 389 if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { 390 error_setg_errno(errp, errno, "socketpair() failed"); 391 return false; 392 } 393 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"); 397 goto err; 398 } 399 if (!qemu_chr_fe_init(&g->vhost_chr, chr, errp)) { 400 goto err; 401 } 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); 405 goto err; 406 } 407 408 g->vhost_gpu_fd = sv[0]; 409 vhost_user_gpu_update_blocked(g, false); 410 close(sv[1]); 411 return true; 412 413 err: 414 close(sv[0]); 415 close(sv[1]); 416 if (chr) { 417 object_unref(OBJECT(chr)); 418 } 419 return false; 420 } 421 422 static void 423 vhost_user_gpu_get_config(VirtIODevice *vdev, uint8_t *config_data) 424 { 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; 429 int ret; 430 431 memset(config_data, 0, sizeof(struct virtio_gpu_config)); 432 433 ret = vhost_dev_get_config(&g->vhost->dev, 434 config_data, sizeof(struct virtio_gpu_config)); 435 if (ret) { 436 error_report("vhost-user-gpu: get device config space failed"); 437 return; 438 } 439 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; 444 } 445 446 static void 447 vhost_user_gpu_set_config(VirtIODevice *vdev, 448 const uint8_t *config_data) 449 { 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; 454 int ret; 455 456 if (vgconfig->events_clear) { 457 b->virtio_config.events_read &= ~vgconfig->events_clear; 458 } 459 460 ret = vhost_dev_set_config(&g->vhost->dev, config_data, 461 0, sizeof(struct virtio_gpu_config), 462 VHOST_SET_CONFIG_TYPE_MASTER); 463 if (ret) { 464 error_report("vhost-user-gpu: set device config space failed"); 465 return; 466 } 467 } 468 469 static void 470 vhost_user_gpu_set_status(VirtIODevice *vdev, uint8_t val) 471 { 472 VhostUserGPU *g = VHOST_USER_GPU(vdev); 473 Error *err = NULL; 474 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); 478 return; 479 } 480 vhost_user_backend_start(g->vhost); 481 } else { 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; 487 } 488 vhost_user_backend_stop(g->vhost); 489 } 490 } 491 492 static bool 493 vhost_user_gpu_guest_notifier_pending(VirtIODevice *vdev, int idx) 494 { 495 VhostUserGPU *g = VHOST_USER_GPU(vdev); 496 497 return vhost_virtqueue_pending(&g->vhost->dev, idx); 498 } 499 500 static void 501 vhost_user_gpu_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask) 502 { 503 VhostUserGPU *g = VHOST_USER_GPU(vdev); 504 505 vhost_virtqueue_mask(&g->vhost->dev, vdev, idx, mask); 506 } 507 508 static void 509 vhost_user_gpu_instance_init(Object *obj) 510 { 511 VhostUserGPU *g = VHOST_USER_GPU(obj); 512 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); 516 } 517 518 static void 519 vhost_user_gpu_instance_finalize(Object *obj) 520 { 521 VhostUserGPU *g = VHOST_USER_GPU(obj); 522 523 object_unref(OBJECT(g->vhost)); 524 } 525 526 static void 527 vhost_user_gpu_reset(VirtIODevice *vdev) 528 { 529 VhostUserGPU *g = VHOST_USER_GPU(vdev); 530 531 virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev)); 532 533 vhost_user_backend_stop(g->vhost); 534 } 535 536 static int 537 vhost_user_gpu_config_change(struct vhost_dev *dev) 538 { 539 error_report("vhost-user-gpu: unhandled backend config change"); 540 return -1; 541 } 542 543 static const VhostDevConfigOps config_ops = { 544 .vhost_dev_config_notifier = vhost_user_gpu_config_change, 545 }; 546 547 static void 548 vhost_user_gpu_device_realize(DeviceState *qdev, Error **errp) 549 { 550 VhostUserGPU *g = VHOST_USER_GPU(qdev); 551 VirtIODevice *vdev = VIRTIO_DEVICE(g); 552 553 vhost_dev_set_config_notifier(&g->vhost->dev, &config_ops); 554 if (vhost_user_backend_dev_init(g->vhost, vdev, 2, errp) < 0) { 555 return; 556 } 557 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; 560 } 561 562 if (!virtio_gpu_base_device_realize(qdev, NULL, NULL, errp)) { 563 return; 564 } 565 566 g->vhost_gpu_fd = -1; 567 } 568 569 static Property vhost_user_gpu_properties[] = { 570 VIRTIO_GPU_BASE_PROPERTIES(VhostUserGPU, parent_obj.conf), 571 DEFINE_PROP_END_OF_LIST(), 572 }; 573 574 static void 575 vhost_user_gpu_class_init(ObjectClass *klass, void *data) 576 { 577 DeviceClass *dc = DEVICE_CLASS(klass); 578 VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); 579 VirtIOGPUBaseClass *vgc = VIRTIO_GPU_BASE_CLASS(klass); 580 581 vgc->gl_unblock = vhost_user_gpu_gl_unblock; 582 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; 590 591 device_class_set_props(dc, vhost_user_gpu_properties); 592 } 593 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, 601 }; 602 603 static void vhost_user_gpu_register_types(void) 604 { 605 type_register_static(&vhost_user_gpu_info); 606 } 607 608 type_init(vhost_user_gpu_register_types) 609