summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_rm_emulation.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/gsp_rm_emulation.c')
-rw-r--r--kernel/gsp_rm_emulation.c2703
1 files changed, 2614 insertions, 89 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index cd9cbf2..0cef9e7 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -19,6 +19,7 @@
#include "gsp_sema_service.h"
#include "gsp_phys_booter.h"
#include "gsp_phys_gpu.h"
+#include "gsp_display_engine.h"
/* ============================================================
* Guest-memory access ("virtual DMA" on behalf of the device)
@@ -511,11 +512,14 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
: GSPRM_EVENT_BURST_MAX;
/* m171: expire the budget by wall time so a long-ago
- * flood can never starve a fresh completion post. */
+ * flood can never starve a fresh completion post. Only an
+ * admitted record moves the window: a refused attempt that
+ * retried (the display completion retry runs every 250 ms)
+ * must not keep the window perpetually fresh, or the budget
+ * would never expire and the retry would livelock. */
if (time_after(jiffies, rm->event_burst_at +
GSPRM_EVENT_BURST_WINDOW))
rm->event_burst = 0;
- rm->event_burst_at = jiffies;
if (rm->event_burst >= cap) {
rm->events_dropped++;
if (rm->events_dropped <= 8 ||
@@ -528,6 +532,7 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
kfree(buf);
return -EAGAIN;
}
+ rm->event_burst_at = jiffies;
rm->event_burst++;
} else {
rm->event_burst = 0;
@@ -566,8 +571,22 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
u32 peer_read = 0;
bool have_peer = false;
int w;
+ int wait_iters = 50;
+
+ /*
+ * r1615: critical event wake-ups (channel completions, and in
+ * particular the display/mode-set completion the X server's
+ * 2D sync waits for) must not be dropped just because the
+ * guest is briefly behind. Pump the consumption cursor for up
+ * to 200 ms so the guest's RM thread can drain; only drop if
+ * the ring is still full after that (the 0.5 s hint re-post
+ * retries). Non-critical event wake-ups keep the old
+ * drop-immediately policy; replies keep their 50 ms wait.
+ */
+ if (function == GSPRM_RPC_EVENT_POST_EVENT)
+ wait_iters = critical ? 200 : 0;
- for (w = 0; w < 51; w++) {
+ for (w = 0; w <= wait_iters; w++) {
u32 used, space, rxoff = 0;
u8 shdr[32];
@@ -617,9 +636,20 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
* two fn=0x1 replies dropped at boot -> the device
* never comes up).
*/
- if (function == GSPRM_RPC_EVENT_POST_EVENT || w == 50)
+ if (w == wait_iters)
break;
mutex_unlock(&rm->stat_lock);
+ /*
+ * r1668: a full status ring means the guest's RM
+ * consumer is blocked, and the usual cause is the
+ * CeUtils scrub wait holding the RM lock the event
+ * callback needs. Authoring the scrubber's finish
+ * payload is the only way out, but the pump cannot
+ * reach its next pass while this post waits, so run
+ * the scrub step here (service thread only; no-op
+ * elsewhere).
+ */
+ gsp_sema_service_scrub_pump(ctx->vgpu);
msleep(1);
mutex_lock(&rm->stat_lock);
}
@@ -707,11 +737,15 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
return ret;
}
- /* Notify the guest (MSI-X vector 0). The driver's bottom-half drains
- * events under the GPU lock; the rpcRecvPoll poller holds the same
- * lock from send to poll, so a reply can't be consumed by the event
- * drain while a poller waits for it (same as real GSP-RM). */
- gsp_emu_inject_completion_interrupt(ctx, rm->stat_seq);
+ /* Notify the guest. An event must also raise the GSP engine interrupt
+ * (falcon SWGEN0 + CPU_INTR leaf/top) so the guest's ISR drains the
+ * status queue and nv_post_event() wakes a client blocked in poll() on
+ * /dev/nvidia0 (e.g. a GLX swap). Replies only need the MSI-X ping
+ * because the client polls the queue for them. */
+ if (function == GSPRM_RPC_EVENT_POST_EVENT)
+ gsp_emu_raise_gsp_event_irq(ctx);
+ else
+ gsp_emu_inject_completion_interrupt(ctx, rm->stat_seq);
mutex_unlock(&rm->stat_lock);
return 0;
@@ -726,6 +760,117 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function,
}
/*
+ * r576 DIAG: event-post payload witness (gsp_rm_ev_log=N budget per boot).
+ * Every EVENT_POST_EVENT record actually sent is logged with its full
+ * payload and send result: when a client parks waiting for a completion
+ * wake-up that never arrives, this shows whether the post was generated,
+ * with which hClient/hEvent, and whether the status-queue write accepted it
+ * (rc=0) or dropped it (burst cap / ring full => rc<0). The companion
+ * sentinel fires only when post_all_events found valid events but could not
+ * post any of them while none were dedup-skipped - the "client waits, host
+ * thinks it posted" anomaly. 0 (default) = off.
+ */
+unsigned gsp_rm_ev_log_max;
+static unsigned gsp_rm_ev_log;
+
+static void gsp_rm_evlog_post(struct nv_vgpu_device *vgpu,
+ const struct gsp_rm_emu *rm, u32 idx,
+ const char *src, int rc)
+{
+ if (!gsp_rm_ev_log_max || gsp_rm_ev_log >= gsp_rm_ev_log_max)
+ return;
+ gsp_rm_ev_log++;
+ nv_vgpu_info(vgpu,
+ "vgsp-rm: EVPOST %s ev[%u] cl=0x%x ev=0x%x nidx=0x%x data=0x%x rc=%d",
+ src, idx, rm->events[idx].h_client,
+ rm->events[idx].h_event, rm->events[idx].notify_index,
+ rm->events[idx].data, rc);
+}
+
+/*
+ * r577 experiment knobs (runtime-writable; default 0 = legacy behaviour).
+ * The r576 live experiment showed the guest consumes our POST_EVENT records
+ * (rmstate cursors match) yet never enters nv_post_event (clean single-probe
+ * window, forced rmpost rounds -> entries-written 0): the guest RM drops
+ * them somewhere between the status-queue read and osNotifyEvent. These
+ * knobs let one boot A/B the record contents without rebuilding:
+ * gsp_rm_ev_seq - when nonzero, event records carry the status-queue
+ * sequence as the RPC-level sequence (legacy: 0)
+ * gsp_rm_ev_nlist - when nonzero, bNotifyList (payload byte 28) is set,
+ * selecting the driver's notifier-list dispatch path
+ * gsp_rm_ev_cl - when nonzero, overrides the payload hClient (e.g. to
+ * the kernel RM client) for the CliGetEventInfo lookup
+ */
+unsigned gsp_rm_ev_seq;
+unsigned gsp_rm_ev_nlist;
+unsigned gsp_rm_ev_cl;
+/*
+ * r578: reachability probe for the guest-RM event drop. When nonzero, event
+ * records carry this RPC function instead of POST_EVENT (0x1003). Posting a
+ * function with an unmistakable guest-side side effect (e.g.
+ * NV_VGPU_MSG_EVENT_UCODE_LIBOS_PRINT 0x100C with an unsupported ucodeEngDesc
+ * trips NV_ASSERT_FAILED in _kgspRpcUcodeLibosPrint, or
+ * NV_VGPU_MSG_EVENT_OS_ERROR_LOG 0x1006 logs an errString) proves whether
+ * the guest ever DISPATCHES our status-queue event records: visible side
+ * effect => the dispatch works and POST_EVENT is dropped inside
+ * _kgspRpcPostEvent; no side effect => the records are consumed without
+ * dispatch.
+ */
+unsigned gsp_rm_ev_func;
+/*
+ * r1767: gate the r1765 flip-event association (hSrcResource == 0 ->
+ * alloc-header hParent). Default 0 keeps the pre-r1766 event semantics;
+ * the RCA exonerated the association from the r1766 soft-locks but not
+ * from the r1766 ISR crash class, so it stays off until validated
+ * end-to-end.
+ */
+static bool gsp_rm_ev_assoc; /* default 0: pre-r1766 event semantics */
+module_param_named(ev_assoc, gsp_rm_ev_assoc, bool, 0644);
+MODULE_PARM_DESC(ev_assoc,
+ "associate hSrcResource==0 events with their alloc parent (r1765 flip-event fix; default 0)");
+
+static void gsp_rm_fill_ev_payload(const struct gsp_rm_emu *rm, u32 i,
+ u8 *payload)
+{
+ u32 h_client = rm->events[i].h_client;
+ /*
+ * r579 THE EVENT-DROP ROOT CAUSE: NV0005_ALLOC_PARAMETERS.notifyIndex
+ * is OVERLOADED (cl0005.h): INDEX=bits[15:0], SUBDEVICE=bits[23:16],
+ * FLAGS=bits[31:24]. The registration word (e.g. 0x3c00000c) must not
+ * be posted verbatim: the guest's _kgspRpcPostEvent hands the payload
+ * notifyIndex straight to gpuNotifySubDeviceEvent_IMPL, which indexes
+ * its notifier array by it - posting 0x3c00000c produced a wild
+ * pointer (base + 0x3c00000c*4 + 0x1508) and a kernel Oops
+ * ("BUG: unable to handle page fault ... gpuNotifySubDeviceEvent_IMPL
+ * +0x21f", RAX=0x3c00000c) on every dispatched event, which is why
+ * nv_post_event never fired and clients waiting on event wakes
+ * wedged. Post the INDEX field only.
+ */
+ u32 notify_index = rm->events[i].notify_index & 0xffffu;
+
+ memset(payload, 0, 32);
+ if (gsp_rm_ev_cl)
+ h_client = gsp_rm_ev_cl;
+ memcpy(payload + 0, &h_client, 4);
+ memcpy(payload + 4, &rm->events[i].h_event, 4);
+ memcpy(payload + 8, &notify_index, 4);
+ memcpy(payload + 12, &rm->events[i].data, 4);
+ /* info16@16, status@20, eventDataSize@24 = 0 */
+ if (gsp_rm_ev_nlist)
+ payload[28] = 1; /* bNotifyList */
+}
+
+static u32 gsp_rm_ev_sequence(const struct gsp_rm_emu *rm)
+{
+ return gsp_rm_ev_seq ? rm->stat_seq : 0;
+}
+
+static u32 gsp_rm_ev_function(void)
+{
+ return gsp_rm_ev_func ? gsp_rm_ev_func : GSPRM_RPC_EVENT_POST_EVENT;
+}
+
+/*
* Post the GPU event that tells the guest's CPU-side RM a channel's
* submitted work is complete. A real GPU raises an interrupt, the host GSP
* turns it into an NV_VGPU_MSG_EVENT_POST_EVENT record for the client's
@@ -735,7 +880,8 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function,
* Safe to call from the progress service's thread: the status queue write is
* serialized by rm->stat_lock and the guest is woken with MSI-X vector 0.
*/
-int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src)
+static int gsp_rm_post_channel_event_common(struct nv_vgpu_device *vgpu,
+ u32 h_src, bool force, bool strict)
{
struct gsp_emu_context *ctx = vgpu ? vgpu->gsp_comm_ctx : NULL;
struct gsp_rm_emu *rm;
@@ -757,20 +903,50 @@ int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src)
if (!rm->events[i].valid || rm->events[i].h_src != h_src)
continue;
- memset(payload, 0, sizeof(payload));
- memcpy(payload + 0, &rm->events[i].h_client, 4);
- memcpy(payload + 4, &rm->events[i].h_event, 4);
- memcpy(payload + 8, &rm->events[i].notify_index, 4);
- memcpy(payload + 12, &rm->events[i].data, 4);
- /* info16@16, status@20, eventDataSize@24, bNotifyList@28 = 0 */
- if (gsp_rm_send_flags(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0,
- payload, sizeof(payload), true))
+ /*
+ * r1780: the directly-associated post is the flood path. The
+ * X channel's completion edge fires per consumed submission and
+ * the 48/s burst cap then drops the wake-ups a blocked client is
+ * waiting for (live: "event burst capped (48/48), dropped
+ * total=3000", DDX "Failed to idle DMA"). A re-post within
+ * GSPRM_EVENT_HINT_DEDUP adds no wake-up - the client re-reads
+ * channel state on any event and the 0.5 s hint re-posts late
+ * waiters - so apply the dedup the fallback loop already uses.
+ */
+ if (!force &&
+ time_before(jiffies,
+ rm->events[i].last_post +
+ GSPRM_EVENT_HINT_DEDUP))
continue;
+
+ gsp_rm_fill_ev_payload(rm, i, payload);
+ {
+ int rc = gsp_rm_send_flags(ctx, gsp_rm_ev_function(),
+ gsp_rm_ev_sequence(rm), 0,
+ payload, sizeof(payload),
+ true);
+
+ gsp_rm_evlog_post(vgpu, rm, i, "chan", rc);
+ if (rc)
+ continue;
+ }
rm->events[i].last_post = jiffies;
posted++;
}
/*
+ * r1765: strict mode is the display engine's 1:1 contract - a
+ * channel-scoped completion posts ONLY the events registered on the
+ * channel (a real RM fires the channel's own event list on AWAKEN).
+ * Never fall through to the all-events shotgun for such a post: the
+ * shotgun coalesces flip completions and lets unrelated completions
+ * nudge the flip event (stray FLIP_OCCURRED -> the nvidia-drm
+ * dequeue WARN). The caller's retry pump re-posts a refused edge.
+ */
+ if (!posted && strict)
+ return -ENOENT;
+
+ /*
* Fallback when the event's source object could not be associated with
* the channel: nudge every registered event. The client's dispatcher
* re-reads each channel's state on any event, so a spurious wake-up is
@@ -780,15 +956,45 @@ int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src)
for (i = 0; i < rm->events_n; i++) {
if (!rm->events[i].valid)
continue;
- memset(payload, 0, sizeof(payload));
- memcpy(payload + 0, &rm->events[i].h_client, 4);
- memcpy(payload + 4, &rm->events[i].h_event, 4);
- memcpy(payload + 8, &rm->events[i].notify_index, 4);
- memcpy(payload + 12, &rm->events[i].data, 4);
- if (gsp_rm_send_flags(ctx, GSPRM_RPC_EVENT_POST_EVENT,
- 0, 0, payload,
- sizeof(payload), true))
+ /*
+ * r1765: display-channel events are driven 1:1 by
+ * the display engine and must not be nudged by
+ * unrelated completions. The predicate answers
+ * false in the legacy default mode (gated on
+ * disp_events_strict), so this is inert unless the
+ * strict path is enabled.
+ */
+ if (gsp_disp_engine_owns_object(vgpu,
+ rm->events[i].h_src))
+ continue;
+ /*
+ * r580: apply the hint dedup to the per-completion
+ * shotgun. Every completion posts ALL registered
+ * events (the ABI gives no source association), so a
+ * completion burst burns the burst budget 8 records
+ * per completion and real wake-ups get dropped
+ * (r579 boot: events_dropped=724). A redundant
+ * re-post within 250 ms adds no wake-up - the
+ * client's dispatcher re-reads channel state on any
+ * event - and the 0.5 s hint re-posts late waiters.
+ */
+ if (!force &&
+ time_before(jiffies, rm->events[i].last_post +
+ GSPRM_EVENT_HINT_DEDUP))
continue;
+ gsp_rm_fill_ev_payload(rm, i, payload);
+ {
+ int rc = gsp_rm_send_flags(ctx,
+ gsp_rm_ev_function(),
+ gsp_rm_ev_sequence(rm),
+ 0, payload,
+ sizeof(payload),
+ true);
+
+ gsp_rm_evlog_post(vgpu, rm, i, "chanfb", rc);
+ if (rc)
+ continue;
+ }
rm->events[i].last_post = jiffies;
posted++;
}
@@ -805,6 +1011,86 @@ int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src)
return posted ? 0 : -ENOENT;
}
+int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src)
+{
+ return gsp_rm_post_channel_event_common(vgpu, h_src, false, false);
+}
+
+/*
+ * r1765 strict variants (display engine, disp_events_strict=1): exact-match
+ * only - no all-events fallback; -ENOENT rather than a shotgun when the
+ * channel has no registered event (yet).
+ */
+int gsp_rm_post_channel_event_strict(struct nv_vgpu_device *vgpu, u32 h_src)
+{
+ return gsp_rm_post_channel_event_common(vgpu, h_src, false, true);
+}
+
+/*
+ * Force a completion wake-up past the hint dedup. The 250 ms dedup assumes a
+ * recent post already woke the client, but a waiter that arrived after that
+ * post misses it, and a busy event stream keeps refreshing every event's
+ * stamp so the coalesced completion is never delivered (r1649: the X server's
+ * display UPDATE completion was refused - the coincident hint returned
+ * -EAGAIN because every event was inside the window - and the flip wait spun
+ * in the nvidia ioctl forever). Callers use this only for a refused
+ * completion edge, rate-limited by their own retry window; the event burst
+ * cap and the status-ring free-space discipline still apply inside
+ * gsp_rm_send_flags().
+ */
+int gsp_rm_post_channel_event_force(struct nv_vgpu_device *vgpu, u32 h_src)
+{
+ return gsp_rm_post_channel_event_common(vgpu, h_src, true, false);
+}
+
+int gsp_rm_post_channel_event_force_strict(struct nv_vgpu_device *vgpu,
+ u32 h_src)
+{
+ return gsp_rm_post_channel_event_common(vgpu, h_src, true, true);
+}
+
+/*
+ * r623 experiment: deliver a synthetic OS_ERROR_LOG (0x1006) record to the
+ * guest's RM queue - the error report a real GSP sends into the client's
+ * message queue when an engine faults. r618's synth-fault writes the channel
+ * notifier and posts a generic event; after it, the NEXT client stuck
+ * pre-submission (r620 atomics; r616b saxpy/math). If the guest's fault
+ * handling marks the device recoverable only when the 0x1006 report arrives,
+ * this record is the missing half. Payload mirrors the GSP ABI decode in
+ * gsp_phys_booter.c (GSP_PHYS_OSERR_OFF_*): exceptType u32, runlistId u32,
+ * chid u32, errString[256], prevXid u32. Manual/debug only.
+ */
+int gsp_rm_send_os_error_log(struct nv_vgpu_device *vgpu, u32 except_type,
+ u32 runlist_id, u32 chid, const char *engine)
+{
+ struct gsp_emu_context *ctx = vgpu ? vgpu->gsp_comm_ctx : NULL;
+ struct gsp_rm_emu *rm;
+ u8 payload[272] = {0};
+ int rc;
+
+ if (!ctx)
+ return -ENODEV;
+ rm = ctx->rm_emu;
+ if (!rm || !rm->parsed)
+ return -EAGAIN;
+
+ memcpy(payload + 0, &except_type, 4);
+ memcpy(payload + 4, &runlist_id, 4);
+ memcpy(payload + 8, &chid, 4);
+ snprintf((char *)payload + 12, 255, "ENGINE %s",
+ engine && *engine ? engine : "GR");
+ /* prevXid stays 0 */
+
+ rc = gsp_rm_send(ctx, 0x1006, (u32)rm->stat_seq + 1, 0, payload,
+ sizeof(payload));
+ if (!rc)
+ nv_vgpu_info(vgpu,
+ "vgsp-rm: OS_ERROR_LOG sent (synthetic) except=0x%x rl=%u chid=%u engine=%s\n",
+ except_type, runlist_id, chid,
+ engine && *engine ? engine : "GR");
+ return rc;
+}
+
/*
* Debug: post the event record for one registered event (or all of them with
* idx 0xffffffff). Used to test the guest's event path on demand without
@@ -819,14 +1105,10 @@ static int gsp_rm_post_event_idx(struct gsp_emu_context *ctx, u32 idx)
if (idx >= rm->events_n || !rm->events[idx].valid)
return -EINVAL;
- memset(payload, 0, sizeof(payload));
- memcpy(payload + 0, &rm->events[idx].h_client, 4);
- memcpy(payload + 4, &rm->events[idx].h_event, 4);
- memcpy(payload + 8, &rm->events[idx].notify_index, 4);
- memcpy(payload + 12, &rm->events[idx].data, 4);
+ gsp_rm_fill_ev_payload(rm, idx, payload);
- rc = gsp_rm_send(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0,
- payload, sizeof(payload));
+ rc = gsp_rm_send(ctx, gsp_rm_ev_function(),
+ gsp_rm_ev_sequence(rm), 0, payload, sizeof(payload));
if (!rc)
rm->events[idx].last_post = jiffies;
return rc;
@@ -898,24 +1180,48 @@ int gsp_rm_post_all_events(struct nv_vgpu_device *vgpu)
for (i = 0; i < rm->events_n; i++) {
if (!rm->events[i].valid)
continue;
+ /*
+ * r1765: the hint must not nudge display-channel events -
+ * they are driven 1:1 by the display engine's UPDATE
+ * completions. False in the legacy default mode (the
+ * predicate is gated on disp_events_strict), so the hint is
+ * unchanged unless the strict path is enabled.
+ */
+ if (gsp_disp_engine_owns_object(vgpu, rm->events[i].h_src))
+ continue;
if (time_before(jiffies, rm->events[i].last_post +
GSPRM_EVENT_HINT_DEDUP)) {
skipped++;
continue;
}
- memset(payload, 0, sizeof(payload));
- memcpy(payload + 0, &rm->events[i].h_client, 4);
- memcpy(payload + 4, &rm->events[i].h_event, 4);
- memcpy(payload + 8, &rm->events[i].notify_index, 4);
- memcpy(payload + 12, &rm->events[i].data, 4);
- if (gsp_rm_send(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0,
- payload, sizeof(payload)))
- continue;
+ gsp_rm_fill_ev_payload(rm, i, payload);
+ {
+ int rc = gsp_rm_send(ctx, gsp_rm_ev_function(),
+ gsp_rm_ev_sequence(rm), 0,
+ payload, sizeof(payload));
+
+ gsp_rm_evlog_post(vgpu, rm, i, "all", rc);
+ if (rc)
+ continue;
+ }
rm->events[i].last_post = jiffies;
n++;
}
+ /*
+ * r576 DIAG: the anomaly sentinel - valid events existed, none were
+ * dedup-skipped, yet not one post could be sent. Every send rc is in
+ * the EVPOST lines above (budget permitting).
+ */
+ if (!n && !skipped && gsp_rm_ev_log_max &&
+ gsp_rm_ev_log < gsp_rm_ev_log_max) {
+ gsp_rm_ev_log++;
+ nv_vgpu_info(vgpu,
+ "vgsp-rm: EVPOST all: no postable event (events_n=%u dropped=%llu)",
+ rm->events_n, rm->events_dropped);
+ }
+
if (n)
return 0;
/* -EAGAIN: valid events existed but every one was dedup-skipped
@@ -1169,6 +1475,7 @@ static const char *gsp_rm_rpc_name(u32 function)
case GSPRM_RPC_FUNC_GSP_RM_CONTROL: return "GSP_RM_CONTROL";
case GSPRM_RPC_FUNC_UNLOADING_GUEST_DRIVER: return "UNLOADING_GUEST_DRIVER";
case GSPRM_RPC_FUNC_UPDATE_BAR_PDE: return "UPDATE_BAR_PDE";
+ case GSPRM_RPC_FUNC_DUP_OBJECT: return "DUP_OBJECT";
default: return "UNKNOWN";
}
}
@@ -1739,7 +2046,20 @@ static void gsp_rm_handle_bus_get_pci_bar_info(struct gsp_emu_context *ctx,
* 36 bytes (observed paramsSize): feHwSysCap(0) windowPresentMask(4)
* bFbRemapperEnabled(8) numHeads(12) i2cPort(16) internalDispActiveMask(20)
* embeddedDisplayPortMask(24) muxSupported(28) numDispChannels(32).
- * Report a minimal headless display: 1 head, 1 (core) channel.
+ *
+ * r651: this reply is also the gate for the WINDOW-channel alloc path.
+ * Live gdb on the guest nvidia.ko pinned the display path 0x56 to
+ * kdispGetChannelNum_v03_00: for dispChnClass_Win it requires
+ * pStaticInfo->windowPresentMask & NVBIT32(instance) and returns
+ * NV_ERR_NOT_SUPPORTED (0x56) when the bit is clear; the Curs class reads
+ * HEAD_EXISTS bits from feHwSysCap the same way (0x49+instance channel num).
+ * The previous reply left both masks zero, so nvRmApiAlloc(0xc57e) died in
+ * the guest's local resource construct (clientAllocResource ->
+ * resservResourceFactory -> dispchnConstruct -> kdispGetChannelNum) before
+ * any GSP-RM RPC was issued. Report the same one-head / 8-window model the
+ * NV0073 shim and the caps notifier expose (feHwSysCap head-0 exists,
+ * windowPresentMask bits 0..7, numDispChannels covering the cursor channel
+ * numbers 73..80, i.e. 81 entries).
*/
static void gsp_rm_handle_display_get_static_info(struct gsp_emu_context *ctx,
u32 sequence,
@@ -1751,15 +2071,205 @@ static void gsp_rm_handle_display_get_static_info(struct gsp_emu_context *ctx,
memset(out_params, 0, 36);
+ /*
+ * feHwSysCap: NV_PDISP_FE_HW_SYS_CAP_HEAD_EXISTS(i) occupies bit i
+ * (dev_disp.h v03_00); head 0 exists on the emulated board.
+ */
+ val32 = 0x1;
+ memcpy(out_params + 0, &val32, 4);
+
+ /*
+ * windowPresentMask: bit i = window instance i present. Mirrors the
+ * caps notifier's SYS_CAPB=0xff and the NV0073 window assignment
+ * (windows 0/1 flexible) - eight usable windows.
+ */
+ val32 = 0xff;
+ memcpy(out_params + 4, &val32, 4);
+
val32 = 1; /* numHeads */
memcpy(out_params + 12, &val32, 4);
- val32 = 1; /* numDispChannels (core channel) */
+
+ /*
+ * numDispChannels sizes pClientChannelTable[], indexed by the packed
+ * channel number kdispGetChannelNum returns: CORE=0, WIN(i)=1+i,
+ * WINIM(i)=33+i, CURS(i)=73+i. Cover CURS(7)=80 -> 81 entries so any
+ * instance the guest allocates stays in bounds.
+ */
+ val32 = 81;
memcpy(out_params + 32, &val32, 4);
*out_params_size = 36;
*out_status = 0;
- nv_vgpu_info(ctx->vgpu, "vgsp-rm: DISPLAY_GET_STATIC_INFO -> heads=1 dispChannels=1");
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DISPLAY_GET_STATIC_INFO -> heads=1 dispChannels=81 winMask=0xff feHwSysCap=0x1");
+}
+
+/*
+ * INTERNAL_DISPLAY_GET_ACTIVE_DISPLAY_DEVICES (0x20800a5d)
+ *
+ * dispswConstruct_IMPL (disp_sw.c) asks for {displayMask, numHeads} and
+ * rejects every logicalHeadId >= numHeads with "invalid logical head
+ * number: N" + NV_ERR_INSUFFICIENT_RESOURCES; the generic all-zero reply
+ * made numHeads = 0, so nvkms' DISPLAY_SW objects could not be constructed
+ * on any head (observed r653: heads 0..3 all rejected -> vkCreateDevice -3).
+ * Report the same one-head model as DISPLAY_GET_STATIC_INFO: display id 0
+ * active, one head.
+ *
+ * Params ABI (NV2080_CTRL_INTERNAL_DISPLAY_GET_ACTIVE_DISPLAY_DEVICES_PARAMS,
+ * 8 bytes): displayMask(0) numHeads(4).
+ */
+static void gsp_rm_handle_display_active_devices(struct gsp_emu_context *ctx,
+ u32 *out_status, u8 *out_params,
+ u32 *out_params_size)
+{
+ u32 displayMask = 0x1; /* display id 0 */
+ u32 numHeads = 1;
+
+ memcpy(out_params + 0, &displayMask, 4);
+ memcpy(out_params + 4, &numHeads, 4);
+
+ *out_params_size = 8;
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DISPLAY_GET_ACTIVE_DISPLAY_DEVICES -> displayMask=0x1 numHeads=1");
+}
+
+/*
+ * NVC370_CTRL_CMD_GET_CHANNEL_INFO (0xc3700104)
+ *
+ * nvRMSyncEvoChannel() (nvkms-rm.c) polls this control through the display
+ * object after every EVO channel kickoff and waits up to 2 s for the channel
+ * to report NVC370_CTRL_GET_CHANNEL_INFO_STATE_IDLE, otherwise it logs
+ * "Idling display engine timed out: <class>:<instance>:<sd>:<token>" and
+ * fails the whole modeset init (observed on the r652 window path: 0xc57d and
+ * 0xc57e instances 0/1). The emulated display engine has no pushbuffer
+ * consumer, so the state would never leave BUSY; report IDLE immediately.
+ *
+ * Params ABI (NVC370_CTRL_CMD_GET_CHANNEL_INFO_PARAMS, 20 bytes):
+ * subdeviceIndex(0) channelClass(4) channelInstance(8)
+ * IsChannelInDebugMode(12) channelState(16 = NVBIT(0) IDLE).
+ * Echo the request so class/instance round-trip and set the state word.
+ */
+static void gsp_rm_handle_display_channel_info(struct gsp_emu_context *ctx,
+ const u8 *in_params, u32 in_size,
+ u32 *out_status, u8 *out_params,
+ u32 *out_params_size)
+{
+ u32 idle = 1; /* NVC370_CTRL_GET_CHANNEL_INFO_STATE_IDLE */
+ u32 class_id = 0, instance = 0;
+ u32 size = in_size;
+
+ if (size > 64)
+ size = 64; /* defensive cap; the real params are 20 bytes */
+ if (size >= 20) {
+ memcpy(out_params, in_params, size);
+ memcpy(&class_id, in_params + 4, 4);
+ memcpy(&instance, in_params + 8, 4);
+ memcpy(out_params + 16, &idle, 4);
+ } else {
+ memset(out_params, 0, 20);
+ memcpy(out_params + 16, &idle, 4);
+ size = 20;
+ }
+
+ *out_params_size = size;
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: GET_CHANNEL_INFO class=0x%x instance=%u -> IDLE",
+ class_id, instance);
+}
+
+/*
+ * NV0080_CTRL_CMD_FIFO_GET_ENGINE_CONTEXT_PROPERTIES (0x801707)
+ *
+ * The Vulkan/GL client queries the device for the size and alignment of an
+ * engine's context save region while building its GR channel context
+ * (observed: engineId 0x8 = GRAPHICS_ZCULL, one query at the end of every
+ * vkCreateDevice attempt). The generic all-zero reply handed back size = 0;
+ * the client's context-buffer allocation then failed (memdesc of size 0)
+ * and device creation aborted with VK_ERROR_INITIALIZATION_FAILED
+ * (observed r661: four create attempts, each ending at this control, then
+ * teardown and VKFAIL -3).
+ *
+ * The sizes below mirror the real 570.144 GSP-RM geometry captured on this
+ * TU104 (rm-call golden runs); entries marked "fill" were not covered by
+ * the capture and use nonzero page-aligned placeholders - revisit them when
+ * a real capture for the remaining engine ids exists.
+ *
+ * Params ABI (NV0080_CTRL_FIFO_GET_ENGINE_CONTEXT_PROPERTIES_PARAMS,
+ * 12 bytes): engineId(0, in) alignment(4, out) size(8, out).
+ */
+static const struct gsp_rm_engine_ctx_geom {
+ u32 engine_id;
+ u32 alignment;
+ u32 size;
+} gsp_rm_engine_ctx_geoms[] = {
+ { 0x00, 0x1000, 0xf2d00 }, /* GRAPHICS (captured) */
+ { 0x08, 0x1000, 0x10000 }, /* GRAPHICS_ZCULL (fill) */
+ { 0x09, 0x1000, 0xd100 }, /* GRAPHICS_PM (captured) */
+ { 0x0a, 0x1000, 0x1000 }, /* COMPUTE_PREEMPT (fill) */
+ { 0x0b, 0x1000, 0x1000 }, /* GRAPHICS_PREEMPT (fill) */
+ { 0x0c, 0x1000, 0x1000 }, /* GRAPHICS_SPILL (fill) */
+ { 0x0d, 0x100, 0x20000 }, /* GRAPHICS_PAGEPOOL (fill) */
+ { 0x0e, 0x100, 0x8000 }, /* GRAPHICS_BETACB (fill) */
+ { 0x0f, 0x100, 0x80000 }, /* GRAPHICS_RTV (fill) */
+ { 0x10, 0x1000, 0x5000 }, /* GRAPHICS_PATCH (captured) */
+ { 0x11, 0x100, 0x3000 }, /* GRAPHICS_BUNDLE_CB (captured) */
+ { 0x12, 0x100, 0x20000 }, /* GRAPHICS_PAGEPOOL_GLOBAL (captured) */
+ { 0x13, 0x1000, 0x7d7400 }, /* GRAPHICS_ATTRIBUTE_CB (captured) */
+ { 0x14, 0x100, 0x80000 }, /* GRAPHICS_RTV_CB_GLOBAL (captured) */
+ { 0x15, 0x100, 0x20000 }, /* GRAPHICS_GFXP_POOL (fill) */
+ { 0x16, 0x100, 0x1000 }, /* GRAPHICS_GFXP_CTRL_BLK (fill) */
+ { 0x17, 0x1000, 0x10000 }, /* GRAPHICS_FECS_EVENT (captured) */
+ { 0x18, 0x1000, 0x80000 }, /* GRAPHICS_PRIV_ACCESS_MAP (captured) */
+ { 0x19, 0x100, 0x1000 }, /* GRAPHICS_SETUP (fill) */
+};
+
+static void gsp_rm_handle_fifo_engine_ctx_props(struct gsp_emu_context *ctx,
+ const u8 *in_params, u32 in_size,
+ u32 *out_status, u8 *out_params,
+ u32 *out_params_size)
+{
+ u32 engine_id = ~0u;
+ u32 alignment = 0x1000, size = 0x1000;
+ u32 i;
+ bool found = false;
+
+ if (in_size >= 4)
+ memcpy(&engine_id, in_params + 0, 4);
+
+ for (i = 0; i < ARRAY_SIZE(gsp_rm_engine_ctx_geoms); i++) {
+ if (gsp_rm_engine_ctx_geoms[i].engine_id == engine_id) {
+ alignment = gsp_rm_engine_ctx_geoms[i].alignment;
+ size = gsp_rm_engine_ctx_geoms[i].size;
+ found = true;
+ break;
+ }
+ }
+
+ if (engine_id > 0x19) {
+ /* Same rejection the real RM's VF handler uses for ids
+ * outside the engine context properties range. */
+ *out_status = 0x56; /* NV_ERR_NOT_SUPPORTED */
+ *out_params_size = 0;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: FIFO_GET_ENGINE_CONTEXT_PROPERTIES engineId=0x%x -> NOT_SUPPORTED",
+ engine_id);
+ return;
+ }
+
+ memcpy(out_params + 0, &engine_id, 4);
+ memcpy(out_params + 4, &alignment, 4);
+ memcpy(out_params + 8, &size, 4);
+ *out_params_size = 12;
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: FIFO_GET_ENGINE_CONTEXT_PROPERTIES engineId=0x%x -> alignment=0x%x size=0x%x%s",
+ engine_id, alignment, size, found ? "" : " (default)");
}
/*
@@ -1785,6 +2295,231 @@ static void gsp_rm_handle_display_get_ip_version(struct gsp_emu_context *ctx,
}
/*
+ * NV0073 display-common heads + one static HDMI connector
+ * (0x730102 / 0x730287 / 0x7302ad / 0x730107 / 0x73028b / 0x730250 /
+ * 0x731140)
+ *
+ * nvidia-modeset's nvkms-rm.c ProbeHeadCountAndWindowAssignment asks the
+ * display-common object for the head count (NV0073_CTRL_CMD_SYSTEM_
+ * GET_NUM_HEADS = 0x730102) and a zero reply makes it log "No heads found on
+ * board!" and fail -- which kills Xorg's ScreenInit and every Vulkan
+ * vkCreateDevice (measured: the emulated device replies all-zero through the
+ * generic path, so nvkms never opens). The kernel driver already sees 1 head
+ * (DISPLAY_GET_STATIC_INFO reports numHeads=1); report the same 1 head here,
+ * a single-head mask for GET_ALL_HEAD_MASK (0x730287), and a fully-flexible
+ * window assignment for windows 0/1 (0xFF = FLEXIBLE; windows above the head
+ * count stay 0 = not present).
+ *
+ * The next wall after the heads (measured r643): nvRmAllocDisplays fails on
+ * NoValidDisplays() - nvkms requires at least one valid display. Report one
+ * static TMDS/HDMI connector on display id 0: GET_SUPPORTED (0x730107)
+ * displayMask=1|DDC=1, SPECIFIC_OR_GET_INFO (0x73028b) SOR /
+ * SOR_SINGLE_TMDS_A (TMDS avoids the whole DP-AUX lib), SPECIFIC_
+ * GET_CONNECTOR_DATA (0x730250) one HDMI_A entry, DFP_GET_INFO (0x731140)
+ * TMDS flags.
+ *
+ * r1617: the connector was still reported disconnected. NVKMS
+ * nvRmGetConnectedDpys() polls SYSTEM_GET_CONNECT_STATE (0x730108) and a zero
+ * displayMask makes it see no displays; the driver then sets the NoScanout
+ * NULL mode and the NVKMS deferred display close bumps the shared nvClip
+ * modeNum past the GL client's mode-switch test (which requires bit0 clear).
+ * Report display 0 connected, its type as DFP (0x730240) and a 600 MHz pclk
+ * limit (0x73028a); the SOR caps seed already gives 600 MHz TMDS. The EDID
+ * itself is served by gsp_rm_handle_display_get_edid() (0x730245).
+ */
+static void gsp_rm_handle_display_heads(struct gsp_emu_context *ctx, u32 cmd,
+ u32 req_size, const u8 *req_params,
+ u32 *out_status, u8 *out_params,
+ u32 *out_params_size)
+{
+ u32 one = 1;
+ u32 sz = req_size ? req_size : 72;
+ u32 v;
+ u32 req_w0 = 0, req_w1 = 0, req_w2 = 0;
+
+ memset(out_params, 0, sz);
+ if (req_params) {
+ if (req_size >= 4)
+ memcpy(&req_w0, req_params, 4);
+ if (req_size >= 8)
+ memcpy(&req_w1, req_params + 4, 4);
+ if (req_size >= 12)
+ memcpy(&req_w2, req_params + 8, 4);
+ }
+ switch (cmd) {
+ case 0x730102: /* SYSTEM_GET_NUM_HEADS: {subDev, flags, numHeads} */
+ if (sz >= 12)
+ memcpy(out_params + 8, &one, 4);
+ break;
+ case 0x730287: /* SPECIFIC_GET_ALL_HEAD_MASK: {subDev, headMask} */
+ if (sz >= 8)
+ memcpy(out_params + 4, &one, 4);
+ break;
+ case 0x7302ad: /* SPECIFIC_GET_VALID_HEAD_WINDOW_ASSIGNMENT:
+ * {subDev, windowHeadMask[32]} - windows 0 and 1
+ * flexible (0xFF), the rest absent (0). */
+ if (sz >= 36) {
+ out_params[4] = 0xff;
+ out_params[5] = 0xff;
+ }
+ break;
+ case 0x730107: /* SYSTEM_GET_SUPPORTED: {subDev, displayMask,
+ * displayMaskDDC} - display id 0 supported + DDC. */
+ if (sz >= 12) {
+ memcpy(out_params + 4, &one, 4);
+ memcpy(out_params + 8, &one, 4);
+ }
+ break;
+ case 0x730108: /* SYSTEM_GET_CONNECT_STATE: {subDev, flags,
+ * displayMask, retryTimeMs} - report the requested
+ * display 0 connected (NVKMS polls this ~126x while
+ * opening the display; zero made it set NoScanout). */
+ if (sz >= 16) {
+ u32 mask = 0;
+
+ if (req_params)
+ memcpy(&mask, req_params + 8, 4);
+ mask &= 1;
+ if (!mask)
+ mask = 1;
+ memcpy(out_params + 8, &mask, 4);
+ }
+ break;
+ case 0x730240: /* SPECIFIC_GET_TYPE: {subDev, displayId,
+ * displayType} - display 0 is our static DFP. */
+ if (sz >= 12) {
+ v = 2; /* NV0073_CTRL_SPECIFIC_DISPLAY_TYPE_DFP */
+ memcpy(out_params + 8, &v, 4);
+ }
+ break;
+ case 0x73028a: /* SPECIFIC_GET_PCLK_LIMIT: {subDev, displayId,
+ * pclkLimit, orPclkLimit, vbPclkLimit} in kHz. The
+ * TMDS SOR caps (600 MHz) are the real limit; a sane
+ * RM value also keeps non-TMDS validation working. */
+ if (sz >= 20) {
+ v = 600000;
+ memcpy(out_params + 8, &v, 4);
+ memcpy(out_params + 12, &v, 4);
+ memcpy(out_params + 16, &v, 4);
+ }
+ break;
+ case 0x73028b: /* SPECIFIC_OR_GET_INFO: {subDev, displayId, index,
+ * type, protocol, ditherType, ditherAlgo, location,
+ * rootPortId, dcbIndex, vbiosAddress, bIsLitByVbios,
+ * bIsDispDynamic} */
+ if (sz >= 52) {
+ v = 2; /* NV0073_CTRL_SPECIFIC_OR_TYPE_SOR */
+ memcpy(out_params + 12, &v, 4);
+ v = 1; /* SOR_SINGLE_TMDS_A (no DP aux) */
+ memcpy(out_params + 16, &v, 4);
+ /* location=0 (chip), remaining out-fields stay 0:
+ * not lit by vbios, not dynamic. */
+ }
+ break;
+ case 0x730250: /* SPECIFIC_GET_CONNECTOR_DATA: {subDev,
+ * displayId, flags, DDCPartners, count,
+ * data[4]{index,type,location}, platform} */
+ if (sz >= 72) {
+ v = 1; /* CONNECTOR_DATA_FLAGS_PRESENT_YES */
+ memcpy(out_params + 8, &v, 4);
+ v = 1; /* count */
+ memcpy(out_params + 16, &v, 4);
+ /* data[0].index = 0 */
+ v = 0x61; /* CONNECTOR_DATA_TYPE_HDMI_A */
+ memcpy(out_params + 24, &v, 4);
+ /* data[0].location = 0 */
+ }
+ break;
+ case 0x73010b: /* SYSTEM_GET_HEAD_ROUTING_MAP: {subDev,
+ * displayMask, oldDisplayMask, oldHeadRoutingMap,
+ * headRoutingMap} - the generic zero reply dropped
+ * displayMask (0), so the boot display init's head-
+ * routing step saw no display. Keep display 0 routed
+ * to head 0 (headRoutingMap=0). */
+ if (sz >= 20) {
+ u32 mask = 0;
+
+ if (req_params)
+ memcpy(&mask, req_params + 4, 4);
+ mask &= 1;
+ if (!mask)
+ mask = 1;
+ memcpy(out_params + 4, &mask, 4);
+ /* headRoutingMap stays 0: display 0 -> head 0 */
+ }
+ break;
+ case 0x731140: /* DFP_GET_INFO: {subDev, displayId, flags,
+ * UHBRSupportedByDfp} - flags signal bits = TMDS(0) */
+ break; /* zeros are the TMDS signal value */
+ }
+ *out_params_size = sz;
+ *out_status = 0;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DISPLAY shim cmd=0x%x -> 1 head / 1 HDMI connector (size %u req=%#x/%#x/%#x)",
+ cmd, sz, req_w0, req_w1, req_w2);
+}
+
+/*
+ * NV0073_CTRL_CMD_SPECIFIC_GET_EDID_V2 (0x730245)
+ *
+ * nvidia-modeset's ReadEdidFromResman() reads the display EDID through this
+ * control, not through the I2C_TRANSACTION (0x402c0105) path. A zero or
+ * invalid reply leaves the DFP with no modes, so the driver falls back to
+ * the NoScanout NULL mode and the NVKMS deferred close bumps the shared
+ * nvClip modeNum. Serve a 128-byte EDID 1.3 base block with a 1920x1080@60
+ * detailed timing plus the 640x480/800x600/1024x768 established timings
+ * (checksum valid, no extension block).
+ *
+ * Params (NV0073_CTRL_SPECIFIC_GET_EDID_V2_PARAMS): subDeviceInstance@0,
+ * displayId@4, bufferSize@8, flags@12, edidBuffer@16[2048]. Echo the whole
+ * request and patch bufferSize + edidBuffer (the request is zero-initialized
+ * by the caller; the reply must stay the same serialized length).
+ */
+static void gsp_rm_handle_display_get_edid(struct gsp_emu_context *ctx,
+ u32 req_size,
+ const u8 *req_params,
+ u32 *out_status,
+ u8 *out_params,
+ u32 *out_params_size)
+{
+ static const u8 edid[128] = {
+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x3a, 0xc4, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x22, 0x01, 0x03, 0x80, 0x33, 0x1d, 0x78,
+ 0x0a, 0xee, 0x91, 0xa3, 0x54, 0x4c, 0x99, 0x26,
+ 0x0f, 0x50, 0x54, 0x21, 0x08, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3a,
+ 0x80, 0x18, 0x71, 0x38, 0x2d, 0x40, 0x58, 0x2c,
+ 0x45, 0x00, 0xfd, 0x1e, 0x11, 0x00, 0x00, 0x1a,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32,
+ 0x4b, 0x1e, 0x53, 0x11, 0x00, 0x0a, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41,
+ };
+ u32 buffer_size = sizeof(edid);
+ u32 display_id = 0;
+
+ if (!req_params || req_size < 16) {
+ *out_params_size = 0;
+ *out_status = 0x1; /* NV_ERR_INVALID_ARGUMENT */
+ return;
+ }
+ memcpy(out_params, req_params, req_size);
+ memcpy(out_params + 8, &buffer_size, 4);
+ memcpy(out_params + 16, edid, sizeof(edid));
+ memcpy(&display_id, out_params + 4, 4);
+ *out_params_size = req_size;
+ *out_status = 0;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: GET_EDID_V2 displayId=%u -> %u-byte EDID (1920x1080@60)",
+ display_id, buffer_size);
+}
+
+/*
* CE_GET_FAULT_METHOD_BUFFER_SIZE (0x20802a08)
*
* kchangrpInit allocates one fault method buffer per runqueue of this
@@ -2063,6 +2798,18 @@ static void gsp_rm_handle_kgr_get_info(struct gsp_emu_context *ctx,
info->infoList[TU104_GR_INFO_IDX_MAX_LEGACY_SUBCONTEXT_COUNT].data = TU104_MAX_SUBCONTEXT_COUNT;
info->infoList[TU104_GR_INFO_IDX_MAX_PER_ENGINE_SUBCONTEXT_COUNT].data = TU104_MAX_SUBCONTEXT_COUNT;
+ /*
+ * GR identity for the nvidia-3d library. SM_VERSION in particular
+ * gates nv3dAllocDevice (GetSpaVersion -> FALSE on
+ * SM_VERSION_NONE), which makes nvHsAllocDevice fail silently and
+ * nvidia-drm report "Failed to allocate NvKmsKapiDevice".
+ */
+ info->infoList[TU104_GR_INFO_IDX_SM_VERSION].data = TU104_GR_INFO_SM_VERSION_7_5;
+ info->infoList[TU104_GR_INFO_IDX_MAX_WARPS_PER_SM].data = TU104_MAX_WARPS_PER_SM;
+ info->infoList[TU104_GR_INFO_IDX_MAX_THREADS_PER_WARP].data = TU104_MAX_THREADS_PER_WARP;
+ info->infoList[TU104_GR_INFO_IDX_THREAD_STACK_SCALING_FACTOR].data =
+ TU104_THREAD_STACK_SCALING_FACTOR;
+
*out_params_size = TU104_GR_INFO_MAX_ENGINES *
sizeof(struct tu104_gr_static_info);
*out_status = 0;
@@ -2161,11 +2908,16 @@ static void gsp_rm_handle_kgr_get_context_buffers_info(
for (e = 0; e < TU104_CTX_GPU_MAX_ENGINES; e++) {
for (i = 0; i < TU104_CTX_ENGINE_ID_COUNT; i++) {
- /* engine IDs the chip does not support report
- * NV_U32_MAX (the client skips those entries) */
+ /* r1781/CONT.435: use 0 (not NV_U32_MAX) for buffer
+ * kinds this chip does not support. kgraphics treats
+ * size==0 as "not supported" and skips the buffer
+ * (kernel_graphics.c:1785); an NV_U32_MAX byte length
+ * would instead round up to exactly 4 GiB in the
+ * page-count computation. The reserve-size sum also
+ * guards on NV_U32_MAX, so 0 is both safe and honest. */
if (sizes[i].size == 0) {
- info->engineContextBuffersInfo[e].engine[i].size = 0xFFFFFFFF;
- info->engineContextBuffersInfo[e].engine[i].alignment = 0xFFFFFFFF;
+ info->engineContextBuffersInfo[e].engine[i].size = 0;
+ info->engineContextBuffersInfo[e].engine[i].alignment = 0;
continue;
}
info->engineContextBuffersInfo[e].engine[i].size = sizes[i].size;
@@ -2191,6 +2943,20 @@ static void gsp_rm_handle_kgr_get_context_buffers_info(
* L2/LTC topology from here. Echo the requested indices and fill in the
* emulated TU104 values.
*/
+/*
+ * m113: FB_GET_INFO_V2 COMPRESSION_SIZE (index 0x01, bytes). The header:
+ * "A return value of 0 indicates the GPU does not support compression" -
+ * but the guest driver still attempts a COMPR-enabled, 64 KB-aligned
+ * comptag allocation (vendor VidHeap alloc, attr COMPR=2) and fails it
+ * NV_ERR_NO_MEMORY (0x51) when the comptag pool has no space, which
+ * libcuda maps to VK_ERROR_OUT_OF_DEVICE_MEMORY (the vkAllocateMemory -2
+ * gate). Report a comptag pool sized like the real Turing parts (~fb/16).
+ * 0 = the historic "no compression" reply.
+ */
+static uint gsp_rm_fb_compr_mb = 128;
+module_param_named(wall2_fb_compr_mb, gsp_rm_fb_compr_mb, uint, 0644);
+MODULE_PARM_DESC(wall2_fb_compr_mb, "FB_GET_INFO_V2 COMPRESSION_SIZE in MiB (default 128, 0 = none)");
+
static u32 tu104_fb_info_data(struct gsp_emu_context *ctx, u32 index)
{
u64 fb = nv_vgpu_fb_emul_size(ctx->vgpu);
@@ -2199,18 +2965,28 @@ static u32 tu104_fb_info_data(struct gsp_emu_context *ctx, u32 index)
case TU104_FB_INFO_IDX_TILE_REGION_COUNT:
return 0;
case TU104_FB_INFO_IDX_COMPRESSION_SIZE:
- return 0;
+ /* m113: non-zero comptag pool (bytes) - see gsp_rm_fb_compr_mb. */
+ return gsp_rm_fb_compr_mb << 20;
case TU104_FB_INFO_IDX_PARTITION_COUNT:
return 1;
+ /*
+ * r1781/CONT.435: every size index below is documented in KB
+ * (ctrl2080fb.h: RAM_SIZE/TOTAL_RAM_SIZE/USABLE_RAM_SIZE/HEAP_SIZE/
+ * MAPPABLE_HEAP_SIZE/HEAP_FREE/BAR1_SIZE/BAR1_AVAIL_SIZE are all
+ * "in kilobytes"). Returning the byte count over-reported the
+ * emulated FB size by 1024x to the X DDX (FbTotalSizeKb =
+ * 0x20000000 = 512 GB). COMPRESSION_SIZE and L2CACHE_SIZE stay
+ * in bytes per the same header.
+ */
case TU104_FB_INFO_IDX_BAR1_SIZE:
- return (u32)fb;
+ return (u32)(fb >> 10);
case TU104_FB_INFO_IDX_RAM_SIZE:
case TU104_FB_INFO_IDX_TOTAL_RAM_SIZE:
case TU104_FB_INFO_IDX_USABLE_RAM_SIZE:
- return (u32)fb;
+ return (u32)(fb >> 10);
case TU104_FB_INFO_IDX_HEAP_SIZE:
case TU104_FB_INFO_IDX_MAPPABLE_HEAP_SIZE:
- return (u32)fb;
+ return (u32)(fb >> 10);
case TU104_FB_INFO_IDX_BUS_WIDTH:
return 256;
case TU104_FB_INFO_IDX_RAM_TYPE:
@@ -2223,7 +2999,7 @@ static u32 tu104_fb_info_data(struct gsp_emu_context *ctx, u32 index)
case TU104_FB_INFO_IDX_PARTITION_MASK:
return 1;
case TU104_FB_INFO_IDX_HEAP_FREE:
- return (u32)fb;
+ return (u32)(fb >> 10);
case TU104_FB_INFO_IDX_RAM_LOCATION:
return TU104_RAM_LOCATION_GPU_DEDICATED;
case TU104_FB_INFO_IDX_FBP_COUNT:
@@ -2236,7 +3012,7 @@ static u32 tu104_fb_info_data(struct gsp_emu_context *ctx, u32 index)
return TU104_MEMORYINFO_VENDOR_ID_SAMSUNG;
case TU104_FB_INFO_IDX_BAR1_AVAIL_SIZE:
case TU104_FB_INFO_IDX_BAR1_MAX_CONTIG_AVAIL_SIZE:
- return (u32)fb;
+ return (u32)(fb >> 10);
case TU104_FB_INFO_IDX_LTC_COUNT:
return TU104_LTC_COUNT;
case TU104_FB_INFO_IDX_LTS_COUNT:
@@ -2356,6 +3132,13 @@ static void gsp_rm_handle_clk_domain_info(struct gsp_emu_context *ctx, u32 cmd,
* sysmem connection type"). Report a PCIe Gen3 x16 link for the emulated
* vGPU and echo every requested index.
*/
+static u32 gsp_rm_bus_gart_mb = 256;
+module_param_named(bus_gart_mb, gsp_rm_bus_gart_mb, uint, 0644);
+MODULE_PARM_DESC(bus_gart_mb,
+ "BUS_GET_INFO_V2 GPU_GART_SIZE in MiB (default 256; 0 = historical zero reply, A/B). "
+ "4096 made the guest allocate a 4 GiB GART sysmem object at GL init (c15-c22 OOM/crash); "
+ "256 keeps the GART well below guest RAM.");
+
static u32 tu104_bus_info_data(u32 index)
{
switch (index) {
@@ -2376,6 +3159,14 @@ static u32 tu104_bus_info_data(u32 index)
return TU104_PCIE_LINK_CTRL_STATUS_GEN3_X16;
case TU104_BUS_INFO_IDX_SYSMEM_CONNECTION_TYPE:
return TU104_BUS_INFO_SYSMEM_CONN_PCIE;
+ case TU104_BUS_INFO_IDX_GPU_GART_SIZE:
+ return READ_ONCE(gsp_rm_bus_gart_mb);
+ case TU104_BUS_INFO_IDX_GPU_GART_SIZE_ALT:
+ return 0;
+ case TU104_BUS_INFO_IDX_GPU_GART_DMA_FLAGS_0:
+ case TU104_BUS_INFO_IDX_GPU_GART_DMA_FLAGS_1:
+ /* GPUGART | CTXDMA (bits 0 | 2). */
+ return 0x5u;
default:
return 0;
}
@@ -2456,6 +3247,7 @@ static void gsp_rm_handle_bus_get_info_v2(struct gsp_emu_context *ctx,
#define GSPRM_COPY_PDES_MAX_LEVELS 6
static void gsp_rm_handle_copy_server_pdes(struct gsp_emu_context *ctx,
+ u32 h_object,
const u8 *params, u32 params_size)
{
struct gsp_rm_emu *rm = ctx->rm_emu;
@@ -2506,6 +3298,46 @@ static void gsp_rm_handle_copy_server_pdes(struct gsp_emu_context *ctx,
"vgsp-rm: pt level[%u] phys=0x%llx size=0x%llx aperture=%u pageShift=%u",
i, phys, size, aperture, page_shift);
}
+
+ /*
+ * r496j: this control IS the kernel RM publishing the page-table
+ * chain of its internal VAS (the level[0..] instances with their
+ * phys addresses and page shifts). The chain can name the X/GL
+ * channel's pushbuffer pages and progress semaphore, for which no
+ * other translation source exists (the fn-54 SET_PAGE_DIRECTORY
+ * path is external-VAS/UVM only). It is NOT registered directly:
+ * a blast registration of every client's directory let a stale
+ * kernel directory answer another client's VA in the walker's
+ * first-success order (the r459 stale-directory class; observed
+ * as a CUDA client hanging after DEVICE with r496g). Instead the
+ * root is offered as a CANDIDATE; the progress service registers
+ * it only after it has been VERIFIED against a content-validated
+ * pair of that very client (the ring found by the RAM content
+ * scan).
+ */
+ if (num_levels >= 1 && rm->pt.levels[0].phys &&
+ rm->pt.levels[0].size >= 16 &&
+ rm->pt.levels[0].size <= 4096 * 8) {
+ /*
+ * The level phys values are guest PAs on this platform
+ * (observed 4+ GiB, above any FB shadow); aperture 3 in the
+ * message does not match the walker's FB/SYS encoding, so
+ * classify by address: values inside the emulated VRAM are
+ * FB, everything else sysmem. The walker also retries the
+ * other aperture, so a misclassification only costs a walk.
+ */
+ bool vidmem = rm->pt.levels[0].phys <
+ (u64)ctx->vgpu->bars[1].size;
+
+ gsp_sema_service_vaspace_candidate_add(
+ h_object, rm->pt.levels[0].phys,
+ (u32)(rm->pt.levels[0].size / 8), vidmem);
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: VAS 0x%x page directory offered as candidate pdb=0x%llx entries=%u (%s; registered only after a content-verified ring walk)",
+ h_object, (u64)rm->pt.levels[0].phys,
+ (u32)(rm->pt.levels[0].size / 8),
+ vidmem ? "vidmem" : "sysmem");
+ }
}
static bool gsp_rm_alloc_interesting(u32 class_id);
@@ -2522,8 +3354,21 @@ static bool gsp_rm_is_channel_class(u32 class_id)
case 0x906f: /* MAXWELL_CHANNEL_GPFIFO_A */
case 0xa06f: /* KEPLER_CHANNEL_GPFIFO_A */
case 0xb06f: /* PASCAL_CHANNEL_GPFIFO_A */
- case 0xc06f: /* VOLTA_CHANNEL_GPFIFO_A */
+ case 0xc06f: /* VOLTA_CHANNEL_GPFIFO_A (PRE-TURING NAME) */
+ case 0xc36f: /* VOLTA_CHANNEL_GPFIFO_A */
+ /*
+ * r828: the RC watchdog's channel allocates 0xc36f on this
+ * device (the driver's gpfifoMapping loop picks the first
+ * class the gpu reports supported) - without it in this set
+ * the channel never reaches the progress service, its class2d
+ * notifier cycle (SET_NOTIFY_A/B + NOTIFY) never executes and
+ * the guest's krcWatchdog declares the GPU locked after its
+ * 7 s timeout as soon as robust channels are in play.
+ */
case 0xc46f: /* TURING_CHANNEL_GPFIFO_A */
+ case 0xc56f: /* AMPERE_CHANNEL_GPFIFO_A */
+ case 0xc86f: /* HOPPER_CHANNEL_GPFIFO_A */
+ case 0xc96f: /* BLACKWELL_CHANNEL_GPFIFO_A */
return true;
default:
return false;
@@ -2539,14 +3384,317 @@ static bool gsp_rm_is_channel_class(u32 class_id)
#define GSPRM_CHAN_USERD_MEM 168
#define GSPRM_CHAN_MEM_APERTURE 16 /* offset of addressSpace in the desc */
#define GSPRM_CHAN_NOTIFIER_MEM 248
+#define GSPRM_CHAN_HUSERD_MEMORY 32 /* NvHandle hUserdMemory[NV_MAX_SUBDEVICES] */
+#define GSPRM_CHAN_USERD_OFFSET 64 /* NvU64 userdOffset[NV_MAX_SUBDEVICES] */
+
+/*
+ * CONT.438: client memory-object ALLOCs - the objects hUserdMemory can name.
+ *
+ * NV_MEMORY_ALLOCATION_PARAMS (nvos.h:1631-1668; ABI asserted by
+ * gsp_phys_booter.c static_asserts): 128 bytes, u64 fields 8-byte aligned.
+ * attr @24 (u32) location bits 1:0 (NVOS32_ATTR_LOCATION_VIDMEM = 0,
+ * NVOS32_ATTR_LOCATION_PCI = 1)
+ * size @64 (u64) [IN/OUT]
+ * offset @80 (u64) [IN/OUT] - the server-returned GPU physical base for
+ * sysmem (class 0x3e NV01_MEMORY_SYSTEM) or the FB
+ * offset for vidmem (class 0x40 NV01_MEMORY_LOCAL_USER).
+ * Both classes share this layout (cl003e.h:33,
+ * cl0040.h:34; the allocation call is nv_gpu_ops.c:
+ * 4644).
+ *
+ * NV_MEMORY_VIRTUAL_ALLOCATION_PARAMS (cl0070.h:66-70) is only 24 bytes
+ * { offset @0, limit @8, hVASpace @16 } - a VA-range reservation, not a
+ * physical allocation, so it must never be turned into a PA. Class 0x50a0
+ * NV50_MEMORY_VIRTUAL is allocated with NV_MEMORY_ALLOCATION_PARAMS and also
+ * returns a VA in .offset (nv_gpu_ops.c:4727-4730); same rule.
+ */
+#define GSPRM_MEM_ALLOC_SIZE 64
+#define GSPRM_MEM_ALLOC_OFFSET 80
+#define GSPRM_MEM_VIRT_OFFSET 0
+#define GSPRM_MEM_VIRT_LIMIT 8
+
+static struct gsp_rm_mem_object *
+gsp_rm_mem_object_find(struct gsp_rm_emu *rm, u32 h_client, u32 h_object)
+{
+ u32 i;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ struct gsp_rm_mem_object *e = &rm->mem_objects[i];
+
+ if (e->valid && e->h_client == h_client &&
+ e->h_object == h_object)
+ return e;
+ }
+ return NULL;
+}
+
+/* Reuse a retired slot, append while the table has room, else evict. */
+static struct gsp_rm_mem_object *gsp_rm_mem_object_slot(struct gsp_rm_emu *rm)
+{
+ struct gsp_rm_mem_object *e;
+ u32 i, slot;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ if (!rm->mem_objects[i].valid)
+ return &rm->mem_objects[i];
+ }
+ if (rm->mem_objects_n < GSPRM_MAX_MEM_OBJECTS)
+ return &rm->mem_objects[rm->mem_objects_n++];
+
+ slot = rm->mem_objects_next++ % GSPRM_MAX_MEM_OBJECTS;
+ e = &rm->mem_objects[slot];
+ if (rm->mem_object_evictions < 8) {
+ rm->mem_object_evictions++;
+ pr_info("vgsp-rm: mem-object table full; evicting (hClient=0x%x hObject=0x%x class=0x%x) for a new allocation\n",
+ e->h_client, e->h_object, e->class_id);
+ }
+ return e;
+}
+
+static void gsp_rm_mem_object_note(struct gsp_rm_emu *rm, u32 h_client,
+ u32 h_parent, u32 h_object, u32 class_id,
+ const u8 *params, u32 params_size)
+{
+ struct gsp_rm_mem_object *e;
+ u64 base = 0, size = 0;
+ u32 address_space;
+
+ switch (class_id) {
+ case 0x3e: /* NV01_MEMORY_SYSTEM */
+ case 0x40: /* NV01_MEMORY_LOCAL_USER */
+ if (params_size < GSPRM_MEM_ALLOC_OFFSET + 8)
+ return;
+ memcpy(&base, params + GSPRM_MEM_ALLOC_OFFSET, 8);
+ if (params_size >= GSPRM_MEM_ALLOC_SIZE + 8)
+ memcpy(&size, params + GSPRM_MEM_ALLOC_SIZE, 8);
+ address_space = class_id == 0x3e ? GSPRM_MEM_AS_SYSMEM
+ : GSPRM_MEM_AS_VIDMEM;
+ break;
+ case 0x70: /* NV01_MEMORY_VIRTUAL (VA range) */
+ if (params_size < 16)
+ return;
+ memcpy(&base, params + GSPRM_MEM_VIRT_OFFSET, 8);
+ memcpy(&size, params + GSPRM_MEM_VIRT_LIMIT, 8);
+ address_space = GSPRM_MEM_AS_VIRTUAL;
+ break;
+ case 0x50a0: /* NV50_MEMORY_VIRTUAL (VA in .offset) */
+ if (params_size < GSPRM_MEM_ALLOC_OFFSET + 8)
+ return;
+ memcpy(&base, params + GSPRM_MEM_ALLOC_OFFSET, 8);
+ if (params_size >= GSPRM_MEM_ALLOC_SIZE + 8)
+ memcpy(&size, params + GSPRM_MEM_ALLOC_SIZE, 8);
+ address_space = GSPRM_MEM_AS_VIRTUAL;
+ break;
+ default:
+ return;
+ }
+
+ e = gsp_rm_mem_object_find(rm, h_client, h_object);
+ if (!e)
+ e = gsp_rm_mem_object_slot(rm);
+ e->h_object = h_object;
+ e->h_client = h_client;
+ e->h_parent = h_parent;
+ e->class_id = class_id;
+ e->base = base;
+ e->size = size;
+ e->address_space = (u8)address_space;
+ e->valid = true;
+
+ if ((class_id == 0x3e || class_id == 0x40) &&
+ rm->mem_object_logs < 64) {
+ rm->mem_object_logs++;
+ nv_vgpu_info(rm->vgpu,
+ "vgsp-rm: mem-object hClient=0x%x hParent=0x%x hObject=0x%x class=0x%x base=0x%llx size=0x%llx as=%u\n",
+ h_client, h_parent, h_object, class_id, base,
+ size, address_space);
+ }
+}
-static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_object,
- const u8 *params, u32 params_size)
+static void gsp_rm_mem_object_retire(struct gsp_rm_emu *rm, u32 h_root,
+ u32 h_object)
+{
+ u32 i, retired = 0;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ struct gsp_rm_mem_object *e = &rm->mem_objects[i];
+
+ if (!e->valid)
+ continue;
+ /*
+ * A normal FREE names the object (hObjectOld) and the freeing
+ * client (hRoot); a root FREE names the client itself. Handles
+ * are recycled across sessions, so both must match.
+ */
+ if ((h_object && h_object != h_root &&
+ e->h_object == h_object &&
+ (!h_root || e->h_client == h_root)) ||
+ (h_object && h_object == h_root &&
+ e->h_client == h_root)) {
+ e->valid = false;
+ retired++;
+ }
+ }
+ if (retired && gsp_sema_dbg)
+ nv_vgpu_info(rm->vgpu,
+ "vgsp-rm: retired %u mem-object record(s) on FREE hObject=0x%x hRoot=0x%x\n",
+ retired, h_object, h_root);
+}
+
+static bool gsp_rm_mem_object_alias(struct gsp_rm_emu *rm, u32 h_dst_client,
+ u32 h_dst_object, u32 h_src_client,
+ u32 h_src_object)
+{
+ struct gsp_rm_mem_object *src, *dst;
+
+ src = gsp_rm_mem_object_find(rm, h_src_client, h_src_object);
+ if (!src)
+ return false;
+
+ dst = gsp_rm_mem_object_find(rm, h_dst_client, h_dst_object);
+ if (!dst)
+ dst = gsp_rm_mem_object_slot(rm);
+
+ *dst = *src;
+ dst->h_client = h_dst_client;
+ dst->h_object = h_dst_object;
+ dst->valid = true;
+ return true;
+}
+
+/*
+ * CONT.438: resolve a GSP-client channel's client-allocated USERD.
+ *
+ * On GSP, bClientAllocatedUserD channels carry hUserdMemory[subdev]
+ * (NV_CHANNEL_ALLOC_PARAMS @32; gsp_abi_check.c:1764) and
+ * userdOffset[subdev] (@64), while the userdMem memdesc (@168) stays zero.
+ * The real server resolves the handle to a memory object and uses
+ * memdescGetPhysAddr(..., userdOffset) (kernel_channel_gv100.c:204).
+ * We can do the same only when the object's ALLOC already taught us a
+ * physical base; otherwise leave USERD zero and say why. Never invent a PA:
+ * the service later writes GP_GET through the adopted block.
+ */
+static void gsp_rm_resolve_channel_userd(struct gsp_emu_context *ctx,
+ struct sema_channel_desc *desc,
+ u32 h_client, u32 h_object,
+ u32 h_userd, u64 userd_offset)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ struct gsp_rm_mem_object *e;
+ u64 pa;
+
+ e = gsp_rm_mem_object_find(rm, h_client, h_userd);
+ if (!e) {
+ struct gsp_rm_mem_object *alias = NULL;
+ u32 i;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ struct gsp_rm_mem_object *cand = &rm->mem_objects[i];
+
+ if (cand->valid && cand->h_object == h_userd) {
+ alias = cand;
+ break;
+ }
+ }
+ if (alias)
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x off=0x%llx UNRESOLVED (handle exists under hClient=0x%x class=0x%x base=0x%llx, channel hClient=0x%x); leaving USERD zero\n",
+ h_object, h_userd,
+ (unsigned long long)userd_offset,
+ alias->h_client, alias->class_id,
+ (unsigned long long)alias->base,
+ h_client);
+ else
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x off=0x%llx UNRESOLVED (no memory object in table, n=%u); leaving USERD zero\n",
+ h_object, h_userd,
+ (unsigned long long)userd_offset,
+ rm->mem_objects_n);
+ return;
+ }
+
+ if (e->address_space == GSPRM_MEM_AS_VIRTUAL) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x is a virtual range (base=0x%llx); refusing to guess a PA, leaving USERD zero\n",
+ h_object, h_userd, e->class_id,
+ (unsigned long long)e->base);
+ return;
+ }
+ if (!e->base) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x base=0 (server did not echo a physical base); leaving USERD zero\n",
+ h_object, h_userd, e->class_id);
+ return;
+ }
+ /* A physical base is page-granular; anything else is not a PA we know. */
+ if (e->base & 0xfff) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x unaligned base=0x%llx; leaving USERD zero\n",
+ h_object, h_userd, e->class_id,
+ (unsigned long long)e->base);
+ return;
+ }
+ /* GP_GET @0x88 / GP_PUT @0x8c must lie inside the object. */
+ if (e->size && userd_offset + 0x90 > e->size) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x off=0x%llx outside size=0x%llx; leaving USERD zero\n",
+ h_object, h_userd,
+ (unsigned long long)userd_offset,
+ (unsigned long long)e->size);
+ return;
+ }
+
+ pa = e->base + userd_offset;
+ if (e->address_space == GSPRM_MEM_AS_VIDMEM &&
+ pa + 0x90 > nv_vgpu_fb_emul_size(ctx->vgpu)) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x pa=0x%llx beyond emulated FB (0x%llx); leaving USERD zero\n",
+ h_object, h_userd, (unsigned long long)pa,
+ (unsigned long long)nv_vgpu_fb_emul_size(ctx->vgpu));
+ return;
+ }
+
+ desc->userd_pa = pa;
+ desc->userd_as = e->address_space == GSPRM_MEM_AS_VIDMEM ? 2 : 1;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x base=0x%llx off=0x%llx as=%u -> pa=0x%llx (memory-object table)\n",
+ h_object, h_userd, e->class_id,
+ (unsigned long long)e->base,
+ (unsigned long long)userd_offset,
+ desc->userd_as, (unsigned long long)pa);
+}
+
+static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_client,
+ u32 h_object, const u8 *params,
+ u32 params_size)
{
struct sema_channel_desc desc;
+ u64 userd_wire_base = 0;
+
+ /*
+ * CONT.405: name the registration attempt for the direct-GL client's
+ * channel families. A "beef"/"bfef" handle that appears more than
+ * once is the client's alloc -> setup -> free -> retry loop; the
+ * matching beef step trace in gsp_rm_handle_rm_control names the
+ * last control it issued before the retry. Bounded.
+ */
+ if (((h_object & 0xffff0000u) == 0xbeef0000u ||
+ (h_object & 0xffffff00u) == 0xbfef0000u)) {
+ static u32 beef_reg_log;
+
+ if (beef_reg_log < 64) {
+ beef_reg_log++;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: beef chan register #%u h=0x%x (setup step: alloc_channel params=%u; next client step is BIND/GPFIFO_SCHEDULE, then its first kick)",
+ beef_reg_log, h_object, params_size);
+ }
+ }
- if (params_size < GSPRM_CHAN_NOTIFIER_MEM + 8)
+ if (params_size < GSPRM_CHAN_NOTIFIER_MEM + 8) {
+ /*
+ * r828 diag: a channel-class alloc whose params are shorter
+ * than the notifier field is not registered, silently. The
+ * RC watchdog channel (hObject 0x31415900) never appeared in
+ * the sema table; name every such skip with its size so the
+ * shape question is answered by a log, not inference.
+ */
+ pr_info("vgsp-rm: channel alloc hObject=0x%x params_size=%u too short (< %u); NOT registered\n",
+ h_object, params_size, GSPRM_CHAN_NOTIFIER_MEM + 8);
return;
+ }
memset(&desc, 0, sizeof(desc));
desc.vgpu = ctx->vgpu;
@@ -2554,19 +3702,300 @@ static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_object,
memcpy(&desc.gpfifo_va, params + GSPRM_CHAN_GPFIFO_OFFSET, 8);
memcpy(&desc.gpfifo_entries, params + GSPRM_CHAN_GPFIFO_ENTRIES, 4);
memcpy(&desc.engine_type, params + GSPRM_CHAN_ENGINE_TYPE, 4);
+ /*
+ * The wire carries NV2080_ENGINE_TYPE_*: NULL = 0, GR0 (== GRAPHICS)
+ * = 1, COPY0 = 9, NVDEC0 = 0x13, ... The progress service's
+ * convention is "engine_type == 0 means the GR channel" (CUDA's
+ * kernel-RM GR channels arrive with engineType NULL), so an X/GL 3D
+ * channel - which carries the real NV2080_ENGINE_TYPE_GRAPHICS = 1 -
+ * was misclassified as a CE and its push never executed ("Failed to
+ * initialize DMA" in the X driver's pushbuffer NOP test). Normalize
+ * GR0..GR7 (1..8) to 0: the emulated device has a single GR and every
+ * other engine id is >= COPY0 (9), so this cannot capture a CE.
+ */
+ if (desc.engine_type >= 1 && desc.engine_type <= 8)
+ desc.engine_type = 0;
+ /*
+ * r835: the channel's own address space (NV_CHANNEL_ALLOC_PARAMS:
+ * hContextShare @24, hVASpace @28 - confirmed by this struct's own
+ * known offsets: engineType @128 = 32 + 8 handles + 8 subdevice
+ * handles + 8 u64 userd offsets). Two clients map different objects
+ * at overlapping VAs (everyone's RM window sits at 0x1200xxxxx), so
+ * the global VA-keyed translation maps conflate them: the X server's
+ * ring VA resolved through the GL client's page tables (walk-probe:
+ * vas h=0xcaf00005 answered 0x120090000 with a zero page) and its
+ * push stream decoded as empty, wedging X exactly like the RC
+ * watchdog's ring. The service resolves a channel's VAs through this
+ * VAS first.
+ */
+ memcpy(&desc.h_vaspace, params + 28, 4);
memcpy(&desc.cid, params + GSPRM_CHAN_CID, 4);
memcpy(&desc.instance_pa, params + GSPRM_CHAN_INSTANCE_MEM, 8);
memcpy(&desc.userd_pa, params + GSPRM_CHAN_USERD_MEM, 8);
+ userd_wire_base = desc.userd_pa;
+ /*
+ * SESSION 153-CONTINUED-38: the USERD control page sits at
+ * userdMem.address + userdOffset[subDeviceId] (NV_CHANNEL_ALLOC_PARAMS
+ * userdOffset[NV_MAX_SUBDEVICES], @64; subDeviceId @136, the
+ * one-hot bit index is normally 0 but the X client-share channel
+ * carries 1). The GL client's kernel channels declare userdMem fb
+ * 0x18fe00 with userdOffset[0]=0x7fe00, so the service was
+ * probing/monitoring 0x18fe00 while the real control page
+ * (GPGet/GPPut) lives at 0x20fc00 - both the registered candidate
+ * and every FB observation were at the wrong offset, which is why
+ * the direct client's channels never resolved a USERD (CONT-37
+ * alloc/free retry loop). Index by the subdevice.
+ */
+ /*
+ * CONT.404: read the aperture before the flags log so the logged
+ * /u value is the real one (it used to print 0 because the memcpy
+ * sat after the block).
+ */
memcpy(&desc.userd_as, params + GSPRM_CHAN_USERD_MEM +
GSPRM_CHAN_MEM_APERTURE, 4);
+ {
+ u32 sd = 0;
+ u64 uo = 0;
+ u32 flags = 0;
+
+ if (params_size >= 140)
+ memcpy(&sd, params + 136, 4);
+ /*
+ * CONT.402: subDeviceId is a one-hot bitmask
+ * (alloc_channel.h), so the userdOffset[] slot is its first
+ * set bit, not the raw value.
+ */
+ sd = sd ? (u32)__builtin_ctz(sd) : 0;
+ if (sd >= 8)
+ sd = 0;
+ if (params_size >= 64 + (sd + 1) * 8) {
+ memcpy(&uo, params + 64 + (u64)sd * 8, 8);
+ desc.userd_pa += uo;
+ }
+ /*
+ * CONT.402: NVOS04_FLAGS_CHANNEL_USERD_INDEX_VALUE (bits 10:8)
+ * and USERD_INDEX_PAGE_VALUE (bits 20:12) select the 0x200
+ * control block inside the declared userdMem page. Logged so
+ * the block offset can be validated before it is applied.
+ *
+ * CONT.404: apply it. The kernel driver sets idx = ChID % 8 and
+ * page = ChID / 8 (kernel_channel.c, IS_GSP_CLIENT path), so a
+ * channel whose ChID is nonzero has its GPGet/GPPut 0x200-aligned
+ * block somewhere inside the declared userdMem. Reading base+0
+ * made the guest's GP_PUT advances invisible - X's channel got
+ * ChID=3 in r1697 and timed out in "Failed to initialize DMA"
+ * with the host watching fill at the wrong block.
+ */
+ if (params_size >= 24) {
+ u64 base = desc.userd_pa;
+ u32 idx, pg;
+
+ memcpy(&flags, params + 20, 4);
+ idx = (flags >> 8) & 0x7;
+ pg = (flags >> 12) & 0x1ff;
+ if (idx != 0 || pg != 0)
+ desc.userd_pa += (u64)pg * 0x1000 +
+ (u64)idx * 0x200;
+ pr_info("vgsp-rm: chan userd flags=0x%x idx=%u page=%u base=0x%llx block=0x%llx/%u hObject=0x%x\n",
+ flags, idx, pg, base, desc.userd_pa,
+ desc.userd_as, h_object);
+ }
+ /*
+ * CONT.438: a GSP-client kernel channel carries its USERD as
+ * hUserdMemory[subdev] (@32), not in the userdMem memdesc
+ * (@168): the wire memdesc base is zero while userdOffset[]
+ * may be nonzero. Do not let base 0 + offset/flags masquerade
+ * as a small PA - resolve the real memory object, or leave
+ * USERD zero (the service must never write a guessed block).
+ */
+ if (userd_wire_base == 0) {
+ u64 wire_pa = desc.userd_pa;
+ u32 wire_as = desc.userd_as;
+ u32 h_userd = 0;
+
+ desc.userd_pa = 0;
+ desc.userd_as = 0;
+ if (params_size >=
+ GSPRM_CHAN_USERD_OFFSET + (sd + 1) * 8 &&
+ params_size >=
+ GSPRM_CHAN_HUSERD_MEMORY +
+ (sd + 1) * 4) {
+ memcpy(&h_userd,
+ params + GSPRM_CHAN_HUSERD_MEMORY +
+ (u64)sd * 4, 4);
+ if (h_userd)
+ gsp_rm_resolve_channel_userd(ctx,
+ &desc,
+ h_client,
+ h_object,
+ h_userd,
+ uo);
+ }
+ /*
+ * CONT.438b: the X/GLX kernel channel needs its
+ * wire-derived block until the client memory-object
+ * table can name the real USERD. c24 (V30) cleared
+ * it and X failed "Failed to initialize DMA"/
+ * "Failed to allocate push buffer"; c23 (V28) used
+ * base 0 + userdOffset and X started. Keep the old
+ * value as a fallback, but only when it is a real
+ * 4K-backed offset (the resolver still wins when it
+ * has a verified memory object).
+ */
+ if (!desc.userd_pa && wire_pa) {
+ desc.userd_pa = wire_pa;
+ desc.userd_as = wire_as;
+ pr_info_ratelimited("vgsp-rm: chan USERD hObject=0x%x hUserd=0x%x unresolved; fallback to wire base+offset=0x%llx as=%u (no guessed table PA)\n",
+ h_object, h_userd,
+ wire_pa, wire_as);
+ }
+ }
+ }
memcpy(&desc.notifier_pa, params + GSPRM_CHAN_NOTIFIER_MEM, 8);
memcpy(&desc.notifier_as, params + GSPRM_CHAN_NOTIFIER_MEM +
GSPRM_CHAN_MEM_APERTURE, 4);
+ /*
+ * CONT.404 diag: the registered block is not always the block the
+ * client writes - X's live GP_PUT was adopted by the shape scan at
+ * fb 0x910000 while the wire declared 0x190000 (+0x600), and the
+ * direct-GL channel's declared page stayed zero. Name the whole
+ * wire memory-descriptor set and peek the registered block's
+ * GP_GET/GP_PUT, so the next cycle separates "wire base is not the
+ * client's block" from "client never writes FB". Bounded.
+ */
+ {
+ static u32 md_log;
+ u64 inst_b = 0, ramfc_b = 0;
+ u32 inst_sz = 0, ramfc_sz = 0;
+ u32 inst_as = 0, ramfc_as = 0;
+ u32 g0 = 0, p0 = 0;
+
+ if (md_log < 32) {
+ md_log++;
+ memcpy(&inst_b, params + GSPRM_CHAN_INSTANCE_MEM, 8);
+ memcpy(&inst_sz, params + GSPRM_CHAN_INSTANCE_MEM + 8, 4);
+ memcpy(&inst_as, params + GSPRM_CHAN_INSTANCE_MEM +
+ GSPRM_CHAN_MEM_APERTURE, 4);
+ memcpy(&ramfc_b, params + 192, 8);
+ memcpy(&ramfc_sz, params + 200, 4);
+ memcpy(&ramfc_as, params + 192 +
+ GSPRM_CHAN_MEM_APERTURE, 4);
+ if (desc.userd_as == 2 && ctx->vgpu &&
+ ctx->vgpu->bars[1].vaddr &&
+ desc.userd_pa + 0x90 <=
+ nv_vgpu_fb_emul_size(ctx->vgpu)) {
+ memcpy(&g0, ctx->vgpu->bars[1].vaddr +
+ desc.userd_pa + 0x88, 4);
+ memcpy(&p0, ctx->vgpu->bars[1].vaddr +
+ desc.userd_pa + 0x8c, 4);
+ }
+ pr_info("vgsp-rm: chan mds hObject=0x%x inst=0x%llx/%u/%u userd=0x%llx/%u ramfc=0x%llx/%u/%u notif=0x%llx/%u getput=%08x/%08x\n",
+ h_object, inst_b, inst_sz, inst_as,
+ desc.userd_pa, desc.userd_as,
+ ramfc_b, ramfc_sz, ramfc_as,
+ desc.notifier_pa, desc.notifier_as, g0, p0);
+ }
+ }
+
+ /*
+ * SESSION 153-CONTINUED-15 (draw attribution): every channel-class
+ * allocation is named with its wire shape before registration, so a
+ * channel the service silently refuses (gpfifo VA 0 / entries < 2)
+ * cannot hide behind an absent "registered" line. Bounded.
+ */
+ {
+ static u32 chan_alloc_log;
+
+ if (chan_alloc_log < 32) {
+ chan_alloc_log++;
+ pr_info("vgsp-rm: chan alloc hObject=0x%x gpfifo=0x%llx ent=%u eng=%u vas=0x%x cid=%u userd=0x%llx/%u notif=0x%llx/%u\n",
+ h_object, desc.gpfifo_va, desc.gpfifo_entries,
+ desc.engine_type, desc.h_vaspace, desc.cid,
+ desc.userd_pa, desc.userd_as,
+ desc.notifier_pa, desc.notifier_as);
+ }
+ }
+ /*
+ * SESSION 153-CONTINUED-38: name the per-subdevice userdOffset (the
+ * kernel channels' control page may sit inside the declared userdMem
+ * at a nonzero offset; the service currently assumes address+0).
+ * Bounded.
+ */
+ {
+ static u32 uo_log;
+
+ if (uo_log < 8 && params_size >= 80) {
+ u64 uo0 = 0;
+ u32 sd = 0;
+
+ memcpy(&uo0, params + 64, 8);
+ if (params_size >= 140)
+ memcpy(&sd, params + 136, 4);
+ uo_log++;
+ pr_info("vgsp-rm: chan userdOff[0]=0x%llx subDevId=0x%x hObject=0x%x (userd memdesc=0x%llx/%u)\n",
+ uo0, sd, h_object, desc.userd_pa,
+ desc.userd_as);
+ }
+ }
gsp_sema_service_channel_add(&desc);
}
/*
+ * CONT.406: GR class objects (TURING_A 0xc597 and TURING_COMPUTE_A 0xc5c0)
+ * are allocated on a channel and the client frees them again when the next
+ * step of its GR context setup fails. Keep the last few allocations so the
+ * FREE side can name the object class and how long the client kept it: that
+ * turns "the client freed the 3D object" into a measured keep time, and
+ * separates "the alloc reply was rejected" (keep time ~0) from "the client
+ * failed later and cleaned up" (keep time ~80 ms, the observed r1700 case).
+ * All alloc/free RPC handling is serialized on the emulation thread, so the
+ * table needs no lock. Bounded.
+ */
+#define GSPRM_GR3D_LOG_MAX 24
+#define GSPRM_MAX_GROBJS 16
+
+static struct {
+ u32 h;
+ u32 class_id;
+ unsigned long at;
+} gsp_rm_grobjs[GSPRM_MAX_GROBJS];
+static u32 gsp_rm_grobjs_next;
+
+static void gsp_rm_grobj_note(u32 h, u32 class_id)
+{
+ u32 i;
+
+ for (i = 0; i < GSPRM_MAX_GROBJS; i++) {
+ if (gsp_rm_grobjs[i].h == h) {
+ gsp_rm_grobjs[i].class_id = class_id;
+ gsp_rm_grobjs[i].at = jiffies;
+ return;
+ }
+ }
+ i = gsp_rm_grobjs_next++ % GSPRM_MAX_GROBJS;
+ gsp_rm_grobjs[i].h = h;
+ gsp_rm_grobjs[i].class_id = class_id;
+ gsp_rm_grobjs[i].at = jiffies;
+}
+
+static bool gsp_rm_grobj_retire(u32 h, u32 *class_id, unsigned long *at)
+{
+ u32 i;
+
+ for (i = 0; i < GSPRM_MAX_GROBJS; i++) {
+ if (gsp_rm_grobjs[i].h != h || !gsp_rm_grobjs[i].class_id)
+ continue;
+ *class_id = gsp_rm_grobjs[i].class_id;
+ *at = gsp_rm_grobjs[i].at;
+ gsp_rm_grobjs[i].class_id = 0;
+ gsp_rm_grobjs[i].h = 0;
+ return true;
+ }
+ return false;
+}
+
+/*
* GSP_RM_ALLOC (103) - rpc_gsp_rm_alloc_v03_00 (32 byte header) followed by
* the class-specific allocation parameters.
*
@@ -2606,11 +4035,37 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
"vgsp-rm: GSP_RM_ALLOC class=0x%x paramsSize=%u flags=0x%x hClient=0x%x hParent=0x%x hObject=0x%x",
class_id, params_size, flags, hc, hp, ho);
+ /*
+ * CONT.405: same bounded step trace as gsp_rm_handle_rm_control, for
+ * the direct-GL client's object allocations. Channel classes get a
+ * retry counter per handle so the alloc/free loop is visible without
+ * gsp_sema_dbg.
+ */
+ {
+ bool beef = (hc >= 0xc1d00010u && hc <= 0xc1d0002fu) ||
+ (ho & 0xffff0000u) == 0xbeef0000u ||
+ (ho & 0xffffff00u) == 0xbfef0000u;
+
+ if (beef) {
+ static u32 beef_alloc_log;
+
+ if (beef_alloc_log < 128) {
+ beef_alloc_log++;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: beef alloc step #%u class=0x%x hClient=0x%x hParent=0x%x hObject=0x%x sz=%u%s",
+ beef_alloc_log, class_id, hc, hp, ho,
+ params_size,
+ gsp_rm_is_channel_class(class_id) ?
+ " (channel)" : "");
+ }
+ }
+ }
+
/* Full param dump for memory/channel classes and any class we have not
* classified yet (rate-limited). %phN is capped at 64 bytes per print,
* so chunk it. */
if (gsp_sema_dbg && gsp_rm_alloc_interesting(class_id) &&
- rm->alloc_param_dumps < 60) {
+ rm->alloc_param_dumps < 500) {
u32 shown = min(params_size, (u32)384);
u32 off;
@@ -2625,17 +4080,51 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
}
}
+ /*
+ * CONT.438: remember client memory objects (sysmem/vidmem/virtual)
+ * so a GSP-client channel's hUserdMemory[subdev] can be resolved to
+ * a physical USERD base later in this same serialized RPC stream.
+ */
+ gsp_rm_mem_object_note(rm, hc, hp, ho, class_id, params, params_size);
+
/* Channels: hand the progress service everything it needs to run the
* work submitted on this channel (GPFIFO ring + USERD). */
if (gsp_rm_is_channel_class(class_id))
- gsp_rm_register_channel(ctx, ho, params, params_size);
+ gsp_rm_register_channel(ctx, hc, ho, params, params_size);
+
+ /*
+ * r1621: display channel objects (core 0xc57d, window 0xc57e, cursor
+ * 0xc57b, cursor-PIO base 0xc57a). Record class+instance -> hObject
+ * so the display engine can post the completion event the driver
+ * registered on the channel and name the channel in diagnostics.
+ * instance is the first u32 of both the DMA and PIO alloc params.
+ */
+ if ((class_id == 0xc57d || class_id == 0xc57e ||
+ class_id == 0xc57b || class_id == 0xc57a) &&
+ params_size >= 4) {
+ u32 inst = 0;
+
+ memcpy(&inst, params, 4);
+ gsp_disp_engine_note_object(ctx->vgpu, class_id, inst, ho);
+ }
/*
- * NV01_EVENT (allocated with class 0x79 in the guest ABI; params are
- * NV0005_ALLOC_PARAMETERS {hParentClient, hSrcResource, hClass,
- * notifyIndex, data}). The driver allocates one per channel and waits
- * for the GPU to post the event before it considers submitted work
- * complete - remember them so completed work can be signalled back.
+ * NV01_EVENT (params are NV0005_ALLOC_PARAMETERS {hParentClient,
+ * hSrcResource, hClass, notifyIndex, data}). The driver allocates one
+ * per channel and waits for the GPU to post the event before it
+ * considers submitted work complete - remember them so completed work
+ * can be signalled back.
+ *
+ * r682: the guest ABI uses THREE event classes and only the
+ * NV01_EVENT_OS_EVENT (0x79) shape was registered: libcuda's channel
+ * events arrive as 0x79, but the modern nvkms/Vulkan path allocates
+ * NV01_EVENT_KERNEL_CALLBACK_EX (0x7E, nvos.h) - observed live as
+ * `GSP_RM_ALLOC class=0x7e paramsSize=24 hParent=<subdevice>
+ * hObject=0xcaf00003` from the second RM client while its main
+ * thread sat in the vkCreateDevice wait loop (readiness word in a
+ * /dev/nvidiactl notification page, never signalled). Register 0x78
+ * (KERNEL_CALLBACK) and 0x7E alongside 0x79 so the completion
+ * wake-ups reach those events too.
*
* The table is append+retire: FREE retires records (valid=false) but
* never shrinks events_n, so long-lived boots with many client
@@ -2648,7 +4137,8 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
* first retired slot for a new registration, and log loudly when the
* table is genuinely full of live entries.
*/
- if (class_id == 0x79 && params_size >= 24) {
+ if ((class_id == 0x79 || class_id == 0x7e || class_id == 0x78) &&
+ params_size >= 24) {
u32 h_src = 0, notify_index = 0, hclass = 0;
u64 data64 = 0;
int hc_off = -1, i;
@@ -2666,16 +4156,17 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
rm->events_n, ho);
else {
/*
- * Locate hClass (0x79) inside the params instead of
- * trusting a fixed offset: NV0005_ALLOC_PARAMETERS is
- * {hParentClient, hSrcResource, hClass, notifyIndex,
- * data}, with hSrcResource immediately before hClass.
+ * Locate hClass (0x78/0x79/0x7e) inside the params
+ * instead of trusting a fixed offset:
+ * NV0005_ALLOC_PARAMETERS is {hParentClient,
+ * hSrcResource, hClass, notifyIndex, data}, with
+ * hSrcResource immediately before hClass.
*/
for (i = 0; i + 4 <= (int)params_size; i += 4) {
u32 v;
memcpy(&v, params + i, 4);
- if (v == 0x79) {
+ if (v == 0x79 || v == 0x7e || v == 0x78) {
hc_off = i;
break;
}
@@ -2694,6 +4185,27 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
memcpy(&notify_index, params + 12, 4);
memcpy(&data64, params + 16, 8);
}
+ /*
+ * r1765: the modern NVKMS/kernel events are registered
+ * with hSrcResource = 0 in NV0005_ALLOC_PARAMETERS;
+ * their real source is the ALLOC parent object (the
+ * event is allocated ON the channel handle:
+ * hParent=0x1001b for hEvent=0x1007a, verified live).
+ * With h_src = 0 the exact-match post path never fired
+ * for display completions and every flip rode the
+ * all-events fallback, which coalesced/delayed flips
+ * and let ANY other completion nudge the flip event
+ * (stray FLIP_OCCURRED -> the nvidia-drm dequeue
+ * WARN). Fall back to the alloc header's hParent when
+ * the params name no source resource.
+ *
+ * r1767: gated behind ev_assoc (default off) until the
+ * flip fix is validated end-to-end - the r1767 RCA
+ * exonerated the association from the r1766 soft-lock
+ * class, but not from the r1766 ISR crash class.
+ */
+ if (gsp_rm_ev_assoc && h_src == 0 && hp != 0 && hp != hc)
+ h_src = hp;
rm->events[slot].h_src = h_src;
rm->events[slot].h_event = ho;
rm->events[slot].h_client = hc;
@@ -2727,6 +4239,39 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
memcpy(reply, p, reply_len);
memset(reply + 16, 0, 4); /* status = NV_OK */
+ /*
+ * CONT.406: the direct-GL client allocates a TURING_A (0xc597) 3D
+ * object on its channel immediately before it would submit its first
+ * kick, and then frees it again. Record the exact reply the client
+ * receives (bounded) and remember the object so the FREE side can say
+ * how long it was kept. The reply must echo the 16-byte
+ * NV_GR_ALLOCATION_PARAMETERS {version, flags, size, caps} with
+ * status=NV_OK; caps is a class-dependent *output* that has no
+ * defined bits for c597 in any 570 header, so the echoed 0 is the
+ * value the golden/physical path also sends and accepts.
+ */
+ if (class_id == 0xc597u) {
+ u32 rp[4] = { 0, 0, 0, 0 };
+
+ if (echo_len >= 4)
+ memcpy(&rp[0], reply + GSPRM_ALLOC_HDR_SIZE, 4);
+ if (echo_len >= 8)
+ memcpy(&rp[1], reply + GSPRM_ALLOC_HDR_SIZE + 4, 4);
+ if (echo_len >= 12)
+ memcpy(&rp[2], reply + GSPRM_ALLOC_HDR_SIZE + 8, 4);
+ if (echo_len >= 16)
+ memcpy(&rp[3], reply + GSPRM_ALLOC_HDR_SIZE + 12, 4);
+
+ gsp_rm_grobj_note(ho, class_id);
+ if (rm->gr3d_alloc_log < GSPRM_GR3D_LOG_MAX) {
+ rm->gr3d_alloc_log++;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: alloc 0xc597 h=0x%x status=0x%x size=0x%x reply=%08x %08x %08x %08x (parent=0x%x client=0x%x)",
+ ho, 0, params_size,
+ rp[0], rp[1], rp[2], rp[3], hp, hc);
+ }
+ }
+
if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_ALLOC, sequence, 0,
reply, reply_len))
nv_vgpu_err(ctx->vgpu, "vgsp-rm: GSP_RM_ALLOC reply send failed");
@@ -2901,6 +4446,217 @@ gsp_rm_handle_promote_fault_method_buffers(struct gsp_emu_context *ctx,
}
/*
+ * r519 experiment switch, independent of the P3 whitelist: forward ONLY the
+ * BINAPI reserved-interface query 0x20810107 to the live GSP session. The P3
+ * whitelist may be off (its mixed real/emulated device answers perturb the
+ * boot); this one command is what the GL surface init needs answered.
+ */
+static bool gsp_rm_binapi07_fwd_param;
+module_param_named(gsp_rm_binapi07_fwd, gsp_rm_binapi07_fwd_param, bool, 0644);
+MODULE_PARM_DESC(gsp_rm_binapi07_fwd,
+ "Forward only the guest GL core's BINAPI 0x20810107 query to the live GSP session (default 0; diagnostic/experiment)");
+
+/*
+ * SESSION 153-CONTINUED-17: reply word (little-endian u16) for BINAPI
+ * 0x20810107. The guest GL client reads reply byte[1] as a feature flag
+ * (glcore+0xFC74E1: setne <ctx>+0x203 after cmpb $0x0,reply[1]) and the
+ * flag gates method emission at glcore+0xB5B48B (see the control handler).
+ * 0x0101 sets both bytes; 0x0100 sets byte[1] only. Default 0 = the
+ * historical {0,0} reply.
+ */
+static uint gsp_rm_binapi07_rep;
+module_param_named(binapi07_rep, gsp_rm_binapi07_rep, uint, 0644);
+MODULE_PARM_DESC(binapi07_rep,
+ "reply word (LE u16) for BINAPI cmd 0x20810107; the GL client reads byte[1] as a feature flag (default 0)");
+
+/*
+ * session 75: reply words for the client-pending control 0x2080a097 (the
+ * libnvidia-eglcore engine-readiness poll). Runtime-tunable so one boot can
+ * sweep reply variants: the client reads +0x204, +0x488 and +0x48c back
+ * (file offsets 0xecd45f..0xecd475) before its engine loop re-issues the
+ * control. Defaults: flag=1 (the request sets it to 0), the other words 0.
+ */
+static uint gsp_rm_a097_flag = 1;
+module_param_named(wall2_a097_flag, gsp_rm_a097_flag, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_flag, "0x2080a097 reply word at params+0x204 (default 1)");
+static uint gsp_rm_a097_v488;
+module_param_named(wall2_a097_v488, gsp_rm_a097_v488, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_v488, "0x2080a097 reply word at params+0x488 (default 0)");
+static uint gsp_rm_a097_v48c;
+module_param_named(wall2_a097_v48c, gsp_rm_a097_v48c, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_v48c, "0x2080a097 reply word at params+0x48c (default 0)");
+
+/*
+ * session 76: the 0x2080a097 reply's RECORD area (params+4..+0x23). The
+ * real host RM handler overwrites up to four {u32, u32} mode pairs there
+ * (per-mode engine status + a x1000 clock/percent value); the client
+ * (count=1) copies back the first 16 bytes into its per-engine query
+ * buffer, and the second word of the first pair is consumed as the
+ * "engine progress" word (libnvidia-eglcore d5e3e0: *(inner+4) ->
+ * session+0xe104, compared against per-boot config thresholds).
+ * Each word defaults to 0xffffffff = leave the echoed request word
+ * untouched; any other value replaces it, so one boot can sweep the
+ * record matrix without rebuilding.
+ */
+static uint gsp_rm_a097_count = ~0u;
+module_param_named(wall2_a097_count, gsp_rm_a097_count, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_count, "0x2080a097 reply count word at params+0 (0xffffffff = echo)");
+static uint gsp_rm_a097_rec0 = ~0u, gsp_rm_a097_rec1 = ~0u;
+static uint gsp_rm_a097_rec2 = ~0u, gsp_rm_a097_rec3 = ~0u;
+static uint gsp_rm_a097_rec4 = ~0u, gsp_rm_a097_rec5 = ~0u;
+static uint gsp_rm_a097_rec6 = ~0u, gsp_rm_a097_rec7 = ~0u;
+module_param_named(wall2_a097_rec0, gsp_rm_a097_rec0, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec0, "0x2080a097 reply word at params+0x4 (0xffffffff = echo)");
+module_param_named(wall2_a097_rec1, gsp_rm_a097_rec1, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec1, "0x2080a097 reply word at params+0x8 (0xffffffff = echo)");
+module_param_named(wall2_a097_rec2, gsp_rm_a097_rec2, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec2, "0x2080a097 reply word at params+0xc (0xffffffff = echo)");
+module_param_named(wall2_a097_rec3, gsp_rm_a097_rec3, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec3, "0x2080a097 reply word at params+0x10 (0xffffffff = echo)");
+module_param_named(wall2_a097_rec4, gsp_rm_a097_rec4, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec4, "0x2080a097 reply word at params+0x14 (0xffffffff = echo)");
+module_param_named(wall2_a097_rec5, gsp_rm_a097_rec5, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec5, "0x2080a097 reply word at params+0x18 (0xffffffff = echo)");
+module_param_named(wall2_a097_rec6, gsp_rm_a097_rec6, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec6, "0x2080a097 reply word at params+0x1c (0xffffffff = echo)");
+module_param_named(wall2_a097_rec7, gsp_rm_a097_rec7, uint, 0644);
+MODULE_PARM_DESC(wall2_a097_rec7, "0x2080a097 reply word at params+0x20 (0xffffffff = echo)");
+
+/*
+ * m112b: the NV04_VID_HEAP_CONTROL enumeration pair 0x90960106/0x90960107
+ * (legacy NVOS32 VidHeap API, class 0x9096). libnvidia-eglcore (file offset
+ * 0x586d80) walks three heaps: per heap it queries 0x90960106 (12 bytes:
+ * {u32 firstIdx, u32 lastIdx}; the client then enumerates the heap's memory
+ * blocks by index via 0x90960107 (56 bytes) while firstIdx+1 <= idx <=
+ * lastIdx). The 56-byte block entry is read at +0x00/+0x08 (two u64s), +0x28
+ * (a u32 type compared against a table at eglcore 0x101b1c0) and +0x30 (a
+ * flag BYTE: when 0 the heap/block is skipped). The generic zero reply makes
+ * every heap look empty, the client concludes out-of-device-memory and
+ * vkAllocateMemory fails -2 WITHOUT any alloc reaching the emulation
+ * (dmesg-corroborated: only 0x9096* controls + a class 0xc5c0 alloc).
+ * Runtime-tunable so one boot can sweep reply variants (same pattern as the
+ * wall2_a097_* words).
+ */
+static uint gsp_rm_9096_lo; /* 0x90960106 firstIdx */
+static uint gsp_rm_9096_hi = 1; /* 0x90960106 lastIdx */
+module_param_named(wall2_9096_lo, gsp_rm_9096_lo, uint, 0644);
+MODULE_PARM_DESC(wall2_9096_lo, "0x90960106 reply firstIdx (default 0)");
+module_param_named(wall2_9096_hi, gsp_rm_9096_hi, uint, 0644);
+MODULE_PARM_DESC(wall2_9096_hi, "0x90960106 reply lastIdx (default 1)");
+static uint gsp_rm_9096_ent_addr; /* 0x90960107 entry +0x00 (u64) */
+module_param_named(wall2_9096_ent_addr, gsp_rm_9096_ent_addr, uint, 0644);
+MODULE_PARM_DESC(wall2_9096_ent_addr, "0x90960107 entry word at +0x00 (default 0)");
+static uint gsp_rm_9096_ent_size = 0x80000000u; /* entry +0x08 (u64) */
+module_param_named(wall2_9096_ent_size, gsp_rm_9096_ent_size, uint, 0644);
+MODULE_PARM_DESC(wall2_9096_ent_size, "0x90960107 entry word at +0x08 (default 0x80000000)");
+static uint gsp_rm_9096_ent_type = 3; /* entry +0x28 (u32) */
+module_param_named(wall2_9096_ent_type, gsp_rm_9096_ent_type, uint, 0644);
+MODULE_PARM_DESC(wall2_9096_ent_type, "0x90960107 entry word at +0x28 (default 3)");
+static uint gsp_rm_9096_ent_flag = 1; /* entry +0x30 (u8) */
+
+module_param_named(wall2_9096_ent_flag, gsp_rm_9096_ent_flag, uint, 0644);
+MODULE_PARM_DESC(wall2_9096_ent_flag, "0x90960107 entry flag byte at +0x30 (default 1)");
+
+
+/*
+ * The live session's oracle client/object for the 0x20810107 experiment,
+ * created on demand; 0 until the first query builds it. One device per module
+ * load, so file-statics are enough; reset by unload.
+ */
+static u32 gsp_rm_binapi07_be_client;
+static u32 gsp_rm_binapi07_be_object;
+
+/*
+ * One GSP_RM_ALLOC (fn 103) against the live backend, built exactly like the
+ * rm-call ladder's helper (rpc_gsp_rm_alloc_v03_00: hClient, hParent,
+ * hObject, hClass, status, paramsSize, flags, reserved[4] + params).
+ * object_is_out selects the reply read: a ROOT alloc returns the
+ * server-chosen client handle in params[0] (payload +32), anything else
+ * echoes the client-chosen hObject at +8; status is at +16 in both cases.
+ */
+static int gsp_rm_binapi07_backend_alloc(u32 h_client, u32 h_parent,
+ u32 h_object, u32 h_class,
+ const void *params, u32 params_len,
+ bool object_is_out,
+ u32 *out_object, u32 *out_status)
+{
+ u8 *areq;
+ void *arsp = NULL;
+ u32 arsp_len = 0, st = 0xffffffff, ho = 0;
+ int ret;
+
+ areq = kzalloc(32 + params_len, GFP_KERNEL);
+ if (!areq)
+ return -ENOMEM;
+ memcpy(areq + 0, &h_client, 4);
+ memcpy(areq + 4, &h_parent, 4);
+ memcpy(areq + 8, &h_object, 4);
+ memcpy(areq + 12, &h_class, 4);
+ memcpy(areq + 20, &params_len, 4);
+ if (params_len)
+ memcpy(areq + 32, params, params_len);
+
+ ret = gsp_phys_backend_rpc(GSPRM_RPC_FUNC_GSP_RM_ALLOC, areq,
+ 32 + params_len, &arsp, &arsp_len, 500);
+ kfree(areq);
+ if (!ret && arsp) {
+ if (arsp_len >= 20)
+ memcpy(&st, (u8 *)arsp + 16, 4);
+ if (object_is_out) {
+ if (arsp_len >= 36)
+ memcpy(&ho, (u8 *)arsp + 32, 4);
+ } else if (arsp_len >= 12) {
+ memcpy(&ho, (u8 *)arsp + 8, 4);
+ }
+ }
+ kfree(arsp);
+ if (out_object)
+ *out_object = ho;
+ if (out_status)
+ *out_status = st;
+ return ret;
+}
+
+/*
+ * Build the oracle object on the live session: a class-0x2081 child of the
+ * session's internal subdevice, since the server dispatches the method from
+ * the OBJECT's class (the subdevice attempt alone answered
+ * NV_ERR_NOT_SUPPORTED, 0x56, and a fresh root client at runtime answered
+ * NV_ERR_INVALID_CLIENT, 0x23 - the ladder "adopts" the server's own
+ * internal client instead of creating one). A small candidate list of
+ * likely-free handles in that client's namespace is tried; the first
+ * success wins. Returns 0 with *out_object set.
+ */
+static int gsp_rm_binapi07_backend_setup(struct gsp_emu_context *ctx,
+ struct gsp_phys_backend_static_config *snap,
+ u32 *out_object)
+{
+ static const u32 cand[] = { 0xabcd2081u, 0xabcd2001u, 0xabcd2a2au,
+ 0xabcd5f5fu, 0xabcd1234u };
+ u8 p4[4] = { 0, 0, 0, 0 };
+ u32 st = 0xffffffff, obj = 0;
+ int i, ret;
+
+ for (i = 0; i < (int)ARRAY_SIZE(cand); i++) {
+ u32 want = cand[i];
+
+ obj = want;
+ ret = gsp_rm_binapi07_backend_alloc(snap->h_internal_client,
+ snap->h_internal_subdevice,
+ want, 0x2081, p4, 4,
+ false, &obj, &st);
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: BINAPI-EXPERIMENT oracle BINAPI alloc attempt %d: hObject=0x%x ret=%d status=0x%x",
+ i, want, ret, st);
+ if (!ret && st == 0 && obj) {
+ *out_object = obj;
+ return 0;
+ }
+ }
+ return -EIO;
+}
+
+/*
* 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.
@@ -2949,12 +4705,37 @@ static bool gsp_rm_ctrl_forward(struct gsp_emu_context *ctx,
u32 out_params_size;
int ret;
+ bool guest_handles = false;
+
/* 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:
+ if (!gsp_phys_ctrl_forward_enabled())
+ return false;
+ break;
+ case 0x20810107: /* NV2081_BINAPI reserved-interface method 7 */
+ /*
+ * EXPERIMENT (r519): the guest GL core allocates a BINAPI
+ * object (class 0x2081) and sends this 2-byte query during
+ * 3D-surface init; our {0,0} generic reply leaves the
+ * driver's feature flag clear and the EGL surface init tears
+ * down (EGL_BAD_ALLOC at eglMakeCurrent). The guest-side
+ * handles (hClient 0xc1d..., a 0xbeef object) have no backend
+ * counterpart: allocate a real BINAPI object (class 0x2081) on
+ * the live session once, then send the query to it. The first
+ * attempt (subdevice translation) came back NV_ERR_NOT_SUPPORTED
+ * (0x56) - the server dispatches the method by the OBJECT's
+ * class, so a subdevice cannot answer a BINAPI method. The
+ * reply is logged either way; an error falls back to the
+ * emulated generic success until the semantics are known.
+ * Gated by its own parameter so the P3 whitelist can stay off.
+ */
+ if (!gsp_rm_binapi07_fwd_param)
+ return false;
+ guest_handles = true;
break;
default:
return false;
@@ -2966,24 +4747,44 @@ static bool gsp_rm_ctrl_forward(struct gsp_emu_context *ctx,
if (req->paramsSize > GSPRM_MAX_SINGLE_ELEM_DATA - GSP_RM_CTRL_HDR_SIZE)
goto fallback;
+ /* The experiment is scoped to the observed shape: 2-byte in/out. */
+ if (guest_handles && req->paramsSize != 2)
+ goto fallback;
+
/* A valid snapshot implies a READY session and the adopted handles. */
if (gsp_phys_backend_static_get(&snap))
goto fallback;
+ if (guest_handles && !gsp_rm_binapi07_be_object) {
+ u32 obj = 0;
+
+ if (gsp_rm_binapi07_backend_setup(ctx, &snap, &obj))
+ goto fallback;
+ /*
+ * The object lives in the internal client's namespace; the
+ * control is sent as that client (snap.h_internal_client).
+ */
+ gsp_rm_binapi07_be_client = snap.h_internal_client;
+ gsp_rm_binapi07_be_object = obj;
+ }
+
/* 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))
+ if (!guest_handles &&
+ (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.hClient = guest_handles ? gsp_rm_binapi07_be_client :
+ snap.h_internal_client;
+ fwd_hdr.hObject = guest_handles ? gsp_rm_binapi07_be_object :
+ (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;
@@ -3003,6 +4804,27 @@ static bool gsp_rm_ctrl_forward(struct gsp_emu_context *ctx,
}
/*
+ * r519 experiment evidence: log the live GSP's own answer to the
+ * BINAPI query (status + first params bytes) before deciding.
+ */
+ if (req->cmd == 0x20810107) {
+ u32 st = 0xffffffff, ps = 0xffffffff;
+ u8 b[8] = { 0 };
+
+ if (rsp && rsp_len >= GSP_RM_CTRL_HDR_SIZE) {
+ memcpy(&st, (u8 *)rsp + 12, 4);
+ memcpy(&ps, (u8 *)rsp + 16, 4);
+ if (ps <= 8 && ps <= rsp_len - GSP_RM_CTRL_HDR_SIZE)
+ memcpy(b, (u8 *)rsp + GSP_RM_CTRL_HDR_SIZE,
+ ps);
+ }
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: BINAPI-EXPERIMENT 0x20810107 forwarded to live GSP: rsp_len=%u status=0x%x outSize=%u bytes=%02x %02x %02x %02x %02x %02x %02x %02x",
+ rsp_len, st, ps, b[0], b[1], b[2], b[3], b[4],
+ b[5], b[6], b[7]);
+ }
+
+ /*
* 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
@@ -3048,6 +4870,101 @@ fallback:
return false;
}
+/*
+ * r489: a GSP_RM_CONTROL reply whose rpc record exceeds the driver's
+ * pRpc->maxRpcSize must be sent with the GSP's continuation-record protocol
+ * (nv gsp_rpc.c _rpcWriteContinuationEntries): the FIRST record carries
+ * exactly maxRpcSize bytes of [rpc header + ctrl header + params]; each
+ * following NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD record carries up to
+ * maxRpcSize - sizeof(hdr) bytes of the remainder. The driver's
+ * _issueRpcLarge consumes the first frame as min(bufSize, maxRpcSize) and
+ * then requires one continuation record per remaining chunk - without them
+ * it times out ("rpcRecvPoll timedout for fn 71 continuation record") and
+ * the control fails (NV_ERR_INVALID_PARAMETER), which is how the GL VASpace
+ * construction died on cmd 0x2080a0a4 (paramsSize 67392 > maxRpcSize).
+ *
+ * maxRpcSize is the guest driver's GSP_MSG_QUEUE_RPC_SIZE_MAX; the value is
+ * pinned by the driver's own arithmetic: for the 0x2080a0a4 reply
+ * (full_param_size = 32 + 24 + 67392 = 67448) it reported remainingSize
+ * 0x940 = 2368 after the first frame, i.e. maxRpcSize = 67448 - 2368 =
+ * 65080. A build whose constant differs shows up immediately as a
+ * mismatched continuation count in the guest log; adjust here.
+ */
+#define GSPRM_CRM_CONT_MAXRPC 65080u
+
+static int gsp_rm_send_rm_control(struct gsp_emu_context *ctx, u32 sequence,
+ const void *data, u32 data_len)
+{
+ u32 total = sizeof(struct gsprm_rpc_header) + data_len;
+ const u8 *p = data;
+ u32 off = 0;
+ int ret;
+
+ if (total <= GSPRM_CRM_CONT_MAXRPC)
+ return gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_CONTROL,
+ sequence, 0, data, data_len);
+
+ /* Frame 1: rpc.length = maxRpcSize. */
+ ret = gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_CONTROL, sequence, 0,
+ p, GSPRM_CRM_CONT_MAXRPC -
+ sizeof(struct gsprm_rpc_header));
+ if (ret)
+ return ret;
+ off = GSPRM_CRM_CONT_MAXRPC - sizeof(struct gsprm_rpc_header);
+
+ /* Continuations: rpc.length = hdr + chunk. */
+ while (off < data_len) {
+ u32 chunk = data_len - off;
+
+ if (chunk > GSPRM_CRM_CONT_MAXRPC -
+ sizeof(struct gsprm_rpc_header))
+ chunk = GSPRM_CRM_CONT_MAXRPC -
+ sizeof(struct gsprm_rpc_header);
+ ret = gsp_rm_send(ctx, GSPRM_RPC_FUNC_CONTINUATION_RECORD,
+ sequence, 0, p + off, chunk);
+ if (ret)
+ return ret;
+ off += chunk;
+ }
+ return 0;
+}
+
+/*
+ * Real-GSP KGR static-data snapshots (mode-35 boot capture; see
+ * gsp_phys_kgr_snap_get in gsp_phys_booter.c). The physical backend asks the
+ * live GSP the real client's KGR query set at boot and retains the replies;
+ * serve the identical bytes for the same commands so the guest's GL/3D stack
+ * reads the device's true GR static values. Concrete consumer: the X
+ * driver's 3D-engine selection maps GR_GET_INFO index SM_VERSION (0x0c)
+ * through a table and fails with "Failed to select a 3D engine" when it
+ * reads back as SM_VERSION_NONE (0); the live GSP's KGR_GET_INFO reply
+ * carries the real value (0x75 on TU104). Returns true when the reply was
+ * served from the snapshot (out_params_size set); false falls through to the
+ * emulated handler unchanged.
+ */
+static bool gsp_rm_kgr_snap_serve(struct gsp_emu_context *ctx, u32 cmd,
+ u32 req_params_size,
+ u8 *out_params, u32 out_len,
+ u32 *out_params_size)
+{
+ u32 n = 0;
+ int ret;
+
+ ret = gsp_phys_kgr_snap_get(cmd, out_params, out_len, &n);
+ if (ret || !n)
+ return false;
+
+ /* Never answer with more bytes than the guest asked for. */
+ if (n > req_params_size)
+ return false;
+
+ *out_params_size = n;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: KGR cmd=0x%x served from the real-GSP snapshot (%u bytes)",
+ cmd, n);
+ return true;
+}
+
static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
const u8 *elem, u32 sequence)
{
@@ -3060,9 +4977,35 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
u32 out_status = 0;
u32 out_params_size = 0;
u64 t0 = ktime_get_ns();
+ bool beef;
memcpy(&req, req_data, GSP_RM_CTRL_HDR_SIZE);
+ /*
+ * CONT.405: bounded step trace for the direct-GL client's RM clients
+ * (the "beef" device/VAS/BINAPI client and the "bfef" channel
+ * client). Its init is a fixed control sequence; when it gives up it
+ * frees the channel and retries with a fresh client, so the LAST
+ * cmd this trace shows before the next client's device alloc is the
+ * failing step. Necessary because the default branch answers an
+ * unhandled control with generic SUCCESS and that is invisible at
+ * gsp_sema_dbg=0 - which is exactly where the retry loop lived.
+ */
+ beef = (req.hClient >= 0xc1d00010u && req.hClient <= 0xc1d0002fu) ||
+ (req.hObject & 0xffff0000u) == 0xbeef0000u ||
+ (req.hObject & 0xffffff00u) == 0xbfef0000u;
+ if (beef) {
+ static u32 beef_step_log;
+
+ if (beef_step_log < 128) {
+ beef_step_log++;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: beef step #%u cmd=0x%x hClient=0x%x hObject=0x%x in=%u",
+ beef_step_log, req.cmd, req.hClient,
+ req.hObject, req.paramsSize);
+ }
+ }
+
if (gsp_sema_dbg)
nv_vgpu_info(ctx->vgpu, "vgsp-rm: GSP_RM_CONTROL cmd=0x%x hClient=0x%x hObject=0x%x paramsSize=%u flags=0x%x",
req.cmd, req.hClient, req.hObject, req.paramsSize,
@@ -3082,13 +5025,26 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
* return means nothing was sent and the emulated switch below runs
* byte-identically to the parameter-off path.
*/
- if (gsp_phys_ctrl_forward_enabled() &&
+ if ((gsp_phys_ctrl_forward_enabled() || gsp_rm_binapi07_fwd_param) &&
gsp_rm_ctrl_forward(ctx, &req, req_data + GSP_RM_CTRL_HDR_SIZE,
reply, reply_data_len, sequence)) {
kfree(reply);
return;
}
+ /*
+ * KGR static-data snapshots (mode-35 boot capture): the live GSP's own
+ * replies to the real client's KGR query set are served byte-identically
+ * for the same commands, so the guest's GL/3D stack sees the device's
+ * true GR static values. Falls through to the emulated switch when no
+ * snapshot exists (backend not READY, other boot mode, cmd not captured).
+ */
+ if (gsp_rm_kgr_snap_serve(ctx, req.cmd, req.paramsSize,
+ reply + GSP_RM_CTRL_HDR_SIZE,
+ reply_data_len - GSP_RM_CTRL_HDR_SIZE,
+ &out_params_size))
+ goto rm_control_reply;
+
switch (req.cmd) {
case TU104_CTRL_FIFO_GET_DEVICE_INFO_TABLE:
gsp_rm_handle_fifo_device_table(ctx, sequence, &req,
@@ -3098,7 +5054,8 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
break;
case 0x90f10106: /* NV90F1_CTRL_CMD_VASPACE_COPY_SERVER_RESERVED_PDES */
- gsp_rm_handle_copy_server_pdes(ctx, req_data + GSP_RM_CTRL_HDR_SIZE,
+ gsp_rm_handle_copy_server_pdes(ctx, req.hObject,
+ req_data + GSP_RM_CTRL_HDR_SIZE,
req.paramsSize);
out_status = 0;
out_params_size = 0;
@@ -3168,6 +5125,171 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
break;
+ case 0xc3700104: /* NVC370_CTRL_CMD_GET_CHANNEL_INFO */
+ gsp_rm_handle_display_channel_info(ctx,
+ req_data + GSP_RM_CTRL_HDR_SIZE, req.paramsSize,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case 0x20800a5d: /* NV2080_CTRL_CMD_INTERNAL_DISPLAY_GET_ACTIVE_DISPLAY_DEVICES */
+ gsp_rm_handle_display_active_devices(ctx,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case TU104_CTRL_INTERNAL_DISPLAY_CHANNEL_PUSHBUFFER: {
+ /*
+ * r1621: the kernel RM programs each display channel's push
+ * buffer through this internal control right after the channel
+ * object is allocated. It carries the guest-physical push-
+ * buffer address, its size and the display class/instance - the
+ * exact inputs the host-side EVO engine needs (see
+ * gsp_display_engine.c). The reply stays a verbatim echo.
+ */
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+
+ /*
+ * The real struct (NV2080_CTRL_INTERNAL_DISPLAY_CHANNEL_
+ * PUSHBUFFER_PARAMS) is 56 bytes: addressSpace@0,
+ * physicalAddr@8, limit@16, cacheSnoop@24, hclass@28,
+ * channelInstance@32, valid@36 (NvBool = 1 byte!),
+ * pbTargetAperture@40, channelPBSize@44, subDeviceId@48.
+ * Read valid as a byte: the 3 padding bytes after it are not
+ * guaranteed zero, so a u32 load can turn valid=0 (PIO
+ * cursor channel) into a false positive.
+ */
+ pr_info("rm-ctl: display channel pushbuffer hObject=0x%x size=%u pa=0x%llx limit=0x%llx class=0x%x inst=%u valid=%u\n",
+ req.hObject, req.paramsSize,
+ req.paramsSize >= 24 ? *(const u64 *)(pp + 8) : 0,
+ req.paramsSize >= 24 ? *(const u64 *)(pp + 16) : 0,
+ req.paramsSize >= 32 ? *(const u32 *)(pp + 28) : 0,
+ req.paramsSize >= 36 ? *(const u32 *)(pp + 32) : 0,
+ req.paramsSize >= 37 ? pp[36] : 0);
+ if (req.paramsSize >= 40 &&
+ req.paramsSize + GSP_RM_CTRL_HDR_SIZE <=
+ GSPRM_MAX_SINGLE_ELEM_DATA) {
+ u32 address_space = 0, hclass = 0, instance = 0;
+ u8 valid = 0;
+ u64 pa = 0, limit = 0;
+
+ memcpy(&address_space, pp + 0, 4);
+ memcpy(&pa, pp + 8, 8);
+ memcpy(&limit, pp + 16, 8);
+ memcpy(&hclass, pp + 28, 4);
+ memcpy(&instance, pp + 32, 4);
+ valid = pp[36];
+ memcpy(rp, pp, req.paramsSize);
+ gsp_disp_engine_channel_add(ctx->vgpu, hclass, instance,
+ pa, limit, address_space,
+ valid != 0);
+ }
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ break;
+ }
+
+ case TU104_CTRL_INTERNAL_DISPLAY_WRITE_INST_MEM: {
+ /*
+ * The kernel RM announces the display instance-memory block
+ * (8 KB context-DMA hash table + context-DMA entries) and
+ * its location. The host engine resolves the notifier
+ * context-DMA handle carried by SET_NOTIFIER_DMA through
+ * this block; without it the notifier PA stays unknown and
+ * the UPDATE FINISHED word is never written (r1627: UPDATE
+ * armed=1 handle=0x10013 -> pa=0x0).
+ *
+ * Params: instMemPhysAddr@0 (u64), instMemSize@8 (u64),
+ * instMemAddrSpace@16 (u32), instMemCpuCacheAttr@20 (u32).
+ */
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+
+ pr_info("rm-ctl: display write inst mem size=%u pa=0x%llx space=%u\n",
+ req.paramsSize,
+ req.paramsSize >= 16 ? *(const u64 *)(pp + 0) : 0,
+ req.paramsSize >= 24 ? *(const u32 *)(pp + 16) : 0);
+ if (req.paramsSize >= 24 &&
+ req.paramsSize + GSP_RM_CTRL_HDR_SIZE <=
+ GSPRM_MAX_SINGLE_ELEM_DATA) {
+ u64 pa = 0, size = 0;
+ u32 space = 0;
+
+ memcpy(&pa, pp + 0, 8);
+ memcpy(&size, pp + 8, 8);
+ memcpy(&space, pp + 16, 4);
+ memcpy(rp, pp, req.paramsSize);
+ gsp_disp_engine_inst_mem(ctx->vgpu, pa, size, space);
+ }
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ break;
+ }
+
+ case 0x801707: /* NV0080_CTRL_CMD_FIFO_GET_ENGINE_CONTEXT_PROPERTIES */
+ gsp_rm_handle_fifo_engine_ctx_props(ctx,
+ req_data + GSP_RM_CTRL_HDR_SIZE, req.paramsSize,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case 0x730102: /* NV0073_CTRL_CMD_SYSTEM_GET_NUM_HEADS */
+ case 0x730287: /* NV0073_CTRL_CMD_SPECIFIC_GET_ALL_HEAD_MASK */
+ case 0x7302ad: /* NV0073_CTRL_CMD_SPECIFIC_GET_VALID_HEAD_WINDOW_ASSIGNMENT */
+ case 0x730107: /* NV0073_CTRL_CMD_SYSTEM_GET_SUPPORTED */
+ case 0x730108: /* NV0073_CTRL_CMD_SYSTEM_GET_CONNECT_STATE */
+ case 0x730240: /* NV0073_CTRL_CMD_SPECIFIC_GET_TYPE */
+ case 0x73028a: /* NV0073_CTRL_CMD_SPECIFIC_GET_PCLK_LIMIT */
+ case 0x73028b: /* NV0073_CTRL_CMD_SPECIFIC_OR_GET_INFO */
+ case 0x730250: /* NV0073_CTRL_CMD_SPECIFIC_GET_CONNECTOR_DATA */
+ case 0x73010b: /* NV0073_CTRL_CMD_SYSTEM_GET_HEAD_ROUTING_MAP */
+ case 0x731140: /* NV0073_CTRL_CMD_DFP_GET_INFO */
+ gsp_rm_handle_display_heads(ctx, req.cmd, req.paramsSize,
+ req_data + GSP_RM_CTRL_HDR_SIZE,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case 0x730245: /* NV0073_CTRL_CMD_SPECIFIC_GET_EDID_V2 */
+ gsp_rm_handle_display_get_edid(ctx, req.paramsSize,
+ req_data + GSP_RM_CTRL_HDR_SIZE,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case 0xc3720101: { /* NVC372_CTRL_CMD_IS_MODE_POSSIBLE (IMP) */
+ /*
+ * nvidia-modeset's GPU IMP check: 2048-byte params
+ * (NVC372_CTRL_IS_MODE_POSSIBLE_PARAMS) with the mode inputs
+ * and the bIsPossible answer at byte offset 1904 (NvBool is
+ * one byte). The caller memsets the struct before filling
+ * the inputs, so echoing the request and flipping
+ * bIsPossible is coherent. The generic zero reply answered
+ * "not possible", which made every EDID mode fail with
+ * "GPU extended capability check failed" before any mode set.
+ */
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+
+ if (req.paramsSize >= 1905 &&
+ req.paramsSize <= GSPRM_MAX_SINGLE_ELEM_DATA) {
+ memcpy(rp, pp, req.paramsSize);
+ rp[1904] = 1; /* bIsPossible */
+ out_params_size = req.paramsSize;
+ } else {
+ out_params_size = 0;
+ }
+ if (gsp_sema_dbg)
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: IS_MODE_POSSIBLE in=%u -> possible (heads=%u windows=%u)",
+ req.paramsSize,
+ req.paramsSize > 5 ? pp[4] : 0,
+ req.paramsSize > 5 ? pp[5] : 0);
+ out_status = 0;
+ break;
+ }
+
case TU104_CTRL_CE_GET_FAULT_METHOD_BUFFER_SIZE:
gsp_rm_handle_ce_get_fault_method_buffer_size(ctx, sequence,
&out_status,
@@ -3306,6 +5428,32 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
"vgsp-rm: promote[%u] bufferId=%u bInitialize=%u bNonmapped=%u physAttr=0x%x pa=0x%llx va=0x%llx size=0x%llx",
i, bid, e[30], e[31], attr, pa,
eva, esz);
+ /*
+ * r820: register the driver's own statement
+ * of this kernel buffer's backing (r817
+ * Xorg GL init: the page-table walk answered
+ * a stale zero page for the pushbuffer VA
+ * while this entry named the real one; a
+ * userspace pin scanner can never cover
+ * kernel-owned buffers). See
+ * gsp_sema_service_promote_add().
+ */
+ gsp_sema_service_promote_add(eva, pa, esz,
+ attr, bid);
+ /*
+ * SESSION 153-CONTINUED-95: keep the latest
+ * MAIN (0) / PATCH (2) statement per engine
+ * for the launch-time CTX-BIND receipt (the
+ * bInitialize flag is the client's "server,
+ * initialize this buffer" request; the
+ * emulation answers OK but never installs
+ * the buffer on the engine channel - the
+ * receipt makes that visible).
+ */
+ gsp_sema_service_ctx_promote_note(engine_type,
+ bid, pa, eva,
+ esz, attr,
+ e[30]);
}
}
out_status = 0;
@@ -3371,6 +5519,149 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
break;
}
+ case 0x20800afe: { /* NV2080_CTRL_CMD_INTERNAL_INIT_USER_SHARED_DATA
+ *
+ * The guest kernel driver hands the GSP-RM the physical
+ * address of the RUSD (RM user shared data) page it
+ * allocated; on real hardware the GSP registers the
+ * memdesc and keeps the page's sequence-guarded fields
+ * (clocks, BAR1, utilisation, ...) populated, and 0
+ * de-initialises it. Nothing here performs that upkeep,
+ * and consumers treat a zeroed sequence word as "data
+ * invalid" and skip the fields, so the correct emulated
+ * behaviour is to acknowledge the registration, echo the
+ * request (the caller may read the address back) and log
+ * the address once so the page can be correlated with
+ * guest RAM during diagnosis. */
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+
+ if (req.paramsSize >= 8) {
+ u64 phys_addr;
+
+ memcpy(&phys_addr, pp, 8);
+ memcpy(rp, pp, req.paramsSize);
+ rm->rusd_phys_addr = phys_addr;
+ rm->rusd_init_seen = true;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: INIT_USER_SHARED_DATA physAddr=0x%llx (RUSD registered; fields left at sequence 0 = 'data invalid', consumers skip)",
+ phys_addr);
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ } else {
+ out_status = 0;
+ out_params_size = 0;
+ }
+ break;
+ }
+
+ case 0x2080a097: { /* client-pending engine-context query (session 75)
+ *
+ * libnvidia-eglcore issues this via NVOS54 and then
+ * POLLS the reply's params words: the request sets
+ * +0x204 = 0, and the caller re-issues the whole
+ * control every ~1.8 s while its readiness check
+ * stays negative (observed: 32-36 polls against the
+ * generic all-zero reply, then the client gives up
+ * and vkCreateDevice returns -4). The params are an
+ * in/out record block: u32 count at +0, then count
+ * 16-byte records, then +0x204, and the client reads
+ * back +0, +0x204, +0x488 and +0x48c after the call
+ * (libnvidia-eglcore.so.570.144 file offsets
+ * 0xecd45f..0xecd475). The readiness wait itself
+ * lives in the client's engine loop
+ * (0xd6ef40..0xd6f0ab polling byte +0x1b038 of the
+ * engine object, which a91ea0 recomputes from many
+ * state fields), so the reply words below are
+ * runtime-tunable for a single-cycle sweep without
+ * rebuilding: the wall2_a097_* module parameters.
+ *
+ * Reply: verbatim echo (the request records are the
+ * client's own query data) with the tunable words
+ * applied. Defaults keep the completion-style flag. */
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+
+ if (req.paramsSize >= 0x490 &&
+ req.paramsSize + GSP_RM_CTRL_HDR_SIZE <=
+ GSPRM_MAX_SINGLE_ELEM_DATA) {
+ u32 count = 0, flag;
+ static const u32 *const recp[8] = {
+ &gsp_rm_a097_rec0, &gsp_rm_a097_rec1,
+ &gsp_rm_a097_rec2, &gsp_rm_a097_rec3,
+ &gsp_rm_a097_rec4, &gsp_rm_a097_rec5,
+ &gsp_rm_a097_rec6, &gsp_rm_a097_rec7,
+ };
+ int i;
+
+ memcpy(rp, pp, req.paramsSize);
+ memcpy(&count, pp, 4);
+ memcpy(&flag, pp + 0x204, 4);
+ if (gsp_rm_a097_count != ~0u)
+ *(u32 *)(rp + 0) = gsp_rm_a097_count;
+ for (i = 0; i < 8; i++) {
+ if (*recp[i] != ~0u)
+ *(u32 *)(rp + 4 + i * 4) = *recp[i];
+ }
+ *(u32 *)(rp + 0x204) = gsp_rm_a097_flag;
+ *(u32 *)(rp + 0x488) = gsp_rm_a097_v488;
+ *(u32 *)(rp + 0x48c) = gsp_rm_a097_v48c;
+ if (gsp_sema_dbg)
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: ctrl 0x2080a097: echoed %u bytes (count=%u, req flag=%u) -> flag=%u v488=0x%x v48c=0x%x",
+ req.paramsSize, count, flag,
+ gsp_rm_a097_flag, gsp_rm_a097_v488,
+ gsp_rm_a097_v48c);
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ } else {
+ out_status = 0;
+ out_params_size = 0;
+ }
+ break;
+ }
+
+ case 0x90960106: { /* NV04_VID_HEAP_CONTROL block-index range (m112b) */
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+
+ if (req.paramsSize >= 12) {
+ memcpy(rp, pp, req.paramsSize);
+ *(u32 *)(rp + 0) = gsp_rm_9096_lo;
+ *(u32 *)(rp + 4) = gsp_rm_9096_hi;
+ if (gsp_sema_dbg)
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: ctrl 0x90960106 -> first=%u last=%u",
+ gsp_rm_9096_lo, gsp_rm_9096_hi);
+ }
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ break;
+ }
+
+ case 0x90960107: { /* NV04_VID_HEAP_CONTROL block entry by index (m112b) */
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+
+ if (req.paramsSize >= 56) {
+ memcpy(rp, pp, req.paramsSize);
+ *(u64 *)(rp + 0x00) = (u64)gsp_rm_9096_ent_addr;
+ *(u64 *)(rp + 0x08) = (u64)gsp_rm_9096_ent_size;
+ *(u32 *)(rp + 0x28) = gsp_rm_9096_ent_type;
+ *(u8 *)(rp + 0x30) = (u8)gsp_rm_9096_ent_flag;
+ if (gsp_sema_dbg)
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: ctrl 0x90960107 -> addr=0x%x size=0x%x type=%u flag=%u",
+ gsp_rm_9096_ent_addr,
+ gsp_rm_9096_ent_size,
+ gsp_rm_9096_ent_type,
+ gsp_rm_9096_ent_flag);
+ }
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ break;
+ }
+
default:
/* Unknown control command - return success with zeroed params.
* Do NOT echo the request: pure queries pass uninitialized
@@ -3393,6 +5684,52 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
out_params_size = req.paramsSize;
break;
+ case 0x20810107: { /* NV2081_BINAPI reserved-interface method 7 */
+ /*
+ * SESSION 153-CONTINUED-17: the guest GL client
+ * (libnvidia-glcore 570.144, file offset 0xFC74A6) sends this
+ * 2-byte query right after allocating its BINAPI object and
+ * CONSUMES REPLY BYTE [1] AS A FEATURE FLAG:
+ *
+ * glcore+0xFC74D9: cmpb $0x0, 0x131(%rsp)
+ * glcore+0xFC74E1: setne 0x203(%r14) ; flag = replies[1]
+ *
+ * and the flag gates 3D method emission into the command
+ * buffer:
+ *
+ * glcore+0xB5B48B: mov 0x1248(%r14),%rax
+ * cmpb $0x0,0x203(%rax)
+ * je <skip>
+ * glcore+0xB5B49B: movabs $0xf020010e66,%rdi
+ * mov %rdi,(0x80(%r14)) ; emit + advance
+ *
+ * With the historical {0,0} generic reply the flag stays 0 and
+ * the client's submission pipeline never activates (no GL
+ * channel, no pushes, black window). binapi07_rep selects the
+ * reply word (LE u16) at runtime so one boot can test the
+ * feature without a rebuild: 0x0101 = both bytes set,
+ * 0x0100 = byte[1] only. Default 0 reproduces {0,0}.
+ */
+ u8 *rp = reply + GSP_RM_CTRL_HDR_SIZE;
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+
+ if (req.paramsSize >= 2 && req.paramsSize <= 16) {
+ memcpy(rp, pp, req.paramsSize);
+ rp[0] = gsp_rm_binapi07_rep & 0xff;
+ rp[1] = (gsp_rm_binapi07_rep >> 8) & 0xff;
+ out_params_size = req.paramsSize;
+ } else {
+ out_params_size = 0;
+ }
+ if (gsp_sema_dbg)
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: BINAPI 0x20810107 reply in=%u rep=0x%04x bytes=%02x %02x",
+ req.paramsSize, gsp_rm_binapi07_rep,
+ rp[0], rp[1]);
+ out_status = 0;
+ break;
+ }
+
case TU104_CTRL_I2C_TRANSACTION:
/* I2C transactions have variable-length FINN-serialized
* params: an all-zero reply fails the driver's DeserializeUp
@@ -3409,6 +5746,27 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
break;
}
+ /*
+ * r487/r489 THE GL/VULKAN VASPACE WALL (Xorg/EGL/GZDOOM, every
+ * session to date): the reply must follow the GSP's own large-reply
+ * framing. r487 clamped the echo to fit one 64 KB record; the guest
+ * then refused the record ("Incorrect message length 65528") and,
+ * when it fit, waited for CONTINUATION records that never came
+ * ("rpcRecvPoll timedout for fn 71 continuation record
+ * (remainingSize=0x940)") - rpc.c's _issueRpcLarge consumes the
+ * first frame up to pRpc->maxRpcSize (65080) and then requires
+ * NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD frames for the rest
+ * (the GSP's own _rpcWriteContinuationEntries). The size is left
+ * unclamped here; gsp_rm_send_rm_control() frames it per that
+ * protocol. Root evidence: cmd 0x2080a0a4 (PERF_VPSTATES_INFO,
+ * paramsSize 67392, FINN-serialized) sent during Xorg VASpace
+ * construction; its failure made vmmCreateVaspace return ffff and
+ * killed GL - and, after the length was fixed, the 3D-engine
+ * selection ("Failed to select a 3D engine") until the framing was
+ * correct.
+ */
+
+rm_control_reply:
/* Build the reply header */
memcpy(reply + 0, &req.hClient, 4);
memcpy(reply + 4, &req.hObject, 4);
@@ -3426,10 +5784,25 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
req.cmd, req.hClient, req.hObject,
req.paramsSize, out_params_size,
out_status, dt_us);
+ /*
+ * CONT.405: close the beef step row with its outcome so the
+ * last completed step before the retry is unambiguous.
+ */
+ if (beef) {
+ static u32 beef_done_log;
+
+ if (beef_done_log < 128) {
+ beef_done_log++;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: beef done cmd=0x%x -> status=0x%x out=%u",
+ req.cmd, out_status,
+ out_params_size);
+ }
+ }
}
- if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_CONTROL, sequence, 0,
- reply, GSP_RM_CTRL_HDR_SIZE + out_params_size))
+ if (gsp_rm_send_rm_control(ctx, sequence, reply,
+ GSP_RM_CTRL_HDR_SIZE + out_params_size))
nv_vgpu_err(ctx->vgpu, "vgsp-rm: RM_CONTROL reply send failed cmd=0x%x",
req.cmd);
else
@@ -3439,6 +5812,87 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
}
/*
+ * r510gl: DUP_OBJECT (function 21) - NVOS55_PARAMETERS echo with status=NV_OK.
+ *
+ * The guest driver dups an object into a second client at EGL/GLX
+ * MakeCurrent time (memory and VASpace objects: the GL context's client
+ * shares the device's allocations), and the historical generic-SUCCESS
+ * default replied with an EMPTY payload - the driver's reply must carry the
+ * params struct back (rpc_dup_object_v03_00 = NVOS55_PARAMETERS
+ * {hClient, hParent, hObject, hClientSrc, hObjectSrc, flags, status}), so
+ * an empty reply failed the call and the driver tore the whole EGL session
+ * down (guest witness: 'RPC UNKNOWN(21) seq=0 len=60 -> generic SUCCESS',
+ * immediately followed by the session's FREE cascade and
+ * eglMakeCurrent -> EGL_BAD_ALLOC (0x3003), 2026-10-23 r510).
+ *
+ * The emulation does not model per-object state for these classes (controls
+ * and allocs are answered from the request), so the echo with NV_OK is the
+ * complete answer; the dup'd handle is used by later calls exactly like the
+ * source handle.
+ */
+static void gsp_rm_handle_dup_object(struct gsp_emu_context *ctx,
+ const u8 *elem, u32 sequence)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ const struct gsprm_rpc_header *hdr =
+ (const struct gsprm_rpc_header *)(elem + GSPRM_ELEM_HDR_SIZE);
+ const u8 *req = elem + GSPRM_ELEM_HDR_SIZE + sizeof(*hdr);
+ u8 params[28]; /* NVOS55_PARAMETERS_v03_00 */
+ u32 hDstClient = 0, hDstObject = 0, hSrcClient = 0, hSrcObject = 0;
+
+ if (hdr->length >= sizeof(*hdr) + sizeof(params))
+ memcpy(params, req, sizeof(params));
+ else
+ memset(params, 0, sizeof(params));
+
+ memcpy(&hDstClient, params + 0, 4);
+ memcpy(&hDstObject, params + 8, 4);
+ memcpy(&hSrcClient, params + 12, 4);
+ memcpy(&hSrcObject, params + 16, 4);
+ memset(params + 24, 0, 4); /* status = NV_OK */
+
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DUP_OBJECT hDstClient=0x%x hDstObject=0x%x hSrcClient=0x%x hSrcObject=0x%x seq=%u -> NV_OK echo",
+ hDstClient, hDstObject, hSrcClient, hSrcObject, sequence);
+
+ /*
+ * CONT.407: when the duplicated object is a published VA space, the
+ * destination client's new handle names the SAME page directory (the
+ * GL client dups X's VAS 0x10005 as 0xbfef0001 and allocates its GR
+ * channel with that hVASpace). Register the alias so the channel-
+ * scoped lookup resolves the gpfifo VA through the client's own
+ * directory instead of the newest-first global walk (r1702/r1703
+ * answered 0x1bcfff0/ap=2 because 0xbfef0001 was never known).
+ * Non-VAS dups simply return false and stay a pure handle echo.
+ */
+ if (gsp_sema_service_vaspace_alias(hDstObject, hSrcObject))
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DUP_OBJECT VAS alias h=0x%x -> src h=0x%x (gpfifo VA now resolves through the source directory)\n",
+ hDstObject, hSrcObject);
+
+ /*
+ * CONT.438: a duplicated memory object keeps the same physical base
+ * under the destination client's handle (the client may pass the dup
+ * as hUserdMemory).
+ */
+ if (gsp_rm_mem_object_alias(rm, hDstClient, hDstObject,
+ hSrcClient, hSrcObject))
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DUP_OBJECT mem-object alias h=0x%x (client 0x%x) -> src h=0x%x (client 0x%x)\n",
+ hDstObject, hDstClient, hSrcObject, hSrcClient);
+
+ /* Small delay before the reply: same reply-matching race mitigation as
+ * FREE/UNLOADING. */
+ udelay(2000);
+
+ if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_DUP_OBJECT, sequence, 0,
+ params, sizeof(params)))
+ nv_vgpu_err(ctx->vgpu, "vgsp-rm: DUP_OBJECT reply send failed");
+ else
+ rm->rpcs_replied++;
+}
+
+/*
* GSP_RM_FREE (function 10) - NVOS00_PARAMETERS echo with status=NV_OK.
*
* rpc_gsp_free: { hRoot, hObjectParent, hObjectOld, status }. Echo the
@@ -3466,6 +5920,7 @@ static void gsp_rm_handle_free(struct gsp_emu_context *ctx, const u8 *elem,
memcpy(&hObjOld, params + 8, 4);
memset(params + 12, 0, 4); /* status = NV_OK */
+ gsp_rm_mem_object_retire(rm, hRoot, hObjOld);
gsp_sema_service_channel_del(ctx->vgpu, hObjOld);
/*
@@ -3499,6 +5954,24 @@ static void gsp_rm_handle_free(struct gsp_emu_context *ctx, const u8 *elem,
"vgsp-rm: retired %u event record(s) on FREE hObjOld=0x%x hRoot=0x%x",
removed, hObjOld, hRoot);
+ /* CONT.406: if this is a GR object we noted at alloc time, name the
+ * class and the keep time. A ~0 ms keep means the alloc reply (or the
+ * client's params copy-out) was rejected; a longer keep means the
+ * client proceeded and cleaned up after a later step failed. */
+ {
+ u32 gcls = 0;
+ unsigned long gat = 0;
+
+ if (gsp_rm_grobj_retire(hObjOld, &gcls, &gat) &&
+ rm->gr3d_free_log < GSPRM_GR3D_LOG_MAX) {
+ rm->gr3d_free_log++;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: gr-obj free h=0x%x class=0x%x after %ums (client kept it)",
+ hObjOld, gcls,
+ jiffies_to_msecs(jiffies - gat));
+ }
+ }
+
if (gsp_sema_dbg)
nv_vgpu_info(ctx->vgpu, "vgsp-rm: FREE hObjOld=0x%x seq=%u cmd_read=%u stat_write=%u",
hObjOld, sequence, rm->cmd_read, rm->stat_write);
@@ -3599,6 +6072,8 @@ static void gsp_rm_handle_set_page_directory(struct gsp_emu_context *ctx,
desc.pdb = phys;
desc.num_entries = num_entries;
desc.b_vidmem = (flags & 0x3) == 0;
+ /* CONT.400: RM's own SET_PAGE_DIRECTORY is authoritative. */
+ desc.verified = true;
gsp_sema_service_vaspace_add(&desc);
/* Show the first entries of the directory: they are the ground
@@ -3685,14 +6160,18 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
return;
case GSPRM_RPC_FUNC_CONTINUATION_RECORD:
- /* CONTINUATION_RECORD: driver expects a reply. Send SUCCESS. */
- nv_vgpu_info(ctx->vgpu, "vgsp-rm: async RPC %s(%u) seq=%u len=%u -> SUCCESS reply",
- gsp_rm_rpc_name(function), function, sequence,
- hdr->length);
- if (gsp_rm_send(ctx, function, sequence, 0, NULL, 0))
- nv_vgpu_err(ctx->vgpu, "vgsp-rm: reply send failed fn=%u", function);
- else
- rm->rpcs_replied++;
+ /*
+ * r489: a CONTINUATION_RECORD from the driver is ONE-WAY data
+ * - the tail of a multi-frame request (its _issueRpcLarge
+ * sends frame 1 then continuation records). The old reply
+ * here was a guess ("driver expects a reply"); it injected an
+ * extra fn-71 record into the reply stream that the driver
+ * drained as an event ("Unexpected RPC event from GPU0: 0x47")
+ * and it corrupted the framing of our own large replies
+ * (which use fn-71 continuations the other way). Consume
+ * silently: the driver waits only for the reply to frame 1.
+ */
+ rm->rpcs_replied++;
return;
case GSPRM_RPC_FUNC_SET_GUEST_SYSTEM_INFO:
@@ -3715,6 +6194,10 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
gsp_rm_handle_free(ctx, elem, sequence);
return;
+ case GSPRM_RPC_FUNC_DUP_OBJECT:
+ gsp_rm_handle_dup_object(ctx, elem, sequence);
+ return;
+
case GSPRM_RPC_FUNC_SET_PAGE_DIRECTORY:
gsp_rm_handle_set_page_directory(ctx, elem, sequence, hdr->length);
return;
@@ -3728,6 +6211,23 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
* For vGPU, we just acknowledge success. */
nv_vgpu_info(ctx->vgpu, "vgsp-rm: RPC UPDATE_BAR_PDE(%u) seq=%u len=%u -> SUCCESS",
function, sequence, hdr->length);
+ {
+ const u8 *pl = (const u8 *)hdr + sizeof(*hdr);
+ u32 n = min_t(u32, hdr->length, 64);
+ char dump[256];
+ int o = 0;
+ u32 i;
+
+ for (i = 0; i < n; i += 4) {
+ u32 v = 0;
+
+ memcpy(&v, pl + i, 4);
+ o += snprintf(dump + o, sizeof(dump) - o,
+ "%08x ", v);
+ }
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: bar-pde payload: %s", dump);
+ }
if (gsp_rm_send(ctx, function, sequence, 0, NULL, 0))
nv_vgpu_err(ctx->vgpu, "vgsp-rm: UPDATE_BAR_PDE reply send failed");
else
@@ -3756,6 +6256,31 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
nv_vgpu_info(ctx->vgpu, "vgsp-rm: RPC %s(%u) seq=%u len=%u -> generic SUCCESS reply (unknown #%llu)",
gsp_rm_rpc_name(function), function,
sequence, hdr->length, rm->unknown_rpcs);
+ /*
+ * Bounded payload dump for the first unknown RPCs: the vGPU
+ * guest maps its client VA spaces through some RPC; the
+ * mapping data (va pages / PAs / memdesc handles) must be
+ * visible here to be captured. Never echo it back (pure
+ * queries would eat their own stack garbage).
+ */
+ if (rm->unknown_rpcs <= 96 && hdr->length) {
+ const u8 *pl = (const u8 *)hdr + sizeof(*hdr);
+ u32 n = min_t(u32, hdr->length, 64);
+ char dump[256];
+ int o = 0;
+ u32 i;
+
+ for (i = 0; i < n; i += 4) {
+ u32 v = 0;
+
+ memcpy(&v, pl + i, 4);
+ o += snprintf(dump + o, sizeof(dump) - o,
+ "%08x ", v);
+ }
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: unknown fn=%u seq=%u len=%u payload: %s",
+ function, sequence, hdr->length, dump);
+ }
if (gsp_rm_send(ctx, function, sequence, 0, NULL, 0))
nv_vgpu_err(ctx->vgpu, "vgsp-rm: reply send failed fn=%u", function);
else