/* * Copyright 2020 Google LLC * SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #ifdef MAJOR_IN_MKDEV #include #endif #ifdef MAJOR_IN_SYSMACROS #include #endif #include "drm-uapi/virtgpu_drm.h" #include "util/sparse_array.h" #define VIRGL_RENDERER_UNSTABLE_APIS #include "virtio-gpu/virglrenderer_hw.h" #include "vn_renderer_internal.h" /* XXX WIP kernel uapi */ #ifndef VIRTGPU_PARAM_CONTEXT_INIT #define VIRTGPU_PARAM_CONTEXT_INIT 6 #define VIRTGPU_CONTEXT_PARAM_CAPSET_ID 0x0001 struct drm_virtgpu_context_set_param { __u64 param; __u64 value; }; struct drm_virtgpu_context_init { __u32 num_params; __u32 pad; __u64 ctx_set_params; }; #define DRM_VIRTGPU_CONTEXT_INIT 0xb #define DRM_IOCTL_VIRTGPU_CONTEXT_INIT \ DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_CONTEXT_INIT, \ struct drm_virtgpu_context_init) #endif /* VIRTGPU_PARAM_CONTEXT_INIT */ #ifndef VIRTGPU_PARAM_MAX_SYNC_QUEUE_COUNT #define VIRTGPU_PARAM_MAX_SYNC_QUEUE_COUNT 100 #endif /* VIRTGPU_PARAM_MAX_SYNC_QUEUE_COUNT */ #ifndef VIRTGPU_PARAM_GUEST_VRAM /* All guest allocations happen via virtgpu dedicated heap. */ #define VIRTGPU_PARAM_GUEST_VRAM 9 #endif #ifndef VIRTGPU_BLOB_MEM_GUEST_VRAM #define VIRTGPU_BLOB_MEM_GUEST_VRAM 0x0004 #endif /* XXX comment these out to really use kernel uapi */ #define SIMULATE_BO_SIZE_FIX 1 //#define SIMULATE_CONTEXT_INIT 1 #define SIMULATE_SYNCOBJ 1 #define SIMULATE_SUBMIT 1 #define VIRTGPU_PCI_VENDOR_ID 0x1af4 #define VIRTGPU_PCI_DEVICE_ID 0x1050 struct virtgpu; struct virtgpu_shmem { struct vn_renderer_shmem base; uint32_t gem_handle; }; struct virtgpu_bo { struct vn_renderer_bo base; uint32_t gem_handle; uint32_t blob_flags; }; struct virtgpu_sync { struct vn_renderer_sync base; /* * drm_syncobj is in one of these states * * - value N: drm_syncobj has a signaled fence chain with seqno N * - pending N->M: drm_syncobj has an unsignaled fence chain with seqno M * (which may point to another unsignaled fence chain with * seqno between N and M, and so on) * * TODO Do we want to use binary drm_syncobjs? They would be * * - value 0: drm_syncobj has no fence * - value 1: drm_syncobj has a signaled fence with seqno 0 * * They are cheaper but require special care. */ uint32_t syncobj_handle; }; struct virtgpu { struct vn_renderer base; struct vn_instance *instance; int fd; bool has_primary; int primary_major; int primary_minor; int render_major; int render_minor; int bustype; drmPciBusInfo pci_bus_info; uint32_t max_sync_queue_count; struct { enum virgl_renderer_capset id; uint32_t version; struct virgl_renderer_capset_venus data; } capset; uint32_t shmem_blob_mem; uint32_t bo_blob_mem; /* note that we use gem_handle instead of res_id to index because * res_id is monotonically increasing by default (see * virtio_gpu_resource_id_get) */ struct util_sparse_array shmem_array; struct util_sparse_array bo_array; mtx_t dma_buf_import_mutex; struct vn_renderer_shmem_cache shmem_cache; }; #ifdef SIMULATE_SYNCOBJ #include "util/hash_table.h" #include "util/u_idalloc.h" static struct { mtx_t mutex; struct hash_table *syncobjs; struct util_idalloc ida; int signaled_fd; } sim; struct sim_syncobj { mtx_t mutex; uint64_t point; int pending_fd; uint64_t pending_point; bool pending_cpu; }; static uint32_t sim_syncobj_create(struct virtgpu *gpu, bool signaled) { struct sim_syncobj *syncobj = calloc(1, sizeof(*syncobj)); if (!syncobj) return 0; mtx_init(&syncobj->mutex, mtx_plain); syncobj->pending_fd = -1; mtx_lock(&sim.mutex); /* initialize lazily */ if (!sim.syncobjs) { sim.syncobjs = _mesa_pointer_hash_table_create(NULL); if (!sim.syncobjs) { mtx_unlock(&sim.mutex); return 0; } util_idalloc_init(&sim.ida, 32); struct drm_virtgpu_execbuffer args = { .flags = VIRTGPU_EXECBUF_FENCE_FD_OUT, }; int ret = drmIoctl(gpu->fd, DRM_IOCTL_VIRTGPU_EXECBUFFER, &args); if (ret || args.fence_fd < 0) { _mesa_hash_table_destroy(sim.syncobjs, NULL); sim.syncobjs = NULL; mtx_unlock(&sim.mutex); return 0; } sim.signaled_fd = args.fence_fd; } const unsigned syncobj_handle = util_idalloc_alloc(&sim.ida) + 1; _mesa_hash_table_insert(sim.syncobjs, (const void *)(uintptr_t)syncobj_handle, syncobj); mtx_unlock(&sim.mutex); return syncobj_handle; } static void sim_syncobj_destroy(struct virtgpu *gpu, uint32_t syncobj_handle) { struct sim_syncobj *syncobj = NULL; mtx_lock(&sim.mutex); struct hash_entry *entry = _mesa_hash_table_search( sim.syncobjs, (const void *)(uintptr_t)syncobj_handle); if (entry) { syncobj = entry->data; _mesa_hash_table_remove(sim.syncobjs, entry); util_idalloc_free(&sim.ida, syncobj_handle - 1); } mtx_unlock(&sim.mutex); if (syncobj) { if (syncobj->pending_fd >= 0) close(syncobj->pending_fd); mtx_destroy(&syncobj->mutex); free(syncobj); } } static VkResult sim_syncobj_poll(int fd, int poll_timeout) { struct pollfd pollfd = { .fd = fd, .events = POLLIN, }; int ret; do { ret = poll(&pollfd, 1, poll_timeout); } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); if (ret < 0 || (ret > 0 && !(pollfd.revents & POLLIN))) { return (ret < 0 && errno == ENOMEM) ? VK_ERROR_OUT_OF_HOST_MEMORY : VK_ERROR_DEVICE_LOST; } return ret ? VK_SUCCESS : VK_TIMEOUT; } static void sim_syncobj_set_point_locked(struct sim_syncobj *syncobj, uint64_t point) { syncobj->point = point; if (syncobj->pending_fd >= 0) { close(syncobj->pending_fd); syncobj->pending_fd = -1; syncobj->pending_point = point; } } static void sim_syncobj_update_point_locked(struct sim_syncobj *syncobj, int poll_timeout) { if (syncobj->pending_fd >= 0) { VkResult result; if (syncobj->pending_cpu) { if (poll_timeout == -1) { const int max_cpu_timeout = 2000; poll_timeout = max_cpu_timeout; result = sim_syncobj_poll(syncobj->pending_fd, poll_timeout); if (result == VK_TIMEOUT) { vn_log(NULL, "cpu sync timed out after %dms; ignoring", poll_timeout); result = VK_SUCCESS; } } else { result = sim_syncobj_poll(syncobj->pending_fd, poll_timeout); } } else { result = sim_syncobj_poll(syncobj->pending_fd, poll_timeout); } if (result == VK_SUCCESS) { close(syncobj->pending_fd); syncobj->pending_fd = -1; syncobj->point = syncobj->pending_point; } } } static struct sim_syncobj * sim_syncobj_lookup(struct virtgpu *gpu, uint32_t syncobj_handle) { struct sim_syncobj *syncobj = NULL; mtx_lock(&sim.mutex); struct hash_entry *entry = _mesa_hash_table_search( sim.syncobjs, (const void *)(uintptr_t)syncobj_handle); if (entry) syncobj = entry->data; mtx_unlock(&sim.mutex); return syncobj; } static int sim_syncobj_reset(struct virtgpu *gpu, uint32_t syncobj_handle) { struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, syncobj_handle); if (!syncobj) return -1; mtx_lock(&syncobj->mutex); sim_syncobj_set_point_locked(syncobj, 0); mtx_unlock(&syncobj->mutex); return 0; } static int sim_syncobj_query(struct virtgpu *gpu, uint32_t syncobj_handle, uint64_t *point) { struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, syncobj_handle); if (!syncobj) return -1; mtx_lock(&syncobj->mutex); sim_syncobj_update_point_locked(syncobj, 0); *point = syncobj->point; mtx_unlock(&syncobj->mutex); return 0; } static int sim_syncobj_signal(struct virtgpu *gpu, uint32_t syncobj_handle, uint64_t point) { struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, syncobj_handle); if (!syncobj) return -1; mtx_lock(&syncobj->mutex); sim_syncobj_set_point_locked(syncobj, point); mtx_unlock(&syncobj->mutex); return 0; } static int sim_syncobj_submit(struct virtgpu *gpu, uint32_t syncobj_handle, int sync_fd, uint64_t point, bool cpu) { struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, syncobj_handle); if (!syncobj) return -1; int pending_fd = dup(sync_fd); if (pending_fd < 0) { vn_log(gpu->instance, "failed to dup sync fd"); return -1; } mtx_lock(&syncobj->mutex); if (syncobj->pending_fd >= 0) { mtx_unlock(&syncobj->mutex); /* TODO */ vn_log(gpu->instance, "sorry, no simulated timeline semaphore"); close(pending_fd); return -1; } if (syncobj->point >= point) vn_log(gpu->instance, "non-monotonic signaling"); syncobj->pending_fd = pending_fd; syncobj->pending_point = point; syncobj->pending_cpu = cpu; mtx_unlock(&syncobj->mutex); return 0; } static int timeout_to_poll_timeout(uint64_t timeout) { const uint64_t ns_per_ms = 1000000; const uint64_t ms = (timeout + ns_per_ms - 1) / ns_per_ms; if (!ms && timeout) return -1; return ms <= INT_MAX ? ms : -1; } static int sim_syncobj_wait(struct virtgpu *gpu, const struct vn_renderer_wait *wait, bool wait_avail) { if (wait_avail) return -1; const int poll_timeout = timeout_to_poll_timeout(wait->timeout); /* TODO poll all fds at the same time */ for (uint32_t i = 0; i < wait->sync_count; i++) { struct virtgpu_sync *sync = (struct virtgpu_sync *)wait->syncs[i]; const uint64_t point = wait->sync_values[i]; struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, sync->syncobj_handle); if (!syncobj) return -1; mtx_lock(&syncobj->mutex); if (syncobj->point < point) sim_syncobj_update_point_locked(syncobj, poll_timeout); if (syncobj->point < point) { if (wait->wait_any && i < wait->sync_count - 1 && syncobj->pending_fd < 0) { mtx_unlock(&syncobj->mutex); continue; } errno = ETIME; mtx_unlock(&syncobj->mutex); return -1; } mtx_unlock(&syncobj->mutex); if (wait->wait_any) break; /* TODO adjust poll_timeout */ } return 0; } static int sim_syncobj_export(struct virtgpu *gpu, uint32_t syncobj_handle) { struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, syncobj_handle); if (!syncobj) return -1; int fd = -1; mtx_lock(&syncobj->mutex); if (syncobj->pending_fd >= 0) fd = dup(syncobj->pending_fd); else fd = dup(sim.signaled_fd); mtx_unlock(&syncobj->mutex); return fd; } static uint32_t sim_syncobj_import(struct virtgpu *gpu, uint32_t syncobj_handle, int fd) { struct sim_syncobj *syncobj = sim_syncobj_lookup(gpu, syncobj_handle); if (!syncobj) return 0; if (sim_syncobj_submit(gpu, syncobj_handle, fd, 1, false)) return 0; return syncobj_handle; } #endif /* SIMULATE_SYNCOBJ */ #ifdef SIMULATE_SUBMIT static int sim_submit_signal_syncs(struct virtgpu *gpu, int sync_fd, struct vn_renderer_sync *const *syncs, const uint64_t *sync_values, uint32_t sync_count, bool cpu) { for (uint32_t i = 0; i < sync_count; i++) { struct virtgpu_sync *sync = (struct virtgpu_sync *)syncs[i]; const uint64_t pending_point = sync_values[i]; #ifdef SIMULATE_SYNCOBJ int ret = sim_syncobj_submit(gpu, sync->syncobj_handle, sync_fd, pending_point, cpu); if (ret) return ret; #else /* we can in theory do a DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE followed by a * DRM_IOCTL_SYNCOBJ_TRANSFER */ return -1; #endif } return 0; } static uint32_t * sim_submit_alloc_gem_handles(struct vn_renderer_bo *const *bos, uint32_t bo_count) { uint32_t *gem_handles = malloc(sizeof(*gem_handles) * bo_count); if (!gem_handles) return NULL; for (uint32_t i = 0; i < bo_count; i++) { struct virtgpu_bo *bo = (struct virtgpu_bo *)bos[i]; gem_handles[i] = bo->gem_handle; } return gem_handles; } static int sim_submit(struct virtgpu *gpu, const struct vn_renderer_submit *submit) { /* TODO replace submit->bos by submit->gem_handles to avoid malloc/loop */ uint32_t *gem_handles = NULL; if (submit->bo_count) { gem_handles = sim_submit_alloc_gem_handles(submit->bos, submit->bo_count); if (!gem_handles) return -1; } int ret = 0; for (uint32_t i = 0; i < submit->batch_count; i++) { const struct vn_renderer_submit_batch *batch = &submit->batches[i]; struct drm_virtgpu_execbuffer args = { .flags = batch->sync_count ? VIRTGPU_EXECBUF_FENCE_FD_OUT : 0, .size = batch->cs_size, .command = (uintptr_t)batch->cs_data, .bo_handles = (uintptr_t)gem_handles, .num_bo_handles = submit->bo_count, }; ret = drmIoctl(gpu->fd, DRM_IOCTL_VIRTGPU_EXECBUFFER, &args); if (ret) { vn_log(gpu->instance, "failed to execbuffer: %s", strerror(errno)); break; } if (batch->sync_count) { ret = sim_submit_signal_syncs(gpu, args.fence_fd, batch->syncs, batch->sync_values, batch->sync_count, batch->sync_queue_cpu); close(args.fence_fd); if (ret) break; } } if (!submit->batch_count && submit->bo_count) { struct drm_virtgpu_execbuffer args = { .bo_handles = (uintptr_t)gem_handles, .num_bo_handles = submit->bo_count, }; ret = drmIoctl(gpu->fd, DRM_IOCTL_VIRTGPU_EXECBUFFER, &args); if (ret) vn_log(gpu->instance, "failed to execbuffer: %s", strerror(errno)); } free(gem_handles); return ret; } #endif /* SIMULATE_SUBMIT */ static int virtgpu_ioctl(struct virtgpu *gpu, unsigned long request, void *args) { return drmIoctl(gpu->fd, request, args); } static uint64_t virtgpu_ioctl_getparam(struct virtgpu *gpu, uint64_t param) { #ifdef SIMULATE_CONTEXT_INIT if (param == VIRTGPU_PARAM_CONTEXT_INIT) return 1; #endif #ifdef SIMULATE_SUBMIT if (param == VIRTGPU_PARAM_MAX_SYNC_QUEUE_COUNT) return 16; #endif /* val must be zeroed because kernel only writes the lower 32 bits */ uint64_t val = 0; struct drm_virtgpu_getparam args = { .param = param, .value = (uintptr_t)&val, }; const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_VIRTGPU_GETPARAM, &args); return ret ? 0 : val; } static int virtgpu_ioctl_get_caps(struct virtgpu *gpu, enum virgl_renderer_capset id, uint32_t version, void *capset, size_t capset_size) { #ifdef SIMULATE_CONTEXT_INIT if (id == VIRGL_RENDERER_CAPSET_VENUS && version == 0) return 0; #endif struct drm_virtgpu_get_caps args = { .cap_set_id = id, .cap_set_ver = version, .addr = (uintptr_t)capset, .size = capset_size, }; return virtgpu_ioctl(gpu, DRM_IOCTL_VIRTGPU_GET_CAPS, &args); } static int virtgpu_ioctl_context_init(struct virtgpu *gpu, enum virgl_renderer_capset capset_id) { #ifdef SIMULATE_CONTEXT_INIT if (capset_id == VIRGL_RENDERER_CAPSET_VENUS) return 0; #endif struct drm_virtgpu_context_init args = { .num_params = 1, .ctx_set_params = (uintptr_t) & (struct drm_virtgpu_context_set_param){ .param = VIRTGPU_CONTEXT_PARAM_CAPSET_ID, .value = capset_id, }, }; return virtgpu_ioctl(gpu, DRM_IOCTL_VIRTGPU_CONTEXT_INIT, &args); } static uint32_t virtgpu_ioctl_resource_create_blob(struct virtgpu *gpu, uint32_t blob_mem, uint32_t blob_flags, size_t blob_size, uint64_t blob_id, uint32_t *res_id) { #ifdef SIMULATE_BO_SIZE_FIX blob_size = align64(blob_size, 4096); #endif struct drm_virtgpu_resource_create_blob args = { .blob_mem = blob_mem, .blob_flags = blob_flags, .size = blob_size, .blob_id = blob_id, }; if (virtgpu_ioctl(gpu, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &args)) return 0; *res_id = args.res_handle; return args.bo_handle; } static int virtgpu_ioctl_resource_info(struct virtgpu *gpu, uint32_t gem_handle, struct drm_virtgpu_resource_info *info) { *info = (struct drm_virtgpu_resource_info){ .bo_handle = gem_handle, }; return virtgpu_ioctl(gpu, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, info); } static void virtgpu_ioctl_gem_close(struct virtgpu *gpu, uint32_t gem_handle) { struct drm_gem_close args = { .handle = gem_handle, }; ASSERTED const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_GEM_CLOSE, &args); assert(!ret); } static int virtgpu_ioctl_prime_handle_to_fd(struct virtgpu *gpu, uint32_t gem_handle, bool mappable) { struct drm_prime_handle args = { .handle = gem_handle, .flags = DRM_CLOEXEC | (mappable ? DRM_RDWR : 0), }; const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args); return ret ? -1 : args.fd; } static uint32_t virtgpu_ioctl_prime_fd_to_handle(struct virtgpu *gpu, int fd) { struct drm_prime_handle args = { .fd = fd, }; const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_PRIME_FD_TO_HANDLE, &args); return ret ? 0 : args.handle; } static void * virtgpu_ioctl_map(struct virtgpu *gpu, uint32_t gem_handle, size_t size) { struct drm_virtgpu_map args = { .handle = gem_handle, }; if (virtgpu_ioctl(gpu, DRM_IOCTL_VIRTGPU_MAP, &args)) return NULL; void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, gpu->fd, args.offset); if (ptr == MAP_FAILED) return NULL; return ptr; } static uint32_t virtgpu_ioctl_syncobj_create(struct virtgpu *gpu, bool signaled) { #ifdef SIMULATE_SYNCOBJ return sim_syncobj_create(gpu, signaled); #endif struct drm_syncobj_create args = { .flags = signaled ? DRM_SYNCOBJ_CREATE_SIGNALED : 0, }; const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_CREATE, &args); return ret ? 0 : args.handle; } static void virtgpu_ioctl_syncobj_destroy(struct virtgpu *gpu, uint32_t syncobj_handle) { #ifdef SIMULATE_SYNCOBJ sim_syncobj_destroy(gpu, syncobj_handle); return; #endif struct drm_syncobj_destroy args = { .handle = syncobj_handle, }; ASSERTED const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_DESTROY, &args); assert(!ret); } static int virtgpu_ioctl_syncobj_handle_to_fd(struct virtgpu *gpu, uint32_t syncobj_handle, bool sync_file) { #ifdef SIMULATE_SYNCOBJ return sync_file ? sim_syncobj_export(gpu, syncobj_handle) : -1; #endif struct drm_syncobj_handle args = { .handle = syncobj_handle, .flags = sync_file ? DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE : 0, }; int ret = virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &args); if (ret) return -1; return args.fd; } static uint32_t virtgpu_ioctl_syncobj_fd_to_handle(struct virtgpu *gpu, int fd, uint32_t syncobj_handle) { #ifdef SIMULATE_SYNCOBJ return syncobj_handle ? sim_syncobj_import(gpu, syncobj_handle, fd) : 0; #endif struct drm_syncobj_handle args = { .handle = syncobj_handle, .flags = syncobj_handle ? DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE : 0, .fd = fd, }; int ret = virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &args); if (ret) return 0; return args.handle; } static int virtgpu_ioctl_syncobj_reset(struct virtgpu *gpu, uint32_t syncobj_handle) { #ifdef SIMULATE_SYNCOBJ return sim_syncobj_reset(gpu, syncobj_handle); #endif struct drm_syncobj_array args = { .handles = (uintptr_t)&syncobj_handle, .count_handles = 1, }; return virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_RESET, &args); } static int virtgpu_ioctl_syncobj_query(struct virtgpu *gpu, uint32_t syncobj_handle, uint64_t *point) { #ifdef SIMULATE_SYNCOBJ return sim_syncobj_query(gpu, syncobj_handle, point); #endif struct drm_syncobj_timeline_array args = { .handles = (uintptr_t)&syncobj_handle, .points = (uintptr_t)point, .count_handles = 1, }; return virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_QUERY, &args); } static int virtgpu_ioctl_syncobj_timeline_signal(struct virtgpu *gpu, uint32_t syncobj_handle, uint64_t point) { #ifdef SIMULATE_SYNCOBJ return sim_syncobj_signal(gpu, syncobj_handle, point); #endif struct drm_syncobj_timeline_array args = { .handles = (uintptr_t)&syncobj_handle, .points = (uintptr_t)&point, .count_handles = 1, }; return virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &args); } static int virtgpu_ioctl_syncobj_timeline_wait(struct virtgpu *gpu, const struct vn_renderer_wait *wait, bool wait_avail) { #ifdef SIMULATE_SYNCOBJ return sim_syncobj_wait(gpu, wait, wait_avail); #endif /* always enable wait-before-submit */ uint32_t flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT; if (!wait->wait_any) flags |= DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL; /* wait for fences to appear instead of signaling */ if (wait_avail) flags |= DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE; /* TODO replace wait->syncs by wait->sync_handles to avoid malloc/loop */ uint32_t *syncobj_handles = malloc(sizeof(*syncobj_handles) * wait->sync_count); if (!syncobj_handles) return -1; for (uint32_t i = 0; i < wait->sync_count; i++) { struct virtgpu_sync *sync = (struct virtgpu_sync *)wait->syncs[i]; syncobj_handles[i] = sync->syncobj_handle; } struct drm_syncobj_timeline_wait args = { .handles = (uintptr_t)syncobj_handles, .points = (uintptr_t)wait->sync_values, .timeout_nsec = os_time_get_absolute_timeout(wait->timeout), .count_handles = wait->sync_count, .flags = flags, }; const int ret = virtgpu_ioctl(gpu, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, &args); free(syncobj_handles); return ret; } static int virtgpu_ioctl_submit(struct virtgpu *gpu, const struct vn_renderer_submit *submit) { #ifdef SIMULATE_SUBMIT return sim_submit(gpu, submit); #endif return -1; } static VkResult virtgpu_sync_write(struct vn_renderer *renderer, struct vn_renderer_sync *_sync, uint64_t val) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_sync *sync = (struct virtgpu_sync *)_sync; const int ret = virtgpu_ioctl_syncobj_timeline_signal(gpu, sync->syncobj_handle, val); return ret ? VK_ERROR_OUT_OF_DEVICE_MEMORY : VK_SUCCESS; } static VkResult virtgpu_sync_read(struct vn_renderer *renderer, struct vn_renderer_sync *_sync, uint64_t *val) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_sync *sync = (struct virtgpu_sync *)_sync; const int ret = virtgpu_ioctl_syncobj_query(gpu, sync->syncobj_handle, val); return ret ? VK_ERROR_OUT_OF_DEVICE_MEMORY : VK_SUCCESS; } static VkResult virtgpu_sync_reset(struct vn_renderer *renderer, struct vn_renderer_sync *_sync, uint64_t initial_val) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_sync *sync = (struct virtgpu_sync *)_sync; int ret = virtgpu_ioctl_syncobj_reset(gpu, sync->syncobj_handle); if (!ret) { ret = virtgpu_ioctl_syncobj_timeline_signal(gpu, sync->syncobj_handle, initial_val); } return ret ? VK_ERROR_OUT_OF_DEVICE_MEMORY : VK_SUCCESS; } static int virtgpu_sync_export_syncobj(struct vn_renderer *renderer, struct vn_renderer_sync *_sync, bool sync_file) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_sync *sync = (struct virtgpu_sync *)_sync; return virtgpu_ioctl_syncobj_handle_to_fd(gpu, sync->syncobj_handle, sync_file); } static void virtgpu_sync_destroy(struct vn_renderer *renderer, struct vn_renderer_sync *_sync) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_sync *sync = (struct virtgpu_sync *)_sync; virtgpu_ioctl_syncobj_destroy(gpu, sync->syncobj_handle); free(sync); } static VkResult virtgpu_sync_create_from_syncobj(struct vn_renderer *renderer, int fd, bool sync_file, struct vn_renderer_sync **out_sync) { struct virtgpu *gpu = (struct virtgpu *)renderer; uint32_t syncobj_handle; if (sync_file) { syncobj_handle = virtgpu_ioctl_syncobj_create(gpu, false); if (!syncobj_handle) return VK_ERROR_OUT_OF_HOST_MEMORY; if (!virtgpu_ioctl_syncobj_fd_to_handle(gpu, fd, syncobj_handle)) { virtgpu_ioctl_syncobj_destroy(gpu, syncobj_handle); return VK_ERROR_INVALID_EXTERNAL_HANDLE; } } else { syncobj_handle = virtgpu_ioctl_syncobj_fd_to_handle(gpu, fd, 0); if (!syncobj_handle) return VK_ERROR_INVALID_EXTERNAL_HANDLE; } struct virtgpu_sync *sync = calloc(1, sizeof(*sync)); if (!sync) { virtgpu_ioctl_syncobj_destroy(gpu, syncobj_handle); return VK_ERROR_OUT_OF_HOST_MEMORY; } sync->syncobj_handle = syncobj_handle; sync->base.sync_id = 0; /* TODO */ *out_sync = &sync->base; return VK_SUCCESS; } static VkResult virtgpu_sync_create(struct vn_renderer *renderer, uint64_t initial_val, uint32_t flags, struct vn_renderer_sync **out_sync) { struct virtgpu *gpu = (struct virtgpu *)renderer; /* TODO */ if (flags & VN_RENDERER_SYNC_SHAREABLE) return VK_ERROR_OUT_OF_DEVICE_MEMORY; /* always false because we don't use binary drm_syncobjs */ const bool signaled = false; const uint32_t syncobj_handle = virtgpu_ioctl_syncobj_create(gpu, signaled); if (!syncobj_handle) return VK_ERROR_OUT_OF_DEVICE_MEMORY; /* add a signaled fence chain with seqno initial_val */ const int ret = virtgpu_ioctl_syncobj_timeline_signal(gpu, syncobj_handle, initial_val); if (ret) { virtgpu_ioctl_syncobj_destroy(gpu, syncobj_handle); return VK_ERROR_OUT_OF_DEVICE_MEMORY; } struct virtgpu_sync *sync = calloc(1, sizeof(*sync)); if (!sync) { virtgpu_ioctl_syncobj_destroy(gpu, syncobj_handle); return VK_ERROR_OUT_OF_HOST_MEMORY; } sync->syncobj_handle = syncobj_handle; /* we will have a sync_id when shareable is true and virtio-gpu associates * a host sync object with guest drm_syncobj */ sync->base.sync_id = 0; *out_sync = &sync->base; return VK_SUCCESS; } static void virtgpu_bo_invalidate(struct vn_renderer *renderer, struct vn_renderer_bo *bo, VkDeviceSize offset, VkDeviceSize size) { /* nop because kernel makes every mapping coherent */ } static void virtgpu_bo_flush(struct vn_renderer *renderer, struct vn_renderer_bo *bo, VkDeviceSize offset, VkDeviceSize size) { /* nop because kernel makes every mapping coherent */ } static void * virtgpu_bo_map(struct vn_renderer *renderer, struct vn_renderer_bo *_bo) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_bo *bo = (struct virtgpu_bo *)_bo; const bool mappable = bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE; /* not thread-safe but is fine */ if (!bo->base.mmap_ptr && mappable) { bo->base.mmap_ptr = virtgpu_ioctl_map(gpu, bo->gem_handle, bo->base.mmap_size); } return bo->base.mmap_ptr; } static int virtgpu_bo_export_dma_buf(struct vn_renderer *renderer, struct vn_renderer_bo *_bo) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_bo *bo = (struct virtgpu_bo *)_bo; const bool mappable = bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE; const bool shareable = bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_SHAREABLE; return shareable ? virtgpu_ioctl_prime_handle_to_fd(gpu, bo->gem_handle, mappable) : -1; } static bool virtgpu_bo_destroy(struct vn_renderer *renderer, struct vn_renderer_bo *_bo) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_bo *bo = (struct virtgpu_bo *)_bo; mtx_lock(&gpu->dma_buf_import_mutex); /* Check the refcount again after the import lock is grabbed. Yes, we use * the double-checked locking anti-pattern. */ if (vn_refcount_is_valid(&bo->base.refcount)) { mtx_unlock(&gpu->dma_buf_import_mutex); return false; } if (bo->base.mmap_ptr) munmap(bo->base.mmap_ptr, bo->base.mmap_size); virtgpu_ioctl_gem_close(gpu, bo->gem_handle); /* set gem_handle to 0 to indicate that the bo is invalid */ bo->gem_handle = 0; mtx_unlock(&gpu->dma_buf_import_mutex); return true; } static uint32_t virtgpu_bo_blob_flags(VkMemoryPropertyFlags flags, VkExternalMemoryHandleTypeFlags external_handles) { uint32_t blob_flags = 0; if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) blob_flags |= VIRTGPU_BLOB_FLAG_USE_MAPPABLE; if (external_handles) blob_flags |= VIRTGPU_BLOB_FLAG_USE_SHAREABLE; if (external_handles & VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) blob_flags |= VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE; return blob_flags; } static VkResult virtgpu_bo_create_from_dma_buf(struct vn_renderer *renderer, VkDeviceSize size, int fd, VkMemoryPropertyFlags flags, struct vn_renderer_bo **out_bo) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct drm_virtgpu_resource_info info; uint32_t gem_handle = 0; struct virtgpu_bo *bo = NULL; mtx_lock(&gpu->dma_buf_import_mutex); gem_handle = virtgpu_ioctl_prime_fd_to_handle(gpu, fd); if (!gem_handle) goto fail; bo = util_sparse_array_get(&gpu->bo_array, gem_handle); if (virtgpu_ioctl_resource_info(gpu, gem_handle, &info)) goto fail; uint32_t blob_flags; size_t mmap_size; if (info.blob_mem) { /* must be VIRTGPU_BLOB_MEM_HOST3D or VIRTGPU_BLOB_MEM_GUEST_VRAM */ if (info.blob_mem != gpu->bo_blob_mem) goto fail; /* blob_flags is not passed to the kernel and is only for internal use * on imports. Set it to what works best for us. */ blob_flags = virtgpu_bo_blob_flags(flags, 0); blob_flags |= VIRTGPU_BLOB_FLAG_USE_SHAREABLE; /* mmap_size is only used when mappable */ mmap_size = 0; if (blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE) { if (info.size < size) goto fail; mmap_size = size; } } else { /* must be classic resource here * set blob_flags to 0 to fail virtgpu_bo_map * set mmap_size to 0 since mapping is not allowed */ blob_flags = 0; mmap_size = 0; } /* we check bo->gem_handle instead of bo->refcount because bo->refcount * might only be memset to 0 and is not considered initialized in theory */ if (bo->gem_handle == gem_handle) { if (bo->base.mmap_size < mmap_size) goto fail; if (blob_flags & ~bo->blob_flags) goto fail; /* we can't use vn_renderer_bo_ref as the refcount may drop to 0 * temporarily before virtgpu_bo_destroy grabs the lock */ vn_refcount_fetch_add_relaxed(&bo->base.refcount, 1); } else { *bo = (struct virtgpu_bo){ .base = { .refcount = VN_REFCOUNT_INIT(1), .res_id = info.res_handle, .mmap_size = mmap_size, }, .gem_handle = gem_handle, .blob_flags = blob_flags, }; } mtx_unlock(&gpu->dma_buf_import_mutex); *out_bo = &bo->base; return VK_SUCCESS; fail: if (gem_handle && bo->gem_handle != gem_handle) virtgpu_ioctl_gem_close(gpu, gem_handle); mtx_unlock(&gpu->dma_buf_import_mutex); return VK_ERROR_INVALID_EXTERNAL_HANDLE; } static VkResult virtgpu_bo_create_from_device_memory( struct vn_renderer *renderer, VkDeviceSize size, vn_object_id mem_id, VkMemoryPropertyFlags flags, VkExternalMemoryHandleTypeFlags external_handles, struct vn_renderer_bo **out_bo) { struct virtgpu *gpu = (struct virtgpu *)renderer; const uint32_t blob_flags = virtgpu_bo_blob_flags(flags, external_handles); uint32_t res_id; uint32_t gem_handle = virtgpu_ioctl_resource_create_blob( gpu, gpu->bo_blob_mem, blob_flags, size, mem_id, &res_id); if (!gem_handle) return VK_ERROR_OUT_OF_DEVICE_MEMORY; struct virtgpu_bo *bo = util_sparse_array_get(&gpu->bo_array, gem_handle); *bo = (struct virtgpu_bo){ .base = { .refcount = VN_REFCOUNT_INIT(1), .res_id = res_id, .mmap_size = size, }, .gem_handle = gem_handle, .blob_flags = blob_flags, }; *out_bo = &bo->base; return VK_SUCCESS; } static void virtgpu_shmem_destroy_now(struct vn_renderer *renderer, struct vn_renderer_shmem *_shmem) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct virtgpu_shmem *shmem = (struct virtgpu_shmem *)_shmem; munmap(shmem->base.mmap_ptr, shmem->base.mmap_size); virtgpu_ioctl_gem_close(gpu, shmem->gem_handle); } static void virtgpu_shmem_destroy(struct vn_renderer *renderer, struct vn_renderer_shmem *shmem) { struct virtgpu *gpu = (struct virtgpu *)renderer; if (vn_renderer_shmem_cache_add(&gpu->shmem_cache, shmem)) return; virtgpu_shmem_destroy_now(&gpu->base, shmem); } static struct vn_renderer_shmem * virtgpu_shmem_create(struct vn_renderer *renderer, size_t size) { struct virtgpu *gpu = (struct virtgpu *)renderer; struct vn_renderer_shmem *cached_shmem = vn_renderer_shmem_cache_get(&gpu->shmem_cache, size); if (cached_shmem) { cached_shmem->refcount = VN_REFCOUNT_INIT(1); return cached_shmem; } uint32_t res_id; uint32_t gem_handle = virtgpu_ioctl_resource_create_blob( gpu, gpu->shmem_blob_mem, VIRTGPU_BLOB_FLAG_USE_MAPPABLE, size, 0, &res_id); if (!gem_handle) return NULL; void *ptr = virtgpu_ioctl_map(gpu, gem_handle, size); if (!ptr) { virtgpu_ioctl_gem_close(gpu, gem_handle); return NULL; } struct virtgpu_shmem *shmem = util_sparse_array_get(&gpu->shmem_array, gem_handle); *shmem = (struct virtgpu_shmem){ .base = { .refcount = VN_REFCOUNT_INIT(1), .res_id = res_id, .mmap_size = size, .mmap_ptr = ptr, }, .gem_handle = gem_handle, }; return &shmem->base; } static VkResult virtgpu_wait(struct vn_renderer *renderer, const struct vn_renderer_wait *wait) { struct virtgpu *gpu = (struct virtgpu *)renderer; const int ret = virtgpu_ioctl_syncobj_timeline_wait(gpu, wait, false); if (ret && errno != ETIME) return VK_ERROR_DEVICE_LOST; return ret ? VK_TIMEOUT : VK_SUCCESS; } static VkResult virtgpu_submit(struct vn_renderer *renderer, const struct vn_renderer_submit *submit) { struct virtgpu *gpu = (struct virtgpu *)renderer; const int ret = virtgpu_ioctl_submit(gpu, submit); return ret ? VK_ERROR_DEVICE_LOST : VK_SUCCESS; } static void virtgpu_init_renderer_info(struct virtgpu *gpu) { struct vn_renderer_info *info = &gpu->base.info; info->drm.has_primary = gpu->has_primary; info->drm.primary_major = gpu->primary_major; info->drm.primary_minor = gpu->primary_minor; info->drm.has_render = true; info->drm.render_major = gpu->render_major; info->drm.render_minor = gpu->render_minor; info->pci.vendor_id = VIRTGPU_PCI_VENDOR_ID; info->pci.device_id = VIRTGPU_PCI_DEVICE_ID; if (gpu->bustype == DRM_BUS_PCI) { info->pci.has_bus_info = true; info->pci.domain = gpu->pci_bus_info.domain; info->pci.bus = gpu->pci_bus_info.bus; info->pci.device = gpu->pci_bus_info.dev; info->pci.function = gpu->pci_bus_info.func; } else { info->pci.has_bus_info = false; } info->has_dma_buf_import = true; /* Kernel makes every mapping coherent. We are better off filtering * incoherent memory types out than silently making them coherent. */ info->has_cache_management = false; /* TODO drm_syncobj */ info->has_external_sync = false; info->has_implicit_fencing = false; info->max_sync_queue_count = gpu->max_sync_queue_count; const struct virgl_renderer_capset_venus *capset = &gpu->capset.data; info->wire_format_version = capset->wire_format_version; info->vk_xml_version = capset->vk_xml_version; info->vk_ext_command_serialization_spec_version = capset->vk_ext_command_serialization_spec_version; info->vk_mesa_venus_protocol_spec_version = capset->vk_mesa_venus_protocol_spec_version; info->supports_blob_id_0 = capset->supports_blob_id_0; /* ensure vk_extension_mask is large enough to hold all capset masks */ STATIC_ASSERT(sizeof(info->vk_extension_mask) >= sizeof(capset->vk_extension_mask1)); memcpy(info->vk_extension_mask, capset->vk_extension_mask1, sizeof(capset->vk_extension_mask1)); info->allow_vk_wait_syncs = capset->allow_vk_wait_syncs; if (gpu->bo_blob_mem == VIRTGPU_BLOB_MEM_GUEST_VRAM) info->has_guest_vram = true; } static void virtgpu_destroy(struct vn_renderer *renderer, const VkAllocationCallbacks *alloc) { struct virtgpu *gpu = (struct virtgpu *)renderer; vn_renderer_shmem_cache_fini(&gpu->shmem_cache); if (gpu->fd >= 0) close(gpu->fd); mtx_destroy(&gpu->dma_buf_import_mutex); util_sparse_array_finish(&gpu->shmem_array); util_sparse_array_finish(&gpu->bo_array); vk_free(alloc, gpu); } static void virtgpu_init_shmem_blob_mem(struct virtgpu *gpu) { /* VIRTGPU_BLOB_MEM_GUEST allocates from the guest system memory. They are * logically contiguous in the guest but are sglists (iovecs) in the host. * That makes them slower to process in the host. With host process * isolation, it also becomes impossible for the host to access sglists * directly. * * While there are ideas (and shipped code in some cases) such as creating * udmabufs from sglists, or having a dedicated guest heap, it seems the * easiest way is to reuse VIRTGPU_BLOB_MEM_HOST3D. That is, when the * renderer sees a request to export a blob where * * - blob_mem is VIRTGPU_BLOB_MEM_HOST3D * - blob_flags is VIRTGPU_BLOB_FLAG_USE_MAPPABLE * - blob_id is 0 * * it allocates a host shmem. * * TODO cache shmems as they are costly to set up and usually require syncs */ gpu->shmem_blob_mem = gpu->capset.data.supports_blob_id_0 ? VIRTGPU_BLOB_MEM_HOST3D : VIRTGPU_BLOB_MEM_GUEST; } static VkResult virtgpu_init_context(struct virtgpu *gpu) { assert(!gpu->capset.version); const int ret = virtgpu_ioctl_context_init(gpu, gpu->capset.id); if (ret) { if (VN_DEBUG(INIT)) { vn_log(gpu->instance, "failed to initialize context: %s", strerror(errno)); } return VK_ERROR_INITIALIZATION_FAILED; } return VK_SUCCESS; } static VkResult virtgpu_init_capset(struct virtgpu *gpu) { gpu->capset.id = VIRGL_RENDERER_CAPSET_VENUS; gpu->capset.version = 0; const int ret = virtgpu_ioctl_get_caps(gpu, gpu->capset.id, gpu->capset.version, &gpu->capset.data, sizeof(gpu->capset.data)); if (ret) { if (VN_DEBUG(INIT)) { vn_log(gpu->instance, "failed to get venus v%d capset: %s", gpu->capset.version, strerror(errno)); } return VK_ERROR_INITIALIZATION_FAILED; } return VK_SUCCESS; } static VkResult virtgpu_init_params(struct virtgpu *gpu) { const uint64_t required_params[] = { VIRTGPU_PARAM_3D_FEATURES, VIRTGPU_PARAM_CAPSET_QUERY_FIX, VIRTGPU_PARAM_RESOURCE_BLOB, VIRTGPU_PARAM_CROSS_DEVICE, VIRTGPU_PARAM_CONTEXT_INIT, }; uint64_t val; for (uint32_t i = 0; i < ARRAY_SIZE(required_params); i++) { val = virtgpu_ioctl_getparam(gpu, required_params[i]); if (!val) { if (VN_DEBUG(INIT)) { vn_log(gpu->instance, "required kernel param %d is missing", (int)required_params[i]); } return VK_ERROR_INITIALIZATION_FAILED; } } val = virtgpu_ioctl_getparam(gpu, VIRTGPU_PARAM_HOST_VISIBLE); if (val) { gpu->bo_blob_mem = VIRTGPU_BLOB_MEM_HOST3D; } else { val = virtgpu_ioctl_getparam(gpu, VIRTGPU_PARAM_GUEST_VRAM); if (val) { gpu->bo_blob_mem = VIRTGPU_BLOB_MEM_GUEST_VRAM; } } if (!val) { vn_log(gpu->instance, "one of required kernel params (%d or %d) is missing", (int)VIRTGPU_PARAM_HOST_VISIBLE, (int)VIRTGPU_PARAM_GUEST_VRAM); return VK_ERROR_INITIALIZATION_FAILED; } val = virtgpu_ioctl_getparam(gpu, VIRTGPU_PARAM_MAX_SYNC_QUEUE_COUNT); if (!val) { if (VN_DEBUG(INIT)) vn_log(gpu->instance, "no sync queue support"); return VK_ERROR_INITIALIZATION_FAILED; } gpu->max_sync_queue_count = val; return VK_SUCCESS; } static VkResult virtgpu_open_device(struct virtgpu *gpu, const drmDevicePtr dev) { bool supported_bus = false; switch (dev->bustype) { case DRM_BUS_PCI: if (dev->deviceinfo.pci->vendor_id == VIRTGPU_PCI_VENDOR_ID && dev->deviceinfo.pci->device_id == VIRTGPU_PCI_DEVICE_ID) supported_bus = true; break; case DRM_BUS_PLATFORM: supported_bus = true; break; default: break; } if (!supported_bus || !(dev->available_nodes & (1 << DRM_NODE_RENDER))) { if (VN_DEBUG(INIT)) { const char *name = "unknown"; for (uint32_t i = 0; i < DRM_NODE_MAX; i++) { if (dev->available_nodes & (1 << i)) { name = dev->nodes[i]; break; } } vn_log(gpu->instance, "skipping DRM device %s", name); } return VK_ERROR_INITIALIZATION_FAILED; } const char *primary_path = dev->nodes[DRM_NODE_PRIMARY]; const char *node_path = dev->nodes[DRM_NODE_RENDER]; int fd = open(node_path, O_RDWR | O_CLOEXEC); if (fd < 0) { if (VN_DEBUG(INIT)) vn_log(gpu->instance, "failed to open %s", node_path); return VK_ERROR_INITIALIZATION_FAILED; } drmVersionPtr version = drmGetVersion(fd); if (!version || strcmp(version->name, "virtio_gpu") || version->version_major != 0) { if (VN_DEBUG(INIT)) { if (version) { vn_log(gpu->instance, "unknown DRM driver %s version %d", version->name, version->version_major); } else { vn_log(gpu->instance, "failed to get DRM driver version"); } } if (version) drmFreeVersion(version); close(fd); return VK_ERROR_INITIALIZATION_FAILED; } gpu->fd = fd; struct stat st; if (stat(primary_path, &st) == 0) { gpu->has_primary = true; gpu->primary_major = major(st.st_rdev); gpu->primary_minor = minor(st.st_rdev); } else { gpu->has_primary = false; gpu->primary_major = 0; gpu->primary_minor = 0; } stat(node_path, &st); gpu->render_major = major(st.st_rdev); gpu->render_minor = minor(st.st_rdev); gpu->bustype = dev->bustype; if (dev->bustype == DRM_BUS_PCI) gpu->pci_bus_info = *dev->businfo.pci; drmFreeVersion(version); if (VN_DEBUG(INIT)) vn_log(gpu->instance, "using DRM device %s", node_path); return VK_SUCCESS; } static VkResult virtgpu_open(struct virtgpu *gpu) { drmDevicePtr devs[8]; int count = drmGetDevices2(0, devs, ARRAY_SIZE(devs)); if (count < 0) { if (VN_DEBUG(INIT)) vn_log(gpu->instance, "failed to enumerate DRM devices"); return VK_ERROR_INITIALIZATION_FAILED; } VkResult result = VK_ERROR_INITIALIZATION_FAILED; for (int i = 0; i < count; i++) { result = virtgpu_open_device(gpu, devs[i]); if (result == VK_SUCCESS) break; } drmFreeDevices(devs, count); return result; } static VkResult virtgpu_init(struct virtgpu *gpu) { util_sparse_array_init(&gpu->shmem_array, sizeof(struct virtgpu_shmem), 1024); util_sparse_array_init(&gpu->bo_array, sizeof(struct virtgpu_bo), 1024); mtx_init(&gpu->dma_buf_import_mutex, mtx_plain); VkResult result = virtgpu_open(gpu); if (result == VK_SUCCESS) result = virtgpu_init_params(gpu); if (result == VK_SUCCESS) result = virtgpu_init_capset(gpu); if (result == VK_SUCCESS) result = virtgpu_init_context(gpu); if (result != VK_SUCCESS) return result; virtgpu_init_shmem_blob_mem(gpu); vn_renderer_shmem_cache_init(&gpu->shmem_cache, &gpu->base, virtgpu_shmem_destroy_now); virtgpu_init_renderer_info(gpu); gpu->base.ops.destroy = virtgpu_destroy; gpu->base.ops.submit = virtgpu_submit; gpu->base.ops.wait = virtgpu_wait; gpu->base.shmem_ops.create = virtgpu_shmem_create; gpu->base.shmem_ops.destroy = virtgpu_shmem_destroy; gpu->base.bo_ops.create_from_device_memory = virtgpu_bo_create_from_device_memory; gpu->base.bo_ops.create_from_dma_buf = virtgpu_bo_create_from_dma_buf; gpu->base.bo_ops.destroy = virtgpu_bo_destroy; gpu->base.bo_ops.export_dma_buf = virtgpu_bo_export_dma_buf; gpu->base.bo_ops.map = virtgpu_bo_map; gpu->base.bo_ops.flush = virtgpu_bo_flush; gpu->base.bo_ops.invalidate = virtgpu_bo_invalidate; gpu->base.sync_ops.create = virtgpu_sync_create; gpu->base.sync_ops.create_from_syncobj = virtgpu_sync_create_from_syncobj; gpu->base.sync_ops.destroy = virtgpu_sync_destroy; gpu->base.sync_ops.export_syncobj = virtgpu_sync_export_syncobj; gpu->base.sync_ops.reset = virtgpu_sync_reset; gpu->base.sync_ops.read = virtgpu_sync_read; gpu->base.sync_ops.write = virtgpu_sync_write; return VK_SUCCESS; } VkResult vn_renderer_create_virtgpu(struct vn_instance *instance, const VkAllocationCallbacks *alloc, struct vn_renderer **renderer) { struct virtgpu *gpu = vk_zalloc(alloc, sizeof(*gpu), VN_DEFAULT_ALIGN, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); if (!gpu) return VK_ERROR_OUT_OF_HOST_MEMORY; gpu->instance = instance; gpu->fd = -1; VkResult result = virtgpu_init(gpu); if (result != VK_SUCCESS) { virtgpu_destroy(&gpu->base, alloc); return result; } *renderer = &gpu->base; return VK_SUCCESS; }