summaryrefslogtreecommitdiffstats
path: root/userspace/lib
diff options
context:
space:
mode:
Diffstat (limited to 'userspace/lib')
-rw-r--r--userspace/lib/Makefile89
-rw-r--r--userspace/lib/libiov.pc.in10
-rw-r--r--userspace/lib/libvgpu.c894
-rw-r--r--userspace/lib/libvgpu.pc.in10
-rw-r--r--userspace/lib/rpc_emulator.c528
-rw-r--r--userspace/lib/rpc_emulator.h53
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_ */