gc_bba_test: tell a spent id_phase from a real fault using the hardware

The ID tests guarded on g_ncra_reset_done, which is software state standing in
for hardware state.  The two come apart exactly when a build is delivered over
the wire: netboot replaces the DOL and restarts the app with the flag clear,
while the FPGA keeps running with id_phase already spent by the previous run.
The app then queries address 0, gets NCRA back -- correct behaviour -- and
reported a FAILURE for it.  That is why the ID tests would sometimes skip and
sometimes fail for the same expected condition.

Both tests now ask the hardware instead.  HIPR and NWAYS are hardwired
constants in the register file and do not depend on id_phase, so if both read
correctly the EXI path is demonstrably fine and a wrong answer at address 0 can
only be a spent id_phase -> SKIP, with "reflash FPGA" in the message.  If they
are wrong too, something real is broken and the test still FAILS.

The flag is kept only as a fast path for a reset issued during the same run,
with a comment saying it can be stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 10:17:41 +02:00
parent 2b0cbdd809
commit 5ee1b9d07b
2 changed files with 52 additions and 8 deletions
+40 -6
View File
@@ -11,6 +11,27 @@ static void msleep(u32 ms) { usleep(ms * 1000); }
/* ───────────────────────── suite 1: identity ───────────────────────── */
/* Is the EXI link demonstrably healthy apart from address 0?
*
* Needed because `id_phase` is a ONE-SHOT in the FPGA, cleared by the first
* NCRA reset, and g_ncra_reset_done only tracks resets THIS RUN of the test
* app issued. That flag is software state standing in for hardware state, and
* the two come apart as soon as a build is delivered over the wire: netboot
* replaces the DOL and restarts the app with the flag clear, while the FPGA
* keeps running with id_phase already spent by the previous run. The ID tests
* then query address 0, get NCRA back -- correct behaviour -- and report a
* FAILURE for it. Reflashing the FPGA is the only thing that restores it.
*
* HIPR and NWAYS are hardwired constants in the register file and do not
* depend on id_phase, so if BOTH read correctly the EXI path is fine and a
* wrong answer at address 0 can only be a spent id_phase. If they are wrong
* too, something real is broken and the test must still fail. */
static int iface_alive(void)
{
return bba_r8(REG_HIPR) == HIPR_PRESENT &&
bba_r8(REG_NWAYS) == NWAYS_LINKUP;
}
static int t_devid(char *m, size_t n)
{
u8 b[4];
@@ -20,8 +41,18 @@ static int t_devid(char *m, size_t n)
return 2;
}
if ((rc = bba_devid(b)) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; }
if (b[0]==0x04 && b[1]==0x02 && b[2]==0x02 && b[3]==0x00) {
snprintf(m, n, "%02X %02X %02X %02X (want 04 02 02 00)",
b[0], b[1], b[2], b[3]);
return 0;
}
if (iface_alive()) {
snprintf(m, n, "%02X %02X %02X %02X: id_phase spent, reflash FPGA",
b[0], b[1], b[2], b[3]);
return 2;
}
snprintf(m, n, "%02X %02X %02X %02X (want 04 02 02 00)", b[0], b[1], b[2], b[3]);
return (b[0]==0x04 && b[1]==0x02 && b[2]==0x02 && b[3]==0x00) ? 0 : 1;
return 1;
}
static int t_hipr(char *m, size_t n)
@@ -238,10 +269,10 @@ static int t_raw_id(char *m, size_t n)
u8 b[8];
int i, at = -1, rc;
u16 h = 0x0000;
/* Same one-shot as t_devid. Without this guard a full run reports a
* FAILURE here purely because an earlier suite issued the NCRA reset
* that clears id_phase -- address 0 then reads back NCRA, which is
* correct behaviour, not a fault. */
/* Fast path for a reset this run issued. Note this flag can be STALE
* (see iface_alive): it is clear after a netbooted update even though the
* FPGA's id_phase is long gone, which is why the result is also checked
* against iface_alive() below rather than trusted to this. */
if (g_ncra_reset_done) {
snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA");
return 2;
@@ -256,7 +287,10 @@ static int t_raw_id(char *m, size_t n)
snprintf(m, n, "%02X %02X %02X %02X %02X %02X %02X %02X | 0x04 %s%d",
b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7],
at < 0 ? "absent" : "at slot ", at < 0 ? 0 : at);
return at == 0 ? 0 : 1;
if (at == 0)
return 0;
/* Same discrimination as t_devid: a spent id_phase is not a fault. */
return iface_alive() ? 2 : 1;
}
/* ───────────────────────── suite 5: ethernet ───────────────────────── */