summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_phys_booter.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-20 19:37:17 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-20 19:37:17 -0400
commitf99e6860e963b841baf486348e14c735ea12c010 (patch)
treeaba723c61a8298d61dd4ca4934706d6295380d9c /kernel/gsp_phys_booter.c
parentdb654ec86465713e13416071cdabc189aa9b1b94 (diff)
downloadopen-iov-f99e6860e963b841baf486348e14c735ea12c010.tar.gz
open-iov-f99e6860e963b841baf486348e14c735ea12c010.zip
sema/boot: r383 kill the blanket bounce copy-out (alias+staleness write surface) + write guards; REPLAY-CAPTURE closes the silent launch-drop hole; CE operand misses feed the pin-demand channel
- bounce copy-out now records staged_hpa[bi] per launch and skips: (a) recs whose hpa != staged slot content (alias/leftover), (b) pages the engine did not change (~955 of 968 per boot launch); the remaining changed pages re-validate via gsp_sema_gr_write_guard before the 4 KiB memcpy. Removes the blanket 968-page write-back that was the remaining boot-corruption lottery vector (guest slab freelist clobbered by a foreign page write). - new bounded WGUARD-SKIP guards: gr_write_guard (pin-first re-resolution) on copy-out, release-word zero/restore (rel_va plumbed through the submit), stale-recover-zero, counter release; svc_write_guard pin-agreement on 4/8-byte SYS/FB service writes; svc_copy SYS-dst hold on a fresh-pin contradiction. - svc_qmd_accum replay hole: a trusted replay of an unseen push now captures (REPLAY-CAPTURE) instead of silently dropping; known push -> LAUNCH-SKIP witness; capture history ring prevents double-capture. - svc_op_translate miss path emits the stalled-VA demand signal for CE operands.
Diffstat (limited to 'kernel/gsp_phys_booter.c')
-rw-r--r--kernel/gsp_phys_booter.c165
1 files changed, 143 insertions, 22 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 75cba95..4e296b9 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -17814,7 +17814,7 @@ static u32 gsp_phys_gr_in_census_log;
* again). Returns the number of pages staged.
*/
static u32 gsp_phys_gr_fill_bounce(const struct gsp_phys_gr_submit *req,
- u32 *csum_in)
+ u32 *csum_in, u64 *staged_hpa)
{
struct gsp_phys_boot_chunk *dchunk = gsp_phys_gr_data_chunk();
struct {
@@ -17839,6 +17839,14 @@ static u32 gsp_phys_gr_fill_bounce(const struct gsp_phys_gr_submit *req,
if (!dst)
continue;
memcpy(dst, src, GSP_PHYS_PAGE_SIZE);
+ /*
+ * r378: record which guest page's content now occupies this
+ * bounce slot. The copy-out may only write the slot back to
+ * that page - a rec whose reloc VA aliases another rec's slot
+ * would otherwise ship the other page's content into this
+ * guest page (the multi-writer clobber class).
+ */
+ staged_hpa[bi] = req->data[di].hpa;
gsp_phys_gr_page_spot_check(dst, src, req->data[di].va,
"gr-bounce-copy-in");
for (ci = 0; ci < GSP_PHYS_PAGE_SIZE; ci += 4)
@@ -17892,7 +17900,7 @@ static u32 gsp_phys_gr_fill_bounce(const struct gsp_phys_gr_submit *req,
* retry, so a retried launch starts from the guest inputs in both sets.
* Returns the number of pages staged.
*/
-static u32 gsp_phys_gr_fill_gap_bounce(u32 *csum_in)
+static u32 gsp_phys_gr_fill_gap_bounce(u32 *csum_in, u64 *staged_hpa)
{
struct gsp_phys_boot_chunk *dchunk = gsp_phys_gr_data_chunk();
u32 gi, copied = 0;
@@ -17905,6 +17913,9 @@ static u32 gsp_phys_gr_fill_gap_bounce(u32 *csum_in)
if (!src || !dst)
continue;
memcpy(dst, src, GSP_PHYS_PAGE_SIZE);
+ /* r378: the slot now holds THIS page's content (see the
+ * tracked-page fill above). */
+ staged_hpa[gsp_phys_gr_gap[gi].bi] = gsp_phys_gr_gap[gi].hpa;
gsp_phys_gr_page_spot_check(dst, src, gsp_phys_gr_gap[gi].va,
"gr-bounce-copy-in");
for (ci = 0; ci < GSP_PHYS_PAGE_SIZE; ci += 4)
@@ -18501,6 +18512,16 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req,
* ordered gsp-phys-work item.
*/
static u32 bounce_csum_in[GSP_PHYS_RMCALL_GR_DATA_BOUNCE_PAGES];
+ /*
+ * r378: which guest page's content each bounce slot holds this
+ * launch (set by the copy-in fills, cleared per submission). The
+ * copy-out writes a slot back only to that exact page, so a rec
+ * aliasing another rec's slot - or a slot left over from a previous
+ * launch - can never ship foreign content into a guest page.
+ * Static like bounce_csum_in: the single-inflight ordered
+ * gsp-phys-work item is the only caller.
+ */
+ static u64 bounce_staged_hpa[GSP_PHYS_RMCALL_GR_DATA_BOUNCE_PAGES];
sked_tick = 0;
sked_first = true;
guest_param_pg = NULL;
@@ -18565,6 +18586,12 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req,
*/
memset(&fault, 0, sizeof(fault));
/*
+ * r378: the bounce staging identity is per launch. A slot left over
+ * from a previous launch (or never staged this one) must not be
+ * treated as this launch's page even when the HPA matches.
+ */
+ memset(bounce_staged_hpa, 0, sizeof(bounce_staged_hpa));
+ /*
* F2: the shortened first window is part of the fast path; with
* gsp_phys_gr_retry_fast=0 the launch keeps today's exact behavior
* (the historical 5 s first window and ladder).
@@ -19585,7 +19612,8 @@ gr_page_unmapped:
ret = -EAGAIN;
goto out;
}
- dcopied = gsp_phys_gr_fill_bounce(req, bounce_csum_in);
+ dcopied = gsp_phys_gr_fill_bounce(req, bounce_csum_in,
+ bounce_staged_hpa);
pr_info("vgsp-gr: bounce copy-in: %u pages, %u skipped (pre-publish)\n",
dcopied, dfailed);
/*
@@ -19655,7 +19683,8 @@ gr_page_unmapped:
* like the block above.
*/
if (gsp_phys_gr_gap_n && gsp_phys_gr_data_chunk()->pages) {
- u32 gcopied = gsp_phys_gr_fill_gap_bounce(bounce_csum_in);
+ u32 gcopied = gsp_phys_gr_fill_gap_bounce(bounce_csum_in,
+ bounce_staged_hpa);
pr_info("vgsp-gr: gap copy-in: %u late-resolved pages, %llu word(s) fixed first=0x%llx\n",
gsp_phys_gr_gap_n, gsp_phys_gr_reloc_fixed,
@@ -20476,7 +20505,15 @@ gr_page_unmapped:
* recovered the no-execute launch (fresh ring
* entry + resched + fresh deposit).
*/
- if (qmd_rel_hpa)
+ /*
+ * r378: re-validate the guest release page
+ * before zeroing it - the HPA was resolved at
+ * submission and the retry runs seconds later.
+ */
+ if (qmd_rel_hpa &&
+ gsp_sema_gr_write_guard(req->rel_va,
+ qmd_rel_hpa,
+ "gr-rel-zero"))
WRITE_ONCE(*(u32 *)((u8 *)__va(qmd_rel_hpa) +
qmd_rel_off), 0);
if (ch_rel_hpa)
@@ -20580,8 +20617,10 @@ gr_page_unmapped:
pr_info("vgsp-gr: outcome retry %u: release words zeroed (win0/qmdwin off)\n",
nw_attempts);
}
- gsp_phys_gr_fill_bounce(req, bounce_csum_in);
- gsp_phys_gr_fill_gap_bounce(bounce_csum_in);
+ gsp_phys_gr_fill_bounce(req, bounce_csum_in,
+ bounce_staged_hpa);
+ gsp_phys_gr_fill_gap_bounce(bounce_csum_in,
+ bounce_staged_hpa);
mutex_lock(&gsp_phys_rm_lock);
ret = gsp_phys_rmcall_publish_locked(
bar0, boot_stage.rmcall_va_pb,
@@ -20797,7 +20836,14 @@ gr_page_unmapped:
if (qmd_rel_hpa) {
u8 *rp = (u8 *)__va(qmd_rel_hpa) + qmd_rel_off;
- if (READ_ONCE(*(u32 *)rp) != want_qmd) {
+ /*
+ * r378: same target re-validation as the
+ * retry zero above.
+ */
+ if (READ_ONCE(*(u32 *)rp) != want_qmd &&
+ gsp_sema_gr_write_guard(req->rel_va,
+ qmd_rel_hpa,
+ "gr-rel-restore")) {
WRITE_ONCE(*(u32 *)rp, want_qmd);
restored = true;
}
@@ -20823,7 +20869,8 @@ gr_page_unmapped:
*/
if (req->n_data && gsp_phys_gr_data_chunk()->pages) {
struct gsp_phys_boot_chunk *dchunk = gsp_phys_gr_data_chunk();
- u32 wbi, wrote = 0, changed = 0;
+ u32 wbi, wrote = 0, changed = 0, eligible = 0;
+ u32 aliased = 0, unchanged = 0, skipped = 0;
u32 chg[8] = { };
u32 outwit = 0; /* COPYWIT: bounded per-launch witness lines */
@@ -20836,9 +20883,35 @@ gr_page_unmapped:
!req->data[wbi].hpa)
continue;
src = gsp_phys_chunk_page_va(dchunk, bi);
+ if (!src)
+ continue;
+ eligible++;
+ /*
+ * r378: only the page whose content was staged into
+ * this slot this launch may be written back. A rec
+ * aliasing another rec's reloc slot would otherwise
+ * ship the other page's content into this guest page
+ * (the multi-writer clobber class), and a slot left
+ * over from a previous launch likewise.
+ */
+ if (bounce_staged_hpa[bi] != req->data[wbi].hpa) {
+ aliased++;
+ continue;
+ }
for (ci = 0; ci < GSP_PHYS_PAGE_SIZE; ci += 4)
csum += get_unaligned_le32(src + ci);
- if (csum != bounce_csum_in[bi]) {
+ if (csum == bounce_csum_in[bi]) {
+ /*
+ * r378: the engine did not touch this page,
+ * so the guest page still holds exactly this
+ * content (the slot was copied in from it).
+ * Writing it back is a no-op at best and
+ * stale-content corruption at worst - skip.
+ */
+ unchanged++;
+ continue;
+ }
+ {
u64 f8 = (u64)get_unaligned_le64(src);
/*
@@ -20921,6 +20994,20 @@ gr_page_unmapped:
chg[changed] = bi;
changed++;
}
+ /*
+ * r378: the target HPA was resolved at submission;
+ * the poll/retire/retry window is seconds wide and a
+ * guest buffer freed and re-homed in it would receive
+ * this full page through the stale target. Re-resolve
+ * the VA and skip (bounded log) on a positive
+ * disagreement.
+ */
+ if (!gsp_sema_gr_write_guard(req->data[wbi].va,
+ req->data[wbi].hpa,
+ "gr-bounce-copy-out")) {
+ skipped++;
+ continue;
+ }
memcpy((u8 *)__va(req->data[wbi].hpa), src,
GSP_PHYS_PAGE_SIZE);
gsp_phys_gr_page_spot_check(
@@ -20932,11 +21019,20 @@ gr_page_unmapped:
pr_info("vgsp: COPYAUDIT path=gr-bounce-copy-out va=0x%llx len=0x%llx done=0x%llx\n",
req->data[0].va, (u64)req->n_data << 12,
(u64)wrote << 12);
- if (wrote < req->n_data)
+ /*
+ * r378: the copy-out is now selective (staged + changed +
+ * re-validated pages only), so completeness is an accounting
+ * check over the eligible recs: every one is either written,
+ * skipped as unchanged, skipped as aliased, or refused by the
+ * write guard (logged as WGUARD-SKIP). This can only fire if
+ * a rec vanished between the filters.
+ */
+ if (wrote + unchanged + aliased + skipped < eligible)
pr_err_ratelimited("vgsp: COPY-TRUNC va=0x%llx want=0x%llx done=0x%llx path=gr-bounce-copy-out\n",
req->data[0].va,
- (u64)req->n_data << 12,
- (u64)wrote << 12);
+ (u64)eligible << 12,
+ (u64)(wrote + unchanged + aliased +
+ skipped) << 12);
/* m143b: unconditional first words of the first page of each
* 32-page relocation window after the run, so the engine's
* read/write footprint per window is provable without relying
@@ -20965,8 +21061,9 @@ gr_page_unmapped:
wc != bounce_csum_in[bi]);
}
}
- pr_info("vgsp-gr: bounce copy-out: %u pages written back, %u changed by engine (first bi: %u %u %u %u %u %u %u %u)\n",
- wrote, changed, chg[0], chg[1], chg[2], chg[3], chg[4],
+ pr_info("vgsp-gr: bounce copy-out: %u pages written back, %u changed by engine, %u unchanged-skipped, %u alias-skipped, %u stale-skipped (first bi: %u %u %u %u %u %u %u %u)\n",
+ wrote, changed, unchanged, aliased, skipped,
+ chg[0], chg[1], chg[2], chg[3], chg[4],
chg[5], chg[6], chg[7]);
}
/*
@@ -20978,6 +21075,7 @@ gr_page_unmapped:
if (gsp_phys_gr_gap_n && gsp_phys_gr_data_chunk()->pages) {
struct gsp_phys_boot_chunk *dchunk = gsp_phys_gr_data_chunk();
u32 gi, gwrote = 0, gchanged = 0;
+ u32 gunchanged = 0, gskipped = 0;
u32 gchg[4] = { };
for (gi = 0; gi < gsp_phys_gr_gap_n; gi++) {
@@ -20987,12 +21085,30 @@ gr_page_unmapped:
if (!src)
continue;
+ /*
+ * r378: same staging-identity rule as the tracked
+ * copy-out above - only the page staged into this
+ * slot may receive the slot's content.
+ */
+ if (bounce_staged_hpa[bi] != gsp_phys_gr_gap[gi].hpa) {
+ gskipped++;
+ continue;
+ }
for (ci = 0; ci < GSP_PHYS_PAGE_SIZE; ci += 4)
csum += get_unaligned_le32(src + ci);
- if (csum != bounce_csum_in[bi]) {
- if (gchanged < 4)
- gchg[gchanged] = bi;
- gchanged++;
+ if (csum == bounce_csum_in[bi]) {
+ /* Engine did not touch it: nothing to ship. */
+ gunchanged++;
+ continue;
+ }
+ if (gchanged < 4)
+ gchg[gchanged] = bi;
+ gchanged++;
+ if (!gsp_sema_gr_write_guard(gsp_phys_gr_gap[gi].va,
+ gsp_phys_gr_gap[gi].hpa,
+ "gr-gap-copy-out")) {
+ gskipped++;
+ continue;
}
memcpy((u8 *)__va(gsp_phys_gr_gap[gi].hpa), src,
GSP_PHYS_PAGE_SIZE);
@@ -21001,14 +21117,19 @@ gr_page_unmapped:
gsp_phys_gr_gap[gi].va, "gr-gap-copy-out");
gwrote++;
}
- pr_info("vgsp-gr: gap copy-out: %u pages written back, %u changed by engine (first bi: %u %u %u %u)\n",
- gwrote, gchanged, gchg[0], gchg[1], gchg[2], gchg[3]);
+ pr_info("vgsp-gr: gap copy-out: %u pages written back, %u changed by engine, %u unchanged-skipped, %u stale-skipped (first bi: %u %u %u %u)\n",
+ gwrote, gchanged, gunchanged, gskipped,
+ gchg[0], gchg[1], gchg[2], gchg[3]);
if ((u64)gsp_phys_gr_gap_n << 12 >= GSP_PHYS_GR_AUDIT_MIN)
pr_info("vgsp: COPYAUDIT path=gr-gap-copy-out va=0x%llx len=0x%llx done=0x%llx\n",
gsp_phys_gr_gap[0].va,
(u64)gsp_phys_gr_gap_n << 12,
(u64)gwrote << 12);
- if (gwrote < gsp_phys_gr_gap_n)
+ /*
+ * r378: same accounting rule as the tracked copy-out above -
+ * the intentional unchanged/stale skips are not truncation.
+ */
+ if (gwrote + gunchanged + gskipped < gsp_phys_gr_gap_n)
pr_err_ratelimited("vgsp: COPY-TRUNC va=0x%llx want=0x%llx done=0x%llx path=gr-gap-copy-out\n",
gsp_phys_gr_gap[0].va,
(u64)gsp_phys_gr_gap_n << 12,