summaryrefslogtreecommitdiffstats
path: root/userspace/lib/libvgpu.c
diff options
context:
space:
mode:
Diffstat (limited to 'userspace/lib/libvgpu.c')
-rw-r--r--userspace/lib/libvgpu.c894
1 files changed, 894 insertions, 0 deletions
diff --git a/userspace/lib/libvgpu.c b/userspace/lib/libvgpu.c
new file mode 100644
index 0000000..a25a228
--- /dev/null
+++ b/userspace/lib/libvgpu.c
@@ -0,0 +1,894 @@
+/*
+ * GSP vGPU-IO Userspace Library Implementation
+ */
+
+#define _GNU_SOURCE
+#include "libvgpu.h"
+#include "rpc_emulator.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <pthread.h>
+#include <poll.h>
+
+/* IOCTL definitions (must match kernel driver) */
+#define GSP_IOCTL_MAGIC 'G'
+#define GSP_IOCTL_CREATE_VF _IOW(GSP_IOCTL_MAGIC, 0x01, struct gsp_vf_config)
+#define GSP_IOCTL_DESTROY_VF _IOW(GSP_IOCTL_MAGIC, 0x02, uint32_t)
+#define GSP_IOCTL_ALLOC _IOWR(GSP_IOCTL_MAGIC, 0x10, struct gsp_alloc_args)
+#define GSP_IOCTL_FREE _IOW(GSP_IOCTL_MAGIC, 0x11, struct gsp_free_args)
+#define GSP_IOCTL_MAP _IOWR(GSP_IOCTL_MAGIC, 0x12, struct gsp_map_args)
+#define GSP_IOCTL_UNMAP _IOW(GSP_IOCTL_MAGIC, 0x13, struct gsp_unmap_args)
+#define GSP_IOCTL_PIN _IOWR(GSP_IOCTL_MAGIC, 0x14, struct gsp_pin_args)
+#define GSP_IOCTL_UNPIN _IOW(GSP_IOCTL_MAGIC, 0x15, struct gsp_unpin_args)
+#define GSP_IOCTL_BAR_CONFIG _IOWR(GSP_IOCTL_MAGIC, 0x16, struct gsp_bar_config_args)
+#define GSP_IOCTL_BAR_FORCE _IOW(GSP_IOCTL_MAGIC, 0x17, struct gsp_bar_force_args)
+#define GSP_IOCTL_BAR_SAVE _IOWR(GSP_IOCTL_MAGIC, 0x18, struct gsp_bar_save_args)
+#define GSP_IOCTL_BAR_RESTORE _IOW(GSP_IOCTL_MAGIC, 0x19, struct gsp_bar_restore_args)
+#define GSP_IOCTL_WPR_PROTECT _IOWR(GSP_IOCTL_MAGIC, 0x1A, struct gsp_wpr_args)
+#define GSP_IOCTL_WPR_UNPROTECT _IOW(GSP_IOCTL_MAGIC, 0x1B, struct gsp_wpr_args)
+#define GSP_IOCTL_WPR_TIMEOUT _IOW(GSP_IOCTL_MAGIC, 0x1C, struct gsp_wpr_timeout_args)
+#define GSP_IOCTL_SCHEDULE _IOW(GSP_IOCTL_MAGIC, 0x1D, struct gsp_schedule_args)
+#define GSP_IOCTL_YIELD _IOW(GSP_IOCTL_MAGIC, 0x1E, uint32_t)
+#define GSP_IOCTL_MIGRATE _IOWR(GSP_IOCTL_MAGIC, 0x1F, struct gsp_migrate_args)
+#define GSP_IOCTL_GET_INFO _IOR(GSP_IOCTL_MAGIC, 0x20, char[256])
+
+/* Kernel argument structures */
+struct gsp_vf_config {
+ uint32_t id;
+ uint64_t bar1_offset;
+ uint64_t bar1_size;
+ uint64_t pt_base;
+ uint64_t pt_size;
+ uint64_t cmd_base;
+ uint64_t cmd_size;
+ uint32_t max_memory_mb;
+ uint32_t num_engines;
+};
+
+struct gsp_alloc_args {
+ uint32_t vf_id;
+ uint64_t size;
+ uint32_t type;
+ uint64_t alignment;
+ uint64_t va_hint;
+ uint32_t flags;
+ uint32_t pid;
+ uint64_t handle;
+ uint64_t va;
+ uint64_t actual_size;
+};
+
+struct gsp_free_args {
+ uint32_t vf_id;
+ uint64_t handle;
+};
+
+struct gsp_map_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t gpu_va;
+ uint64_t size;
+ uint32_t prot;
+ uint32_t flags;
+ uint64_t offset;
+ uint64_t mapped_va;
+};
+
+struct gsp_unmap_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t gpu_va;
+ uint64_t size;
+};
+
+struct gsp_pin_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t offset;
+ uint64_t size;
+ uint32_t flags;
+ uint64_t dma_addr;
+ uint32_t num_pages;
+};
+
+struct gsp_unpin_args {
+ uint32_t vf_id;
+ uint64_t handle;
+ uint64_t offset;
+ uint64_t size;
+};
+
+struct gsp_bar_config_args {
+ uint32_t vf_id;
+ uint64_t offset;
+ uint64_t size;
+};
+
+struct gsp_bar_force_args {
+ uint32_t vf_id;
+ uint32_t enable;
+};
+
+struct gsp_bar_save_args {
+ uint32_t vf_id;
+ void *buf;
+ size_t *size;
+};
+
+struct gsp_bar_restore_args {
+ uint32_t vf_id;
+ const void *buf;
+ size_t size;
+};
+
+struct gsp_wpr_args {
+ uint32_t vf_id;
+ uint64_t base;
+ uint64_t size;
+ uint32_t flags;
+};
+
+struct gsp_wpr_timeout_args {
+ uint32_t vf_id;
+ uint64_t timeout_ns;
+};
+
+struct gsp_schedule_args {
+ uint32_t vf_id;
+ uint64_t work_handle;
+ uint64_t callback;
+ uint64_t arg;
+ uint32_t priority;
+ uint32_t flags;
+};
+
+struct gsp_migrate_args {
+ uint32_t vf_id;
+ uint32_t phase;
+ uint32_t subcmd;
+ uint64_t src_vf;
+ uint64_t dst_vf;
+ uint64_t data_ptr;
+ uint64_t data_size;
+ uint32_t flags;
+ uint64_t token;
+};
+
+/* Internal structures */
+struct vgpu_device {
+ int fd;
+ char path[256];
+ pthread_mutex_t lock;
+ pthread_t event_thread;
+ bool event_thread_running;
+ vgpu_event_callback_t event_cb;
+ void *event_ctx;
+ bool emulation_enabled;
+};
+
+struct vgpu_vf {
+ vgpu_device_t device;
+ uint32_t id;
+};
+
+/* Error mapping */
+static vgpu_error_t map_errno(int err)
+{
+ switch (err) {
+ case 0: return VGPU_OK;
+ case EINVAL: return VGPU_ERR_INVALID_ARG;
+ case ENODEV: return VGPU_ERR_NO_DEVICE;
+ case ENOMEM: return VGPU_ERR_NO_MEMORY;
+ case EBUSY: return VGPU_ERR_BUSY;
+ case ETIMEDOUT: return VGPU_ERR_TIMEOUT;
+ case EPERM: return VGPU_ERR_PERMISSION;
+ case ENOTSUP: return VGPU_ERR_NOT_SUPPORTED;
+ default: return VGPU_ERR_INTERNAL;
+ }
+}
+
+/* ============================================================
+ * Device Management
+ * ============================================================ */
+
+vgpu_error_t vgpu_open(const char *device_path, vgpu_device_t *device)
+{
+ if (!device_path || !device)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = calloc(1, sizeof(*dev));
+ if (!dev)
+ return VGPU_ERR_NO_MEMORY;
+
+ dev->fd = open(device_path, O_RDWR | O_CLOEXEC);
+ if (dev->fd < 0) {
+ free(dev);
+ return map_errno(errno);
+ }
+
+ strncpy(dev->path, device_path, sizeof(dev->path) - 1);
+ pthread_mutex_init(&dev->lock, NULL);
+
+ /* Initialize RPC emulator for fallback when GSP lacks vGPU handlers */
+ rpc_emu_init();
+ dev->emulation_enabled = true;
+ rpc_emu_enable(true);
+
+ *device = dev;
+ return VGPU_OK;
+}
+
+void vgpu_close(vgpu_device_t device)
+{
+ if (!device)
+ return;
+
+ struct vgpu_device *dev = device;
+
+ if (dev->event_thread_running) {
+ dev->event_thread_running = false;
+ pthread_join(dev->event_thread, NULL);
+ }
+
+ if (dev->emulation_enabled) {
+ rpc_emu_fini();
+ dev->emulation_enabled = false;
+ }
+
+ pthread_mutex_destroy(&dev->lock);
+ close(dev->fd);
+ free(dev);
+}
+
+vgpu_error_t vgpu_get_info(vgpu_device_t device, char *buf, size_t len)
+{
+ if (!device || !buf || len == 0)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = device;
+ int ret = ioctl(dev->fd, GSP_IOCTL_GET_INFO, buf);
+ return map_errno(ret < 0 ? errno : 0);
+}
+
+/* ============================================================
+ * VF Management
+ * ============================================================ */
+
+vgpu_error_t vgpu_vf_create(vgpu_device_t device, const vgpu_vf_config_t *config, vgpu_vf_t *vf)
+{
+ if (!device || !config || !vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = device;
+ struct gsp_vf_config kconfig = {0};
+
+ kconfig.id = config->id;
+ kconfig.bar1_offset = config->bar1_offset;
+ kconfig.bar1_size = config->bar1_size;
+ kconfig.pt_base = config->pt_base;
+ kconfig.pt_size = config->pt_size;
+ kconfig.cmd_base = config->cmd_base;
+ kconfig.cmd_size = config->cmd_size;
+ kconfig.max_memory_mb = config->max_memory_mb;
+ kconfig.num_engines = config->num_engines;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_CREATE_VF, &kconfig);
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret < 0)
+ return map_errno(errno);
+
+ struct vgpu_vf *v = calloc(1, sizeof(*v));
+ if (!v)
+ return VGPU_ERR_NO_MEMORY;
+
+ v->device = dev;
+ v->id = config->id;
+ *vf = v;
+ return VGPU_OK;
+}
+
+vgpu_error_t vgpu_vf_destroy(vgpu_vf_t vf)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ uint32_t id = vf->id;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_DESTROY_VF, &id);
+ pthread_mutex_unlock(&dev->lock);
+
+ free(vf);
+ return map_errno(ret < 0 ? errno : 0);
+}
+
+vgpu_error_t vgpu_vf_get_info(vgpu_vf_t vf, char *buf, size_t len)
+{
+ if (!vf || !buf || len == 0)
+ return VGPU_ERR_INVALID_ARG;
+
+ /* VF-specific info would require a new ioctl */
+ snprintf(buf, len, "VF %u", vf->id);
+ return VGPU_OK;
+}
+
+/* ============================================================
+ * Memory Management
+ * ============================================================ */
+
+vgpu_error_t vgpu_mem_alloc(vgpu_vf_t vf, uint64_t size, vgpu_mem_type_t type,
+ uint64_t alignment, uint64_t va_hint,
+ uint32_t flags, vgpu_handle_t *handle, uint64_t *va)
+{
+ if (!vf || !handle || !va)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_alloc_args args = {0};
+
+ args.vf_id = vf->id;
+ args.size = size;
+ args.type = type;
+ args.alignment = alignment;
+ args.va_hint = va_hint;
+ args.flags = flags;
+ args.pid = getpid();
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_ALLOC, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret < 0)
+ return map_errno(errno);
+
+ *handle = args.handle;
+ *va = args.va;
+ return VGPU_OK;
+}
+
+vgpu_error_t vgpu_mem_free(vgpu_vf_t vf, vgpu_handle_t handle)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_free_args args = {0};
+
+ args.vf_id = vf->id;
+ args.handle = handle;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_FREE, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ return map_errno(ret < 0 ? errno : 0);
+}
+
+vgpu_error_t vgpu_mem_map(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t gpu_va,
+ uint64_t size, uint32_t prot, uint32_t flags,
+ uint64_t offset, uint64_t *mapped_va)
+{
+ if (!vf || !mapped_va)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_map_args args = {0};
+
+ args.vf_id = vf->id;
+ args.handle = handle;
+ args.gpu_va = gpu_va;
+ args.size = size;
+ args.prot = prot;
+ args.flags = flags;
+ args.offset = offset;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_MAP, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret < 0)
+ return map_errno(errno);
+
+ *mapped_va = args.mapped_va;
+ return VGPU_OK;
+}
+
+vgpu_error_t vgpu_mem_unmap(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t gpu_va, uint64_t size)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_unmap_args args = {0};
+
+ args.vf_id = vf->id;
+ args.handle = handle;
+ args.gpu_va = gpu_va;
+ args.size = size;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_UNMAP, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ return map_errno(ret < 0 ? errno : 0);
+}
+
+vgpu_error_t vgpu_mem_pin(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t offset,
+ uint64_t size, uint32_t flags, uint64_t *dma_addr)
+{
+ if (!vf || !dma_addr)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_pin_args args = {0};
+
+ args.vf_id = vf->id;
+ args.handle = handle;
+ args.offset = offset;
+ args.size = size;
+ args.flags = flags;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_PIN, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret < 0)
+ return map_errno(errno);
+
+ *dma_addr = args.dma_addr;
+ return VGPU_OK;
+}
+
+vgpu_error_t vgpu_mem_unpin(vgpu_vf_t vf, vgpu_handle_t handle, uint64_t offset, uint64_t size)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_unpin_args args = {0};
+
+ args.vf_id = vf->id;
+ args.handle = handle;
+ args.offset = offset;
+ args.size = size;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_UNPIN, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ return map_errno(ret < 0 ? errno : 0);
+}
+
+/* ============================================================
+ * BAR Emulation
+ * ============================================================ */
+
+static vgpu_error_t bar_fallback(struct vgpu_device *dev, vgpu_vf_t vf, int ioctl_err, int (*emu_fn)(uint32_t, uint64_t, uint64_t))
+{
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int ret = emu_fn(vf->id, 0, 0); /* Will be overridden below */
+ return map_errno(ret);
+ }
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_bar_config(vgpu_vf_t vf, uint64_t offset, uint64_t size)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_bar_config_args args = {0};
+
+ args.vf_id = vf->id;
+ args.offset = offset;
+ args.size = size;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_BAR_CONFIG, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ /* Fallback to userspace emulator if GSP lacks vGPU RPC handlers */
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_bar_config(vf->id, offset, size);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_bar_force_access(vgpu_vf_t vf, bool enable)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_bar_force_args args = {0};
+
+ args.vf_id = vf->id;
+ args.enable = enable ? 1 : 0;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_BAR_FORCE, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_bar_force_access(vf->id, enable);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_bar_save(vgpu_vf_t vf, void *buf, size_t *size)
+{
+ if (!vf || !buf || !size)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_bar_save_args args = {0};
+
+ args.vf_id = vf->id;
+ args.buf = buf;
+ args.size = size;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_BAR_SAVE, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_bar_save(vf->id, buf, size);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_bar_restore(vgpu_vf_t vf, const void *buf, size_t size)
+{
+ if (!vf || !buf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_bar_restore_args args = {0};
+
+ args.vf_id = vf->id;
+ args.buf = buf;
+ args.size = size;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_BAR_RESTORE, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_bar_restore(vf->id, buf, size);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+/* ============================================================
+ * WPR Management
+ * ============================================================ */
+
+vgpu_error_t vgpu_wpr_protect_write(vgpu_vf_t vf, uint64_t base, uint64_t size)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_wpr_args args = {0};
+
+ args.vf_id = vf->id;
+ args.base = base;
+ args.size = size;
+ args.flags = VGPU_WPR_WRITE;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_WPR_PROTECT, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_wpr_protect_write(vf->id, base, size);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_wpr_protect_read(vgpu_vf_t vf, uint64_t base, uint64_t size)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_wpr_args args = {0};
+
+ args.vf_id = vf->id;
+ args.base = base;
+ args.size = size;
+ args.flags = VGPU_WPR_READ;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_WPR_PROTECT, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_wpr_protect_read(vf->id, base, size);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_wpr_unprotect(vgpu_vf_t vf, uint64_t base)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_wpr_args args = {0};
+
+ args.vf_id = vf->id;
+ args.base = base;
+ args.size = 0;
+ args.flags = 0;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_WPR_UNPROTECT, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_wpr_unprotect(vf->id, base);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_wpr_set_timeout(vgpu_vf_t vf, uint64_t timeout_ns)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_wpr_timeout_args args = {0};
+
+ args.vf_id = vf->id;
+ args.timeout_ns = timeout_ns;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_WPR_TIMEOUT, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_wpr_set_timeout(vf->id, timeout_ns);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_wpr_register_callback(vgpu_vf_t vf, uint32_t event_type,
+ vgpu_wpr_callback_t cb, void *ctx)
+{
+ /* Callback registration would require kernel support for eventfd or similar */
+ (void)vf; (void)event_type; (void)cb; (void)ctx;
+ return VGPU_ERR_NOT_SUPPORTED;
+}
+
+/* ============================================================
+ * Scheduling
+ * ============================================================ */
+
+vgpu_error_t vgpu_schedule(vgpu_vf_t vf, uint64_t work_handle, uint64_t callback,
+ uint64_t arg, uint32_t priority, uint32_t flags)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_schedule_args args = {0};
+
+ args.vf_id = vf->id;
+ args.work_handle = work_handle;
+ args.callback = callback;
+ args.arg = arg;
+ args.priority = priority;
+ args.flags = flags;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_SCHEDULE, &args);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ /* Fallback to userspace emulator if GSP lacks vGPU RPC handlers */
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_schedule(vf->id, work_handle, callback, arg, priority, flags);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+vgpu_error_t vgpu_yield(vgpu_vf_t vf)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ uint32_t id = vf->id;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_YIELD, &id);
+ int ioctl_err = ret < 0 ? errno : 0;
+ pthread_mutex_unlock(&dev->lock);
+
+ if (ret == 0)
+ return VGPU_OK;
+
+ if (dev->emulation_enabled && (ioctl_err == ENOTSUP || ioctl_err == ETIMEDOUT || ioctl_err == EIO)) {
+ int emu_ret = rpc_emu_yield(vf->id);
+ return map_errno(emu_ret);
+ }
+
+ return map_errno(ioctl_err);
+}
+
+/* ============================================================
+ * Migration
+ * ============================================================ */
+
+vgpu_error_t vgpu_migrate_start(vgpu_vf_t vf, uint64_t dst_vf, uint32_t flags)
+{
+ if (!vf)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = vf->device;
+ struct gsp_migrate_args args = {0};
+
+ args.vf_id = vf->id;
+ args.phase = VGPU_MIG_PREPARE;
+ args.subcmd = 3; /* Quiesce */
+ args.src_vf = vf->id;
+ args.dst_vf = dst_vf;
+ args.flags = flags;
+
+ pthread_mutex_lock(&dev->lock);
+ int ret = ioctl(dev->fd, GSP_IOCTL_MIGRATE, &args);
+ pthread_mutex_unlock(&dev->lock);
+
+ return map_errno(ret < 0 ? errno : 0);
+}
+
+vgpu_error_t vgpu_migrate_status(vgpu_vf_t vf, vgpu_mig_phase_t *phase, uint64_t *progress)
+{
+ /* Migration status would require a query ioctl */
+ (void)vf; (void)phase; (void)progress;
+ return VGPU_ERR_NOT_SUPPORTED;
+}
+
+vgpu_error_t vgpu_migrate_cancel(vgpu_vf_t vf)
+{
+ /* Migration cancel would require a cancel ioctl */
+ (void)vf;
+ return VGPU_ERR_NOT_SUPPORTED;
+}
+
+/* ============================================================
+ * Error Handling
+ * ============================================================ */
+
+const char *vgpu_strerror(vgpu_error_t err)
+{
+ switch (err) {
+ case VGPU_OK: return "Success";
+ case VGPU_ERR_INVALID_ARG: return "Invalid argument";
+ case VGPU_ERR_NO_DEVICE: return "No such device";
+ case VGPU_ERR_NO_MEMORY: return "Out of memory";
+ case VGPU_ERR_BUSY: return "Device or resource busy";
+ case VGPU_ERR_TIMEOUT: return "Operation timed out";
+ case VGPU_ERR_PERMISSION: return "Permission denied";
+ case VGPU_ERR_NOT_SUPPORTED: return "Operation not supported";
+ case VGPU_ERR_INTERNAL: return "Internal error";
+ default: return "Unknown error";
+ }
+}
+
+/* ============================================================
+ * Event Handling
+ * ============================================================ */
+
+vgpu_error_t vgpu_register_event_callback(vgpu_vf_t vf, vgpu_event_type_t type,
+ vgpu_event_callback_t cb, void *ctx)
+{
+ struct vgpu_device *dev = vf->device;
+
+ /* Try kernel ioctl first */
+ /* Would need a new ioctl for notification registration */
+
+ /* Fallback to userspace emulator */
+ if (dev->emulation_enabled) {
+ rpc_emu_notify_cb_t emu_cb = (rpc_emu_notify_cb_t)cb;
+ int ret = rpc_emu_notify_register(vf->id, type, emu_cb, ctx);
+ return map_errno(ret);
+ }
+
+ return VGPU_ERR_NOT_SUPPORTED;
+}
+
+vgpu_error_t vgpu_unregister_event_callback(vgpu_vf_t vf, vgpu_event_type_t type)
+{
+ struct vgpu_device *dev = vf->device;
+
+ if (dev->emulation_enabled) {
+ int ret = rpc_emu_notify_unregister(vf->id, type);
+ return map_errno(ret);
+ }
+
+ return VGPU_ERR_NOT_SUPPORTED;
+}
+
+vgpu_error_t vgpu_process_events(vgpu_device_t device, int timeout_ms)
+{
+ if (!device)
+ return VGPU_ERR_INVALID_ARG;
+
+ struct vgpu_device *dev = device;
+ struct pollfd pfd = { .fd = dev->fd, .events = POLLIN };
+
+ int ret = poll(&pfd, 1, timeout_ms);
+ if (ret < 0)
+ return map_errno(errno);
+
+ if (ret == 0)
+ return VGPU_ERR_TIMEOUT;
+
+ /* Process events would read from device */
+ return VGPU_OK;
+} \ No newline at end of file