/* * Copyright 2021 Google LLC * SPDX-License-Identifier: MIT */ #include "render_context.h" #include #include "util/u_thread.h" #include "virgl_util.h" #include "virglrenderer.h" #include "vrend_iov.h" #include "render_virgl.h" struct render_context_resource { uint32_t res_id; struct list_head head; }; /* XXX we need a unique res_id to export a blob * * virglrenderer.h does not have the right APIs for us. We should use vkr * (and vrend, if that makes sense) directly. */ #define BLOB_RES_ID (~0u) static void render_context_resource_destroy(struct render_context_resource *res) { list_del(&res->head); virgl_renderer_resource_unref(res->res_id); free(res); } static struct render_context_resource * render_context_resource_import(uint32_t res_id, enum virgl_resource_fd_type fd_type, int res_fd, uint64_t size) { /* TODO pool alloc if resources come and go frequently */ struct render_context_resource *res = calloc(1, sizeof(*res)); if (!res) return NULL; res->res_id = res_id; uint32_t import_fd_type; switch (fd_type) { case VIRGL_RESOURCE_FD_DMABUF: import_fd_type = VIRGL_RENDERER_BLOB_FD_TYPE_DMABUF; break; case VIRGL_RESOURCE_FD_OPAQUE: import_fd_type = VIRGL_RENDERER_BLOB_FD_TYPE_OPAQUE; break; case VIRGL_RESOURCE_FD_SHM: import_fd_type = VIRGL_RENDERER_BLOB_FD_TYPE_SHM; break; default: import_fd_type = 0; break; } const struct virgl_renderer_resource_import_blob_args import_args = { .res_handle = res->res_id, .blob_mem = VIRGL_RENDERER_BLOB_MEM_HOST3D, .fd_type = import_fd_type, .fd = res_fd, .size = size, }; int ret = virgl_renderer_resource_import_blob(&import_args); if (ret) { free(res); return NULL; } return res; } void render_context_update_timeline(struct render_context *ctx, uint32_t ring_idx, uint32_t seqno) { /* this can be called by the context's main thread and sync threads */ atomic_store(&ctx->shmem_timelines[ring_idx], seqno); if (ctx->fence_eventfd >= 0) write_eventfd(ctx->fence_eventfd, 1); } static struct render_context_resource * render_context_find_resource(struct render_context *ctx, uint32_t res_id) { list_for_each_entry (struct render_context_resource, res, &ctx->resources, head) { if (res->res_id == res_id) return res; } return NULL; } static bool render_context_init_virgl_context(struct render_context *ctx, const struct render_context_op_init_request *req, int shmem_fd, int fence_eventfd) { const int timeline_count = req->shmem_size / sizeof(*ctx->shmem_timelines); void *shmem_ptr = mmap(NULL, req->shmem_size, PROT_WRITE, MAP_SHARED, shmem_fd, 0); if (shmem_ptr == MAP_FAILED) return false; int ret = virgl_renderer_context_create_with_flags(ctx->ctx_id, req->flags, ctx->name_len, ctx->name); if (ret) { munmap(shmem_ptr, req->shmem_size); return false; } ctx->shmem_fd = shmem_fd; ctx->shmem_size = req->shmem_size; ctx->shmem_ptr = shmem_ptr; ctx->shmem_timelines = shmem_ptr; for (int i = 0; i < timeline_count; i++) atomic_store(&ctx->shmem_timelines[i], 0); ctx->timeline_count = timeline_count; ctx->fence_eventfd = fence_eventfd; return true; } static bool render_context_export_blob(struct render_context *ctx, const struct render_context_op_get_blob_request *req, enum virgl_resource_fd_type *out_fd_type, uint32_t *out_map_info, int *out_res_fd) { const uint32_t res_id = BLOB_RES_ID; const struct virgl_renderer_resource_create_blob_args blob_args = { .res_handle = res_id, .ctx_id = ctx->ctx_id, .blob_mem = VIRGL_RENDERER_BLOB_MEM_HOST3D, .blob_flags = req->blob_flags, .blob_id = req->blob_id, .size = req->blob_size, }; int ret = virgl_renderer_resource_create_blob(&blob_args); if (ret) { render_log("failed to create blob resource"); return false; } uint32_t map_info; virgl_renderer_resource_get_map_info(res_id, &map_info); uint32_t fd_type; int res_fd; ret = virgl_renderer_resource_export_blob(res_id, &fd_type, &res_fd); virgl_renderer_resource_unref(res_id); if (ret) return false; switch (fd_type) { case VIRGL_RENDERER_BLOB_FD_TYPE_DMABUF: *out_fd_type = VIRGL_RESOURCE_FD_DMABUF; break; case VIRGL_RENDERER_BLOB_FD_TYPE_OPAQUE: *out_fd_type = VIRGL_RESOURCE_FD_OPAQUE; break; case VIRGL_RENDERER_BLOB_FD_TYPE_SHM: *out_fd_type = VIRGL_RESOURCE_FD_SHM; break; default: *out_fd_type = 0; } *out_map_info = map_info; *out_res_fd = res_fd; return true; } static bool render_context_dispatch_submit_fence(struct render_context *ctx, const union render_context_op_request *req, UNUSED const int *fds, UNUSED int fd_count) { /* always merge fences */ assert(!(req->submit_fence.flags & ~VIRGL_RENDERER_FENCE_FLAG_MERGEABLE)); const uint32_t flags = VIRGL_RENDERER_FENCE_FLAG_MERGEABLE; const uint32_t ring_idx = req->submit_fence.ring_index; const uint32_t seqno = req->submit_fence.seqno; assert(ring_idx < (uint32_t)ctx->timeline_count); int ret = virgl_renderer_context_create_fence(ctx->ctx_id, flags, ring_idx, uintptr_to_pointer(seqno)); return !ret; } static bool render_context_dispatch_submit_cmd(struct render_context *ctx, const union render_context_op_request *req, UNUSED const int *fds, UNUSED int fd_count) { const int ndw = req->submit_cmd.size / sizeof(uint32_t); void *cmd = (void *)req->submit_cmd.cmd; if (req->submit_cmd.size > sizeof(req->submit_cmd.cmd)) { cmd = malloc(req->submit_cmd.size); if (!cmd) return true; const size_t inlined = sizeof(req->submit_cmd.cmd); const size_t remain = req->submit_cmd.size - inlined; memcpy(cmd, req->submit_cmd.cmd, inlined); if (!render_socket_receive_data(&ctx->socket, (char *)cmd + inlined, remain)) { free(cmd); return false; } } int ret = virgl_renderer_submit_cmd(cmd, ctx->ctx_id, ndw); if (cmd != req->submit_cmd.cmd) free(cmd); const struct render_context_op_submit_cmd_reply reply = { .ok = !ret, }; if (!render_socket_send_reply(&ctx->socket, &reply, sizeof(reply))) return false; return true; } static bool render_context_dispatch_get_blob(struct render_context *ctx, const union render_context_op_request *req, UNUSED const int *fds, UNUSED int fd_count) { struct render_context_op_get_blob_reply reply = { .fd_type = VIRGL_RESOURCE_FD_INVALID, }; int res_fd; bool ok = render_context_export_blob(ctx, &req->get_blob, &reply.fd_type, &reply.map_info, &res_fd); if (!ok) return render_socket_send_reply(&ctx->socket, &reply, sizeof(reply)); ok = render_socket_send_reply_with_fds(&ctx->socket, &reply, sizeof(reply), &res_fd, 1); close(res_fd); return ok; } static bool render_context_dispatch_detach_resource(struct render_context *ctx, const union render_context_op_request *req, UNUSED const int *fds, UNUSED int fd_count) { const uint32_t res_id = req->detach_resource.res_id; struct render_context_resource *res = render_context_find_resource(ctx, res_id); if (res) render_context_resource_destroy(res); return true; } static bool render_context_dispatch_attach_resource(struct render_context *ctx, const union render_context_op_request *req, const int *fds, int fd_count) { const uint32_t res_id = req->attach_resource.res_id; const enum virgl_resource_fd_type fd_type = req->attach_resource.fd_type; const uint64_t size = req->attach_resource.size; if (res_id == BLOB_RES_ID) { render_log("XXX res_id is %u, which is reserved for blob export", res_id); return false; } if (fd_type == VIRGL_RESOURCE_FD_INVALID || !size || fd_count != 1) { render_log("failed to attach invalid resource %d", res_id); return false; } /* classic 3d resource with valid size reuses the blob import path here */ struct render_context_resource *res = render_context_resource_import(res_id, fd_type, fds[0], size); if (!res) { render_log("failed to import resource %d", res_id); return false; } list_addtail(&res->head, &ctx->resources); virgl_renderer_ctx_attach_resource(ctx->ctx_id, res_id); return true; } static bool render_context_dispatch_init(struct render_context *ctx, const union render_context_op_request *req, const int *fds, int fd_count) { if (fd_count != 1 && fd_count != 2) return false; const int shmem_fd = fds[0]; const int fence_eventfd = fd_count == 2 ? fds[1] : -1; return render_context_init_virgl_context(ctx, &req->init, shmem_fd, fence_eventfd); } static bool render_context_dispatch_nop(UNUSED struct render_context *ctx, UNUSED const union render_context_op_request *req, UNUSED const int *fds, UNUSED int fd_count) { return true; } struct render_context_dispatch_entry { size_t expect_size; int max_fd_count; bool (*dispatch)(struct render_context *ctx, const union render_context_op_request *req, const int *fds, int fd_count); }; static const struct render_context_dispatch_entry render_context_dispatch_table[RENDER_CONTEXT_OP_COUNT] = { #define RENDER_CONTEXT_DISPATCH(NAME, name, max_fd) \ [RENDER_CONTEXT_OP_## \ NAME] = { .expect_size = sizeof(struct render_context_op_##name##_request), \ .max_fd_count = (max_fd), \ .dispatch = render_context_dispatch_##name } RENDER_CONTEXT_DISPATCH(NOP, nop, 0), RENDER_CONTEXT_DISPATCH(INIT, init, 2), RENDER_CONTEXT_DISPATCH(ATTACH_RESOURCE, attach_resource, 1), RENDER_CONTEXT_DISPATCH(DETACH_RESOURCE, detach_resource, 0), RENDER_CONTEXT_DISPATCH(GET_BLOB, get_blob, 0), RENDER_CONTEXT_DISPATCH(SUBMIT_CMD, submit_cmd, 0), RENDER_CONTEXT_DISPATCH(SUBMIT_FENCE, submit_fence, 0), #undef RENDER_CONTEXT_DISPATCH }; static bool render_context_dispatch(struct render_context *ctx) { union render_context_op_request req; size_t req_size; int req_fds[8]; int req_fd_count; if (!render_socket_receive_request_with_fds(&ctx->socket, &req, sizeof(req), &req_size, req_fds, ARRAY_SIZE(req_fds), &req_fd_count)) return false; assert((unsigned int)req_fd_count <= ARRAY_SIZE(req_fds)); if (req.header.op >= RENDER_CONTEXT_OP_COUNT) { render_log("invalid context op %d", req.header.op); goto fail; } const struct render_context_dispatch_entry *entry = &render_context_dispatch_table[req.header.op]; if (entry->expect_size != req_size || entry->max_fd_count < req_fd_count) { render_log("invalid request size (%zu) or fd count (%d) for context op %d", req_size, req_fd_count, req.header.op); goto fail; } render_virgl_lock_dispatch(); const bool ok = entry->dispatch(ctx, &req, req_fds, req_fd_count); render_virgl_unlock_dispatch(); if (!ok) { render_log("failed to dispatch context op %d", req.header.op); goto fail; } return true; fail: for (int i = 0; i < req_fd_count; i++) close(req_fds[i]); return false; } static bool render_context_run(struct render_context *ctx) { while (true) { if (!render_context_dispatch(ctx)) return false; } return true; } static void render_context_fini(struct render_context *ctx) { render_virgl_lock_dispatch(); /* destroy the context first to join its sync threads and ring threads */ virgl_renderer_context_destroy(ctx->ctx_id); list_for_each_entry_safe (struct render_context_resource, res, &ctx->resources, head) render_context_resource_destroy(res); render_virgl_unlock_dispatch(); render_virgl_remove_context(ctx); if (ctx->shmem_ptr) munmap(ctx->shmem_ptr, ctx->shmem_size); if (ctx->shmem_fd >= 0) close(ctx->shmem_fd); if (ctx->fence_eventfd >= 0) close(ctx->fence_eventfd); if (ctx->name) free(ctx->name); render_socket_fini(&ctx->socket); } static bool render_context_init_name(struct render_context *ctx, uint32_t ctx_id, const char *ctx_name) { const size_t name_size = strlen(ctx_name) + 16; ctx->name = malloc(name_size); if (!ctx->name) return false; ctx->name_len = snprintf(ctx->name, name_size, "virgl-%d-%s", ctx_id, ctx_name); if (ctx->name_len >= name_size) ctx->name_len = name_size - 1; u_thread_setname(ctx->name); return true; } static bool render_context_init(struct render_context *ctx, const struct render_context_args *args) { memset(ctx, 0, sizeof(*ctx)); ctx->ctx_id = args->ctx_id; render_socket_init(&ctx->socket, args->ctx_fd); list_inithead(&ctx->resources); ctx->shmem_fd = -1; ctx->fence_eventfd = -1; if (!render_context_init_name(ctx, args->ctx_id, args->ctx_name)) return false; render_virgl_add_context(ctx); return true; } bool render_context_main(const struct render_context_args *args) { struct render_context ctx; assert(args->valid && args->ctx_id && args->ctx_fd >= 0); if (!render_virgl_init(args->init_flags)) { close(args->ctx_fd); return false; } if (!render_context_init(&ctx, args)) { render_virgl_fini(); close(args->ctx_fd); return false; } const bool ok = render_context_run(&ctx); render_context_fini(&ctx); render_virgl_fini(); return ok; }