summaryrefslogtreecommitdiffstats
path: root/kernel/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/main.c')
-rw-r--r--kernel/main.c504
1 files changed, 491 insertions, 13 deletions
diff --git a/kernel/main.c b/kernel/main.c
index 257638a..c96abf8 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -849,25 +849,36 @@ static ssize_t nv_vgpu_vfio_dev_write(struct vfio_device *vdev, const char __use
if (copy_from_user(tmp, buf, count))
return -EFAULT;
- if ((u64)pos == 0x88704 || (gsp_sema_dbg && bar0_log < 64)) {
+ if ((u64)pos >= 0x88000 && (u64)pos < 0x89000) {
+ /*
+ * SESSION 153-CONTINUED-34: ANY write in the PCFG
+ * window is a submission doorbell, not only the
+ * virtual-function doorbell at 0x88704 the old code
+ * dispatched. Feed every 4-byte write to the doorbell
+ * path so the pump is woken for the client's channel
+ * kicks no matter which register the driver poked
+ * (the service thread also watches the mmap'd shadow
+ * for writes that never trap). Logging stays bounded.
+ */
static u32 db_log;
u64 v = 0;
memcpy(&v, tmp, count);
-
- if ((u64)pos == 0x88704) {
- if (gsp_sema_dbg && db_log < 512) {
- pr_info("vgsp: DOORBELL write pos=0x%llx size=%zu val=0x%llx\n",
- (u64)pos, count, v);
- db_log++;
- }
- if (count == 4)
- gsp_sema_service_doorbell((u32)v);
- } else {
- pr_info("vgsp: BAR0 write pos=0x%llx size=%zu val=0x%llx\n",
+ if (gsp_sema_dbg && db_log < 512) {
+ pr_info("vgsp: PCFG write pos=0x%llx size=%zu val=0x%llx\n",
(u64)pos, count, v);
- bar0_log++;
+ db_log++;
}
+ if (count == 4)
+ gsp_sema_service_doorbell((u32)v);
+ } else if ((u64)pos == 0x88704 ||
+ (gsp_sema_dbg && bar0_log < 64)) {
+ u64 v = 0;
+
+ memcpy(&v, tmp, count);
+ pr_info("vgsp: BAR0 write pos=0x%llx size=%zu val=0x%llx\n",
+ (u64)pos, count, v);
+ bar0_log++;
}
ret = nv_vgpu_bar_emul_write(vgpu, 0, (u64)pos, tmp, count);
@@ -2554,6 +2565,25 @@ module_param_cb(sema_probe, &nv_vgpu_sema_probe_ops, NULL, 0200);
MODULE_PARM_DESC(sema_probe, "Dump sema-service map + slot values for a GPU VA");
/*
+ * ISSUE-0002 aid: force a client-session flush past the live-channel skip.
+ * Write anything (echo 1 > .../parameters/sema_force_flush).
+ */
+static int nv_vgpu_sema_force_flush_set(const char *val,
+ const struct kernel_param *kp)
+{
+ gsp_sema_service_sess_flush_force();
+ return 0;
+}
+
+static const struct kernel_param_ops nv_vgpu_sema_force_flush_ops = {
+ .set = nv_vgpu_sema_force_flush_set,
+};
+
+module_param_cb(sema_force_flush, &nv_vgpu_sema_force_flush_ops, NULL, 0200);
+MODULE_PARM_DESC(sema_force_flush,
+ "Force a client-session flush even with live channels (ISSUE-0002 aid)");
+
+/*
* Register the guest PMA scrubber channel buffer words (finish payload PA,
* host-semaphore PA) the guest scanner resolved:
* echo 0x11a8ff004,0x11a8ff000 > .../parameters/sema_scrubbuf_add
@@ -2752,6 +2782,10 @@ MODULE_PARM_DESC(sema_copy_dst2, "Second destination window for sema_copy_dst (d
* the release the client synchronizes on; the translation usually lands within
* a service pass.
*/
+module_param_named(gsp_phys_gr_peek, gsp_phys_gr_peek, uint, 0644);
+MODULE_PARM_DESC(gsp_phys_gr_peek,
+ "Peek the ring slots past GP_PUT at each GR launch decision (diagnostic, default 0)");
+
module_param_named(sema_ce_grace_ms, gsp_sema_ce_grace_ms, uint, 0644);
MODULE_PARM_DESC(sema_ce_grace_ms, "CE pending-entry grace window in ms (default 25000)");
@@ -2764,6 +2798,17 @@ module_param_named(sema_push_hold_ms, gsp_sema_push_hold_ms, uint, 0644);
MODULE_PARM_DESC(sema_push_hold_ms, "CE unreadable-entry work-before-release hold in ms (default 2500, 0 disables)");
/*
+ * SESSION 153-CONTINUED-54: GR setup-push (client MME-setup upload / any
+ * >= 12 KB GR push) pending hold (ms). An unreadable/all-zero setup push
+ * keeps its ring entry pending past the failed-rescue consume until the
+ * demanded 4K-page guest pin lands; 0 disables (demand rows still emitted).
+ * Default 0: the demand channel cannot yet resolve the GL pushbuffer VA
+ * families, so the hold only delays the ring (see gsp_sema_service.c).
+ */
+module_param_named(sema_gr_setup_hold_ms, gsp_sema_gr_setup_hold_ms, uint, 0644);
+MODULE_PARM_DESC(sema_gr_setup_hold_ms, "GR setup-push (MME-setup/>=12KB) pending hold in ms (default 0 = off; 20000 = aggressive, needs a resolvable demand family)");
+
+/*
* Grace window (ms) an untranslatable GR entry stays pending before the pump
* consumes it as stale, so a late push-buffer pin can still make it readable.
*/
@@ -2771,6 +2816,149 @@ module_param_named(sema_gr_grace_ms, gsp_sema_gr_grace_ms, uint, 0644);
MODULE_PARM_DESC(sema_gr_grace_ms, "GR pending-entry grace window in ms (default 8000)");
/*
+ * GRFIX FIX1b (r213): absolute per-launch bound in ms for the GR fill
+ * data-stalled hold (the FIX1 page-drop hold). The deadline is keyed by the
+ * launch push VA, fixed on the first hold and never re-armed; when it passes
+ * the launch proceeds (forwarded best-effort) so a never-resolving page can
+ * never block the guest. 0 keeps the 3000 ms default.
+ */
+module_param_named(gsp_phys_gr_hold_max_ms, gsp_phys_gr_hold_max_ms, uint, 0644);
+MODULE_PARM_DESC(gsp_phys_gr_hold_max_ms, "Absolute GR fill hold bound in ms, keyed by launch push VA (default 3000; launch proceeds when it passes)");
+
+/*
+ * GRFIX FIX1e (r215): master gate for the GR fill hold. At 0 (default) the
+ * fill logs dropped operand pages and takes the historical forward path
+ * immediately - it can never hold or delay a launch. At 1 the page-count
+ * hold and the absolute bound apply.
+ */
+module_param_named(gsp_phys_gr_fill_hold, gsp_phys_gr_fill_hold, uint, 0644);
+MODULE_PARM_DESC(gsp_phys_gr_fill_hold, "GR fill hold on unresolved operand pages (default 0: log-only, never hold)");
+
+/*
+ * r576 DIAG: per-boot witness budget for the GR launch-decision dump (see
+ * svc_gr_dec_dump in gsp_sema_service.c). Nonzero logs, for the first N
+ * launches whose fill saw dropped pages / a partial argument set or whose
+ * decision point carries a dstpend/copy-pending verdict: the decision
+ * verdicts, the launch's resolved data pages (VA -> guest PA), the live
+ * deferred-write records and the pending CE opdef entries. 0 (default) =
+ * off.
+ */
+module_param_named(gsp_phys_gr_dec_log, gsp_phys_gr_dec_log_max, uint, 0644);
+MODULE_PARM_DESC(gsp_phys_gr_dec_log, "GR launch-decision dump budget per boot (default 0: off)");
+
+/*
+ * r576 DIAG: per-boot witness budget for GSP event-post payloads (see
+ * gsp_rm_evlog_post in gsp_rm_emulation.c). Nonzero logs every
+ * EVENT_POST_EVENT record actually sent, with its full payload
+ * (hClient/hEvent/notifyIndex/data) and send result - the evidence needed
+ * when a client parks waiting for a completion wake-up that never arrives.
+ * 0 (default) = off.
+ */
+module_param_named(gsp_rm_ev_log, gsp_rm_ev_log_max, uint, 0644);
+MODULE_PARM_DESC(gsp_rm_ev_log, "GSP event-post payload witness budget per boot (default 0: off)");
+
+/*
+ * r577 experiment knobs for the guest-RM event drop (runtime-writable so
+ * one boot can A/B). See gsp_rm_emulation.c. All default 0 = legacy.
+ */
+module_param_named(gsp_rm_ev_seq, gsp_rm_ev_seq, uint, 0644);
+MODULE_PARM_DESC(gsp_rm_ev_seq, "event records carry the status-queue sequence as RPC sequence (default 0: legacy 0)");
+module_param_named(gsp_rm_ev_nlist, gsp_rm_ev_nlist, uint, 0644);
+MODULE_PARM_DESC(gsp_rm_ev_nlist, "set bNotifyList on event records (default 0: specific-hEvent dispatch)");
+module_param_named(gsp_rm_ev_cl, gsp_rm_ev_cl, uint, 0644);
+MODULE_PARM_DESC(gsp_rm_ev_cl, "override event record hClient (default 0: the allocating client)");
+module_param_named(gsp_rm_ev_func, gsp_rm_ev_func, uint, 0644);
+MODULE_PARM_DESC(gsp_rm_ev_func, "reachability probe: event records carry this RPC function instead of POST_EVENT 0x1003 (default 0)");
+
+module_param_named(gsp_sema_walk_trace, gsp_sema_walk_trace, uint, 0644);
+MODULE_PARM_DESC(gsp_sema_walk_trace, "Trace GMMU walks for unresolved high-VA operands (default 0: off)");
+module_param_named(gsp_sema_winfill, gsp_sema_winfill, bool, 0644);
+MODULE_PARM_DESC(gsp_sema_winfill, "Execute fill-shaped CE transfers to VIDMEM-window VAs (default 0: off)");
+
+/*
+ * F1-b: while a forwarded GR launch is in flight, defer (do not apply) a
+ * RAM-sweep release whose VA is that launch's QMD completion word, so the
+ * backend poll can only break on an engine-authored ack. Deferred releases
+ * are re-seen by later sweep passes; the hold/fallback path applies them when
+ * the hardware copy fails. 0 restores the unconditional sweep apply.
+ */
+module_param_named(gsp_sema_gr_swrel_gate, gsp_sema_gr_swrel_gate, bool, 0644);
+MODULE_PARM_DESC(gsp_sema_gr_swrel_gate, "Defer a sweep-authored in-flight GR release word (default 1; 0 = unconditional sweep apply)");
+
+/*
+ * m164 CLASS-2: hold window (ms) for a readable GR launch push whose decode
+ * saw QMD words but built no launch candidate (replay-suppressed capture,
+ * partial/split QMD, or an unreadable push page). The entry stays unconsumed
+ * for this window and the next pass forces a fresh capture; on expiry the
+ * historical consume applies. 0 disables.
+ */
+module_param_named(sema_gr_nocap_hold_ms, gsp_sema_gr_nocap_hold_ms, uint, 0644);
+MODULE_PARM_DESC(sema_gr_nocap_hold_ms, "GR no-capture entry hold in ms (default 6000, 0 disables)");
+
+/*
+ * m165 (D-F2a): skip the post-registration pool-release hold when a
+ * host-verified injected pin for the VA exists (default on). 0 restores the
+ * legacy blanket 5 s hold.
+ */
+module_param_named(sema_pool_hold_fresh, gsp_sema_pool_hold_fresh, bool, 0644);
+MODULE_PARM_DESC(sema_pool_hold_fresh, "apply pool releases at fresh-pin time instead of registration+5s (default 1)");
+
+/*
+ * m169 (AGENT-F R1): hold device-destination copies translated only by a
+ * page-directory walk until a pin confirms (default on). A wrong-but-valid
+ * stale table entry silently writes the wrong vidmem page; the hold lets
+ * the m163 retry path re-resolve with pins.
+ */
+module_param_named(sema_copy_dst_pin_guard, gsp_sema_copy_dst_pin_guard, bool, 0644);
+module_param_named(sema_wait_gate, gsp_sema_wait_gate, bool, 0644);
+MODULE_PARM_DESC(sema_wait_gate, "honor C56F host-semaphore acquires before running a ring entry's work (default 1 since r408; 0 restores pass-through)");
+module_param_named(sema_wait_hold_ms, gsp_sema_wait_hold_ms, uint, 0644);
+MODULE_PARM_DESC(sema_wait_hold_ms, "bounded acquire-wait hold before fail-open, ms (r401, default 4000; must exceed the emulated launch latency)");
+module_param_named(sema_push_raw, gsp_sema_push_raw, uint, 0644);
+MODULE_PARM_DESC(sema_push_raw, "r481 diagnostic: dump the decoded method stream of the first N large GR pushes (default 0 off)");
+MODULE_PARM_DESC(sema_copy_dst_pin_guard, "hold device-dst copies without a pin-resolved translation (default 1)");
+/*
+ * r609: after a device-dst guard window expires WITHOUT a pin, skip the
+ * window for further first-write device-dst pages of the same channel for
+ * this long (ms) - the r606 held-chain serialization fix. 0 restores the
+ * per-page windows (pre-r609).
+ */
+module_param_named(sema_copy_dst_age_ms, gsp_sema_copy_dst_age_ms, uint, 0644);
+MODULE_PARM_DESC(sema_copy_dst_age_ms, "device-dst guard burst-ageing window after an un-pinned release, ms (r609, default 3000; 0 off)");
+
+/*
+ * r384 (guest boot-corruption lottery): fill-destination guards. A large
+ * virtual fill whose only authority is the walk/scan resolver is held for
+ * gsp_sema_fill_guard_ms while its VA is demanded through the pin channel
+ * (fail-open, mirrors sema_copy_dst_pin_guard); a zero fill targeting a
+ * page-table-shaped guest page is refused loudly (FILL-PT-REFUSED).
+ */
+module_param_named(sema_fill_guard, gsp_sema_fill_guard, bool, 0644);
+MODULE_PARM_DESC(sema_fill_guard, "guard virtual fill destinations (pin-demand hold + page-table-shape refusal, default 1)");
+module_param_named(sema_fill_guard_ms, gsp_sema_fill_guard_ms, uint, 0644);
+MODULE_PARM_DESC(sema_fill_guard_ms, "fill pin-demand hold window in ms (default 800)");
+
+/*
+ * r231 READ-ORDER guard: defer a device-src copy whose source VA has a
+ * pending device-dst write hold so the read re-decodes after the write
+ * resolves (fixes the ctaid_probe RAWA upload/readback inversion).
+ * Default 0: witness only (READ-ORDER-WIT). Opt-in pending an A/B proving
+ * no green regression.
+ */
+module_param_named(sema_read_order_guard, gsp_sema_read_order_guard, bool, 0644);
+MODULE_PARM_DESC(sema_read_order_guard, "defer device-src reads of a VA with a pending held device-dst write (default 0: witness only)");
+
+/*
+ * COPYWIT fail4 (2026-10-18): per-boot budget for the sema-bigcopy resolution
+ * witnesses. svc_copy's large-copy proof logs both operands' resolved
+ * VA -> pa/aperture/provenance, which is the read side of the client data
+ * path; raise this for a failing-client discrimination cycle so the lines
+ * survive past the boot's head. 0 disables.
+ */
+module_param_named(sema_bigcopy_log, gsp_sema_bigcopy_log_max, uint, 0644);
+MODULE_PARM_DESC(sema_bigcopy_log, "sema-bigcopy resolution witness budget per boot (default 24; 0 disables)");
+
+/*
* Debug: inject a verified {GPU VA -> guest PA} translation into the
* semaphore service (used when the client's mapping cannot be discovered
* from guest structures):
@@ -2813,6 +3001,61 @@ module_param_cb(sema_map_add, &nv_vgpu_sema_map_add_ops, NULL, 0200);
MODULE_PARM_DESC(sema_map_add, "Inject a GPU VA -> guest PA mapping (hex va,hex pa)");
/*
+ * Debug: retrigger the client-VAS page-directory discovery with a host
+ * content-verified {ring VA -> ring PA} pair (the ring page's GPFIFO
+ * entries are the fingerprint):
+ * echo 0x<va>,0x<pa> > .../parameters/sema_pdb_discover
+ */
+static int nv_vgpu_sema_pdb_discover_set(const char *val,
+ const struct kernel_param *kp)
+{
+ char buf[64];
+ char *comma;
+ u64 va = 0, pa = 0;
+
+ strscpy(buf, val, sizeof(buf));
+ comma = strchr(buf, ',');
+ if (!comma)
+ return -EINVAL;
+ *comma = '\0';
+ if (kstrtoull(buf, 0, &va) || kstrtoull(comma + 1, 0, &pa))
+ return -EINVAL;
+
+ return gsp_sema_service_pdb_discover(va, pa);
+}
+
+static const struct kernel_param_ops nv_vgpu_sema_pdb_discover_ops = {
+ .set = nv_vgpu_sema_pdb_discover_set,
+};
+
+module_param_cb(sema_pdb_discover, &nv_vgpu_sema_pdb_discover_ops, NULL, 0200);
+MODULE_PARM_DESC(sema_pdb_discover, "Retrigger the VAS pdb discovery with a verified ring pair (hex va,hex pa)");
+
+/*
+ * Debug: bounded channel rewind. Clears the consumed-slot bitmap and the
+ * per-entry rescue state for one channel (hex h_object) or every channel
+ * ("all"), so the pump re-processes its ring from slot 0 - used after the
+ * VAS pdb anchor lands so entries skipped before it re-decode (the release
+ * they carry would otherwise be lost for that put epoch).
+ */
+static int nv_vgpu_sema_chan_rewind_set(const char *val,
+ const struct kernel_param *kp)
+{
+ u32 h = 0xffffffffu;
+
+ if (strncmp(val, "all", 3) && kstrtouint(val, 0, &h))
+ return -EINVAL;
+ return gsp_sema_service_chan_rewind(h);
+}
+
+static const struct kernel_param_ops nv_vgpu_sema_chan_rewind_ops = {
+ .set = nv_vgpu_sema_chan_rewind_set,
+};
+
+module_param_cb(sema_chan_rewind, &nv_vgpu_sema_chan_rewind_ops, NULL, 0200);
+MODULE_PARM_DESC(sema_chan_rewind, "Rewind a channel's ring (hex h_object or 'all')");
+
+/*
* Debug: register a channel's GPU completion counter (guest PA of the u32
* tracking counter the GPU would write when a push completes):
* echo <h_object>,<pa> > .../parameters/sema_counter
@@ -2876,6 +3119,62 @@ module_param_cb(sema_vidmem_dump, &nv_vgpu_sema_vidmem_dump_ops, NULL, 0200);
MODULE_PARM_DESC(sema_vidmem_dump, "Dump emulated vidmem (addr,len) to the kernel log");
/*
+ * X/GL frame-pacer mirror (SESSION 148 perf): mirror the X server's
+ * submitted-byte counter (guest PA) into its GPU-progress word (guest PA)
+ * so the driver's per-frame 10 s wait completes in milliseconds.
+ * echo 0x112345678,0x112345000 > .../parameters/sema_xw_mirror
+ * echo 0,0 > .../parameters/sema_xw_mirror (stop)
+ */
+static int nv_vgpu_sema_xw_mirror_set(const char *val,
+ const struct kernel_param *kp)
+{
+ char buf[64];
+ char *comma;
+ u64 cnt = 0, mem = 0;
+
+ strscpy(buf, val, sizeof(buf));
+ comma = strchr(buf, ',');
+ if (!comma)
+ return -EINVAL;
+ *comma = '\0';
+ if (kstrtoull(buf, 0, &cnt) || kstrtoull(comma + 1, 0, &mem))
+ return -EINVAL;
+
+ return gsp_sema_service_xw_mirror(cnt, mem);
+}
+
+static const struct kernel_param_ops nv_vgpu_sema_xw_mirror_ops = {
+ .set = nv_vgpu_sema_xw_mirror_set,
+};
+
+module_param_cb(sema_xw_mirror, &nv_vgpu_sema_xw_mirror_ops, NULL, 0200);
+MODULE_PARM_DESC(sema_xw_mirror, "Mirror X frame counter (cnt_pa,mem_pa; 0,0=stop)");
+
+/*
+ * X/GL submit-kick completion clearer (SESSION 148 r873): PA of the u16
+ * status word (member page offset 0xBE) that the X submit path arms to
+ * 0x8000 and polls until 0. Cleared host-side so the kick settles in ms.
+ * echo 0x1123456be > .../parameters/sema_xw_comp
+ * echo 0 > .../parameters/sema_xw_comp (stop)
+ */
+static int nv_vgpu_sema_xw_comp_set(const char *val,
+ const struct kernel_param *kp)
+{
+ u64 pa = 0;
+
+ if (kstrtoull(val, 0, &pa))
+ return -EINVAL;
+ return gsp_sema_service_xw_comp(pa);
+}
+
+static const struct kernel_param_ops nv_vgpu_sema_xw_comp_ops = {
+ .set = nv_vgpu_sema_xw_comp_set,
+};
+
+module_param_cb(sema_xw_comp, &nv_vgpu_sema_xw_comp_ops, NULL, 0200);
+MODULE_PARM_DESC(sema_xw_comp, "Clear X submit member status at PA (0=stop)");
+
+/*
* Debug: search the whole emulated vidmem for an 8-byte pattern:
* echo 0x0000000400000001,16 > .../parameters/sema_vidmem_find
*/
@@ -3280,6 +3579,47 @@ static int nv_vgpu_dump_vidmem_set(const char *val, const struct kernel_param *k
mutex_unlock(&nv_vgpu_driver.lock);
return 0;
}
+ if (!strncmp(buf, "map", 3)) {
+ /*
+ * Coarse non-zero map: one line per 1 MB block that holds
+ * any non-zero dword (SESSION 131: locate the live client's
+ * uploaded content - the image BO, the code heap, the rings -
+ * inside the 256 MB aperture without 65536 range dumps).
+ */
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_for_each_entry(vgpu, &nv_vgpu_driver.devices, list) {
+ u8 *base = vgpu->bars[1].vaddr;
+ size_t size = vgpu->bars[1].size;
+ size_t blk, nblk = (size + (1 << 20) - 1) >> 20;
+ size_t o;
+
+ if (!base || !size)
+ continue;
+ for (blk = 0; blk < nblk; blk++) {
+ size_t start = blk << 20;
+ size_t end = start + (1 << 20);
+ u32 nz = 0, first = 0;
+
+ if (end > size)
+ end = size;
+ for (o = start; o + 4 <= end; o += 4) {
+ u32 v;
+
+ memcpy(&v, base + o, 4);
+ if (v) {
+ if (!nz)
+ first = (u32)(o & 0xFFFFF);
+ nz++;
+ }
+ }
+ if (nz)
+ pr_info("vgsp: vidmem map blk 0x%zx nz=%u first=0x%x\n",
+ start, nz, first);
+ }
+ }
+ mutex_unlock(&nv_vgpu_driver.lock);
+ return 0;
+ }
comma = strchr(buf, ',');
if (comma)
@@ -3746,5 +4086,143 @@ static const struct kernel_param_ops nv_vgpu_rmpost_ops = {
module_param_cb(rmpost, &nv_vgpu_rmpost_ops, NULL, 0200);
MODULE_PARM_DESC(rmpost, "Post a GSP-RM event to the guest by event index (debug)");
+/*
+ * Debug: poke a channel's error notifier with an engine-exception shape and
+ * post its channel event (the relayed-fault delivery edge, manually):
+ * echo 0x5c000019 > .../parameters/grfaultpoke
+ * echo 0 > .../parameters/grfaultpoke (every valid channel)
+ * Experiment: a refused/degenerate GR launch ends its client's
+ * cudaDeviceSynchronize spin if a synthetic engine error completes the wait
+ * (r616; see gsp_sema_channel_fault_poke).
+ */
+struct nv_vgpu_faultpoke_req {
+ u32 h_object;
+ int rc;
+ struct completion done;
+};
+
+static int nv_vgpu_faultpoke_thread(void *arg)
+{
+ struct nv_vgpu_faultpoke_req *req = arg;
+ struct nv_vgpu_device *vgpu;
+
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_for_each_entry(vgpu, &nv_vgpu_driver.devices, list) {
+ if (atomic_read(&vgpu->vfio_dev_open_count) <= 0)
+ continue;
+ req->rc = gsp_sema_channel_fault_poke(vgpu, req->h_object);
+ pr_info("vgsp: grfaultpoke h=0x%x -> %d\n",
+ req->h_object, req->rc);
+ }
+ mutex_unlock(&nv_vgpu_driver.lock);
+
+ complete(&req->done);
+ return 0;
+}
+
+static int nv_vgpu_faultpoke_set(const char *val, const struct kernel_param *kp)
+{
+ static struct nv_vgpu_faultpoke_req req;
+ struct task_struct *task;
+
+ memset(&req, 0, sizeof(req));
+ req.rc = -ENODEV;
+ if (kstrtou32(val, 0, &req.h_object))
+ return -EINVAL;
+
+ init_completion(&req.done);
+ task = kthread_run(nv_vgpu_faultpoke_thread, &req, "vgpu-faultpoke");
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+ wait_for_completion(&req.done);
+ return 0;
+}
+
+static const struct kernel_param_ops nv_vgpu_faultpoke_ops = {
+ .set = nv_vgpu_faultpoke_set,
+};
+
+module_param_cb(grfaultpoke, &nv_vgpu_faultpoke_ops, NULL, 0200);
+MODULE_PARM_DESC(grfaultpoke, "Write a channel error notifier + post its event (r616 experiment)");
+
+/*
+ * Debug r623: deliver a synthetic OS_ERROR_LOG (0x1006) engine-fault report
+ * to the guest's RM queue:
+ * echo 1 > .../parameters/oserrlog
+ * echo 0x1,0,0 > .../parameters/oserrlog (exceptType,runlistId,chid)
+ * Experiment: after a synth-fault, the next client sticks pre-submission; if
+ * the guest's fault handling completes only when the 0x1006 report arrives,
+ * this record is the missing recovery half.
+ */
+struct nv_vgpu_oserrlog_req {
+ u32 except_type, runlist_id, chid;
+ int rc;
+ struct completion done;
+};
+
+static int nv_vgpu_oserrlog_thread(void *arg)
+{
+ struct nv_vgpu_oserrlog_req *req = arg;
+ struct nv_vgpu_device *vgpu;
+
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_for_each_entry(vgpu, &nv_vgpu_driver.devices, list) {
+ if (atomic_read(&vgpu->vfio_dev_open_count) <= 0)
+ continue;
+ req->rc = gsp_rm_send_os_error_log(vgpu, req->except_type,
+ req->runlist_id, req->chid,
+ "GR");
+ pr_info("vgsp: oserrlog except=0x%x rl=%u chid=%u -> %d\n",
+ req->except_type, req->runlist_id, req->chid, req->rc);
+ }
+ mutex_unlock(&nv_vgpu_driver.lock);
+
+ complete(&req->done);
+ return 0;
+}
+
+static int nv_vgpu_oserrlog_set(const char *val, const struct kernel_param *kp)
+{
+ static struct nv_vgpu_oserrlog_req req;
+ struct task_struct *task;
+ char buf[64];
+ char *c1, *c2;
+
+ memset(&req, 0, sizeof(req));
+ req.rc = -ENODEV;
+ strscpy(buf, val, sizeof(buf));
+ req.except_type = 1; /* generic engine exception, as the synth-fault */
+ {
+ c1 = strchr(buf, ',');
+ if (c1) {
+ *c1++ = '\0';
+ c2 = strchr(c1, ',');
+ if (c2) {
+ *c2++ = '\0';
+ if (kstrtou32(c2, 0, &req.chid))
+ return -EINVAL;
+ }
+ if (kstrtou32(c1, 0, &req.runlist_id))
+ return -EINVAL;
+ }
+ if (kstrtou32(buf, 0, &req.except_type))
+ return -EINVAL;
+ }
+
+ init_completion(&req.done);
+ task = kthread_run(nv_vgpu_oserrlog_thread, &req, "vgpu-oserrlog");
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+ wait_for_completion(&req.done);
+ return 0;
+}
+
+static const struct kernel_param_ops nv_vgpu_oserrlog_ops = {
+ .set = nv_vgpu_oserrlog_set,
+};
+
+module_param_cb(oserrlog, &nv_vgpu_oserrlog_ops, NULL, 0200);
+MODULE_PARM_DESC(oserrlog, "Send a synthetic OS_ERROR_LOG (0x1006) RM record to the guest (r623 experiment)");
+
module_init(nv_vgpu_init);
module_exit(nv_vgpu_exit);