diff options
Diffstat (limited to 'kernel/gsp_phys_gpu.c')
| -rw-r--r-- | kernel/gsp_phys_gpu.c | 257 |
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(); } |
