From c3a295efd1b1132bbaa5d422cd05b3fa8e89a8bf Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 13:06:51 -0400 Subject: kernel: Fix GSP emulation shadow binding, eventfd wiring, RPC livelocks + docs suite + repo reorg CRITICAL FIXES (guest driver bring-up): - gsp_emulation.c: emulation engine bound to the WRONG BAR0 - it polled the host physical BAR0 (bars[0].vaddr ioremap) for the doorbell and read RPCs from a local dma_alloc_wc buffer, while the guest writes land in the vzalloc'd bar0_shadow. The RPC path could never work; PBA writes even corrupted the host GPU's physical BAR0+0x3000. gsp_emu_init now binds exclusively to bar0_shadow (doorbell/mailbox/PBA aliases; shared_mem_dma==0 marks the alias so cleanup never dma_free's it). - main.c SET_IRQS: DATA_EVENTFD eventfds were DISCARDED ('TODO' + kfree) - the guest never received MSI-X completions. Full wiring now: eventfd_ctx_fdget per vector, fd -1 clears, dev_close releases refs. gsp_emu_inject_completion_interrupt looks up vgpu->irqs[0].trigger LIVE (init-time copy was permanently NULL since QEMU wires after gsp_emu_init). - gsp_rpc_client.c: BAR_TRAP/MIGRATE in emulation mode called gsp_rpc_submit_raw which rings the SAME doorbell the thread polls - self-triggered RPC livelock + multi-second stalls. Now fail fast with -ENOTSUPP + pr_warn_ratelimited (guest BAR access is direct MMIO). MORE FIXES: - main.c: nv_vgpu_config_write returned SIZE_MAX (while(count--) wrap) and was dead code while the VFIO write handler did unfiltered byte copies (guest could rewrite vendor ID/BARs). Return fixed; VFIO write handler now routes through the per-register filter. - kernel/Makefile: M=$(PWD) used the env var which is stale when invoked via the top-level make (pointed at repo root -> kbuild silently built NOTHING; explains every 'stale .ko' incident). Now M=$(CURDIR). - Warnings sweep: header prototypes for mdev/type functions, format casts in gsp_comm.c DRY-RUN logs, unused variable. ROBUSTNESS (harness/tooling): - vgpu_vm.py: run had no timeout (start could block forever); guest_shell recv could block forever on a wedged guest. Both now bounded; guest_shell pre-flights guest-ping and reports wedge diagnostics. - stack_manager.sh: qemu/qemu-debug now launch via the vgpu_vm.py harness (workflow rule: never raw QEMU). REPO REORGANIZATION: - driver/ (legacy guest-side gsp-vgpu.ko) moved to legacy-driver-guest/ (reference only, not built; README explains). 3 legacy un-buildable test files moved to legacy-driver-guest/kernel-tests/. Top-level Makefile cleaned of the dead DRIVER_DIR reference. DOCUMENTATION SUITE (new): - docs/GSP_EMULATION_ARCHITECTURE.md - emulator deep-dive: shadow BAR0 model, doorbell protocol, dispatch table, MSI-X path, lifecycle, memory- model caveats - docs/DEBUGGING.md - QEMU GDB stub workflow, serial console, QMP NMI, failure taxonomy, full bring-up validation checklist, EPT-livelock case study, version-matrix discipline - docs/ONBOARDING.md - repo tour, coding standards (incl. virt_to_phys / dma_addr_t / get-put rules), first build & test - docs/VERSION_UPGRADE.md - end-to-end new-driver-version procedure (firmware fetch, Ghidra analysis, configs, signatures, validation) - docs/PERFORMANCE.md - bare-metal A/B methodology + targets - README.md doc index; STATE.md refreshed; DEVELOPMENT_WORKFLOW.md corrected (load order: open-iov-vfio BEFORE nvidia; stock-driver blacklist note) --- driver/migration/gsp_migration.c | 339 --------------------------------------- 1 file changed, 339 deletions(-) delete mode 100644 driver/migration/gsp_migration.c (limited to 'driver/migration') diff --git a/driver/migration/gsp_migration.c b/driver/migration/gsp_migration.c deleted file mode 100644 index 52d1931..0000000 --- a/driver/migration/gsp_migration.c +++ /dev/null @@ -1,339 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - Live Migration - */ - -#include "gsp_core.h" -#include -#include -#include -#include - -/* Forward declarations */ -static int gsp_migration_prepare(struct gsp_migration *mig); -static int gsp_migration_transfer_iter(struct gsp_migration *mig); -static int gsp_migration_commit(struct gsp_migration *mig); -static void gsp_migration_cleanup(struct gsp_migration *mig); - -/* Migration Thread */ -static int gsp_migration_thread(void *data) -{ - struct gsp_migration *mig = data; - int ret = 0; - - while (!kthread_should_stop() && !mig->cancelled) { - mutex_lock(&mig->lock); - - switch (mig->state) { - case GSP_MIG_S_PREPARE: - ret = gsp_migration_prepare(mig); - if (!ret) - mig->state = GSP_MIG_S_TRANSFER; - else - mig->state = GSP_MIG_S_FAILED; - break; - - case GSP_MIG_S_TRANSFER: - ret = gsp_migration_transfer_iter(mig); - if (ret > 0) { /* Transfer complete */ - mig->state = GSP_MIG_S_COMMIT; - } else if (ret < 0) { - mig->state = GSP_MIG_S_FAILED; - } - break; - - case GSP_MIG_S_COMMIT: - ret = gsp_migration_commit(mig); - if (!ret) - mig->state = GSP_MIG_S_CLEANUP; - else - mig->state = GSP_MIG_S_FAILED; - break; - - case GSP_MIG_S_CLEANUP: - gsp_migration_cleanup(mig); - mig->state = GSP_MIG_S_COMPLETED; - complete(&mig->comp); - mutex_unlock(&mig->lock); - return 0; - - case GSP_MIG_S_FAILED: - mig->error = ret; - complete(&mig->comp); - mutex_unlock(&mig->lock); - return ret; - - default: - break; - } - - mutex_unlock(&mig->lock); - - if (mig->state == GSP_MIG_S_TRANSFER) - msleep(10); /* Throttle transfer iterations */ - } - - return 0; -} - -/* Migration Query */ -static int gsp_migration_query(struct gsp_vf *vf, bool *migratable) -{ - struct gsp_migrate_payload payload = { - .phase = GSP_MIG_QUERY, - .subcmd = 0, - .src_vf = vf->id, - .dst_vf = 0, - }; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - *migratable = (desc.subcmd_id != 0); - return 0; -} - -/* Phase 1: Prepare */ -static int gsp_migration_prepare(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - struct gsp_migrate_payload payload = {0}; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - /* Quiesce VF (subcmd 3 -> returns ID 0x10) */ - payload.phase = GSP_MIG_PREPARE; - payload.subcmd = 3; - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.flags = mig->flags; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - mig->token = desc.subcmd_id; - - /* Snapshot BAR state */ - ret = gsp_migration_bar_save(vf, mig->bar_state.buf, &mig->bar_state.size); - if (ret) - return ret; - - /* Snapshot WPR regions */ - /* TODO: WPR snapshot via migration RPC */ - - dev_info(vf->gsp->dev, "VF %u: Migration prepare done, token=0x%llx\n", - vf->id, mig->token); - - return 0; -} - -/* Phase 2: Transfer (Iterative) */ -static int gsp_migration_transfer_iter(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - struct gsp_migrate_payload payload = {0}; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - if (!mig->progress.page_list || mig->progress.page_count == 0) - return 1; /* Nothing to transfer */ - - payload.phase = GSP_MIG_TRANSFER; - payload.subcmd = 0; - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.data_ptr = (uint64_t)mig->progress.page_list; - payload.data_size = mig->progress.page_count * sizeof(uint64_t); - payload.token = mig->token; - payload.flags = mig->flags; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - /* Update progress from response */ - /* Note: Actual progress parsing depends on firmware response format */ - mig->progress.pages_transferred += 100; /* Placeholder */ - mig->progress.bytes_transferred += 100 * 4096; - - /* Check for dirty pages */ - if (mig->progress.dirty_pages > 0 && !(mig->flags & GSP_MIG_POSTCOPY)) - return 0; /* Continue iterating */ - - return 1; /* Transfer complete */ -} - -/* Phase 3: Commit */ -static int gsp_migration_commit(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - struct gsp_migrate_payload payload = {0}; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - /* Final dirty page transfer */ - if (mig->progress.dirty_pages > 0) { - payload.phase = GSP_MIG_TRANSFER; - payload.subcmd = 11; /* Dirty tracking */ - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.token = mig->token; - payload.flags = GSP_MIG_POSTCOPY; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - } - - /* Commit (subcmd 12) */ - payload.phase = GSP_MIG_COMMIT; - payload.subcmd = 12; - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.token = mig->token; - payload.flags = mig->flags | GSP_MIG_VERIFY; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - /* Restore BAR on destination */ - ret = gsp_migration_bar_restore(vf, mig->bar_state.buf, mig->bar_state.size); - if (ret) - return ret; - - /* Restore WPR on destination */ - /* TODO: WPR restore */ - - dev_info(vf->gsp->dev, "VF %u: Migration commit done\n", vf->id); - - return 0; -} - -/* Phase 4: Cleanup */ -static void gsp_migration_cleanup(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - - /* Free page list */ - kfree(mig->progress.page_list); - mig->progress.page_list = NULL; - mig->progress.page_count = 0; - - /* Free BAR state buffer */ - kfree(mig->bar_state.buf); - mig->bar_state.buf = NULL; - mig->bar_state.size = 0; - - dev_info(vf->gsp->dev, "VF %u: Migration cleanup done\n", vf->id); -} - -/* Public Migration API */ -int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf) -{ - struct gsp_migration *mig; - bool migratable; - int ret; - - if (vf->migration) - return -EBUSY; - - /* Query migratability */ - ret = gsp_migration_query(vf, &migratable); - if (ret || !migratable) - return ret ? ret : -EINVAL; - - mig = kzalloc(sizeof(*mig), GFP_KERNEL); - if (!mig) - return -ENOMEM; - - mig->vf = vf; - mig->dst_vf = dst_vf; - mig->flags = GSP_MIG_LIVE | GSP_MIG_VERIFY; - mig->state = GSP_MIG_S_PREPARE; - mutex_init(&mig->lock); - init_completion(&mig->comp); - - /* Allocate page list (placeholder) */ - mig->progress.page_count = vf->pt_size / 4096; - mig->progress.page_list = kcalloc(mig->progress.page_count, sizeof(uint64_t), GFP_KERNEL); - if (!mig->progress.page_list) { - kfree(mig); - return -ENOMEM; - } - - /* Allocate BAR state buffer */ - mig->bar_state.buf = kmalloc(65536, GFP_KERNEL); /* 64KB max */ - if (!mig->bar_state.buf) { - kfree(mig->progress.page_list); - kfree(mig); - return -ENOMEM; - } - - vf->migration = mig; - - /* Start migration thread */ - mig->thread = kthread_run(gsp_migration_thread, mig, "gsp-mig-%u", vf->id); - if (IS_ERR(mig->thread)) { - ret = PTR_ERR(mig->thread); - mig->thread = NULL; - goto err_free; - } - - return 0; - -err_free: - kfree(mig->progress.page_list); - kfree(mig->bar_state.buf); - kfree(mig); - vf->migration = NULL; - return ret; -} - -void gsp_migration_cancel(struct gsp_migration *mig) -{ - if (!mig) - return; - - mutex_lock(&mig->lock); - mig->cancelled = true; - mig->state = GSP_MIG_S_FAILED; - mutex_unlock(&mig->lock); - - if (mig->thread) - kthread_stop(mig->thread); - - wait_for_completion(&mig->comp); - - gsp_migration_cleanup(mig); - kfree(mig); -} - -int gsp_migration_status(struct gsp_migration *mig) -{ - if (!mig) - return -EINVAL; - - mutex_lock(&mig->lock); - int state = mig->state; - int error = mig->error; - mutex_unlock(&mig->lock); - - if (state == GSP_MIG_S_FAILED) - return error; - - return state; -} \ No newline at end of file -- cgit v1.2.3