diff options
Diffstat (limited to 'userspace/lib/rpc_emulator.c')
| -rw-r--r-- | userspace/lib/rpc_emulator.c | 528 |
1 files changed, 528 insertions, 0 deletions
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); +} |
