From 68cdd7728a7cc6715bbbff7decfe2b11c3bdf558 Mon Sep 17 00:00:00 2001 From: Open-IOV Date: Wed, 23 Sep 2026 05:44:08 -0400 Subject: Open-IOV: initial public release Open-IOV virtualizes NVIDIA GPUs so that unmodified guest drivers run with real GPU functionality - CUDA first, 3D and video to follow - on unmodified hosts, without a proprietary vGPU license and without patching any driver, firmware or hypervisor. Core principle: emulate the interface, pass through the work. kernel/ open-iov-vfio.ko - host VFIO mdev driver: virtual GPU device (PCI config space, BAR shadows, MSI-X), GSP boot emulation, GSP-RM message-queue/RPC emulation, memory model (GPU-VA to guest-PA discovery) and the semaphore-completion service that advances guest UVM progress. userspace/ libiov.so management API and examples. tools/ guest diagnostics, firmware analysis wrappers, RM probes. scripts/ stack manager, VM harness, build/load/test automation. config/ hardware/version profiles (the portability surface). docs/ documentation tree - start at docs/README.md. wiki/ long-form firmware and protocol deep dives. Status: initialization works end to end for a stock 570.144 Linux guest on Turing (TU104): driver probe, nvidia-smi, the full GSP-RM control battery, object creation and the early CUDA/UVM start-up path. GPU work forwarding - the step that makes CUDA kernels execute on real hardware - is the active work item; see docs/04-implementation/01-known-knowns-and-unknowns.md. License: BSD-3-Clause for the project; GPL-2.0 for kernel modules that link GPL-only kernel interfaces. See LICENSE and docs/09-reference/legal-and-licensing.md. --- userspace/lib/libvgpu.c | 894 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 894 insertions(+) create mode 100644 userspace/lib/libvgpu.c (limited to 'userspace/lib/libvgpu.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 -- cgit v1.2.3