You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
15 KiB
511 lines
15 KiB
3 years ago
|
/*
|
||
|
* Copyright 2021 Google LLC
|
||
|
* SPDX-License-Identifier: MIT
|
||
|
*/
|
||
|
|
||
|
#include "render_context.h"
|
||
|
|
||
|
#include <sys/mman.h>
|
||
|
|
||
|
#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;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|