summaryrefslogtreecommitdiffstats
path: root/legacy-driver-guest/migration
diff options
context:
space:
mode:
authorOpen-IOV <dev@open-iov.org>2026-09-23 05:44:08 -0400
committerOpen-IOV <dev@open-iov.org>2026-09-23 05:46:43 -0400
commit2302fc09080bad51c73f293294e19fd4db464eb0 (patch)
treec722d186347fdef5a34131e9e143b1549b1daf8e /legacy-driver-guest/migration
downloadopen-iov-2302fc09080bad51c73f293294e19fd4db464eb0.tar.gz
open-iov-2302fc09080bad51c73f293294e19fd4db464eb0.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 'legacy-driver-guest/migration')
-rw-r--r--legacy-driver-guest/migration/gsp_migration.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/legacy-driver-guest/migration/gsp_migration.c b/legacy-driver-guest/migration/gsp_migration.c
new file mode 100644
index 0000000..52d1931
--- /dev/null
+++ b/legacy-driver-guest/migration/gsp_migration.c
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * GSP vGPU-IO Driver - Live Migration
+ */
+
+#include "gsp_core.h"
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+
+/* 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