summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_phys_gpu.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/gsp_phys_gpu.c')
-rw-r--r--kernel/gsp_phys_gpu.c257
1 files changed, 206 insertions, 51 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();
}