diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 02:07:52 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 02:07:52 -0400 |
| commit | 129991a063cbef7cdfc7f10faafdb97107d15e14 (patch) | |
| tree | afab2def10f5505b11df84a3724811dbf29bcd73 /kernel/gsp_rm_emulation.c | |
| parent | fe3055491bfa7d0753fae8069cf6ed30d1b668dd (diff) | |
| download | open-iov-129991a063cbef7cdfc7f10faafdb97107d15e14.tar.gz open-iov-129991a063cbef7cdfc7f10faafdb97107d15e14.zip | |
gsp-phys: freeze-safe release, boot-proof READY, and guest-wiring P2/P3/P5-wave1
Host-freeze fix bundle (validated 2/2 supervised cycles):
- power unlock now latches D0/D3cold by default (new gsp_phys_repower,
default 0); releasing the claim can no longer power-gate the board and
hard-freeze the host on resume
- bounded post-rebind readiness gate before dropping the locks; bounded D0
settle/verify before the unbind
- READY means a live GSP-RM session: the async worker and the module-owned
join only expose READY when init-done is seen and no unload was sent;
otherwise the claim is released fail-open
- FWSEC reads DMEM0/IMEM back before STARTCPU and fails fast when the
staging is not visible
- the claim harness leaves the board latched by default and takes
--restore-power to opt back into the old restore
Guest-wiring:
- P2: generation-keyed fn65 static-config snapshot cache plus a default-off
identity overlay (new gsp_phys_static_live) for name/short-name/VBIOS;
the probe cycle runs once per backend generation at READY
- P3: default-off read-only RM_CONTROL forwarding whitelist (new
gsp_phys_ctrl_forward) with emulated-handle translation to the adopted
server handles and full emulation fallback
- P5 wave 1: default-off shadow execution of guest CE pushes on the ladder
channel (new gsp_phys_work_forward) via a dedicated ordered work item;
the ladder stage-4 submission is refactored into a shared bounded helper
and the software release path stays authoritative
Live: module-owned boot to init-done, guest smoke ALL OK 64/64 with P2/P3
enabled, 514 shadow copies observed on the physical CE; all cycles released
cleanly with WPR2 down and no reboot.
Diffstat (limited to 'kernel/gsp_rm_emulation.c')
| -rw-r--r-- | kernel/gsp_rm_emulation.c | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c index d6aef9e..b0ad98f 100644 --- a/kernel/gsp_rm_emulation.c +++ b/kernel/gsp_rm_emulation.c @@ -10,12 +10,15 @@ #include <linux/slab.h> #include <linux/vfio.h> #include <linux/delay.h> +#include <linux/string.h> #include "nvidia-vgpu-vfio.h" #include "gsp_emulation.h" #include "gsp_rm_emulation.h" #include "gsp_rm_tu104.h" #include "gsp_sema_service.h" +#include "gsp_phys_booter.h" +#include "gsp_phys_gpu.h" /* ============================================================ * Guest-memory access ("virtual DMA" on behalf of the device) @@ -1167,6 +1170,64 @@ static void gsp_rm_build_static_info(u8 *buf) memcpy(buf + TU104_GSCI_OFF_fwWprLayoutOffset + 8, &val64, 8); } +/* + * P2 static-config identity overlay: replace the emulated identity fields + * (GPU name, short name, VBIOS valid/subvendor/subdevice) with the live + * decode from the backend snapshot. FB geometry, masks, engine caps, PDE + * bases and handles stay emulated in this phase. The wait is bounded and + * sleeps; any miss leaves the emulated reply intact (fail-open). + */ +#define GSPRM_STATIC_LIVE_WAIT_MS 2000 + +static void gsp_rm_apply_static_overlay(struct gsp_emu_context *ctx, u8 *buf) +{ + struct gsp_rm_emu *rm = ctx->rm_emu; + struct gsp_phys_backend_static_config snap; + u16 val16; + int ret; + + if (!gsp_phys_static_live_enabled()) + return; + + ret = gsp_phys_backend_static_wait(&snap, GSPRM_STATIC_LIVE_WAIT_MS); + if (ret) { + rm->static_fallback++; + if (ret == -ENODATA) + rm->static_timeout++; + return; + } + + /* Wire slots: name 64 B at 1260, short name 64 B at 1324. An empty + * live string never clears the emulated one. */ + if (snap.gpu_name[0]) + strscpy((char *)(buf + TU104_GSCI_OFF_gpuNameString), + snap.gpu_name, + TU104_GSCI_OFF_gpuShortNameString - + TU104_GSCI_OFF_gpuNameString); + if (snap.gpu_short_name[0]) + strscpy((char *)(buf + TU104_GSCI_OFF_gpuShortNameString), + snap.gpu_short_name, + TU104_GSCI_OFF_gpuNameString_Unicode - + TU104_GSCI_OFF_gpuShortNameString); + + /* Only overlay the VBIOS identity when the live decode carries a valid + * VBIOS; never clear an emulated valid flag. */ + if (snap.b_vbios_valid) { + buf[TU104_GSCI_OFF_bVbiosValid] = 1; + val16 = (u16)snap.vbios_sub_vendor; + memcpy(buf + TU104_GSCI_OFF_vbiosSubVendor, &val16, sizeof(val16)); + val16 = (u16)snap.vbios_sub_device; + memcpy(buf + TU104_GSCI_OFF_vbiosSubDevice, &val16, sizeof(val16)); + } + + rm->static_live++; + nv_vgpu_info(ctx->vgpu, + "GSPRM: static-info overlay applied (generation=%u): name='%s' short='%s' vbiosValid=%u subVendor=0x%04x subDevice=0x%04x", + snap.generation, snap.gpu_name, snap.gpu_short_name, + snap.b_vbios_valid, (u16)snap.vbios_sub_vendor, + (u16)snap.vbios_sub_device); +} + static void gsp_rm_handle_get_static_info(struct gsp_emu_context *ctx, u32 sequence) { @@ -1180,6 +1241,7 @@ static void gsp_rm_handle_get_static_info(struct gsp_emu_context *ctx, } gsp_rm_build_static_info(reply); + gsp_rm_apply_static_overlay(ctx, reply); nv_vgpu_info(ctx->vgpu, "GSPRM: GET_GSP_STATIC_INFO seq=%u -> %u bytes (fb=%lluMB, engines=0x%x/0x%x)", sequence, TU104_GSCI_SIZE, @@ -2608,6 +2670,154 @@ gsp_rm_handle_promote_fault_method_buffers(struct gsp_emu_context *ctx, } } +/* + * Bounded logging for the P3 forward counters: the first four fallbacks (so a + * session that cannot forward is visible) and every 64th event. Never called + * when the parameter is off. + */ +static void gsp_rm_ctrl_forward_note(struct gsp_emu_context *ctx) +{ + struct gsp_rm_emu *rm = ctx->rm_emu; + u64 events = rm->ctrl_fwd_ok + rm->ctrl_fwd_fallback; + + if ((rm->ctrl_fwd_fallback && rm->ctrl_fwd_fallback <= 4) || + (events && (events % 64) == 0)) + nv_vgpu_info(ctx->vgpu, + "GSPRM: RM_CONTROL forward: ok=%llu fallback=%llu timeout=%llu", + rm->ctrl_fwd_ok, rm->ctrl_fwd_fallback, + rm->ctrl_fwd_timeout); +} + +/* + * P3 read-only control forwarding (gsp_phys_ctrl_forward=1; default off). + * + * A small whitelist of side-effect-free queries is sent to the live backend + * session with the guest's emulated internal handles translated to the + * snapshot's adopted server handles; the server's echoed params become the + * guest reply. Everything else - the vGPU-specific controls, anything + * mutating, unknown IDs, an unreadable/absent snapshot, unmappable handles, + * a multi-element request, a backend error/timeout or a malformed reply - + * returns false and the emulated per-command handler runs unchanged + * (fail-open). + * + * gsp_phys_backend_rpc() returns with gsp_phys_rm_lock released and the reply + * in a kmalloc'd copy; the status queue is only written afterwards by + * gsp_rm_send(), never while a backend wait is pending (doc 11 section 6.5). + */ +static bool gsp_rm_ctrl_forward(struct gsp_emu_context *ctx, + const struct gsp_rm_control_req *req, + const u8 *req_params, u8 *reply, + u32 reply_data_len, u32 sequence) +{ + struct gsp_rm_emu *rm = ctx->rm_emu; + struct gsp_phys_backend_static_config snap; + struct gsp_rm_control_req fwd_hdr; + u8 *fwd_req; + void *rsp = NULL; + u32 rsp_len = 0; + u32 out_status; + u32 out_params_size; + int ret; + + /* Wave 1 whitelist: compile-time side-effect-free queries only. */ + switch (req->cmd) { + case TU104_CTRL_BUS_GET_PCI_BAR_INFO: + case TU104_CTRL_BUS_GET_INFO_V2: + case TU104_CTRL_FB_GET_INFO_V2: + case TU104_CTRL_INTERNAL_GMMU_GET_STATIC_INFO: + break; + default: + return false; + } + + /* The capture hands us only the first queue element, so a request that + * does not fit one element (paramsSize+24 <= GSPRM_MAX_SINGLE_ELEM_DATA) + * cannot be reassembled and stays emulated. */ + if (req->paramsSize > GSPRM_MAX_SINGLE_ELEM_DATA - GSP_RM_CTRL_HDR_SIZE) + goto fallback; + + /* A valid snapshot implies a READY session and the adopted handles. */ + if (gsp_phys_backend_static_get(&snap)) + goto fallback; + + /* Translate the guest's emulated internal handles; any other handle + * domain stays emulated. */ + if (req->hClient != TU104_H_INTERNAL_CLIENT || + (req->hObject != TU104_H_INTERNAL_DEVICE && + req->hObject != TU104_H_INTERNAL_SUBDEVICE)) + goto fallback; + + fwd_req = kmalloc(GSP_RM_CTRL_HDR_SIZE + req->paramsSize, GFP_KERNEL); + if (!fwd_req) + goto fallback; + memset(&fwd_hdr, 0, sizeof(fwd_hdr)); + fwd_hdr.hClient = snap.h_internal_client; + fwd_hdr.hObject = req->hObject == TU104_H_INTERNAL_DEVICE ? + snap.h_internal_device : snap.h_internal_subdevice; + fwd_hdr.cmd = req->cmd; + fwd_hdr.paramsSize = req->paramsSize; + fwd_hdr.flags = req->flags; + memcpy(fwd_req, &fwd_hdr, GSP_RM_CTRL_HDR_SIZE); + if (req->paramsSize) + memcpy(fwd_req + GSP_RM_CTRL_HDR_SIZE, req_params, + req->paramsSize); + + ret = gsp_phys_backend_rpc(GSPRM_RPC_FUNC_GSP_RM_CONTROL, fwd_req, + GSP_RM_CTRL_HDR_SIZE + req->paramsSize, + &rsp, &rsp_len, 500); + kfree(fwd_req); + if (ret) { + if (ret == -ETIMEDOUT) + rm->ctrl_fwd_timeout++; + goto fallback; + } + + /* + * The reply is rpc_gsp_rm_control_v03_00: status at +12, paramsSize + * at +16, echoed params at +24 - the same 24-byte prefix the emulated + * handler builds below. The RPC-level rpc_result was already checked + * by gsp_phys_backend_rpc(). + */ + if (!rsp || rsp_len < GSP_RM_CTRL_HDR_SIZE) + goto fallback; + memcpy(&out_status, (u8 *)rsp + 12, 4); + memcpy(&out_params_size, (u8 *)rsp + 16, 4); + if (out_status != 0 || + out_params_size > rsp_len - GSP_RM_CTRL_HDR_SIZE || + out_params_size > reply_data_len - GSP_RM_CTRL_HDR_SIZE) + goto fallback; + + /* Build the guest reply exactly as the emulated handler does: the + * guest's own handles/cmd and the server's status/params. */ + memcpy(reply + 0, &req->hClient, 4); + memcpy(reply + 4, &req->hObject, 4); + memcpy(reply + 8, &req->cmd, 4); + memcpy(reply + 12, &out_status, 4); + memcpy(reply + 16, &out_params_size, 4); + memcpy(reply + 20, &req->flags, 4); + if (out_params_size) + memcpy(reply + GSP_RM_CTRL_HDR_SIZE, + (u8 *)rsp + GSP_RM_CTRL_HDR_SIZE, out_params_size); + kfree(rsp); + + if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_CONTROL, sequence, 0, + reply, GSP_RM_CTRL_HDR_SIZE + out_params_size)) { + nv_vgpu_err(ctx->vgpu, "GSPRM: RM_CONTROL forward reply send failed cmd=0x%x", + req->cmd); + rm->ctrl_fwd_fallback++; + } else { + rm->ctrl_fwd_ok++; + } + gsp_rm_ctrl_forward_note(ctx); + return true; + +fallback: + kfree(rsp); + rm->ctrl_fwd_fallback++; + gsp_rm_ctrl_forward_note(ctx); + return false; +} + static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx, const u8 *elem, u32 sequence) { @@ -2636,6 +2846,19 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx, return; } + /* + * P3: when enabled, forward a whitelist of read-only queries to the + * live backend. The helper builds and sends the reply itself; a false + * return means nothing was sent and the emulated switch below runs + * byte-identically to the parameter-off path. + */ + if (gsp_phys_ctrl_forward_enabled() && + gsp_rm_ctrl_forward(ctx, &req, req_data + GSP_RM_CTRL_HDR_SIZE, + reply, reply_data_len, sequence)) { + kfree(reply); + return; + } + switch (req.cmd) { case TU104_CTRL_FIFO_GET_DEVICE_INFO_TABLE: gsp_rm_handle_fifo_device_table(ctx, sequence, &req, |
