summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 22:22:21 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 22:22:21 -0400
commit2cecb0c172375e45ab554a42c0693135238c03ed (patch)
tree393179d97eeb617e49b2da02aa284a774d09383f
parentaa4cf303dd028b24ca29edbc0a5f3c237ab98e3f (diff)
downloadopen-iov-2cecb0c172375e45ab554a42c0693135238c03ed.tar.gz
open-iov-2cecb0c172375e45ab554a42c0693135238c03ed.zip
gsp-phys: mdev-owned backend lifecycle (P1)
Add the gsp_phys_mdev_owned ownership selector (default 0: unchanged load-time module-owned claim/boot). With 1, module load claims nothing; the first mdev create starts a refcounted backend session whose claim+boot runs asynchronously from a work item, so mdev create and init_work never block. The last mdev remove releases after the emulation thread is stopped. Failures are fail-open: the backend stays OFF and the mdev keeps serving the emulation path. nv_vgpu_exit now gates the session STOPPING under the session lock, drains the emulation threads via mdev_unregister_driver(), and only then releases the claim; the module-owned path keeps its previous release position. A start while STOPPING is refused and a concurrent second stopper cannot release twice. No forwarding is added.
-rw-r--r--kernel/gsp_phys_gpu.c257
-rw-r--r--kernel/gsp_phys_gpu.h40
-rw-r--r--kernel/include/nvidia-vgpu-vfio.h6
-rw-r--r--kernel/main.c75
4 files changed, 315 insertions, 63 deletions
diff --git a/kernel/gsp_phys_gpu.c b/kernel/gsp_phys_gpu.c
index 22cdc33..1000eaf 100644
--- a/kernel/gsp_phys_gpu.c
+++ b/kernel/gsp_phys_gpu.c
@@ -59,6 +59,7 @@
#include <linux/crc32.h>
#include <linux/firmware.h>
#include <linux/string.h>
+#include <linux/workqueue.h>
#include "gsp_phys_gpu.h"
#include "gsp_phys_boot.h"
@@ -97,6 +98,21 @@ module_param_named(gsp_phys_hazard_ack, gsp_phys_hazard_ack, bool, 0644);
MODULE_PARM_DESC(gsp_phys_hazard_ack,
"Acknowledge the host-freeze hazard of unbinding the distribution driver; required in addition to gsp_phys_gpu=1 (default 0)");
+/*
+ * Ownership selector (P1). Default 0 keeps the supervised module-owned
+ * behavior: gsp_phys_gpu=1 claims and boots at module load and releases at
+ * rmmod. With 1, module load claims nothing; the first mdev create starts the
+ * refcounted backend session (claim+boot asynchronously, so mdev create never
+ * blocks) and the last mdev remove stops and releases it after the emulation
+ * thread is stopped. Requires gsp_phys_gpu=1 and gsp_phys_hazard_ack=1; every
+ * failure is fail-open - the backend stays OFF and the mdev keeps working on
+ * the existing emulation path.
+ */
+static bool gsp_phys_mdev_owned_param;
+module_param_named(gsp_phys_mdev_owned, gsp_phys_mdev_owned_param, bool, 0644);
+MODULE_PARM_DESC(gsp_phys_mdev_owned,
+ "Let the first mdev create claim/boot the physical GPU and the last remove release it; requires gsp_phys_gpu=1 + gsp_phys_hazard_ack=1 (default 0)");
+
/* BAR0 registers sampled after claim as proof of live access. */
#define GSP_PHYS_BOOT_0 0x00000000
#define GSP_PHYS_BOOT_1 0x00000004
@@ -1004,14 +1020,15 @@ void gsp_phys_gpu_release(void)
}
/*
- * Backend session state (P0 skeleton, inert until a caller wires it). One
- * physical GPU, one live claim, refcounted between the module-owned
- * load-time claim and the guest-facing mdev owner. gsp_phys_session_lock is
- * the outermost backend lock: it is held for state/refcount/owner changes
- * only and dropped before gsp_phys_gpu_claim()/_release(), which take
- * gsp_phys_rm_lock inside. The generation counter is bumped when the session
- * leaves READY so a forward that captured the previous value can re-check it
- * under the rm lock and refuse instead of racing the release.
+ * Backend session state (P0 skeleton + P1 lifecycle). One physical GPU, one
+ * live claim, refcounted between the module-owned load-time claim and the
+ * guest-facing mdev owner. gsp_phys_session_lock is the outermost backend
+ * lock: it is held for state/refcount/owner changes only and dropped before
+ * gsp_phys_gpu_claim()/_release(), which take gsp_phys_rm_lock inside (lock
+ * order: session lock outermost, rm lock inner; no guest lock is ever held
+ * across either). The generation counter is bumped when the session leaves
+ * READY so a forward that captured the previous value can re-check it under
+ * the rm lock and refuse instead of racing the release.
*/
static DEFINE_MUTEX(gsp_phys_session_lock);
@@ -1020,10 +1037,20 @@ static struct gsp_phys_backend_session {
u32 refcount;
u32 generation;
bool module_owned;
+ bool releasing;
} backend = {
.state = GSP_PHYS_BACKEND_OFF,
};
+static void gsp_phys_backend_start_work(struct work_struct *work);
+static DECLARE_WORK(gsp_phys_backend_start_work_item,
+ gsp_phys_backend_start_work);
+
+bool gsp_phys_mdev_owned_enabled(void)
+{
+ return gsp_phys_mdev_owned_param;
+}
+
int gsp_phys_backend_state(void)
{
return READ_ONCE(backend.state);
@@ -1034,63 +1061,166 @@ u32 gsp_phys_backend_generation(void)
return READ_ONCE(backend.generation);
}
+/*
+ * Claim the release of the session under the session lock. Returns true only
+ * for the caller that must run the sleeping release sequence. A module-owned
+ * claim is never released; a concurrent second stopper sees releasing=true
+ * and returns without touching the device.
+ */
+static bool gsp_phys_backend_claim_release_locked(const char *who)
+{
+ if (backend.state == GSP_PHYS_BACKEND_OFF)
+ return false;
+ if (backend.module_owned) {
+ pr_info("gsp-phys: backend %s: module-owned claim stays up (released at rmmod)\n",
+ who);
+ return false;
+ }
+ if (backend.releasing) {
+ pr_info("gsp-phys: backend %s: release already in progress\n",
+ who);
+ return false;
+ }
+ if (backend.state != GSP_PHYS_BACKEND_STOPPING) {
+ WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_STOPPING);
+ backend.generation++;
+ pr_info("gsp-phys: backend %s: STOPPING (generation=%u, refcount=%u)\n",
+ who, backend.generation, backend.refcount);
+ }
+ backend.releasing = true;
+ return true;
+}
+
+/*
+ * The sleeping half of a release: wait for an in-flight claim/boot worker to
+ * finish (cancel_work_sync is a no-op when it never ran or already finished),
+ * then release the claim and mark the session OFF. A worker that observes
+ * STOPPING after its claim leaves the claim in place for this function to
+ * release, so the device is never claimed without an owner.
+ */
+static void gsp_phys_backend_release_run(void)
+{
+ cancel_work_sync(&gsp_phys_backend_start_work_item);
+
+ if (gsp_phys_gpu_claimed())
+ gsp_phys_gpu_release();
+
+ mutex_lock(&gsp_phys_session_lock);
+ backend.refcount = 0;
+ backend.module_owned = false;
+ backend.releasing = false;
+ WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_OFF);
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_info("gsp-phys: backend release: OFF (owner=mdev)\n");
+}
+
int gsp_phys_backend_start(void)
{
bool was_claimed;
- int ret;
mutex_lock(&gsp_phys_session_lock);
+
+ if (backend.state == GSP_PHYS_BACKEND_STOPPING) {
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_info("gsp-phys: backend start refused: session is STOPPING\n");
+ return -EAGAIN;
+ }
+
if (backend.state != GSP_PHYS_BACKEND_OFF) {
backend.refcount++;
- pr_info("gsp-phys: backend start: session already up (state=%d, refcount=%u)\n",
- backend.state, backend.refcount);
+ pr_info("gsp-phys: backend start: joining live session (state=%d, refcount=%u, owner=%s)\n",
+ backend.state, backend.refcount,
+ backend.module_owned ? "module" : "mdev");
mutex_unlock(&gsp_phys_session_lock);
return 0;
}
/*
- * A live claim can only pre-exist from the load-time module-owned
- * path; join it without claiming again and keep the module as the
- * owner so a later stop() drops only the reference and leaves the
- * release to rmmod.
+ * Gate: an mdev-owned start requires gsp_phys_gpu=1; the claim itself
+ * additionally requires gsp_phys_hazard_ack=1 (gsp_phys_gpu_claim()
+ * refuses -EPERM without it). A pre-existing live claim is the
+ * load-time module-owned path and is joined, not claimed again.
*/
was_claimed = gsp_phys_gpu_claimed();
+ if (!was_claimed && !gsp_phys_gpu_enabled()) {
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_info("gsp-phys: backend start refused: gsp_phys_gpu=0, no claim requested (guest stays on emulation)\n");
+ return -EOPNOTSUPP;
+ }
+
backend.refcount = 1;
backend.module_owned = was_claimed;
+
+ if (was_claimed) {
+ WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_READY);
+ pr_info("gsp-phys: backend start: joined the live module-owned claim (READY)\n");
+ mutex_unlock(&gsp_phys_session_lock);
+ return 0;
+ }
+
WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_CLAIMING);
+ pr_info("gsp-phys: backend start: mdev-owned claim+boot scheduled (refcount=1)\n");
+ schedule_work(&gsp_phys_backend_start_work_item);
mutex_unlock(&gsp_phys_session_lock);
+ return 0;
+}
- if (!was_claimed) {
- pr_info("gsp-phys: backend start: claiming the physical GPU\n");
- ret = gsp_phys_gpu_claim();
- if (ret) {
- mutex_lock(&gsp_phys_session_lock);
- backend.refcount = 0;
- backend.module_owned = false;
- WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_OFF);
- mutex_unlock(&gsp_phys_session_lock);
- pr_err("gsp-phys: backend start: claim failed: %d (backend OFF)\n",
- ret);
- return ret;
- }
- }
+/*
+ * Asynchronous claim+boot worker (mdev-owned). The full claim lifecycle -
+ * including the stage-3c/3d/3e boot when gsp_phys_boot=1 - runs here so mdev
+ * create and init_work never block on the 60 s init-done poll. A failure
+ * leaves the backend OFF: the mdev keeps working on the existing emulation
+ * path.
+ */
+static void gsp_phys_backend_start_work(struct work_struct *work)
+{
+ int ret;
/*
- * gsp_phys_gpu_claim() runs the stage-3d boot inline when the boot
- * parameters are set, so READY here means the session is owned and
- * live; gsp_phys_backend_rpc() additionally gates on init-done.
+ * Re-check under the session lock: a stop that set STOPPING after
+ * schedule_work() but before this worker ran (cancel_work_sync loses
+ * the race when the worker is already running) must not trigger a
+ * pointless claim. The window is only narrowed, not closed - a stop
+ * arriving between this check and the claim is caught by the STOPPING
+ * re-check after the claim and released by the stopper.
*/
mutex_lock(&gsp_phys_session_lock);
+ if (backend.state != GSP_PHYS_BACKEND_CLAIMING) {
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_info("gsp-phys: backend start: worker aborted, session state=%d\n",
+ backend.state);
+ return;
+ }
+ mutex_unlock(&gsp_phys_session_lock);
+
+ pr_info("gsp-phys: backend start: claiming + booting the physical GPU (mdev-owned)\n");
+
+ ret = gsp_phys_gpu_claim();
+
+ mutex_lock(&gsp_phys_session_lock);
+ if (ret) {
+ backend.refcount = 0;
+ backend.module_owned = false;
+ WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_OFF);
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_err("gsp-phys: backend start: claim/boot failed: %d; backend OFF, guest stays on emulation\n",
+ ret);
+ return;
+ }
+ if (backend.state == GSP_PHYS_BACKEND_STOPPING) {
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_info("gsp-phys: backend start: claim completed while STOPPING; release deferred to the stopper\n");
+ return;
+ }
WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_READY);
- pr_info("gsp-phys: backend start: READY (module-owned=%u)\n",
- backend.module_owned);
+ pr_info("gsp-phys: backend start: READY (owner=mdev, refcount=%u)\n",
+ backend.refcount);
mutex_unlock(&gsp_phys_session_lock);
- return 0;
}
void gsp_phys_backend_stop(void)
{
- u32 generation;
+ bool release;
mutex_lock(&gsp_phys_session_lock);
if (backend.state == GSP_PHYS_BACKEND_OFF) {
@@ -1101,30 +1231,55 @@ void gsp_phys_backend_stop(void)
if (backend.refcount)
backend.refcount--;
if (backend.refcount) {
- pr_info("gsp-phys: backend stop: refcount=%u, session stays up\n",
- backend.refcount);
+ pr_info("gsp-phys: backend stop: refcount=%u, session stays up (owner=%s)\n",
+ backend.refcount,
+ backend.module_owned ? "module" : "mdev");
mutex_unlock(&gsp_phys_session_lock);
return;
}
- if (backend.module_owned ||
- backend.state != GSP_PHYS_BACKEND_READY) {
- pr_info("gsp-phys: backend stop: state=%d owner=%s, no release\n",
- backend.state,
- backend.module_owned ? "module" : "mdev");
+ release = gsp_phys_backend_claim_release_locked("stop");
+ mutex_unlock(&gsp_phys_session_lock);
+
+ if (release)
+ gsp_phys_backend_release_run();
+}
+
+/*
+ * Two-phase stop for the module-exit path: begin_stop() gates the session
+ * (STOPPING + generation bump) while the caller drains the emulation threads,
+ * then end_stop() runs the release. Together they keep the section 1.4
+ * ordering "STOPPING -> drain -> release" without holding the session lock
+ * across the drain or the release.
+ */
+void gsp_phys_backend_begin_stop(void)
+{
+ mutex_lock(&gsp_phys_session_lock);
+ if (backend.state == GSP_PHYS_BACKEND_OFF) {
mutex_unlock(&gsp_phys_session_lock);
return;
}
-
- WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_STOPPING);
- generation = ++backend.generation;
+ if (backend.module_owned) {
+ mutex_unlock(&gsp_phys_session_lock);
+ pr_info("gsp-phys: backend begin stop: module-owned claim; no state change\n");
+ return;
+ }
+ if (backend.state != GSP_PHYS_BACKEND_STOPPING) {
+ WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_STOPPING);
+ backend.generation++;
+ pr_info("gsp-phys: backend begin stop: STOPPING (generation=%u, refcount=%u)\n",
+ backend.generation, backend.refcount);
+ }
mutex_unlock(&gsp_phys_session_lock);
- pr_info("gsp-phys: backend stop: STOPPING (generation=%u)\n", generation);
+}
- gsp_phys_gpu_release();
+void gsp_phys_backend_end_stop(void)
+{
+ bool release;
mutex_lock(&gsp_phys_session_lock);
- backend.module_owned = false;
- WRITE_ONCE(backend.state, GSP_PHYS_BACKEND_OFF);
+ release = gsp_phys_backend_claim_release_locked("end stop");
mutex_unlock(&gsp_phys_session_lock);
- pr_info("gsp-phys: backend stop: OFF\n");
+
+ if (release)
+ gsp_phys_backend_release_run();
}
diff --git a/kernel/gsp_phys_gpu.h b/kernel/gsp_phys_gpu.h
index 36532a5..c8beb1d 100644
--- a/kernel/gsp_phys_gpu.h
+++ b/kernel/gsp_phys_gpu.h
@@ -13,10 +13,17 @@ void gsp_phys_gpu_release(void);
void __iomem *gsp_phys_gpu_bar0(void);
/*
- * Backend session state (P0 skeleton; one physical GPU, one live claim).
- * OFF until a start claims the device; READY is the only state in which live
- * RPC forwarding is legal. The refcount and owner flag decide who releases:
- * the load-time module-owned claim is joined, never released, by a stop().
+ * Read by the backend session to enforce the mdev-owned start gate; the
+ * gsp_phys_gpu module parameter itself is owned by main.c.
+ */
+bool gsp_phys_gpu_enabled(void);
+
+/*
+ * Backend session state (P0 skeleton + P1 lifecycle; one physical GPU, one
+ * live claim). OFF until a start claims the device; READY is the only state
+ * in which live RPC forwarding is legal. The refcount and owner flag decide
+ * who releases: the load-time module-owned claim is joined, never released,
+ * by a stop(); an mdev-owned claim is released by the last stop().
*/
enum gsp_phys_backend_state {
GSP_PHYS_BACKEND_OFF = 0,
@@ -28,15 +35,30 @@ enum gsp_phys_backend_state {
/*
* Refcounted session start/stop around gsp_phys_gpu_claim()/_release(); safe
- * (and idempotent) in any state. The generation counter is bumped when the
- * session leaves READY; a forward that captured the previous value re-checks
- * it under gsp_phys_rm_lock and refuses, so a stop cannot race an in-flight
- * forward. The lock itself stays private to gsp_phys_gpu.c; the state and
- * generation accessors are lock-free reads for use from the RPC path.
+ * (and idempotent) in any state. gsp_phys_backend_start() never blocks on the
+ * claim/boot: an mdev-owned start schedules a work item and returns, and a
+ * failed claim leaves the session OFF (fail-open: the caller keeps using the
+ * emulation path). The generation counter is bumped when the session leaves
+ * READY; a forward that captured the previous value re-checks it under
+ * gsp_phys_rm_lock and refuses, so a stop cannot race an in-flight forward.
+ * The lock itself stays private to gsp_phys_gpu.c; the state and generation
+ * accessors are lock-free reads for use from the RPC path.
+ *
+ * Module exit uses the two-phase form so the ยง1.4 ordering can be kept:
+ * begin_stop() gates the session (STOPPING + generation bump) before the
+ * caller drains the emulation threads, then end_stop() runs the release.
*/
int gsp_phys_backend_start(void);
void gsp_phys_backend_stop(void);
+void gsp_phys_backend_begin_stop(void);
+void gsp_phys_backend_end_stop(void);
int gsp_phys_backend_state(void);
u32 gsp_phys_backend_generation(void);
+/*
+ * True when the gsp_phys_mdev_owned module parameter is set (default 0:
+ * load-time module-owned claim/boot, today's behavior).
+ */
+bool gsp_phys_mdev_owned_enabled(void);
+
#endif /* GSP_PHYS_GPU_H */
diff --git a/kernel/include/nvidia-vgpu-vfio.h b/kernel/include/nvidia-vgpu-vfio.h
index 961bba2..aed3dc5 100644
--- a/kernel/include/nvidia-vgpu-vfio.h
+++ b/kernel/include/nvidia-vgpu-vfio.h
@@ -135,6 +135,12 @@ struct nv_vgpu_device {
/* VFIO device registration state */
bool vfio_dev_registered;
+
+ /* True when this mdev holds a reference on the physical backend
+ * session (gsp_phys_backend_start() succeeded in init_work); the
+ * matching gsp_phys_backend_stop() runs in cleanup_work after the
+ * emulation thread is stopped. */
+ bool phys_backend_ref;
};
typedef struct nv_vgpu_device nv_vgpu_device;
diff --git a/kernel/main.c b/kernel/main.c
index babcc22..f47ef69 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -553,6 +553,9 @@ static int nv_vgpu_mdev_create(struct mdev_device *mdev)
/* Initialize VFIO device registration state */
vgpu->vfio_dev_registered = false;
+ /* No physical backend reference until init_work starts one */
+ vgpu->phys_backend_ref = false;
+
/* Initialize VFIO device open/close tracking */
atomic_set(&vgpu->vfio_dev_open_count, 0);
init_waitqueue_head(&vgpu->vfio_dev_close_wait);
@@ -1447,11 +1450,15 @@ static int __init nv_vgpu_init(void)
}
pr_info("gsp-resman-vfio: VFIO ops set\n");
- if (gsp_phys_gpu_param) {
+ if (gsp_phys_gpu_param && !gsp_phys_mdev_owned_enabled()) {
ret = gsp_phys_gpu_claim();
if (ret)
pr_warn("gsp-resman-vfio: physical GPU claim failed: %d\n",
ret);
+ } else if (gsp_phys_gpu_param) {
+ pr_info("gsp-resman-vfio: gsp_phys_mdev_owned=1: physical GPU claim+boot deferred to the first mdev create\n");
+ } else if (gsp_phys_mdev_owned_enabled()) {
+ pr_warn("gsp-resman-vfio: gsp_phys_mdev_owned=1 requires gsp_phys_gpu=1; physical backend stays off\n");
}
/* Test-enabling trigger for the GSP-RM unload RPC plus the read-only
@@ -1487,6 +1494,7 @@ err_class:
static void __exit nv_vgpu_exit(void)
{
struct pci_dev *pdev = NULL;
+ bool mdev_owned = gsp_phys_mdev_owned_enabled();
pr_info("gsp-resman-vfio: nv_vgpu_exit: starting cleanup\n");
@@ -1497,7 +1505,25 @@ static void __exit nv_vgpu_exit(void)
g_vgpu_vfio_ops = NULL;
gsp_phys_boot_debugfs_fini();
- gsp_phys_gpu_release();
+
+ if (mdev_owned) {
+ /*
+ * Mdev-owned session: gate every live forward first (STOPPING
+ * under the session lock, generation bump), drain the
+ * emulation threads with the mdev unregistration below, and
+ * only then release the claim - release must not race a live
+ * forward. The mdev remove path releases when live mdevs
+ * exist; end_stop() below covers the no-mdev case.
+ */
+ gsp_phys_backend_begin_stop();
+ } else {
+ /*
+ * Module-owned claim (gsp_phys_gpu=1, mdev_owned=0): keep
+ * today's position relative to the debugfs teardown and the
+ * emulation threads; module-owned sessions never forward live.
+ */
+ gsp_phys_gpu_release();
+ }
if (nv_vgpu_driver.device_count > 0) {
struct nv_vgpu_device *vgpu;
@@ -1526,10 +1552,17 @@ static void __exit nv_vgpu_exit(void)
pr_info("gsp-resman-vfio: nv_vgpu_exit: sema service stopped\n");
pr_info("gsp-resman-vfio: nv_vgpu_exit: unregistering mdev driver\n");
- /* Unregister mdev driver FIRST, then mdev parent */
+ /* Unregister mdev driver FIRST, then mdev parent. This drains the
+ * mdev remove callbacks, hence the emulation threads. */
mdev_unregister_driver(&nv_vgpu_mdev_driver);
pr_info("gsp-resman-vfio: nv_vgpu_exit: mdev driver unregistered\n");
+ if (mdev_owned) {
+ /* Drain complete: release the mdev-owned claim (no-op when
+ * the mdev remove path already released it). */
+ gsp_phys_backend_end_stop();
+ }
+
/* Unregister mdev parent via nvidia.ko */
if (pdev) {
pr_info("gsp-resman-vfio: nv_vgpu_exit: unregistering mdev parent\n");
@@ -1592,6 +1625,23 @@ static void nv_vgpu_device_init_work(struct work_struct *work)
nv_vgpu_warn(vgpu, "Migration init failed: %d", ret);
}
+ /*
+ * mdev-owned physical backend: the first mdev create (refcount 0 -> 1)
+ * claims and boots the real GPU asynchronously (gsp_phys_backend_start()
+ * only schedules the claim/boot work item, so this never blocks). Any
+ * failure is fail-open: log it, leave the backend OFF and keep serving
+ * the guest from the emulation path.
+ */
+ if (gsp_phys_mdev_owned_enabled()) {
+ ret = gsp_phys_backend_start();
+ if (ret) {
+ nv_vgpu_warn(vgpu, "Physical backend start refused/failed: %d; continuing on the emulation path",
+ ret);
+ } else {
+ vgpu->phys_backend_ref = true;
+ }
+ }
+
mutex_lock(&vgpu->lock);
vgpu->state = NV_VGPU_STATE_READY;
mutex_unlock(&vgpu->lock);
@@ -1617,6 +1667,18 @@ static void nv_vgpu_device_cleanup_work(struct work_struct *work)
nv_vgpu_migration_cleanup(vgpu);
gsp_emu_cleanup(vgpu);
+
+ /*
+ * Last-reference backend teardown: gsp_emu_cleanup() above has stopped
+ * this mdev's emulation thread, so no forward can be in flight when the
+ * claim is released. The backend session stays up until the last mdev
+ * removes (refcount 1 -> 0).
+ */
+ if (vgpu->phys_backend_ref) {
+ vgpu->phys_backend_ref = false;
+ gsp_phys_backend_stop();
+ }
+
nv_vgpu_memory_cleanup(vgpu);
nv_vgpu_irq_cleanup(vgpu);
nv_vgpu_bar_cleanup(vgpu);
@@ -2457,6 +2519,13 @@ MODULE_PARM_DESC(chan_dump, "Dump the sema service channel table (any value)");
module_param_named(gsp_phys_gpu, gsp_phys_gpu_param, bool, 0644);
MODULE_PARM_DESC(gsp_phys_gpu, "Claim the physical GPU for the real-GSP backend (default 0)");
+/* Read by the backend session (gsp_phys_gpu.c) to enforce the mdev-owned
+ * start gate; the parameter itself stays owned here. */
+bool gsp_phys_gpu_enabled(void)
+{
+ return gsp_phys_gpu_param;
+}
+
/*
* GR ring discovery: pin the compute channels' rings/USERDs, which follow the
* CE rings in the channel video-memory allocation. Default on; set 0 to leave