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 "qemu/error-report.h" 15 #include "qemu/sockets.h" 16 #include "hw/qdev-properties.h" 17 #include "hw/virtio/virtio-gpu.h" 18 #include "chardev/char-fe.h" 19 #include "qapi/error.h" 20 #include "migration/blocker.h" 21 22 typedef enum VhostUserGpuRequest { 23 VHOST_USER_GPU_NONE = 0, 24 VHOST_USER_GPU_GET_PROTOCOL_FEATURES, 25 VHOST_USER_GPU_SET_PROTOCOL_FEATURES, 26 VHOST_USER_GPU_GET_DISPLAY_INFO, 27 VHOST_USER_GPU_CURSOR_POS, 28 VHOST_USER_GPU_CURSOR_POS_HIDE, 29 VHOST_USER_GPU_CURSOR_UPDATE, 30 VHOST_USER_GPU_SCANOUT, 31 VHOST_USER_GPU_UPDATE, 32 VHOST_USER_GPU_DMABUF_SCANOUT, 33 VHOST_USER_GPU_DMABUF_UPDATE, 34 VHOST_USER_GPU_GET_EDID, 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 VhostUserGpuEdidRequest { 83 uint32_t scanout_id; 84 } QEMU_PACKED VhostUserGpuEdidRequest; 85 86 typedef struct VhostUserGpuMsg { 87 uint32_t request; /* VhostUserGpuRequest */ 88 uint32_t flags; 89 uint32_t size; /* the following payload size */ 90 union { 91 VhostUserGpuCursorPos cursor_pos; 92 VhostUserGpuCursorUpdate cursor_update; 93 VhostUserGpuScanout scanout; 94 VhostUserGpuUpdate update; 95 VhostUserGpuDMABUFScanout dmabuf_scanout; 96 VhostUserGpuEdidRequest edid_req; 97 struct virtio_gpu_resp_edid resp_edid; 98 struct virtio_gpu_resp_display_info display_info; 99 uint64_t u64; 100 } payload; 101 } QEMU_PACKED VhostUserGpuMsg; 102 103 static VhostUserGpuMsg m __attribute__ ((unused)); 104 #define VHOST_USER_GPU_HDR_SIZE \ 105 (sizeof(m.request) + sizeof(m.size) + sizeof(m.flags)) 106 107 #define VHOST_USER_GPU_MSG_FLAG_REPLY 0x4 108 109 #define VHOST_USER_GPU_PROTOCOL_F_EDID 0 110 111 static void vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked); 112 113 static void 114 vhost_user_gpu_handle_cursor(VhostUserGPU *g, VhostUserGpuMsg *msg) 115 { 116 VhostUserGpuCursorPos *pos = &msg->payload.cursor_pos; 117 struct virtio_gpu_scanout *s; 118 119 if (pos->scanout_id >= g->parent_obj.conf.max_outputs) { 120 return; 121 } 122 s = &g->parent_obj.scanout[pos->scanout_id]; 123 124 if (msg->request == VHOST_USER_GPU_CURSOR_UPDATE) { 125 VhostUserGpuCursorUpdate *up = &msg->payload.cursor_update; 126 if (!s->current_cursor) { 127 s->current_cursor = cursor_alloc(64, 64); 128 } 129 130 s->current_cursor->hot_x = up->hot_x; 131 s->current_cursor->hot_y = up->hot_y; 132 133 memcpy(s->current_cursor->data, up->data, 134 64 * 64 * sizeof(uint32_t)); 135 136 dpy_cursor_define(s->con, s->current_cursor); 137 } 138 139 dpy_mouse_set(s->con, pos->x, pos->y, 140 msg->request != VHOST_USER_GPU_CURSOR_POS_HIDE); 141 } 142 143 static void 144 vhost_user_gpu_send_msg(VhostUserGPU *g, const VhostUserGpuMsg *msg) 145 { 146 qemu_chr_fe_write(&g->vhost_chr, (uint8_t *)msg, 147 VHOST_USER_GPU_HDR_SIZE + msg->size); 148 } 149 150 static void 151 vhost_user_gpu_unblock(VhostUserGPU *g) 152 { 153 VhostUserGpuMsg msg = { 154 .request = VHOST_USER_GPU_DMABUF_UPDATE, 155 .flags = VHOST_USER_GPU_MSG_FLAG_REPLY, 156 }; 157 158 vhost_user_gpu_send_msg(g, &msg); 159 } 160 161 static void 162 vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg) 163 { 164 QemuConsole *con = NULL; 165 struct virtio_gpu_scanout *s; 166 167 switch (msg->request) { 168 case VHOST_USER_GPU_GET_PROTOCOL_FEATURES: { 169 VhostUserGpuMsg reply = { 170 .request = msg->request, 171 .flags = VHOST_USER_GPU_MSG_FLAG_REPLY, 172 .size = sizeof(uint64_t), 173 .payload = { 174 .u64 = (1 << VHOST_USER_GPU_PROTOCOL_F_EDID) 175 } 176 }; 177 178 vhost_user_gpu_send_msg(g, &reply); 179 break; 180 } 181 case VHOST_USER_GPU_SET_PROTOCOL_FEATURES: { 182 break; 183 } 184 case VHOST_USER_GPU_GET_DISPLAY_INFO: { 185 struct virtio_gpu_resp_display_info display_info = { {} }; 186 VhostUserGpuMsg reply = { 187 .request = msg->request, 188 .flags = VHOST_USER_GPU_MSG_FLAG_REPLY, 189 .size = sizeof(struct virtio_gpu_resp_display_info), 190 }; 191 192 display_info.hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO; 193 virtio_gpu_base_fill_display_info(VIRTIO_GPU_BASE(g), &display_info); 194 memcpy(&reply.payload.display_info, &display_info, 195 sizeof(display_info)); 196 vhost_user_gpu_send_msg(g, &reply); 197 break; 198 } 199 case VHOST_USER_GPU_GET_EDID: { 200 VhostUserGpuEdidRequest *m = &msg->payload.edid_req; 201 struct virtio_gpu_resp_edid resp = { {} }; 202 VhostUserGpuMsg reply = { 203 .request = msg->request, 204 .flags = VHOST_USER_GPU_MSG_FLAG_REPLY, 205 .size = sizeof(reply.payload.resp_edid), 206 }; 207 208 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 209 error_report("invalid scanout: %d", m->scanout_id); 210 break; 211 } 212 213 resp.hdr.type = VIRTIO_GPU_RESP_OK_EDID; 214 virtio_gpu_base_generate_edid(VIRTIO_GPU_BASE(g), m->scanout_id, &resp); 215 memcpy(&reply.payload.resp_edid, &resp, sizeof(resp)); 216 vhost_user_gpu_send_msg(g, &reply); 217 break; 218 } 219 case VHOST_USER_GPU_SCANOUT: { 220 VhostUserGpuScanout *m = &msg->payload.scanout; 221 222 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 223 return; 224 } 225 226 g->parent_obj.enable = 1; 227 s = &g->parent_obj.scanout[m->scanout_id]; 228 con = s->con; 229 230 if (m->width == 0) { 231 dpy_gfx_replace_surface(con, NULL); 232 } else { 233 s->ds = qemu_create_displaysurface(m->width, m->height); 234 /* replace surface on next update */ 235 } 236 237 break; 238 } 239 case VHOST_USER_GPU_DMABUF_SCANOUT: { 240 VhostUserGpuDMABUFScanout *m = &msg->payload.dmabuf_scanout; 241 int fd = qemu_chr_fe_get_msgfd(&g->vhost_chr); 242 QemuDmaBuf *dmabuf; 243 244 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 245 error_report("invalid scanout: %d", m->scanout_id); 246 if (fd >= 0) { 247 close(fd); 248 } 249 break; 250 } 251 252 g->parent_obj.enable = 1; 253 con = g->parent_obj.scanout[m->scanout_id].con; 254 dmabuf = &g->dmabuf[m->scanout_id]; 255 if (dmabuf->fd >= 0) { 256 close(dmabuf->fd); 257 dmabuf->fd = -1; 258 } 259 dpy_gl_release_dmabuf(con, dmabuf); 260 if (fd == -1) { 261 dpy_gl_scanout_disable(con); 262 break; 263 } 264 *dmabuf = (QemuDmaBuf) { 265 .fd = fd, 266 .width = m->fd_width, 267 .height = m->fd_height, 268 .stride = m->fd_stride, 269 .fourcc = m->fd_drm_fourcc, 270 .y0_top = m->fd_flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP, 271 }; 272 dpy_gl_scanout_dmabuf(con, dmabuf); 273 break; 274 } 275 case VHOST_USER_GPU_DMABUF_UPDATE: { 276 VhostUserGpuUpdate *m = &msg->payload.update; 277 278 if (m->scanout_id >= g->parent_obj.conf.max_outputs || 279 !g->parent_obj.scanout[m->scanout_id].con) { 280 error_report("invalid scanout update: %d", m->scanout_id); 281 vhost_user_gpu_unblock(g); 282 break; 283 } 284 285 con = g->parent_obj.scanout[m->scanout_id].con; 286 if (!console_has_gl(con)) { 287 error_report("console doesn't support GL!"); 288 vhost_user_gpu_unblock(g); 289 break; 290 } 291 g->backend_blocked = true; 292 dpy_gl_update(con, m->x, m->y, m->width, m->height); 293 break; 294 } 295 case VHOST_USER_GPU_UPDATE: { 296 VhostUserGpuUpdate *m = &msg->payload.update; 297 298 if (m->scanout_id >= g->parent_obj.conf.max_outputs) { 299 break; 300 } 301 s = &g->parent_obj.scanout[m->scanout_id]; 302 con = s->con; 303 pixman_image_t *image = 304 pixman_image_create_bits(PIXMAN_x8r8g8b8, 305 m->width, 306 m->height, 307 (uint32_t *)m->data, 308 m->width * 4); 309 310 pixman_image_composite(PIXMAN_OP_SRC, 311 image, NULL, s->ds->image, 312 0, 0, 0, 0, m->x, m->y, m->width, m->height); 313 314 pixman_image_unref(image); 315 if (qemu_console_surface(con) != s->ds) { 316 dpy_gfx_replace_surface(con, s->ds); 317 } else { 318 dpy_gfx_update(con, m->x, m->y, m->width, m->height); 319 } 320 break; 321 } 322 default: 323 g_warning("unhandled message %d %d", msg->request, msg->size); 324 } 325 326 if (con && qemu_console_is_gl_blocked(con)) { 327 vhost_user_gpu_update_blocked(g, true); 328 } 329 } 330 331 static void 332 vhost_user_gpu_chr_read(void *opaque) 333 { 334 VhostUserGPU *g = opaque; 335 VhostUserGpuMsg *msg = NULL; 336 VhostUserGpuRequest request; 337 uint32_t size, flags; 338 int r; 339 340 r = qemu_chr_fe_read_all(&g->vhost_chr, 341 (uint8_t *)&request, sizeof(uint32_t)); 342 if (r != sizeof(uint32_t)) { 343 error_report("failed to read msg header: %d, %d", r, errno); 344 goto end; 345 } 346 347 r = qemu_chr_fe_read_all(&g->vhost_chr, 348 (uint8_t *)&flags, sizeof(uint32_t)); 349 if (r != sizeof(uint32_t)) { 350 error_report("failed to read msg flags"); 351 goto end; 352 } 353 354 r = qemu_chr_fe_read_all(&g->vhost_chr, 355 (uint8_t *)&size, sizeof(uint32_t)); 356 if (r != sizeof(uint32_t)) { 357 error_report("failed to read msg size"); 358 goto end; 359 } 360 361 msg = g_malloc(VHOST_USER_GPU_HDR_SIZE + size); 362 363 r = qemu_chr_fe_read_all(&g->vhost_chr, 364 (uint8_t *)&msg->payload, size); 365 if (r != size) { 366 error_report("failed to read msg payload %d != %d", r, size); 367 goto end; 368 } 369 370 msg->request = request; 371 msg->flags = size; 372 msg->size = size; 373 374 if (request == VHOST_USER_GPU_CURSOR_UPDATE || 375 request == VHOST_USER_GPU_CURSOR_POS || 376 request == VHOST_USER_GPU_CURSOR_POS_HIDE) { 377 vhost_user_gpu_handle_cursor(g, msg); 378 } else { 379 vhost_user_gpu_handle_display(g, msg); 380 } 381 382 end: 383 g_free(msg); 384 } 385 386 static void 387 vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked) 388 { 389 qemu_set_fd_handler(g->vhost_gpu_fd, 390 blocked ? NULL : vhost_user_gpu_chr_read, NULL, g); 391 } 392 393 static void 394 vhost_user_gpu_gl_flushed(VirtIOGPUBase *b) 395 { 396 VhostUserGPU *g = VHOST_USER_GPU(b); 397 398 if (g->backend_blocked) { 399 vhost_user_gpu_unblock(g); 400 g->backend_blocked = false; 401 } 402 403 vhost_user_gpu_update_blocked(g, false); 404 } 405 406 static bool 407 vhost_user_gpu_do_set_socket(VhostUserGPU *g, Error **errp) 408 { 409 Chardev *chr; 410 int sv[2]; 411 412 if (qemu_socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { 413 error_setg_errno(errp, errno, "socketpair() failed"); 414 return false; 415 } 416 417 chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET)); 418 if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) { 419 error_setg(errp, "Failed to make socket chardev"); 420 goto err; 421 } 422 if (!qemu_chr_fe_init(&g->vhost_chr, chr, errp)) { 423 goto err; 424 } 425 if (vhost_user_gpu_set_socket(&g->vhost->dev, sv[1]) < 0) { 426 error_setg(errp, "Failed to set vhost-user-gpu socket"); 427 qemu_chr_fe_deinit(&g->vhost_chr, false); 428 goto err; 429 } 430 431 g->vhost_gpu_fd = sv[0]; 432 vhost_user_gpu_update_blocked(g, false); 433 close(sv[1]); 434 return true; 435 436 err: 437 close(sv[0]); 438 close(sv[1]); 439 if (chr) { 440 object_unref(OBJECT(chr)); 441 } 442 return false; 443 } 444 445 static void 446 vhost_user_gpu_get_config(VirtIODevice *vdev, uint8_t *config_data) 447 { 448 VhostUserGPU *g = VHOST_USER_GPU(vdev); 449 VirtIOGPUBase *b = VIRTIO_GPU_BASE(vdev); 450 struct virtio_gpu_config *vgconfig = 451 (struct virtio_gpu_config *)config_data; 452 Error *local_err = NULL; 453 int ret; 454 455 memset(config_data, 0, sizeof(struct virtio_gpu_config)); 456 457 ret = vhost_dev_get_config(&g->vhost->dev, 458 config_data, sizeof(struct virtio_gpu_config), 459 &local_err); 460 if (ret) { 461 error_report_err(local_err); 462 return; 463 } 464 465 /* those fields are managed by qemu */ 466 vgconfig->num_scanouts = b->virtio_config.num_scanouts; 467 vgconfig->events_read = b->virtio_config.events_read; 468 vgconfig->events_clear = b->virtio_config.events_clear; 469 } 470 471 static void 472 vhost_user_gpu_set_config(VirtIODevice *vdev, 473 const uint8_t *config_data) 474 { 475 VhostUserGPU *g = VHOST_USER_GPU(vdev); 476 VirtIOGPUBase *b = VIRTIO_GPU_BASE(vdev); 477 const struct virtio_gpu_config *vgconfig = 478 (const struct virtio_gpu_config *)config_data; 479 int ret; 480 481 if (vgconfig->events_clear) { 482 b->virtio_config.events_read &= ~vgconfig->events_clear; 483 } 484 485 ret = vhost_dev_set_config(&g->vhost->dev, config_data, 486 0, sizeof(struct virtio_gpu_config), 487 VHOST_SET_CONFIG_TYPE_FRONTEND); 488 if (ret) { 489 error_report("vhost-user-gpu: set device config space failed"); 490 return; 491 } 492 } 493 494 static void 495 vhost_user_gpu_set_status(VirtIODevice *vdev, uint8_t val) 496 { 497 VhostUserGPU *g = VHOST_USER_GPU(vdev); 498 Error *err = NULL; 499 500 if (val & VIRTIO_CONFIG_S_DRIVER_OK && vdev->vm_running) { 501 if (!vhost_user_gpu_do_set_socket(g, &err)) { 502 error_report_err(err); 503 return; 504 } 505 vhost_user_backend_start(g->vhost); 506 } else { 507 /* unblock any wait and stop processing */ 508 if (g->vhost_gpu_fd != -1) { 509 vhost_user_gpu_update_blocked(g, true); 510 qemu_chr_fe_deinit(&g->vhost_chr, true); 511 g->vhost_gpu_fd = -1; 512 } 513 vhost_user_backend_stop(g->vhost); 514 } 515 } 516 517 static bool 518 vhost_user_gpu_guest_notifier_pending(VirtIODevice *vdev, int idx) 519 { 520 VhostUserGPU *g = VHOST_USER_GPU(vdev); 521 522 /* 523 * Add the check for configure interrupt, Use VIRTIO_CONFIG_IRQ_IDX -1 524 * as the Marco of configure interrupt's IDX, If this driver does not 525 * support, the function will return 526 */ 527 528 if (idx == VIRTIO_CONFIG_IRQ_IDX) { 529 return false; 530 } 531 return vhost_virtqueue_pending(&g->vhost->dev, idx); 532 } 533 534 static void 535 vhost_user_gpu_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask) 536 { 537 VhostUserGPU *g = VHOST_USER_GPU(vdev); 538 539 /* 540 * Add the check for configure interrupt, Use VIRTIO_CONFIG_IRQ_IDX -1 541 * as the Marco of configure interrupt's IDX, If this driver does not 542 * support, the function will return 543 */ 544 545 if (idx == VIRTIO_CONFIG_IRQ_IDX) { 546 return; 547 } 548 vhost_virtqueue_mask(&g->vhost->dev, vdev, idx, mask); 549 } 550 551 static void 552 vhost_user_gpu_instance_init(Object *obj) 553 { 554 VhostUserGPU *g = VHOST_USER_GPU(obj); 555 556 g->vhost = VHOST_USER_BACKEND(object_new(TYPE_VHOST_USER_BACKEND)); 557 object_property_add_alias(obj, "chardev", 558 OBJECT(g->vhost), "chardev"); 559 } 560 561 static void 562 vhost_user_gpu_instance_finalize(Object *obj) 563 { 564 VhostUserGPU *g = VHOST_USER_GPU(obj); 565 566 object_unref(OBJECT(g->vhost)); 567 } 568 569 static void 570 vhost_user_gpu_reset(VirtIODevice *vdev) 571 { 572 VhostUserGPU *g = VHOST_USER_GPU(vdev); 573 574 virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev)); 575 576 vhost_user_backend_stop(g->vhost); 577 } 578 579 static int 580 vhost_user_gpu_config_change(struct vhost_dev *dev) 581 { 582 error_report("vhost-user-gpu: unhandled backend config change"); 583 return -1; 584 } 585 586 static const VhostDevConfigOps config_ops = { 587 .vhost_dev_config_notifier = vhost_user_gpu_config_change, 588 }; 589 590 static void 591 vhost_user_gpu_device_realize(DeviceState *qdev, Error **errp) 592 { 593 VhostUserGPU *g = VHOST_USER_GPU(qdev); 594 VirtIODevice *vdev = VIRTIO_DEVICE(g); 595 596 vhost_dev_set_config_notifier(&g->vhost->dev, &config_ops); 597 if (vhost_user_backend_dev_init(g->vhost, vdev, 2, errp) < 0) { 598 return; 599 } 600 601 /* existing backend may send DMABUF, so let's add that requirement */ 602 g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_DMABUF_ENABLED; 603 if (virtio_has_feature(g->vhost->dev.features, VIRTIO_GPU_F_VIRGL)) { 604 g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_VIRGL_ENABLED; 605 } 606 if (virtio_has_feature(g->vhost->dev.features, VIRTIO_GPU_F_EDID)) { 607 g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_EDID_ENABLED; 608 } else { 609 error_report("EDID requested but the backend doesn't support it."); 610 g->parent_obj.conf.flags &= ~(1 << VIRTIO_GPU_FLAG_EDID_ENABLED); 611 } 612 613 if (!virtio_gpu_base_device_realize(qdev, NULL, NULL, errp)) { 614 return; 615 } 616 617 g->vhost_gpu_fd = -1; 618 } 619 620 static struct vhost_dev *vhost_user_gpu_get_vhost(VirtIODevice *vdev) 621 { 622 VhostUserGPU *g = VHOST_USER_GPU(vdev); 623 return &g->vhost->dev; 624 } 625 626 static Property vhost_user_gpu_properties[] = { 627 VIRTIO_GPU_BASE_PROPERTIES(VhostUserGPU, parent_obj.conf), 628 DEFINE_PROP_END_OF_LIST(), 629 }; 630 631 static void 632 vhost_user_gpu_class_init(ObjectClass *klass, void *data) 633 { 634 DeviceClass *dc = DEVICE_CLASS(klass); 635 VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); 636 VirtIOGPUBaseClass *vgc = VIRTIO_GPU_BASE_CLASS(klass); 637 638 vgc->gl_flushed = vhost_user_gpu_gl_flushed; 639 640 vdc->realize = vhost_user_gpu_device_realize; 641 vdc->reset = vhost_user_gpu_reset; 642 vdc->set_status = vhost_user_gpu_set_status; 643 vdc->guest_notifier_mask = vhost_user_gpu_guest_notifier_mask; 644 vdc->guest_notifier_pending = vhost_user_gpu_guest_notifier_pending; 645 vdc->get_config = vhost_user_gpu_get_config; 646 vdc->set_config = vhost_user_gpu_set_config; 647 vdc->get_vhost = vhost_user_gpu_get_vhost; 648 649 device_class_set_props(dc, vhost_user_gpu_properties); 650 } 651 652 static const TypeInfo vhost_user_gpu_info = { 653 .name = TYPE_VHOST_USER_GPU, 654 .parent = TYPE_VIRTIO_GPU_BASE, 655 .instance_size = sizeof(VhostUserGPU), 656 .instance_init = vhost_user_gpu_instance_init, 657 .instance_finalize = vhost_user_gpu_instance_finalize, 658 .class_init = vhost_user_gpu_class_init, 659 }; 660 module_obj(TYPE_VHOST_USER_GPU); 661 module_kconfig(VHOST_USER_GPU); 662 663 static void vhost_user_gpu_register_types(void) 664 { 665 type_register_static(&vhost_user_gpu_info); 666 } 667 668 type_init(vhost_user_gpu_register_types) 669