| /* | |
| * Virtio vhost-user GPU Device | |
| * | |
| * Copyright Red Hat, Inc. 2013-2018 | |
| * | |
| * Authors: | |
| * Dave Airlie <airlied@redhat.com> | |
| * Gerd Hoffmann <kraxel@redhat.com> | |
| * Marc-André Lureau <marcandre.lureau@redhat.com> | |
| * | |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
| * See the COPYING file in the top-level directory. | |
| */ | |
| #include "qemu/osdep.h" | |
| #include "qemu/drm.h" | |
| #include "qapi/error.h" | |
| #include "qemu/sockets.h" | |
| #include <pixman.h> | |
| #include <glib-unix.h> | |
| #include "vugpu.h" | |
| #include "hw/virtio/virtio-gpu-bswap.h" | |
| #include "hw/virtio/virtio-gpu-pixman.h" | |
| #include "virgl.h" | |
| #include "vugbm.h" | |
| enum { | |
| VHOST_USER_GPU_MAX_QUEUES = 2, | |
| }; | |
| struct virtio_gpu_simple_resource { | |
| uint32_t resource_id; | |
| uint32_t width; | |
| uint32_t height; | |
| uint32_t format; | |
| struct iovec *iov; | |
| unsigned int iov_cnt; | |
| pixman_image_t *image; | |
| struct vugbm_buffer buffer; | |
| QTAILQ_ENTRY(virtio_gpu_simple_resource) next; | |
| }; | |
| static gboolean opt_print_caps; | |
| static int opt_fdnum = -1; | |
| static char *opt_socket_path; | |
| static char *opt_render_node; | |
| static gboolean opt_virgl; | |
| static void vg_handle_ctrl(VuDev *dev, int qidx); | |
| static void vg_cleanup_mapping(VuGpu *g, | |
| struct virtio_gpu_simple_resource *res); | |
| static const char * | |
| vg_cmd_to_string(int cmd) | |
| { | |
| #define CMD(cmd) [cmd] = #cmd | |
| static const char *vg_cmd_str[] = { | |
| CMD(VIRTIO_GPU_UNDEFINED), | |
| /* 2d commands */ | |
| CMD(VIRTIO_GPU_CMD_GET_DISPLAY_INFO), | |
| CMD(VIRTIO_GPU_CMD_RESOURCE_CREATE_2D), | |
| CMD(VIRTIO_GPU_CMD_RESOURCE_UNREF), | |
| CMD(VIRTIO_GPU_CMD_SET_SCANOUT), | |
| CMD(VIRTIO_GPU_CMD_RESOURCE_FLUSH), | |
| CMD(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D), | |
| CMD(VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING), | |
| CMD(VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING), | |
| CMD(VIRTIO_GPU_CMD_GET_CAPSET_INFO), | |
| CMD(VIRTIO_GPU_CMD_GET_CAPSET), | |
| /* 3d commands */ | |
| CMD(VIRTIO_GPU_CMD_CTX_CREATE), | |
| CMD(VIRTIO_GPU_CMD_CTX_DESTROY), | |
| CMD(VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE), | |
| CMD(VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE), | |
| CMD(VIRTIO_GPU_CMD_RESOURCE_CREATE_3D), | |
| CMD(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D), | |
| CMD(VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D), | |
| CMD(VIRTIO_GPU_CMD_SUBMIT_3D), | |
| /* cursor commands */ | |
| CMD(VIRTIO_GPU_CMD_UPDATE_CURSOR), | |
| CMD(VIRTIO_GPU_CMD_MOVE_CURSOR), | |
| }; | |
| #undef REQ | |
| if (cmd >= 0 && cmd < G_N_ELEMENTS(vg_cmd_str)) { | |
| return vg_cmd_str[cmd]; | |
| } else { | |
| return "unknown"; | |
| } | |
| } | |
| static int | |
| vg_sock_fd_read(int sock, void *buf, ssize_t buflen) | |
| { | |
| int ret; | |
| do { | |
| ret = read(sock, buf, buflen); | |
| } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); | |
| g_warn_if_fail(ret == buflen); | |
| return ret; | |
| } | |
| static void | |
| vg_sock_fd_close(VuGpu *g) | |
| { | |
| if (g->sock_fd >= 0) { | |
| close(g->sock_fd); | |
| g->sock_fd = -1; | |
| } | |
| } | |
| static gboolean | |
| source_wait_cb(gint fd, GIOCondition condition, gpointer user_data) | |
| { | |
| VuGpu *g = user_data; | |
| if (!vg_recv_msg(g, VHOST_USER_GPU_DMABUF_UPDATE, 0, NULL)) { | |
| return G_SOURCE_CONTINUE; | |
| } | |
| /* resume */ | |
| g->wait_in = 0; | |
| vg_handle_ctrl(&g->dev.parent, 0); | |
| return G_SOURCE_REMOVE; | |
| } | |
| void | |
| vg_wait_ok(VuGpu *g) | |
| { | |
| assert(g->wait_in == 0); | |
| g->wait_in = g_unix_fd_add(g->sock_fd, G_IO_IN | G_IO_HUP, | |
| source_wait_cb, g); | |
| } | |
| static int | |
| vg_sock_fd_write(int sock, const void *buf, ssize_t buflen, int fd) | |
| { | |
| ssize_t ret; | |
| struct iovec iov = { | |
| .iov_base = (void *)buf, | |
| .iov_len = buflen, | |
| }; | |
| struct msghdr msg = { | |
| .msg_iov = &iov, | |
| .msg_iovlen = 1, | |
| }; | |
| union { | |
| struct cmsghdr cmsghdr; | |
| char control[CMSG_SPACE(sizeof(int))]; | |
| } cmsgu; | |
| struct cmsghdr *cmsg; | |
| if (fd != -1) { | |
| msg.msg_control = cmsgu.control; | |
| msg.msg_controllen = sizeof(cmsgu.control); | |
| cmsg = CMSG_FIRSTHDR(&msg); | |
| cmsg->cmsg_len = CMSG_LEN(sizeof(int)); | |
| cmsg->cmsg_level = SOL_SOCKET; | |
| cmsg->cmsg_type = SCM_RIGHTS; | |
| *((int *)CMSG_DATA(cmsg)) = fd; | |
| } | |
| do { | |
| ret = sendmsg(sock, &msg, 0); | |
| } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); | |
| g_warn_if_fail(ret == buflen); | |
| return ret; | |
| } | |
| void | |
| vg_send_msg(VuGpu *vg, const VhostUserGpuMsg *msg, int fd) | |
| { | |
| if (vg_sock_fd_write(vg->sock_fd, msg, | |
| VHOST_USER_GPU_HDR_SIZE + msg->size, fd) < 0) { | |
| vg_sock_fd_close(vg); | |
| } | |
| } | |
| bool | |
| vg_recv_msg(VuGpu *g, uint32_t expect_req, uint32_t expect_size, | |
| gpointer payload) | |
| { | |
| uint32_t req, flags, size; | |
| if (vg_sock_fd_read(g->sock_fd, &req, sizeof(req)) < 0 || | |
| vg_sock_fd_read(g->sock_fd, &flags, sizeof(flags)) < 0 || | |
| vg_sock_fd_read(g->sock_fd, &size, sizeof(size)) < 0) { | |
| goto err; | |
| } | |
| g_return_val_if_fail(req == expect_req, false); | |
| g_return_val_if_fail(flags & VHOST_USER_GPU_MSG_FLAG_REPLY, false); | |
| g_return_val_if_fail(size == expect_size, false); | |
| if (size && vg_sock_fd_read(g->sock_fd, payload, size) != size) { | |
| goto err; | |
| } | |
| return true; | |
| err: | |
| vg_sock_fd_close(g); | |
| return false; | |
| } | |
| static struct virtio_gpu_simple_resource * | |
| virtio_gpu_find_resource(VuGpu *g, uint32_t resource_id) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| QTAILQ_FOREACH(res, &g->reslist, next) { | |
| if (res->resource_id == resource_id) { | |
| return res; | |
| } | |
| } | |
| return NULL; | |
| } | |
| void | |
| vg_ctrl_response(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd, | |
| struct virtio_gpu_ctrl_hdr *resp, | |
| size_t resp_len) | |
| { | |
| size_t s; | |
| if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE) { | |
| resp->flags |= VIRTIO_GPU_FLAG_FENCE; | |
| resp->fence_id = cmd->cmd_hdr.fence_id; | |
| resp->ctx_id = cmd->cmd_hdr.ctx_id; | |
| } | |
| virtio_gpu_ctrl_hdr_bswap(resp); | |
| s = iov_from_buf(cmd->elem.in_sg, cmd->elem.in_num, 0, resp, resp_len); | |
| if (s != resp_len) { | |
| g_critical("%s: response size incorrect %zu vs %zu", | |
| __func__, s, resp_len); | |
| } | |
| vu_queue_push(&g->dev.parent, cmd->vq, &cmd->elem, s); | |
| vu_queue_notify(&g->dev.parent, cmd->vq); | |
| cmd->state = VG_CMD_STATE_FINISHED; | |
| } | |
| void | |
| vg_ctrl_response_nodata(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd, | |
| enum virtio_gpu_ctrl_type type) | |
| { | |
| struct virtio_gpu_ctrl_hdr resp = { | |
| .type = type, | |
| }; | |
| vg_ctrl_response(g, cmd, &resp, sizeof(resp)); | |
| } | |
| static gboolean | |
| get_display_info_cb(gint fd, GIOCondition condition, gpointer user_data) | |
| { | |
| struct virtio_gpu_resp_display_info dpy_info = { {} }; | |
| VuGpu *vg = user_data; | |
| struct virtio_gpu_ctrl_command *cmd = QTAILQ_LAST(&vg->fenceq); | |
| g_debug("disp info cb"); | |
| assert(cmd->cmd_hdr.type == VIRTIO_GPU_CMD_GET_DISPLAY_INFO); | |
| if (!vg_recv_msg(vg, VHOST_USER_GPU_GET_DISPLAY_INFO, | |
| sizeof(dpy_info), &dpy_info)) { | |
| return G_SOURCE_CONTINUE; | |
| } | |
| QTAILQ_REMOVE(&vg->fenceq, cmd, next); | |
| vg_ctrl_response(vg, cmd, &dpy_info.hdr, sizeof(dpy_info)); | |
| vg->wait_in = 0; | |
| vg_handle_ctrl(&vg->dev.parent, 0); | |
| return G_SOURCE_REMOVE; | |
| } | |
| void | |
| vg_get_display_info(VuGpu *vg, struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_GET_DISPLAY_INFO, | |
| .size = 0, | |
| }; | |
| assert(vg->wait_in == 0); | |
| vg_send_msg(vg, &msg, -1); | |
| vg->wait_in = g_unix_fd_add(vg->sock_fd, G_IO_IN | G_IO_HUP, | |
| get_display_info_cb, vg); | |
| cmd->state = VG_CMD_STATE_PENDING; | |
| } | |
| static gboolean | |
| get_edid_cb(gint fd, GIOCondition condition, gpointer user_data) | |
| { | |
| struct virtio_gpu_resp_edid resp_edid; | |
| VuGpu *vg = user_data; | |
| struct virtio_gpu_ctrl_command *cmd = QTAILQ_LAST(&vg->fenceq); | |
| g_debug("get edid cb"); | |
| assert(cmd->cmd_hdr.type == VIRTIO_GPU_CMD_GET_EDID); | |
| if (!vg_recv_msg(vg, VHOST_USER_GPU_GET_EDID, | |
| sizeof(resp_edid), &resp_edid)) { | |
| return G_SOURCE_CONTINUE; | |
| } | |
| QTAILQ_REMOVE(&vg->fenceq, cmd, next); | |
| vg_ctrl_response(vg, cmd, &resp_edid.hdr, sizeof(resp_edid)); | |
| vg->wait_in = 0; | |
| vg_handle_ctrl(&vg->dev.parent, 0); | |
| return G_SOURCE_REMOVE; | |
| } | |
| void | |
| vg_get_edid(VuGpu *vg, struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_cmd_get_edid get_edid; | |
| VUGPU_FILL_CMD(get_edid); | |
| virtio_gpu_bswap_32(&get_edid, sizeof(get_edid)); | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_GET_EDID, | |
| .size = sizeof(VhostUserGpuEdidRequest), | |
| .payload.edid_req = { | |
| .scanout_id = get_edid.scanout, | |
| }, | |
| }; | |
| assert(vg->wait_in == 0); | |
| vg_send_msg(vg, &msg, -1); | |
| vg->wait_in = g_unix_fd_add(vg->sock_fd, G_IO_IN | G_IO_HUP, | |
| get_edid_cb, vg); | |
| cmd->state = VG_CMD_STATE_PENDING; | |
| } | |
| static void | |
| vg_resource_create_2d(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| pixman_format_code_t pformat; | |
| struct virtio_gpu_simple_resource *res; | |
| struct virtio_gpu_resource_create_2d c2d; | |
| VUGPU_FILL_CMD(c2d); | |
| virtio_gpu_bswap_32(&c2d, sizeof(c2d)); | |
| if (c2d.resource_id == 0) { | |
| g_critical("%s: resource id 0 is not allowed", __func__); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| res = virtio_gpu_find_resource(g, c2d.resource_id); | |
| if (res) { | |
| g_critical("%s: resource already exists %d", __func__, c2d.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| res = g_new0(struct virtio_gpu_simple_resource, 1); | |
| res->width = c2d.width; | |
| res->height = c2d.height; | |
| res->format = c2d.format; | |
| res->resource_id = c2d.resource_id; | |
| pformat = virtio_gpu_get_pixman_format(c2d.format); | |
| if (!pformat) { | |
| g_critical("%s: host couldn't handle guest format %d", | |
| __func__, c2d.format); | |
| g_free(res); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; | |
| return; | |
| } | |
| if (!vugbm_buffer_create(&res->buffer, &g->gdev, c2d.width, c2d.height)) { | |
| g_critical("%s: buffer creation failed %d %d %d", | |
| __func__, c2d.resource_id, c2d.width, c2d.height); | |
| g_free(res); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; | |
| return; | |
| } | |
| res->image = pixman_image_create_bits(pformat, | |
| c2d.width, | |
| c2d.height, | |
| (uint32_t *)res->buffer.mmap, | |
| res->buffer.stride); | |
| if (!res->image) { | |
| g_critical("%s: resource creation failed %d %d %d", | |
| __func__, c2d.resource_id, c2d.width, c2d.height); | |
| vugbm_buffer_destroy(&res->buffer); | |
| g_free(res); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; | |
| return; | |
| } | |
| QTAILQ_INSERT_HEAD(&g->reslist, res, next); | |
| } | |
| static void | |
| vg_disable_scanout(VuGpu *g, int scanout_id) | |
| { | |
| struct virtio_gpu_scanout *scanout = &g->scanout[scanout_id]; | |
| if (scanout->resource_id == 0) { | |
| return; | |
| } | |
| scanout->resource_id = 0; | |
| scanout->width = 0; | |
| scanout->height = 0; | |
| if (g->sock_fd >= 0) { | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_SCANOUT, | |
| .size = sizeof(VhostUserGpuScanout), | |
| .payload.scanout.scanout_id = scanout_id, | |
| }; | |
| vg_send_msg(g, &msg, -1); | |
| } | |
| } | |
| static void | |
| vg_resource_destroy(VuGpu *g, | |
| struct virtio_gpu_simple_resource *res) | |
| { | |
| int i; | |
| for (i = 0; i < VIRTIO_GPU_MAX_SCANOUTS; i++) { | |
| if (g->scanout[i].resource_id == res->resource_id) { | |
| vg_disable_scanout(g, i); | |
| } | |
| } | |
| vugbm_buffer_destroy(&res->buffer); | |
| vg_cleanup_mapping(g, res); | |
| pixman_image_unref(res->image); | |
| QTAILQ_REMOVE(&g->reslist, res, next); | |
| g_free(res); | |
| } | |
| static void | |
| vg_resource_unref(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| struct virtio_gpu_resource_unref unref; | |
| VUGPU_FILL_CMD(unref); | |
| virtio_gpu_bswap_32(&unref, sizeof(unref)); | |
| res = virtio_gpu_find_resource(g, unref.resource_id); | |
| if (!res) { | |
| g_critical("%s: illegal resource specified %d", | |
| __func__, unref.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| vg_resource_destroy(g, res); | |
| } | |
| int | |
| vg_create_mapping_iov(VuGpu *g, | |
| struct virtio_gpu_resource_attach_backing *ab, | |
| struct virtio_gpu_ctrl_command *cmd, | |
| struct iovec **iov) | |
| { | |
| g_autofree struct virtio_gpu_mem_entry *ents = NULL; | |
| size_t esize, s; | |
| int i; | |
| if (ab->nr_entries > 16384) { | |
| g_critical("%s: nr_entries is too big (%d > 16384)", | |
| __func__, ab->nr_entries); | |
| return -1; | |
| } | |
| esize = sizeof(*ents) * ab->nr_entries; | |
| ents = g_try_malloc(esize); | |
| if (!ents && esize) { | |
| return -1; | |
| } | |
| s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, | |
| sizeof(*ab), ents, esize); | |
| if (s != esize) { | |
| g_critical("%s: command data size incorrect %zu vs %zu", | |
| __func__, s, esize); | |
| return -1; | |
| } | |
| *iov = g_try_new0(struct iovec, ab->nr_entries); | |
| if (!*iov && ab->nr_entries) { | |
| return -1; | |
| } | |
| for (i = 0; i < ab->nr_entries; i++) { | |
| uint64_t len = ents[i].length; | |
| (*iov)[i].iov_len = ents[i].length; | |
| (*iov)[i].iov_base = vu_gpa_to_va(&g->dev.parent, &len, ents[i].addr); | |
| if (!(*iov)[i].iov_base || len != ents[i].length) { | |
| g_critical("%s: resource %d element %d", | |
| __func__, ab->resource_id, i); | |
| g_free(*iov); | |
| *iov = NULL; | |
| return -1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void | |
| vg_resource_attach_backing(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| struct virtio_gpu_resource_attach_backing ab; | |
| int ret; | |
| VUGPU_FILL_CMD(ab); | |
| virtio_gpu_bswap_32(&ab, sizeof(ab)); | |
| res = virtio_gpu_find_resource(g, ab.resource_id); | |
| if (!res) { | |
| g_critical("%s: illegal resource specified %d", | |
| __func__, ab.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| if (res->iov) { | |
| cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; | |
| return; | |
| } | |
| ret = vg_create_mapping_iov(g, &ab, cmd, &res->iov); | |
| if (ret != 0) { | |
| cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; | |
| return; | |
| } | |
| res->iov_cnt = ab.nr_entries; | |
| } | |
| /* Though currently only free iov, maybe later will do more work. */ | |
| void vg_cleanup_mapping_iov(VuGpu *g, | |
| struct iovec *iov, uint32_t count) | |
| { | |
| g_free(iov); | |
| } | |
| static void | |
| vg_cleanup_mapping(VuGpu *g, | |
| struct virtio_gpu_simple_resource *res) | |
| { | |
| vg_cleanup_mapping_iov(g, res->iov, res->iov_cnt); | |
| res->iov = NULL; | |
| res->iov_cnt = 0; | |
| } | |
| static void | |
| vg_resource_detach_backing(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| struct virtio_gpu_resource_detach_backing detach; | |
| VUGPU_FILL_CMD(detach); | |
| virtio_gpu_bswap_32(&detach, sizeof(detach)); | |
| res = virtio_gpu_find_resource(g, detach.resource_id); | |
| if (!res || !res->iov) { | |
| g_critical("%s: illegal resource specified %d", | |
| __func__, detach.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| vg_cleanup_mapping(g, res); | |
| } | |
| static void | |
| vg_transfer_to_host_2d(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| int h; | |
| uint32_t src_offset, dst_offset, stride; | |
| int bpp; | |
| pixman_format_code_t format; | |
| struct virtio_gpu_transfer_to_host_2d t2d; | |
| VUGPU_FILL_CMD(t2d); | |
| virtio_gpu_t2d_bswap(&t2d); | |
| res = virtio_gpu_find_resource(g, t2d.resource_id); | |
| if (!res || !res->iov) { | |
| g_critical("%s: illegal resource specified %d", | |
| __func__, t2d.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| if (t2d.r.x > res->width || | |
| t2d.r.y > res->height || | |
| t2d.r.width > res->width || | |
| t2d.r.height > res->height || | |
| t2d.r.x + t2d.r.width > res->width || | |
| t2d.r.y + t2d.r.height > res->height) { | |
| g_critical("%s: transfer bounds outside resource" | |
| " bounds for resource %d: %d %d %d %d vs %d %d", | |
| __func__, t2d.resource_id, t2d.r.x, t2d.r.y, | |
| t2d.r.width, t2d.r.height, res->width, res->height); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; | |
| return; | |
| } | |
| format = pixman_image_get_format(res->image); | |
| bpp = (PIXMAN_FORMAT_BPP(format) + 7) / 8; | |
| stride = pixman_image_get_stride(res->image); | |
| if (t2d.offset || t2d.r.x || t2d.r.y || | |
| t2d.r.width != pixman_image_get_width(res->image)) { | |
| void *img_data = pixman_image_get_data(res->image); | |
| for (h = 0; h < t2d.r.height; h++) { | |
| src_offset = t2d.offset + stride * h; | |
| dst_offset = (t2d.r.y + h) * stride + (t2d.r.x * bpp); | |
| iov_to_buf(res->iov, res->iov_cnt, src_offset, | |
| img_data | |
| + dst_offset, t2d.r.width * bpp); | |
| } | |
| } else { | |
| iov_to_buf(res->iov, res->iov_cnt, 0, | |
| pixman_image_get_data(res->image), | |
| pixman_image_get_stride(res->image) | |
| * pixman_image_get_height(res->image)); | |
| } | |
| } | |
| static void | |
| vg_set_scanout(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| struct virtio_gpu_scanout *scanout; | |
| struct virtio_gpu_set_scanout ss; | |
| int fd; | |
| VUGPU_FILL_CMD(ss); | |
| virtio_gpu_bswap_32(&ss, sizeof(ss)); | |
| if (ss.scanout_id >= VIRTIO_GPU_MAX_SCANOUTS) { | |
| g_critical("%s: illegal scanout id specified %d", | |
| __func__, ss.scanout_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID; | |
| return; | |
| } | |
| if (ss.resource_id == 0) { | |
| vg_disable_scanout(g, ss.scanout_id); | |
| return; | |
| } | |
| /* create a surface for this scanout */ | |
| res = virtio_gpu_find_resource(g, ss.resource_id); | |
| if (!res) { | |
| g_critical("%s: illegal resource specified %d", | |
| __func__, ss.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| if (ss.r.x > res->width || | |
| ss.r.y > res->height || | |
| ss.r.width > res->width || | |
| ss.r.height > res->height || | |
| ss.r.x + ss.r.width > res->width || | |
| ss.r.y + ss.r.height > res->height) { | |
| g_critical("%s: illegal scanout %d bounds for" | |
| " resource %d, (%d,%d)+%d,%d vs %d %d", | |
| __func__, ss.scanout_id, ss.resource_id, ss.r.x, ss.r.y, | |
| ss.r.width, ss.r.height, res->width, res->height); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; | |
| return; | |
| } | |
| scanout = &g->scanout[ss.scanout_id]; | |
| scanout->resource_id = ss.resource_id; | |
| scanout->x = ss.r.x; | |
| scanout->y = ss.r.y; | |
| scanout->width = ss.r.width; | |
| scanout->height = ss.r.height; | |
| struct vugbm_buffer *buffer = &res->buffer; | |
| if (vugbm_buffer_can_get_dmabuf_fd(buffer)) { | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_DMABUF_SCANOUT, | |
| .size = sizeof(VhostUserGpuDMABUFScanout), | |
| .payload.dmabuf_scanout = (VhostUserGpuDMABUFScanout) { | |
| .scanout_id = ss.scanout_id, | |
| .x = ss.r.x, | |
| .y = ss.r.y, | |
| .width = ss.r.width, | |
| .height = ss.r.height, | |
| .fd_width = buffer->width, | |
| .fd_height = buffer->height, | |
| .fd_stride = buffer->stride, | |
| .fd_drm_fourcc = buffer->format | |
| } | |
| }; | |
| if (vugbm_buffer_get_dmabuf_fd(buffer, &fd)) { | |
| vg_send_msg(g, &msg, fd); | |
| close(fd); | |
| } | |
| } else { | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_SCANOUT, | |
| .size = sizeof(VhostUserGpuScanout), | |
| .payload.scanout = (VhostUserGpuScanout) { | |
| .scanout_id = ss.scanout_id, | |
| .width = scanout->width, | |
| .height = scanout->height | |
| } | |
| }; | |
| vg_send_msg(g, &msg, -1); | |
| } | |
| } | |
| static void | |
| vg_resource_flush(VuGpu *g, | |
| struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| struct virtio_gpu_resource_flush rf; | |
| pixman_region16_t flush_region; | |
| int i; | |
| VUGPU_FILL_CMD(rf); | |
| virtio_gpu_bswap_32(&rf, sizeof(rf)); | |
| res = virtio_gpu_find_resource(g, rf.resource_id); | |
| if (!res) { | |
| g_critical("%s: illegal resource specified %d\n", | |
| __func__, rf.resource_id); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID; | |
| return; | |
| } | |
| if (rf.r.x > res->width || | |
| rf.r.y > res->height || | |
| rf.r.width > res->width || | |
| rf.r.height > res->height || | |
| rf.r.x + rf.r.width > res->width || | |
| rf.r.y + rf.r.height > res->height) { | |
| g_critical("%s: flush bounds outside resource" | |
| " bounds for resource %d: %d %d %d %d vs %d %d\n", | |
| __func__, rf.resource_id, rf.r.x, rf.r.y, | |
| rf.r.width, rf.r.height, res->width, res->height); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; | |
| return; | |
| } | |
| pixman_region_init_rect(&flush_region, | |
| rf.r.x, rf.r.y, rf.r.width, rf.r.height); | |
| for (i = 0; i < VIRTIO_GPU_MAX_SCANOUTS; i++) { | |
| struct virtio_gpu_scanout *scanout; | |
| pixman_region16_t region, finalregion; | |
| pixman_box16_t *extents; | |
| scanout = &g->scanout[i]; | |
| if (scanout->resource_id != res->resource_id) { | |
| continue; | |
| } | |
| pixman_region_init(&finalregion); | |
| pixman_region_init_rect(®ion, scanout->x, scanout->y, | |
| scanout->width, scanout->height); | |
| pixman_region_intersect(&finalregion, &flush_region, ®ion); | |
| extents = pixman_region_extents(&finalregion); | |
| size_t width = extents->x2 - extents->x1; | |
| size_t height = extents->y2 - extents->y1; | |
| if (vugbm_buffer_can_get_dmabuf_fd(&res->buffer)) { | |
| VhostUserGpuMsg vmsg = { | |
| .request = VHOST_USER_GPU_DMABUF_UPDATE, | |
| .size = sizeof(VhostUserGpuUpdate), | |
| .payload.update = (VhostUserGpuUpdate) { | |
| .scanout_id = i, | |
| .x = extents->x1, | |
| .y = extents->y1, | |
| .width = width, | |
| .height = height, | |
| } | |
| }; | |
| vg_send_msg(g, &vmsg, -1); | |
| vg_wait_ok(g); | |
| } else { | |
| size_t bpp = | |
| PIXMAN_FORMAT_BPP(pixman_image_get_format(res->image)) / 8; | |
| size_t size = width * height * bpp; | |
| void *p = g_try_malloc(VHOST_USER_GPU_HDR_SIZE + | |
| sizeof(VhostUserGpuUpdate) + size); | |
| if (!p) { | |
| pixman_region_fini(®ion); | |
| pixman_region_fini(&finalregion); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; | |
| break; | |
| } | |
| VhostUserGpuMsg *msg = p; | |
| msg->request = VHOST_USER_GPU_UPDATE; | |
| msg->size = sizeof(VhostUserGpuUpdate) + size; | |
| msg->payload.update = (VhostUserGpuUpdate) { | |
| .scanout_id = i, | |
| .x = extents->x1, | |
| .y = extents->y1, | |
| .width = width, | |
| .height = height, | |
| }; | |
| pixman_image_t *img = | |
| pixman_image_create_bits(pixman_image_get_format(res->image), | |
| msg->payload.update.width, | |
| msg->payload.update.height, | |
| p + offsetof(VhostUserGpuMsg, | |
| payload.update.data), | |
| width * bpp); | |
| pixman_image_composite(PIXMAN_OP_SRC, | |
| res->image, NULL, img, | |
| extents->x1, extents->y1, | |
| 0, 0, 0, 0, | |
| width, height); | |
| pixman_image_unref(img); | |
| vg_send_msg(g, msg, -1); | |
| g_free(msg); | |
| } | |
| pixman_region_fini(®ion); | |
| pixman_region_fini(&finalregion); | |
| } | |
| pixman_region_fini(&flush_region); | |
| } | |
| static void | |
| vg_process_cmd(VuGpu *vg, struct virtio_gpu_ctrl_command *cmd) | |
| { | |
| switch (cmd->cmd_hdr.type) { | |
| case VIRTIO_GPU_CMD_GET_DISPLAY_INFO: | |
| vg_get_display_info(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: | |
| vg_resource_create_2d(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_RESOURCE_UNREF: | |
| vg_resource_unref(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_RESOURCE_FLUSH: | |
| vg_resource_flush(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: | |
| vg_transfer_to_host_2d(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_SET_SCANOUT: | |
| vg_set_scanout(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: | |
| vg_resource_attach_backing(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING: | |
| vg_resource_detach_backing(vg, cmd); | |
| break; | |
| case VIRTIO_GPU_CMD_GET_EDID: | |
| vg_get_edid(vg, cmd); | |
| break; | |
| default: | |
| g_warning("TODO handle ctrl %x\n", cmd->cmd_hdr.type); | |
| cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC; | |
| break; | |
| } | |
| if (cmd->state == VG_CMD_STATE_NEW) { | |
| vg_ctrl_response_nodata(vg, cmd, cmd->error ? cmd->error : | |
| VIRTIO_GPU_RESP_OK_NODATA); | |
| } | |
| } | |
| static void | |
| vg_handle_ctrl(VuDev *dev, int qidx) | |
| { | |
| VuGpu *vg = container_of(dev, VuGpu, dev.parent); | |
| VuVirtq *vq = vu_get_queue(dev, qidx); | |
| struct virtio_gpu_ctrl_command *cmd = NULL; | |
| size_t len; | |
| for (;;) { | |
| if (vg->wait_in != 0) { | |
| return; | |
| } | |
| cmd = vu_queue_pop(dev, vq, sizeof(struct virtio_gpu_ctrl_command)); | |
| if (!cmd) { | |
| break; | |
| } | |
| cmd->vq = vq; | |
| cmd->error = 0; | |
| cmd->state = VG_CMD_STATE_NEW; | |
| len = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, | |
| 0, &cmd->cmd_hdr, sizeof(cmd->cmd_hdr)); | |
| if (len != sizeof(cmd->cmd_hdr)) { | |
| g_warning("%s: command size incorrect %zu vs %zu\n", | |
| __func__, len, sizeof(cmd->cmd_hdr)); | |
| memset(&cmd->cmd_hdr, 0, sizeof(cmd->cmd_hdr)); | |
| vg_ctrl_response_nodata( | |
| vg, cmd, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER); | |
| } else { | |
| virtio_gpu_ctrl_hdr_bswap(&cmd->cmd_hdr); | |
| g_debug("%d %s\n", cmd->cmd_hdr.type, | |
| vg_cmd_to_string(cmd->cmd_hdr.type)); | |
| if (vg->virgl) { | |
| vg_virgl_process_cmd(vg, cmd); | |
| } else { | |
| vg_process_cmd(vg, cmd); | |
| } | |
| } | |
| if (cmd->state != VG_CMD_STATE_FINISHED) { | |
| QTAILQ_INSERT_TAIL(&vg->fenceq, cmd, next); | |
| vg->inflight++; | |
| } else { | |
| free(cmd); | |
| } | |
| } | |
| } | |
| static void | |
| update_cursor_data_simple(VuGpu *g, uint32_t resource_id, gpointer data) | |
| { | |
| struct virtio_gpu_simple_resource *res; | |
| res = virtio_gpu_find_resource(g, resource_id); | |
| g_return_if_fail(res != NULL); | |
| g_return_if_fail(pixman_image_get_width(res->image) == 64); | |
| g_return_if_fail(pixman_image_get_height(res->image) == 64); | |
| g_return_if_fail( | |
| PIXMAN_FORMAT_BPP(pixman_image_get_format(res->image)) == 32); | |
| memcpy(data, pixman_image_get_data(res->image), 64 * 64 * sizeof(uint32_t)); | |
| } | |
| static void | |
| vg_process_cursor_cmd(VuGpu *g, struct virtio_gpu_update_cursor *cursor) | |
| { | |
| switch (cursor->hdr.type) { | |
| case VIRTIO_GPU_CMD_MOVE_CURSOR: { | |
| VhostUserGpuMsg msg = { | |
| .request = cursor->resource_id ? | |
| VHOST_USER_GPU_CURSOR_POS : VHOST_USER_GPU_CURSOR_POS_HIDE, | |
| .size = sizeof(VhostUserGpuCursorPos), | |
| .payload.cursor_pos = { | |
| .scanout_id = cursor->pos.scanout_id, | |
| .x = cursor->pos.x, | |
| .y = cursor->pos.y, | |
| } | |
| }; | |
| g_debug("%s: move", G_STRFUNC); | |
| vg_send_msg(g, &msg, -1); | |
| break; | |
| } | |
| case VIRTIO_GPU_CMD_UPDATE_CURSOR: { | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_CURSOR_UPDATE, | |
| .size = sizeof(VhostUserGpuCursorUpdate), | |
| .payload.cursor_update = { | |
| .pos = { | |
| .scanout_id = cursor->pos.scanout_id, | |
| .x = cursor->pos.x, | |
| .y = cursor->pos.y, | |
| }, | |
| .hot_x = cursor->hot_x, | |
| .hot_y = cursor->hot_y, | |
| } | |
| }; | |
| g_debug("%s: update", G_STRFUNC); | |
| if (g->virgl) { | |
| vg_virgl_update_cursor_data(g, cursor->resource_id, | |
| msg.payload.cursor_update.data); | |
| } else { | |
| update_cursor_data_simple(g, cursor->resource_id, | |
| msg.payload.cursor_update.data); | |
| } | |
| vg_send_msg(g, &msg, -1); | |
| break; | |
| } | |
| default: | |
| g_debug("%s: unknown cmd %d", G_STRFUNC, cursor->hdr.type); | |
| break; | |
| } | |
| } | |
| static void | |
| vg_handle_cursor(VuDev *dev, int qidx) | |
| { | |
| VuGpu *g = container_of(dev, VuGpu, dev.parent); | |
| VuVirtq *vq = vu_get_queue(dev, qidx); | |
| VuVirtqElement *elem; | |
| size_t len; | |
| struct virtio_gpu_update_cursor cursor; | |
| for (;;) { | |
| elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); | |
| if (!elem) { | |
| break; | |
| } | |
| g_debug("cursor out:%d in:%d\n", elem->out_num, elem->in_num); | |
| len = iov_to_buf(elem->out_sg, elem->out_num, | |
| 0, &cursor, sizeof(cursor)); | |
| if (len != sizeof(cursor)) { | |
| g_warning("%s: cursor size incorrect %zu vs %zu\n", | |
| __func__, len, sizeof(cursor)); | |
| } else { | |
| virtio_gpu_bswap_32(&cursor, sizeof(cursor)); | |
| vg_process_cursor_cmd(g, &cursor); | |
| } | |
| vu_queue_push(dev, vq, elem, 0); | |
| vu_queue_notify(dev, vq); | |
| free(elem); | |
| } | |
| } | |
| static void | |
| vg_panic(VuDev *dev, const char *msg) | |
| { | |
| g_critical("%s\n", msg); | |
| exit(1); | |
| } | |
| static void | |
| vg_queue_set_started(VuDev *dev, int qidx, bool started) | |
| { | |
| VuVirtq *vq = vu_get_queue(dev, qidx); | |
| g_debug("queue started %d:%d\n", qidx, started); | |
| switch (qidx) { | |
| case 0: | |
| vu_set_queue_handler(dev, vq, started ? vg_handle_ctrl : NULL); | |
| break; | |
| case 1: | |
| vu_set_queue_handler(dev, vq, started ? vg_handle_cursor : NULL); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| static gboolean | |
| protocol_features_cb(gint fd, GIOCondition condition, gpointer user_data) | |
| { | |
| const uint64_t protocol_edid = (1 << VHOST_USER_GPU_PROTOCOL_F_EDID); | |
| const uint64_t protocol_dmabuf2 = (1 << VHOST_USER_GPU_PROTOCOL_F_DMABUF2); | |
| VuGpu *g = user_data; | |
| uint64_t protocol_features; | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_GET_PROTOCOL_FEATURES | |
| }; | |
| if (!vg_recv_msg(g, msg.request, | |
| sizeof(protocol_features), &protocol_features)) { | |
| return G_SOURCE_CONTINUE; | |
| } | |
| protocol_features &= (protocol_edid | protocol_dmabuf2); | |
| msg = (VhostUserGpuMsg) { | |
| .request = VHOST_USER_GPU_SET_PROTOCOL_FEATURES, | |
| .size = sizeof(uint64_t), | |
| .payload.u64 = protocol_features, | |
| }; | |
| vg_send_msg(g, &msg, -1); | |
| g->wait_in = 0; | |
| vg_handle_ctrl(&g->dev.parent, 0); | |
| if (g->edid_inited && !(protocol_features & protocol_edid)) { | |
| g_printerr("EDID feature set by the frontend but it does not support " | |
| "the EDID vhost-user-gpu protocol.\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| g->use_modifiers = !!(protocol_features & protocol_dmabuf2); | |
| return G_SOURCE_REMOVE; | |
| } | |
| static void | |
| set_gpu_protocol_features(VuGpu *g) | |
| { | |
| VhostUserGpuMsg msg = { | |
| .request = VHOST_USER_GPU_GET_PROTOCOL_FEATURES, | |
| }; | |
| vg_send_msg(g, &msg, -1); | |
| assert(g->wait_in == 0); | |
| g->wait_in = g_unix_fd_add(g->sock_fd, G_IO_IN | G_IO_HUP, | |
| protocol_features_cb, g); | |
| } | |
| static int | |
| vg_process_msg(VuDev *dev, VhostUserMsg *msg, int *do_reply) | |
| { | |
| VuGpu *g = container_of(dev, VuGpu, dev.parent); | |
| switch (msg->request) { | |
| case VHOST_USER_GPU_SET_SOCKET: { | |
| g_return_val_if_fail(msg->fd_num == 1, 1); | |
| g_return_val_if_fail(g->sock_fd == -1, 1); | |
| g->sock_fd = msg->fds[0]; | |
| set_gpu_protocol_features(g); | |
| return 1; | |
| } | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static uint64_t | |
| vg_get_features(VuDev *dev) | |
| { | |
| uint64_t features = 0; | |
| if (opt_virgl) { | |
| features |= 1 << VIRTIO_GPU_F_VIRGL; | |
| } | |
| features |= 1 << VIRTIO_GPU_F_EDID; | |
| return features; | |
| } | |
| static void | |
| vg_set_features(VuDev *dev, uint64_t features) | |
| { | |
| VuGpu *g = container_of(dev, VuGpu, dev.parent); | |
| bool virgl = features & (1 << VIRTIO_GPU_F_VIRGL); | |
| if (virgl && !g->virgl_inited) { | |
| if (!vg_virgl_init(g)) { | |
| vg_panic(dev, "Failed to initialize virgl"); | |
| } | |
| g->virgl_inited = true; | |
| } | |
| g->edid_inited = !!(features & (1 << VIRTIO_GPU_F_EDID)); | |
| g->virgl = virgl; | |
| } | |
| static int | |
| vg_get_config(VuDev *dev, uint8_t *config, uint32_t len) | |
| { | |
| VuGpu *g = container_of(dev, VuGpu, dev.parent); | |
| if (len > sizeof(struct virtio_gpu_config)) { | |
| return -1; | |
| } | |
| if (opt_virgl) { | |
| g->virtio_config.num_capsets = vg_virgl_get_num_capsets(); | |
| } | |
| memcpy(config, &g->virtio_config, len); | |
| return 0; | |
| } | |
| static int | |
| vg_set_config(VuDev *dev, const uint8_t *data, | |
| uint32_t offset, uint32_t size, | |
| uint32_t flags) | |
| { | |
| VuGpu *g = container_of(dev, VuGpu, dev.parent); | |
| struct virtio_gpu_config *config = (struct virtio_gpu_config *)data; | |
| if (config->events_clear) { | |
| g->virtio_config.events_read &= ~config->events_clear; | |
| } | |
| return 0; | |
| } | |
| static const VuDevIface vuiface = { | |
| .set_features = vg_set_features, | |
| .get_features = vg_get_features, | |
| .queue_set_started = vg_queue_set_started, | |
| .process_msg = vg_process_msg, | |
| .get_config = vg_get_config, | |
| .set_config = vg_set_config, | |
| }; | |
| static void | |
| vg_destroy(VuGpu *g) | |
| { | |
| struct virtio_gpu_simple_resource *res, *tmp; | |
| vug_deinit(&g->dev); | |
| vg_sock_fd_close(g); | |
| QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) { | |
| vg_resource_destroy(g, res); | |
| } | |
| vugbm_device_destroy(&g->gdev); | |
| } | |
| static GOptionEntry entries[] = { | |
| { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &opt_print_caps, | |
| "Print capabilities", NULL }, | |
| { "fd", 'f', 0, G_OPTION_ARG_INT, &opt_fdnum, | |
| "Use inherited fd socket", "FDNUM" }, | |
| { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &opt_socket_path, | |
| "Use UNIX socket path", "PATH" }, | |
| { "render-node", 'r', 0, G_OPTION_ARG_FILENAME, &opt_render_node, | |
| "Specify DRM render node", "PATH" }, | |
| { "virgl", 'v', 0, G_OPTION_ARG_NONE, &opt_virgl, | |
| "Turn virgl rendering on", NULL }, | |
| { NULL, } | |
| }; | |
| int | |
| main(int argc, char *argv[]) | |
| { | |
| GOptionContext *context; | |
| GError *error = NULL; | |
| GMainLoop *loop = NULL; | |
| int fd; | |
| VuGpu g = { .sock_fd = -1, .drm_rnode_fd = -1 }; | |
| QTAILQ_INIT(&g.reslist); | |
| QTAILQ_INIT(&g.fenceq); | |
| context = g_option_context_new("QEMU vhost-user-gpu"); | |
| g_option_context_add_main_entries(context, entries, NULL); | |
| if (!g_option_context_parse(context, &argc, &argv, &error)) { | |
| g_printerr("Option parsing failed: %s\n", error->message); | |
| exit(EXIT_FAILURE); | |
| } | |
| g_option_context_free(context); | |
| if (opt_print_caps) { | |
| g_print("{\n"); | |
| g_print(" \"type\": \"gpu\",\n"); | |
| g_print(" \"features\": [\n"); | |
| g_print(" \"render-node\",\n"); | |
| g_print(" \"virgl\"\n"); | |
| g_print(" ]\n"); | |
| g_print("}\n"); | |
| exit(EXIT_SUCCESS); | |
| } | |
| g.drm_rnode_fd = qemu_drm_rendernode_open(opt_render_node); | |
| if (opt_render_node && g.drm_rnode_fd == -1) { | |
| g_printerr("Failed to open DRM rendernode.\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| vugbm_device_init(&g.gdev, g.drm_rnode_fd); | |
| if ((!!opt_socket_path + (opt_fdnum != -1)) != 1) { | |
| g_printerr("Please specify either --fd or --socket-path\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| if (opt_socket_path) { | |
| int lsock = unix_listen(opt_socket_path, &error_fatal); | |
| if (lsock < 0) { | |
| g_printerr("Failed to listen on %s.\n", opt_socket_path); | |
| exit(EXIT_FAILURE); | |
| } | |
| fd = accept(lsock, NULL, NULL); | |
| close(lsock); | |
| } else { | |
| fd = opt_fdnum; | |
| } | |
| if (fd == -1) { | |
| g_printerr("Invalid vhost-user socket.\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| if (!vug_init(&g.dev, VHOST_USER_GPU_MAX_QUEUES, fd, vg_panic, &vuiface)) { | |
| g_printerr("Failed to initialize libvhost-user-glib.\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| loop = g_main_loop_new(NULL, FALSE); | |
| g_main_loop_run(loop); | |
| g_main_loop_unref(loop); | |
| vg_destroy(&g); | |
| if (g.drm_rnode_fd >= 0) { | |
| close(g.drm_rnode_fd); | |
| } | |
| return 0; | |
| } |