mesa/src/amd/common/virtio/amdgpu_virtio_device.c
Pierre-Eric Pelloux-Prayer 22263616ed amd: amdgpu-virtio implementation
Native context support is implemented by diverting the libdrm_amdgpu functions
into new functions that use virtio-gpu.
VA allocations are done directly in the guest, using newly exposed libdrm_amdgpu
helpers (retrieved through dlopen/dlsym).

Guest <-> Host roundtrips can be expensive so we try to avoid them as much as
possible. When possible we also don't wait for the host reply in case where
it's not needed to get correct result.

Implicit sync works because virtio-gpu commands are submitted in order to the
host (there a single queue per device, shared by all the guest processes).

virtio-gpu also only supports one context per file description (but multiple
file descriptions per process) while amdgpu only allows one fd per process,
but multiple contexts per fd. This causes synchronization problems, because
virtio-gpu drops all sync primitive if they belong to the same fd/context/ring:
ie the amdgpu_ctx can't be expressed in virtio-gpu terms.

For now the solution is to only allocate a single amdgpu_ctx per application.

Contrary to radeonsi/radv, amdgpu_virtio can use libdrm_amdgpu directly: the
ones that don't rely on ioctl() are safe to use here.

Tested-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Reviewed-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Reviewed-by: Marek Olšák <marek.olsak@amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/21658>
2025-01-16 12:24:32 +00:00

188 lines
5.5 KiB
C

/*
* Copyright 2024 Advanced Micro Devices, Inc.
*
* SPDX-License-Identifier: MIT
*/
#include "amdgpu_virtio_private.h"
#include "util/bitscan.h"
#include "util/log.h"
#include "util/os_file.h"
#include "util/u_debug.h"
#include <xf86drm.h>
/* amdvgpu_device manage the virtual GPU.
*
* It owns a vdrm_device instance, the rings and manage seqno.
* Since it's a drop-in replacement for libdrm_amdgpu's amdgpu_device,
* it follows its behavior: if the same device is opened multiple times,
* the same amdvgpu_device will be used.
*/
static simple_mtx_t dev_mutex = SIMPLE_MTX_INITIALIZER;
static amdvgpu_device_handle dev_list;
static int fd_compare(int fd1, int fd2)
{
char *name1 = drmGetPrimaryDeviceNameFromFd(fd1);
char *name2 = drmGetPrimaryDeviceNameFromFd(fd2);
int result;
if (name1 == NULL || name2 == NULL) {
free(name1);
free(name2);
return 0;
}
result = strcmp(name1, name2);
free(name1);
free(name2);
return result;
}
static void amdvgpu_device_reference(struct amdvgpu_device **dst,
struct amdvgpu_device *src)
{
if (update_references(*dst ? &(*dst)->refcount : NULL,
src ? &src->refcount : NULL)) {
struct amdvgpu_device *dev, *prev = NULL;
for (dev = dev_list; dev; dev = dev->next) {
if (dev == (*dst)) {
if (prev == NULL)
dev_list = dev->next;
else
prev->next = dev->next;
break;
}
prev = dev;
}
dev = *dst;
/* Destroy BOs before closing vdrm */
hash_table_foreach(dev->handle_to_vbo, entry) {
struct amdvgpu_bo *bo = entry->data;
amdvgpu_bo_free(dev, bo);
}
_mesa_hash_table_destroy(dev->handle_to_vbo, NULL);
/* Destroy contextx. */
hash_table_foreach(&dev->contexts, entry)
amdvgpu_cs_ctx_free(dev, (uint32_t)(uintptr_t)entry->key);
_mesa_hash_table_clear(&dev->contexts, NULL);
simple_mtx_destroy(&dev->handle_to_vbo_mutex);
simple_mtx_destroy(&dev->contexts_mutex);
amdgpu_va_manager_deinit(dev->va_mgr);
vdrm_device_close(dev->vdev);
close(dev->fd);
free(dev);
}
*dst = src;
}
int amdvgpu_device_deinitialize(amdvgpu_device_handle dev) {
simple_mtx_lock(&dev_mutex);
amdvgpu_device_reference(&dev, NULL);
simple_mtx_unlock(&dev_mutex);
return 0;
}
int amdvgpu_device_initialize(int fd, uint32_t *drm_major, uint32_t *drm_minor,
amdvgpu_device_handle* dev_out) {
simple_mtx_lock(&dev_mutex);
amdvgpu_device_handle dev;
for (dev = dev_list; dev; dev = dev->next)
if (fd_compare(dev->fd, fd) == 0)
break;
if (dev) {
*dev_out = NULL;
amdvgpu_device_reference(dev_out, dev);
*drm_major = dev->vdev->caps.version_major;
*drm_minor = dev->vdev->caps.version_minor;
simple_mtx_unlock(&dev_mutex);
return 0;
}
/* fd is owned by the amdgpu_screen_winsys that called this function.
* amdgpu_screen_winsys' lifetime may be shorter than the device's one,
* so dup fd to tie its lifetime to the device's one.
*/
fd = os_dupfd_cloexec(fd);
struct vdrm_device *vdev = vdrm_device_connect(fd, VIRTGPU_DRM_CONTEXT_AMDGPU);
if (vdev == NULL) {
mesa_loge("vdrm_device_connect failed\n");
simple_mtx_unlock(&dev_mutex);
return -1;
}
dev = calloc(1, sizeof(struct amdvgpu_device));
dev->refcount = 1;
dev->next = dev_list;
dev_list = dev;
dev->fd = fd;
dev->vdev = vdev;
simple_mtx_init(&dev->handle_to_vbo_mutex, mtx_plain);
simple_mtx_init(&dev->contexts_mutex, mtx_plain);
dev->handle_to_vbo = _mesa_hash_table_create_u32_keys(NULL);
p_atomic_set(&dev->next_blob_id, 1);
*dev_out = dev;
simple_mtx_unlock(&dev_mutex);
struct drm_amdgpu_info info;
info.return_pointer = (uintptr_t)&dev->dev_info;
info.query = AMDGPU_INFO_DEV_INFO;
info.return_size = sizeof(dev->dev_info);
int r = amdvgpu_query_info(dev, &info);
assert(r == 0);
/* Ring idx 0 is reserved for commands running on CPU. */
unsigned next_ring_idx = 1;
for (unsigned i = 0; i < AMD_NUM_IP_TYPES; ++i) {
struct drm_amdgpu_info_hw_ip ip_info = {0};
struct drm_amdgpu_info request = {0};
request.return_pointer = (uintptr_t)&ip_info;
request.return_size = sizeof(ip_info);
request.query = AMDGPU_INFO_HW_IP_INFO;
request.query_hw_ip.type = i;
request.query_hw_ip.ip_instance = 0;
r = amdvgpu_query_info(dev, &request);
if (r == 0 && ip_info.available_rings) {
int count = util_bitcount(ip_info.available_rings);
dev->virtio_ring_mapping[i] = next_ring_idx;
next_ring_idx += count;
}
}
/* VIRTGPU_CONTEXT_PARAM_NUM_RINGS is hardcoded for now. */
assert(next_ring_idx <= 64);
dev->num_virtio_rings = next_ring_idx - 1;
dev->va_mgr = amdgpu_va_manager_alloc();
amdgpu_va_manager_init(dev->va_mgr,
dev->dev_info.virtual_address_offset, dev->dev_info.virtual_address_max,
dev->dev_info.high_va_offset, dev->dev_info.high_va_max,
dev->dev_info.virtual_address_alignment);
_mesa_hash_table_init(&dev->contexts, NULL,
_mesa_hash_pointer, _mesa_key_pointer_equal);
dev->allow_multiple_amdgpu_ctx = debug_get_bool_option("MULTIPLE_AMDGPU_CTX", false);
dev->sync_cmd = debug_get_num_option("VIRTIO_SYNC_CMD", 0);
*drm_major = dev->vdev->caps.version_major;
*drm_minor = dev->vdev->caps.version_minor;
return 0;
}