diff options
Diffstat (limited to 'kernel/gsp_rm_emulation.c')
| -rw-r--r-- | kernel/gsp_rm_emulation.c | 154 |
1 files changed, 139 insertions, 15 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c index 0afb013..8cd82d1 100644 --- a/kernel/gsp_rm_emulation.c +++ b/kernel/gsp_rm_emulation.c @@ -452,8 +452,26 @@ static u32 gsp_rm_build_record(struct gsp_rm_emu *rm, u8 *buf, u32 buf_size, /* * Write a status queue record (1+ elements) and advance the writePtr. */ -static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, - u32 sequence, u32 result, const void *data, u32 data_len) +/* + * m171: the event burst budget is a rate over a short time window, not a + * monotonic session counter. Without the time reset a flood left the counter + * pinned at the cap and silently dropped the completion post of a client + * that only started waiting later - a minimal cudaEventSynchronize repro sat + * forever while its registrations were valid and 'completion hint rc=-22' + * repeated. Channel-completion posts get headroom over hint re-posts: a hint + * can be retried a moment later, a completion wake-up cannot. + */ +#define GSPRM_EVENT_BURST_CRIT_MAX 48 +#define GSPRM_EVENT_BURST_WINDOW (HZ) +/* Strictly shorter than the service's 0.5 s hint cadence: an equal window + * races the boundary and every later hint skips forever (observed r181: + * 'completion hint rc=-2' forever after one success and the stream client + * still stalled - the late-waiter wake-up never fired again). */ +#define GSPRM_EVENT_HINT_DEDUP (HZ / 4) + +static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function, + u32 sequence, u32 result, const void *data, + u32 data_len, bool critical) { struct gsp_rm_emu *rm = ctx->rm_emu; u8 *buf; @@ -489,13 +507,22 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, * newer record and the client aborts with "Bad sequence number". */ if (function == GSPRM_RPC_EVENT_POST_EVENT) { - if (rm->event_burst >= GSPRM_EVENT_BURST_MAX) { + u32 cap = critical ? GSPRM_EVENT_BURST_CRIT_MAX + : GSPRM_EVENT_BURST_MAX; + + /* m171: expire the budget by wall time so a long-ago + * flood can never starve a fresh completion post. */ + 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 || (rm->events_dropped % 1000) == 0) nv_vgpu_info(ctx->vgpu, - "vgsp-rm: event burst capped (%u), dropped total=%llu", - GSPRM_EVENT_BURST_MAX, + "vgsp-rm: event burst capped (%u/%u), dropped total=%llu", + rm->event_burst, cap, rm->events_dropped); mutex_unlock(&rm->stat_lock); kfree(buf); @@ -504,6 +531,7 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, rm->event_burst++; } else { rm->event_burst = 0; + rm->event_burst_at = jiffies; } record_size = gsp_rm_build_record(rm, buf, buf_size, function, @@ -589,6 +617,14 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, return 0; } +/* Non-event sends: the historical entry point, budget untouched. */ +static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, + u32 sequence, u32 result, const void *data, u32 data_len) +{ + return gsp_rm_send_flags(ctx, function, sequence, result, data, + data_len, false); +} + /* * 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 @@ -627,9 +663,10 @@ int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src) 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(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0, - payload, sizeof(payload))) + if (gsp_rm_send_flags(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0, + payload, sizeof(payload), true)) continue; + rm->events[i].last_post = jiffies; posted++; } @@ -648,9 +685,11 @@ int gsp_rm_post_channel_event(struct nv_vgpu_device *vgpu, u32 h_src) 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))) + if (gsp_rm_send_flags(ctx, GSPRM_RPC_EVENT_POST_EVENT, + 0, 0, payload, + sizeof(payload), true)) continue; + rm->events[i].last_post = jiffies; posted++; } } @@ -675,6 +714,7 @@ static int gsp_rm_post_event_idx(struct gsp_emu_context *ctx, u32 idx) { struct gsp_rm_emu *rm = ctx->rm_emu; u8 payload[32]; + int rc; if (idx >= rm->events_n || !rm->events[idx].valid) return -EINVAL; @@ -685,8 +725,11 @@ static int gsp_rm_post_event_idx(struct gsp_emu_context *ctx, u32 idx) memcpy(payload + 8, &rm->events[idx].notify_index, 4); memcpy(payload + 12, &rm->events[idx].data, 4); - return gsp_rm_send(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0, - payload, sizeof(payload)); + rc = gsp_rm_send(ctx, GSPRM_RPC_EVENT_POST_EVENT, 0, 0, + payload, sizeof(payload)); + if (!rc) + rm->events[idx].last_post = jiffies; + return rc; } int gsp_rm_force_post(struct nv_vgpu_device *vgpu, u32 idx) @@ -709,8 +752,14 @@ int gsp_rm_force_post(struct nv_vgpu_device *vgpu, u32 idx) } for (i = 0; i < rm->events_n; i++) { - int r = gsp_rm_post_event_idx(ctx, i); + int r; + /* m171: retired slots are not errors - skipping them + * keeps the hint's rc meaningful (a valid table must not + * report EINVAL because the last allocated slot is dead). */ + if (!rm->events[i].valid) + continue; + r = gsp_rm_post_event_idx(ctx, i); if (r) last = r; else @@ -725,10 +774,53 @@ int gsp_rm_force_post(struct nv_vgpu_device *vgpu, u32 idx) * guest allocates carry no source object, so a per-channel association is * not available in the ABI). Used by the service's completion hint so a * client that starts waiting after the event fired still gets a wake-up. + * + * m171: this is a HINT, and hints repeat every 0.5 s while the window is + * open. Re-posting an event that a real completion post already woke moments + * ago burns the burst budget and floods the status ring without adding a + * wake-up, so skip events posted within GSPRM_EVENT_HINT_DEDUP. A waiter + * that arrives after the edge still gets a re-post within half a second. */ int gsp_rm_post_all_events(struct nv_vgpu_device *vgpu) { - return gsp_rm_force_post(vgpu, 0xffffffffu); + struct gsp_emu_context *ctx = vgpu ? vgpu->gsp_comm_ctx : NULL; + struct gsp_rm_emu *rm; + u8 payload[32]; + u32 i; + int n = 0, skipped = 0; + + if (!ctx) + return -ENODEV; + rm = ctx->rm_emu; + if (!rm) + return -ENODEV; + + for (i = 0; i < rm->events_n; i++) { + if (!rm->events[i].valid) + 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; + rm->events[i].last_post = jiffies; + n++; + } + + if (n) + return 0; + /* -EAGAIN: valid events existed but every one was dedup-skipped + * (normal during an active post burst); -ENOENT: nothing to post. */ + return skipped ? -EAGAIN : -ENOENT; } /* @@ -3283,11 +3375,19 @@ static void gsp_rm_handle_free(struct gsp_emu_context *ctx, const u8 *elem, * client's notifier events (hSrcResource 0) linger across runs and the * fallback wake-up nudge would hit them. Also retire a client's events * when its root object is freed (hObjOld == hRoot). + * + * m176: the hEvent match must also match the owning client. Handle + * values are recycled across runs, so a late FREE of an old client's + * hEvent must never retire a new client's record with the same numeric + * handle - the new client's completion wake-up would be lost and it + * would wait forever (late-client stall class). hRoot is the freeing + * client's own handle in NVOS00_PARAMETERS. */ for (i = 0; i < rm->events_n; i++) { if (!rm->events[i].valid) continue; - if (rm->events[i].h_event == hObjOld || + if ((rm->events[i].h_event == hObjOld && + (!hRoot || rm->events[i].h_client == hRoot)) || (hObjOld && hObjOld == hRoot && rm->events[i].h_client == hRoot)) { rm->events[i].valid = false; @@ -3687,8 +3787,32 @@ void gsp_rm_emu_fini(struct gsp_emu_context *ctx) void gsp_rm_emu_reset(struct gsp_emu_context *ctx) { struct gsp_rm_emu *rm = ctx->rm_emu; + u32 i, stale; - if (!rm || !rm->parsed) + if (!rm) + return; + + /* + * m176: a reset means the emulated GSP is gone and every RM client + * with it (probe/unload, engine reset). Event records left valid from + * the previous session would be posted to with stale + * (hClient, hEvent) pairs - the guest RM asserts in CliGetEventInfo + * and drops the record - and a recycled hEvent can absorb a wake-up + * meant for the new client. Drop them regardless of parse state. + */ + stale = 0; + for (i = 0; i < GSPRM_MAX_EVENTS; i++) { + if (rm->events[i].valid) { + rm->events[i].valid = false; + stale++; + } + } + if (stale) + nv_vgpu_info(ctx->vgpu, + "vgsp-rm: reset dropped %u stale event record(s)", + stale); + + if (!rm->parsed) return; nv_vgpu_info(ctx->vgpu, "vgsp-rm: reset (rpcs=%llu replied=%llu unknown=%llu)", |
