/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2026 Open-IOV contributors */ /* * 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; }