diff options
| author | Open-IOV <dev@open-iov.org> | 2026-09-23 05:44:08 -0400 |
|---|---|---|
| committer | Open-IOV <dev@open-iov.org> | 2026-09-23 05:46:43 -0400 |
| commit | 68cdd7728a7cc6715bbbff7decfe2b11c3bdf558 (patch) | |
| tree | 3dc8ce2e6af313a97ec6852f5c663a9a93665d2e /userspace/lib | |
| download | open-iov-68cdd7728a7cc6715bbbff7decfe2b11c3bdf558.tar.gz open-iov-68cdd7728a7cc6715bbbff7decfe2b11c3bdf558.zip | |
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.
Diffstat (limited to 'userspace/lib')
| -rw-r--r-- | userspace/lib/Makefile | 89 | ||||
| -rw-r--r-- | userspace/lib/libiov.pc.in | 10 | ||||
| -rw-r--r-- | userspace/lib/libvgpu.c | 894 | ||||
| -rw-r--r-- | userspace/lib/libvgpu.pc.in | 10 | ||||
| -rw-r--r-- | userspace/lib/rpc_emulator.c | 528 | ||||
| -rw-r--r-- | userspace/lib/rpc_emulator.h | 53 |
6 files changed, 1584 insertions, 0 deletions
diff --git a/userspace/lib/Makefile b/userspace/lib/Makefile new file mode 100644 index 0000000..05bb05b --- /dev/null +++ b/userspace/lib/Makefile @@ -0,0 +1,89 @@ +# libiov - Userspace library for Open-IOV vGPU +# Makefile + +PREFIX ?= /usr/local +LIBDIR ?= $(PREFIX)/lib +INCLUDEDIR ?= $(PREFIX)/include +PKGCONFIGDIR ?= $(LIBDIR)/pkgconfig + +CC ?= gcc +CFLAGS ?= -O2 -g -Wall -Wextra -fPIC -I../include +LDFLAGS ?= -shared + +LIB_NAME = libiov.so +LIB_MAJOR = 1 +LIB_MINOR = 0 +LIB_PATCH = 0 +LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR).$(LIB_PATCH) +LIB_SONAME = $(LIB_NAME).$(LIB_MAJOR) + +# Compatibility symlinks for libvgpu +COMPAT_NAMES = libvgpu.so libvgpu.so.1 libvgpu.so.1.0.0 + +SRC_DIR = . +OBJ_DIR = obj +SRC = $(SRC_DIR)/libvgpu.c $(SRC_DIR)/rpc_emulator.c +OBJ = $(OBJ_DIR)/libvgpu.o $(OBJ_DIR)/rpc_emulator.o + +HEADERS = ../include/libvgpu.h rpc_emulator.h + +all: $(OBJ_DIR) $(LIB_NAME) compat-links + +$(OBJ_DIR): + mkdir -p $(OBJ_DIR) + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(HEADERS) + $(CC) $(CFLAGS) -c $< -o $@ + +$(LIB_NAME): $(OBJ) + $(CC) $(LDFLAGS) -Wl,-soname,$(LIB_SONAME) -o $@ $^ + ln -sf $(LIB_NAME) $(LIB_SONAME) + ln -sf $(LIB_NAME) $(LIB_NAME).$(LIB_VERSION) + +# Create compatibility symlinks for libvgpu +compat-links: $(LIB_NAME) + @for link in $(COMPAT_NAMES); do \ + ln -sf $(LIB_NAME) $$link; \ + done + +install: all + install -d $(DESTDIR)$(LIBDIR) + install -d $(DESTDIR)$(INCLUDEDIR) + install -d $(DESTDIR)$(PKGCONFIGDIR) + install -m 755 $(LIB_NAME) $(DESTDIR)$(LIBDIR)/$(LIB_NAME).$(LIB_VERSION) + ln -sf $(LIB_NAME).$(LIB_VERSION) $(DESTDIR)$(LIBDIR)/$(LIB_SONAME) + ln -sf $(LIB_NAME).$(LIB_VERSION) $(DESTDIR)$(LIBDIR)/$(LIB_NAME) + # Install compatibility symlinks + @for link in $(COMPAT_NAMES); do \ + ln -sf $(LIB_NAME).$(LIB_VERSION) $(DESTDIR)$(LIBDIR)/$$link; \ + done + install -m 644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/ + sed -e "s|@PREFIX@|$(PREFIX)|g" \ + -e "s|@LIBDIR@|$(LIBDIR)|g" \ + -e "s|@INCLUDEDIR@|$(INCLUDEDIR)|g" \ + -e "s|@VERSION@|$(LIB_VERSION)|g" \ + libvgpu.pc.in > $(DESTDIR)$(PKGCONFIGDIR)/libvgpu.pc + # Also create libiov.pc + sed -e "s|@PREFIX@|$(PREFIX)|g" \ + -e "s|@LIBDIR@|$(LIBDIR)|g" \ + -e "s|@INCLUDEDIR@|$(INCLUDEDIR)|g" \ + -e "s|@VERSION@|$(LIB_VERSION)|g" \ + -e "s|libvgpu|libiov|g" \ + libvgpu.pc.in > $(DESTDIR)$(PKGCONFIGDIR)/libiov.pc + +uninstall: + rm -f $(DESTDIR)$(LIBDIR)/$(LIB_NAME) + rm -f $(DESTDIR)$(LIBDIR)/$(LIB_SONAME) + rm -f $(DESTDIR)$(LIBDIR)/$(LIB_NAME).$(LIB_VERSION) + @for link in $(COMPAT_NAMES); do \ + rm -f $(DESTDIR)$(LIBDIR)/$$link; \ + done + rm -f $(DESTDIR)$(INCLUDEDIR)/libvgpu.h + rm -f $(DESTDIR)$(INCLUDEDIR)/rpc_emulator.h + rm -f $(DESTDIR)$(PKGCONFIGDIR)/libvgpu.pc + rm -f $(DESTDIR)$(PKGCONFIGDIR)/libiov.pc + +clean: + rm -rf $(OBJ_DIR) $(LIB_NAME) $(LIB_SONAME) $(LIB_NAME).$(LIB_VERSION) $(COMPAT_NAMES) + +.PHONY: all install uninstall clean compat-links
\ No newline at end of file diff --git a/userspace/lib/libiov.pc.in b/userspace/lib/libiov.pc.in new file mode 100644 index 0000000..ddc1fc3 --- /dev/null +++ b/userspace/lib/libiov.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +exec_prefix=${prefix} +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: libiov +Description: Open-IOV vGPU Userspace Library +Version: @VERSION@ +Libs: -L${libdir} -liov +Cflags: -I${includedir}
\ No newline at end of file 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 diff --git a/userspace/lib/libvgpu.pc.in b/userspace/lib/libvgpu.pc.in new file mode 100644 index 0000000..82ba073 --- /dev/null +++ b/userspace/lib/libvgpu.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +exec_prefix=${prefix} +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: libvgpu +Description: GSP vGPU-IO Userspace Library +Version: @VERSION@ +Libs: -L${libdir} -lvgpu +Cflags: -I${includedir}
\ No newline at end of file diff --git a/userspace/lib/rpc_emulator.c b/userspace/lib/rpc_emulator.c new file mode 100644 index 0000000..9f7f2c7 --- /dev/null +++ b/userspace/lib/rpc_emulator.c @@ -0,0 +1,528 @@ +/* + * GSP vGPU-IO Userspace RPC Emulator + * + * Emulates missing vGPU RPC handlers (BAR_TRAP, WPR, etc.) that exist only + * in GRID host driver GSP firmware. Allows operation with consumer/OpenRM GSP. + */ + +#define _GNU_SOURCE +#include "rpc_emulator.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> + +/* ============================================================ + * BAR Emulation State + * ============================================================ */ + +struct emu_bar_state { + uint64_t aperture_offset; + uint64_t aperture_size; + bool force_access; + uint32_t force_vf; + void *backing_store; + size_t backing_size; + pthread_mutex_t lock; +}; + +static struct emu_bar_state g_bar_state = { + .aperture_offset = 0, + .aperture_size = 0, + .force_access = false, + .force_vf = 0, + .backing_store = NULL, + .backing_size = 0, +}; + +/* ============================================================ + * WPR Emulation State + * ============================================================ */ + +struct emu_wpr_region { + uint64_t base; + uint64_t size; + uint32_t flags; + uint64_t timeout_ns; + struct emu_wpr_region *next; +}; + +struct emu_wpr_state { + struct emu_wpr_region *regions; + pthread_mutex_t lock; +}; + +static struct emu_wpr_state g_wpr_state = { + .regions = NULL, +}; + +/* ============================================================ + * Emulation Mode Control + * ============================================================ */ + +static bool g_emulation_enabled = false; +static pthread_mutex_t g_emu_lock = PTHREAD_MUTEX_INITIALIZER; + +void rpc_emu_enable(bool enable) +{ + pthread_mutex_lock(&g_emu_lock); + g_emulation_enabled = enable; + pthread_mutex_unlock(&g_emu_lock); +} + +bool rpc_emu_is_enabled(void) +{ + bool enabled; + pthread_mutex_lock(&g_emu_lock); + enabled = g_emulation_enabled; + pthread_mutex_unlock(&g_emu_lock); + return enabled; +} + +/* ============================================================ + * BAR Emulation Implementation + * ============================================================ */ + +int rpc_emu_bar_config(uint32_t vf_id, uint64_t offset, uint64_t size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (offset & 0xFFF || size & 0xFFF) + return -EINVAL; + + if (size > 0x40000) /* 256KB max per firmware spec */ + return -EINVAL; + + pthread_mutex_lock(&g_bar_state.lock); + + g_bar_state.aperture_offset = offset; + g_bar_state.aperture_size = size; + + /* Allocate/resize backing store */ + if (g_bar_state.backing_store) { + free(g_bar_state.backing_store); + g_bar_state.backing_store = NULL; + g_bar_state.backing_size = 0; + } + + if (size > 0) { + g_bar_state.backing_size = size; + g_bar_state.backing_store = calloc(1, size); + if (!g_bar_state.backing_store) { + g_bar_state.backing_size = 0; + pthread_mutex_unlock(&g_bar_state.lock); + return -ENOMEM; + } + } + + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_force_access(uint32_t vf_id, bool enable) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_bar_state.lock); + g_bar_state.force_access = enable; + g_bar_state.force_vf = enable ? vf_id : 0; + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_save(uint32_t vf_id, void *buf, size_t *size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (!buf || !size) + return -EINVAL; + + pthread_mutex_lock(&g_bar_state.lock); + + size_t needed = sizeof(struct emu_bar_state) + g_bar_state.backing_size; + if (*size < needed) { + *size = needed; + pthread_mutex_unlock(&g_bar_state.lock); + return -ENOSPC; + } + + memcpy(buf, &g_bar_state, sizeof(struct emu_bar_state)); + if (g_bar_state.backing_store && g_bar_state.backing_size > 0) + memcpy(buf + sizeof(struct emu_bar_state), g_bar_state.backing_store, g_bar_state.backing_size); + + *size = needed; + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_restore(uint32_t vf_id, const void *buf, size_t size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (!buf || size < sizeof(struct emu_bar_state)) + return -EINVAL; + + pthread_mutex_lock(&g_bar_state.lock); + + memcpy(&g_bar_state, buf, sizeof(struct emu_bar_state)); + + if (size > sizeof(struct emu_bar_state) && g_bar_state.backing_size > 0) { + if (!g_bar_state.backing_store) { + g_bar_state.backing_store = calloc(1, g_bar_state.backing_size); + if (!g_bar_state.backing_store) { + pthread_mutex_unlock(&g_bar_state.lock); + return -ENOMEM; + } + } + memcpy(g_bar_state.backing_store, buf + sizeof(struct emu_bar_state), + size - sizeof(struct emu_bar_state)); + } + + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +int rpc_emu_bar_get_state(uint32_t vf_id, uint64_t *offset, uint64_t *size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_bar_state.lock); + if (offset) *offset = g_bar_state.aperture_offset; + if (size) *size = g_bar_state.aperture_size; + pthread_mutex_unlock(&g_bar_state.lock); + return 0; +} + +/* ============================================================ + * WPR Emulation Implementation + * ============================================================ */ + +static struct emu_wpr_region *emu_wpr_find(uint64_t base) +{ + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + if (rgn->base == base) + return rgn; + rgn = rgn->next; + } + return NULL; +} + +static struct emu_wpr_region *emu_wpr_find_containing(uint64_t addr) +{ + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + if (addr >= rgn->base && addr < rgn->base + rgn->size) + return rgn; + rgn = rgn->next; + } + return NULL; +} + +int rpc_emu_wpr_protect_write(uint32_t vf_id, uint64_t base, uint64_t size) +{ + return rpc_emu_wpr_protect(vf_id, base, size, VGPU_WPR_WRITE); +} + +int rpc_emu_wpr_protect_read(uint32_t vf_id, uint64_t base, uint64_t size) +{ + return rpc_emu_wpr_protect(vf_id, base, size, VGPU_WPR_READ); +} + +int rpc_emu_wpr_protect(uint32_t vf_id, uint64_t base, uint64_t size, uint32_t flags) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + if (base & 0xFFF || size & 0xFFF) + return -EINVAL; + + pthread_mutex_lock(&g_wpr_state.lock); + + /* Check for overlap */ + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + if (base < rgn->base + rgn->size && base + size > rgn->base) { + pthread_mutex_unlock(&g_wpr_state.lock); + return -EBUSY; /* Overlaps existing region */ + } + rgn = rgn->next; + } + + struct emu_wpr_region *new_rgn = calloc(1, sizeof(*new_rgn)); + if (!new_rgn) { + pthread_mutex_unlock(&g_wpr_state.lock); + return -ENOMEM; + } + + new_rgn->base = base; + new_rgn->size = size; + new_rgn->flags = flags; + new_rgn->timeout_ns = 10000000; /* 10ms default */ + + new_rgn->next = g_wpr_state.regions; + g_wpr_state.regions = new_rgn; + + pthread_mutex_unlock(&g_wpr_state.lock); + return 0; +} + +int rpc_emu_wpr_unprotect(uint32_t vf_id, uint64_t base) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_wpr_state.lock); + + struct emu_wpr_region **prev = &g_wpr_state.regions; + struct emu_wpr_region *rgn = g_wpr_state.regions; + bool found = false; + + while (rgn) { + if (rgn->base == base) { + *prev = rgn->next; + free(rgn); + found = true; + break; + } + prev = &rgn->next; + rgn = rgn->next; + } + + pthread_mutex_unlock(&g_wpr_state.lock); + return found ? 0 : -ENOENT; +} + +int rpc_emu_wpr_set_timeout(uint32_t vf_id, uint64_t timeout_ns) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_wpr_state.lock); + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + rgn->timeout_ns = timeout_ns; + rgn = rgn->next; + } + pthread_mutex_unlock(&g_wpr_state.lock); + return 0; +} + +int rpc_emu_wpr_check_access(uint32_t vf_id, uint64_t addr, uint32_t access_type) +{ + if (!rpc_emu_is_enabled()) + return 0; /* Allow if emulation disabled */ + + pthread_mutex_lock(&g_wpr_state.lock); + struct emu_wpr_region *rgn = emu_wpr_find_containing(addr); + int ret = 0; + + if (rgn) { + if ((access_type == VGPU_WPR_WRITE && (rgn->flags & VGPU_WPR_WRITE)) || + (access_type == VGPU_WPR_READ && (rgn->flags & VGPU_WPR_READ))) { + ret = -EPERM; /* Access violation */ + } + } + + pthread_mutex_unlock(&g_wpr_state.lock); + return 0; +} + +/* ============================================================ + * Scheduling Emulation Implementation + * ============================================================ */ + +struct emu_sched_work { + uint64_t work_handle; + uint64_t callback; + uint64_t arg; + uint32_t priority; + uint32_t flags; + struct emu_sched_work *next; +}; + +static struct emu_sched_work *g_sched_queue = NULL; +static pthread_mutex_t g_sched_lock = PTHREAD_MUTEX_INITIALIZER; + +int rpc_emu_schedule(uint32_t vf_id, uint64_t work_handle, uint64_t callback, uint64_t arg, uint32_t priority, uint32_t flags) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + struct emu_sched_work *work = calloc(1, sizeof(*work)); + if (!work) + return -ENOMEM; + + work->work_handle = work_handle; + work->callback = callback; + work->arg = arg; + work->priority = priority; + work->flags = flags; + + pthread_mutex_lock(&g_sched_lock); + work->next = g_sched_queue; + g_sched_queue = work; + pthread_mutex_unlock(&g_sched_lock); + + /* In emulation, we just return success - work is queued locally */ + (void)vf_id; + return 0; +} + +int rpc_emu_yield(uint32_t vf_id) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + /* In emulation, yield is a no-op */ + (void)vf_id; + return 0; +} + +/* ============================================================ + * Notification Emulation Implementation + * ============================================================ */ + +struct emu_notify_cb { + uint32_t type; + rpc_emu_notify_cb_t callback; + void *ctx; + struct emu_notify_cb *next; +}; + +static struct emu_notify_cb *g_notify_callbacks = NULL; +static pthread_mutex_t g_notify_lock = PTHREAD_MUTEX_INITIALIZER; + +int rpc_emu_notify_register(uint32_t vf_id, uint32_t type, rpc_emu_notify_cb_t cb, void *ctx) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + struct emu_notify_cb *nc = calloc(1, sizeof(*nc)); + if (!nc) + return -ENOMEM; + + nc->type = type; + nc->callback = cb; + nc->ctx = ctx; + + pthread_mutex_lock(&g_notify_lock); + nc->next = g_notify_callbacks; + g_notify_callbacks = nc; + pthread_mutex_unlock(&g_notify_lock); + + (void)vf_id; + return 0; +} + +int rpc_emu_notify_unregister(uint32_t vf_id, uint32_t type) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_notify_lock); + struct emu_notify_cb **prev = &g_notify_callbacks; + struct emu_notify_cb *nc = g_notify_callbacks; + bool found = false; + + while (nc) { + if (nc->type == type) { + *prev = nc->next; + free(nc); + found = true; + break; + } + prev = &nc->next; + nc = nc->next; + } + pthread_mutex_unlock(&g_notify_lock); + + (void)vf_id; + return found ? 0 : -ENOENT; +} + +int rpc_emu_notify_send(uint32_t vf_id, uint32_t type, void *data, size_t size) +{ + if (!rpc_emu_is_enabled()) + return -ENOTSUP; + + pthread_mutex_lock(&g_notify_lock); + struct emu_notify_cb *nc = g_notify_callbacks; + while (nc) { + if (nc->type == type || nc->type == 0) { + nc->callback(vf_id, type, data, size, nc->ctx); + } + nc = nc->next; + } + pthread_mutex_unlock(&g_notify_lock); + + return 0; +} + +/* ============================================================ + * Initialization / Cleanup + * ============================================================ */ + +int rpc_emu_init(void) +{ + pthread_mutex_init(&g_bar_state.lock, NULL); + pthread_mutex_init(&g_wpr_state.lock, NULL); + pthread_mutex_init(&g_sched_lock, NULL); + pthread_mutex_init(&g_notify_lock, NULL); + return 0; +} + +void rpc_emu_fini(void) +{ + pthread_mutex_lock(&g_bar_state.lock); + if (g_bar_state.backing_store) { + free(g_bar_state.backing_store); + g_bar_state.backing_store = NULL; + g_bar_state.backing_size = 0; + } + pthread_mutex_unlock(&g_bar_state.lock); + pthread_mutex_destroy(&g_bar_state.lock); + + pthread_mutex_lock(&g_wpr_state.lock); + struct emu_wpr_region *rgn = g_wpr_state.regions; + while (rgn) { + struct emu_wpr_region *next = rgn->next; + free(rgn); + rgn = next; + } + g_wpr_state.regions = NULL; + pthread_mutex_unlock(&g_wpr_state.lock); + pthread_mutex_destroy(&g_wpr_state.lock); + + pthread_mutex_lock(&g_sched_lock); + struct emu_sched_work *work = g_sched_queue; + while (work) { + struct emu_sched_work *next = work->next; + free(work); + work = next; + } + g_sched_queue = NULL; + pthread_mutex_unlock(&g_sched_lock); + pthread_mutex_destroy(&g_sched_lock); + + pthread_mutex_lock(&g_notify_lock); + struct emu_notify_cb *nc = g_notify_callbacks; + while (nc) { + struct emu_notify_cb *next = nc->next; + free(nc); + nc = next; + } + g_notify_callbacks = NULL; + pthread_mutex_unlock(&g_notify_lock); + pthread_mutex_destroy(&g_notify_lock); + + pthread_mutex_destroy(&g_emu_lock); +} diff --git a/userspace/lib/rpc_emulator.h b/userspace/lib/rpc_emulator.h new file mode 100644 index 0000000..1b641bf --- /dev/null +++ b/userspace/lib/rpc_emulator.h @@ -0,0 +1,53 @@ +/* + * GSP vGPU-IO Userspace RPC Emulator - Header + */ + +#ifndef _RPC_EMULATOR_H_ +#define _RPC_EMULATOR_H_ + +#include <stdint.h> +#include <stdbool.h> +#include "libvgpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Emulation mode control */ +void rpc_emu_enable(bool enable); +bool rpc_emu_is_enabled(void); + +/* BAR Emulation */ +int rpc_emu_bar_config(uint32_t vf_id, uint64_t offset, uint64_t size); +int rpc_emu_bar_force_access(uint32_t vf_id, bool enable); +int rpc_emu_bar_save(uint32_t vf_id, void *buf, size_t *size); +int rpc_emu_bar_restore(uint32_t vf_id, const void *buf, size_t size); +int rpc_emu_bar_get_state(uint32_t vf_id, uint64_t *offset, uint64_t *size); + +/* WPR Emulation */ +int rpc_emu_wpr_protect_write(uint32_t vf_id, uint64_t base, uint64_t size); +int rpc_emu_wpr_protect_read(uint32_t vf_id, uint64_t base, uint64_t size); +int rpc_emu_wpr_protect(uint32_t vf_id, uint64_t base, uint64_t size, uint32_t flags); +int rpc_emu_wpr_unprotect(uint32_t vf_id, uint64_t base); +int rpc_emu_wpr_set_timeout(uint32_t vf_id, uint64_t timeout_ns); +int rpc_emu_wpr_check_access(uint32_t vf_id, uint64_t addr, uint32_t access_type); + +/* Scheduling Emulation */ +int rpc_emu_schedule(uint32_t vf_id, uint64_t work_handle, uint64_t callback, uint64_t arg, uint32_t priority, uint32_t flags); +int rpc_emu_yield(uint32_t vf_id); + +/* Notification Emulation */ +typedef void (*rpc_emu_notify_cb_t)(uint32_t vf_id, uint32_t type, void *data, size_t size, void *ctx); +int rpc_emu_notify_register(uint32_t vf_id, uint32_t type, rpc_emu_notify_cb_t cb, void *ctx); +int rpc_emu_notify_unregister(uint32_t vf_id, uint32_t type); +int rpc_emu_notify_send(uint32_t vf_id, uint32_t type, void *data, size_t size); + +/* Init/Fini */ +int rpc_emu_init(void); +void rpc_emu_fini(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _RPC_EMULATOR_H_ */ |
