2025-07-08 16:06:48 +08:00
|
|
|
/*
|
|
|
|
|
* Copyright 2025 Advanced Micro Devices, Inc.
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
|
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
|
|
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
|
|
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
|
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <libsync.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stddef.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
|
|
#include "virtio_gpu.h"
|
|
|
|
|
|
2025-11-28 09:20:43 +08:00
|
|
|
#define SHMEM_SZ (80 * 0x1000)
|
2025-07-08 16:06:48 +08:00
|
|
|
|
|
|
|
|
static int set_context(int fd) {
|
|
|
|
|
struct drm_virtgpu_context_set_param params[] = {
|
|
|
|
|
{VIRTGPU_CONTEXT_PARAM_CAPSET_ID, VIRGL_RENDERER_CAPSET_HSAKMT},
|
|
|
|
|
{VIRTGPU_CONTEXT_PARAM_NUM_RINGS, 64},
|
|
|
|
|
};
|
|
|
|
|
struct drm_virtgpu_context_init args = {
|
|
|
|
|
.num_params = ARRAY_SIZE(params),
|
|
|
|
|
.ctx_set_params = (uintptr_t)(params),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return virtio_gpu_ioctl(fd, VIRTGPU_CONTEXT_INIT, &args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int virtio_gpu_map_handle(struct virtio_gpu_device* vgdev, uint32_t handle, uint64_t size,
|
|
|
|
|
void** addr, void* fixed_map) {
|
|
|
|
|
struct drm_virtgpu_map args = {
|
|
|
|
|
.handle = handle,
|
|
|
|
|
};
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
r = virtio_gpu_ioctl(vgdev->fd, VIRTGPU_MAP, &args);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
|
|
|
|
|
*addr = mmap(fixed_map, size, PROT_READ | PROT_WRITE, MAP_SHARED | (fixed_map ? MAP_FIXED : 0),
|
|
|
|
|
vgdev->fd, args.offset);
|
|
|
|
|
|
|
|
|
|
if (*addr == MAP_FAILED) return -EINVAL;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void virtio_gpu_unmap(void* addr, uint64_t size) { munmap(addr, size); }
|
|
|
|
|
|
|
|
|
|
static void virtio_gpu_bo_close(struct virtio_gpu_device* vgdev, uint32_t handle) {
|
|
|
|
|
struct drm_gem_close args = {
|
|
|
|
|
.handle = handle,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
virtio_gpu_ioctl(vgdev->fd, GEM_CLOSE, &args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int virtio_gpu_shmem_init(struct virtio_gpu_device* vgdev, size_t size) {
|
|
|
|
|
struct drm_virtgpu_resource_create_blob args = {
|
|
|
|
|
.blob_mem = VIRTGPU_BLOB_MEM_HOST3D,
|
|
|
|
|
.blob_flags = VIRTGPU_BLOB_FLAG_USE_MAPPABLE,
|
|
|
|
|
.size = size,
|
|
|
|
|
.blob_id = 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int r = virtio_gpu_ioctl(vgdev->fd, VIRTGPU_RESOURCE_CREATE_BLOB, &args);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
|
|
|
|
|
r = virtio_gpu_map_handle(vgdev, args.bo_handle, size, (void**)&vgdev->shmem, NULL);
|
|
|
|
|
if (r) {
|
|
|
|
|
virtio_gpu_bo_close(vgdev, args.bo_handle);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vgdev->shmem_handle = args.bo_handle;
|
|
|
|
|
|
|
|
|
|
uint32_t offset = vgdev->shmem->base.rsp_mem_offset;
|
|
|
|
|
vgdev->rsp_mem_len = size - offset;
|
|
|
|
|
vgdev->rsp_mem = &((uint8_t*)vgdev->shmem)[offset];
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct virtio_gpu_device* virtio_gpu_init(int fd, uint32_t context_id) {
|
|
|
|
|
struct virtio_gpu_device* vgdev;
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
r = set_context(fd);
|
|
|
|
|
|
|
|
|
|
if (r) return NULL;
|
|
|
|
|
|
|
|
|
|
vgdev = calloc(1, sizeof(*vgdev));
|
|
|
|
|
if (!vgdev) return NULL;
|
|
|
|
|
|
|
|
|
|
vgdev->fd = fd;
|
|
|
|
|
|
|
|
|
|
vgdev->reqbuf = calloc(1, SHMEM_SZ);
|
|
|
|
|
if (!vgdev->reqbuf) {
|
|
|
|
|
free(vgdev);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r = virtio_gpu_shmem_init(vgdev, SHMEM_SZ);
|
|
|
|
|
if (r) {
|
|
|
|
|
free(vgdev);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pthread_mutex_init(&vgdev->rsp_lock, NULL);
|
|
|
|
|
pthread_mutex_init(&vgdev->eb_lock, NULL);
|
|
|
|
|
|
|
|
|
|
return vgdev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void virtio_gpu_close(struct virtio_gpu_device* vgdev) {
|
|
|
|
|
virtio_gpu_unmap(vgdev->shmem, SHMEM_SZ);
|
|
|
|
|
virtio_gpu_bo_close(vgdev, vgdev->shmem_handle);
|
|
|
|
|
|
|
|
|
|
pthread_mutex_destroy(&vgdev->rsp_lock);
|
|
|
|
|
pthread_mutex_destroy(&vgdev->eb_lock);
|
|
|
|
|
|
|
|
|
|
close(vgdev->fd);
|
|
|
|
|
free(vgdev->reqbuf);
|
|
|
|
|
free(vgdev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void* virtio_gpu_alloc_rsp(struct virtio_gpu_device* vgdev, struct virtio_gpu_ccmd_req* req,
|
|
|
|
|
uint32_t size) {
|
|
|
|
|
uint32_t off;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&vgdev->rsp_lock);
|
|
|
|
|
|
|
|
|
|
size = VHSA_ALIGN_UP(size, 8);
|
|
|
|
|
|
|
|
|
|
if ((vgdev->next_rsp_off + size) >= vgdev->rsp_mem_len) vgdev->next_rsp_off = 0;
|
|
|
|
|
|
|
|
|
|
off = vgdev->next_rsp_off;
|
|
|
|
|
vgdev->next_rsp_off += size;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_unlock(&vgdev->rsp_lock);
|
|
|
|
|
|
|
|
|
|
req->rsp_off = off;
|
|
|
|
|
struct virtio_gpu_ccmd_rsp* rsp = (void*)&vgdev->rsp_mem[off];
|
|
|
|
|
rsp->len = size;
|
|
|
|
|
|
|
|
|
|
return rsp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int virtio_gpu_execbuffer_locked(struct virtio_gpu_device* vgdev, void* cmd,
|
|
|
|
|
uint32_t cmd_size, uint32_t* handles, uint32_t num_handles,
|
|
|
|
|
int* fence_fd, int ring_idx, uint32_t num_in_syncobjs,
|
|
|
|
|
uint32_t num_out_syncobjs,
|
|
|
|
|
struct drm_virtgpu_execbuffer_syncobj* in_syncobjs,
|
|
|
|
|
struct drm_virtgpu_execbuffer_syncobj* out_syncobjs,
|
|
|
|
|
bool in_fence, bool out_fence) {
|
|
|
|
|
struct drm_virtgpu_execbuffer eb = {
|
|
|
|
|
.flags = (out_fence ? VIRTGPU_EXECBUF_FENCE_FD_OUT : 0) |
|
|
|
|
|
(in_fence ? VIRTGPU_EXECBUF_FENCE_FD_IN : 0) | VIRTGPU_EXECBUF_RING_IDX,
|
|
|
|
|
.size = cmd_size,
|
|
|
|
|
.command = (uintptr_t)cmd,
|
|
|
|
|
.bo_handles = (uintptr_t)handles,
|
|
|
|
|
.num_bo_handles = num_handles,
|
|
|
|
|
.fence_fd = *fence_fd,
|
|
|
|
|
.ring_idx = ring_idx,
|
|
|
|
|
.syncobj_stride = sizeof(struct drm_virtgpu_execbuffer_syncobj),
|
|
|
|
|
.num_in_syncobjs = num_in_syncobjs,
|
|
|
|
|
.num_out_syncobjs = num_out_syncobjs,
|
|
|
|
|
.in_syncobjs = (uintptr_t)in_syncobjs,
|
|
|
|
|
.out_syncobjs = (uintptr_t)out_syncobjs,
|
|
|
|
|
};
|
|
|
|
|
int r = virtio_gpu_ioctl(vgdev->fd, VIRTGPU_EXECBUFFER, &eb);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
|
|
|
|
|
if (out_fence) *fence_fd = eb.fence_fd;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int virtio_gpu_flush_locked(struct virtio_gpu_device* vgdev, int* fence) {
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
if (!vgdev->reqbuf_len) return 0;
|
|
|
|
|
|
|
|
|
|
r = virtio_gpu_execbuffer_locked(vgdev, vgdev->reqbuf, vgdev->reqbuf_len, NULL, 0, fence, 0, 0, 0,
|
|
|
|
|
NULL, NULL, false, !!fence);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
|
|
|
|
|
vgdev->reqbuf_len = 0;
|
|
|
|
|
vgdev->reqbuf_cnt = 0;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int virtio_gpu_add_cmd(struct virtio_gpu_device* vgdev, struct virtio_gpu_ccmd_req* req) {
|
|
|
|
|
req->seqno = ++vgdev->next_seqno;
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
if (vgdev->reqbuf_len + req->len > sizeof(vgdev->reqbuf)) {
|
|
|
|
|
r = virtio_gpu_flush_locked(vgdev, NULL);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy(&vgdev->reqbuf[vgdev->reqbuf_len], req, req->len);
|
|
|
|
|
vgdev->reqbuf_len += req->len;
|
|
|
|
|
vgdev->reqbuf_cnt++;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline bool fence_before(uint32_t a, uint32_t b) { return (int32_t)(a - b) < 0; }
|
|
|
|
|
|
|
|
|
|
static void virtio_gpu_seqno_sync(struct virtio_gpu_device* vgdev,
|
|
|
|
|
struct virtio_gpu_ccmd_req* req) {
|
|
|
|
|
while (fence_before(vgdev->shmem->base.seqno, req->seqno)) sched_yield();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int virtio_gpu_exec_cmd(struct virtio_gpu_device* vgdev, struct virtio_gpu_ccmd_req* req,
|
|
|
|
|
bool sync) {
|
|
|
|
|
int r = 0;
|
|
|
|
|
int fence;
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&vgdev->eb_lock);
|
|
|
|
|
|
|
|
|
|
r = virtio_gpu_add_cmd(vgdev, req);
|
|
|
|
|
|
|
|
|
|
if (r || !sync) goto out;
|
|
|
|
|
|
|
|
|
|
r = virtio_gpu_flush_locked(vgdev, &fence);
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
pthread_mutex_unlock(&vgdev->eb_lock);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
|
|
|
|
|
if (sync) {
|
|
|
|
|
sync_wait(fence, -1);
|
|
|
|
|
close(fence);
|
|
|
|
|
virtio_gpu_seqno_sync(vgdev, req);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int virtio_gpu_create_blob(struct virtio_gpu_device* vgdev,
|
|
|
|
|
struct drm_virtgpu_resource_create_blob* args) {
|
|
|
|
|
return virtio_gpu_ioctl(vgdev->fd, VIRTGPU_RESOURCE_CREATE_BLOB, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int virtio_gpu_destroy_handle(struct virtio_gpu_device* vgdev, uint32_t bo_handle) {
|
|
|
|
|
struct drm_gem_close args = {
|
|
|
|
|
.handle = bo_handle,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return virtio_gpu_ioctl(vgdev->fd, GEM_CLOSE, &args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int virtio_gpu_res_id(struct virtio_gpu_device* vgdev, uint32_t handle, uint32_t* res_id) {
|
|
|
|
|
struct drm_virtgpu_resource_info args = {
|
|
|
|
|
.bo_handle = handle,
|
|
|
|
|
};
|
|
|
|
|
int r = virtio_gpu_ioctl(vgdev->fd, VIRTGPU_RESOURCE_INFO, &args);
|
|
|
|
|
if (r) return r;
|
|
|
|
|
|
|
|
|
|
*res_id = args.res_handle;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int virtio_gpu_get_capset(int fd, struct virgl_renderer_capset_hsakmt* caps) {
|
|
|
|
|
struct drm_virtgpu_get_caps args = {
|
|
|
|
|
.cap_set_id = VIRGL_RENDERER_CAPSET_HSAKMT,
|
|
|
|
|
.cap_set_ver = 0,
|
|
|
|
|
.addr = (uintptr_t)caps,
|
|
|
|
|
.size = sizeof(*caps),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
memset(caps, 0, sizeof(*caps));
|
|
|
|
|
|
|
|
|
|
return virtio_gpu_ioctl(fd, VIRTGPU_GET_CAPS, &args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int virtio_gpu_kfd_open(void) {
|
|
|
|
|
drmDevicePtr devices[VHSA_MAX_DEVICES];
|
|
|
|
|
int num_devices = 0;
|
|
|
|
|
int i, fd, ret;
|
|
|
|
|
|
|
|
|
|
num_devices = drmGetDevices2(0, devices, ARRAY_SIZE(devices));
|
|
|
|
|
if (num_devices <= 0) return -1;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < num_devices; i++) {
|
|
|
|
|
fd = open(devices[i]->nodes[DRM_NODE_RENDER], O_RDWR | O_CLOEXEC);
|
|
|
|
|
if (fd < 0) continue;
|
|
|
|
|
|
|
|
|
|
struct virgl_renderer_capset_hsakmt caps;
|
|
|
|
|
ret = virtio_gpu_get_capset(fd, &caps);
|
|
|
|
|
if (ret || caps.context_type != VIRTGPU_DRM_CONTEXT_AMDGPU) {
|
|
|
|
|
close(fd);
|
|
|
|
|
fd = -1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
drmFreeDevices(devices, num_devices);
|
|
|
|
|
return fd;
|
|
|
|
|
}
|