Files
Yaya48andClaude Opus 5.5 f391ae6c85 StarletBootstrapProbe: --broadway-h1a
The probe stands in for Broadway with StarOS's own bench loader code, so
that StarOS's H-1A bench (docs/architecture/h-1a.md, D-H1A-7) runs whole:
its Starlet image on the ARM core, its Broadway side from StarOS's
boot/wii/loader hw0c.c, hw0d.c and h1a.c, vendored unchanged in
Tools/StarletBootstrapProbe/StarOSLoader (StarOS 480119d, stored -text so
their bytes stay StarOS's) with their contract headers, and built over a
host HAL in BroadwayHAL/. CMake computes the nine files' SHA-256s at
configure; the probe prints them when the mode starts.

As StarOS's loader around the jump: hw0c_setup() before the Starlet's
first instruction, one hw0c_step() between each two slices, the timebase
243 MHz / 4 from the Starlet's cycles, until it says 1; once the image's
trace is terminal and the bench is done, the trace, then the report
between main.c's status and END lines on stdout. If the budget runs out,
the report under SBL:TIMEOUT, and the run fails. The client reaches PPC
memory only in the bench window 0x13500000..0x13510000, by whole words:
the first access outside fails the run, and is not made. Dolphin's memory
is coherent: the run rehearses the protocol, the capture and the judge,
not the console's caches.

--cycles may reach 2^34 in this mode only (MAX_CLIENT_CYCLES), 2^30 as
before otherwise. --broadway-h1a takes neither --ipc-script nor
--stage-spk, whose behaviour is unchanged.

StarletProbeBroadwayTest: the window's bounds, the clock, Gecko's number
formats, the budgets, the built-in sources; and, each in a death test's
own process (the loader's state is global), the real loader code against
a scripted Starlet: 3000 rounds and a COHERENT record, no READY, no
Starlet END, a budget too short; a second client refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:00:12 +01:00

214 lines
6.9 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* HW-M4-0D's Broadway side: crates/hw-bench's diag::Probe in C, plus the
* report of the Starlet's record. Built only with -DSTAROS_HW0C
* -DSTAROS_HW0D; never run on the console without its own reviewed GO.
*
* Broadway's events stay in its own memory and are stamped with its 64-bit
* timebase (243 MHz / 4). Broadway does not read HW_TIMER: whether the boot
* path's AHB protection lets the PPC reach that register is not known
* (docs/contracts/ppc-window.md, E20). The Starlet stamps its own events with
* HW_TIMER (243 MHz / 128); READY, which both sides stamp, ties the two
* clocks together at 32 timebase ticks to one HW_TIMER tick.
*
* The Starlet's record is read at the end, from the window's diagnostic
* area, whatever the Starlet's state, as diag::Snapshot::read does: the
* generation, every word, the generation again, at most
* STAROS_HW0D_ATTEMPTS times, never an unbounded wait. A read counts only
* between two equal even generations; otherwise the record is reported
* INCOMPLETE, with the words the last attempt read. Every store is an
* aligned word (write32, or a uint32_t of this file's own memory).
*/
#include <stdint.h>
#include "board.h"
#include "gecko.h"
#include "hw0d.h"
#include "staros_hw0c.h"
#include "staros_hw0d.h"
#define AT(offset) (STAROS_HW0C_BASE + (offset))
/* Broadway's first events and its latest, as diag::Log keeps them. */
static uint32_t kinds[STAROS_HW0D_SLOTS], as[STAROS_HW0D_SLOTS], bs[STAROS_HW0D_SLOTS];
static uint32_t highs[STAROS_HW0D_SLOTS], lows[STAROS_HW0D_SLOTS];
static uint32_t count, last_kind, last_a, last_b, last_high, last_low;
/* The Starlet's area as the last read took it, STAROS_HW0D_F_MAGIC first. */
static uint32_t words[STAROS_HW0D_RAW_WORDS];
#ifdef STAROS_HW0D_HOST
/* A host's build only (the Dolphin probe's Broadway client, H-1A's D-H1A-7):
* its model of the window stands for the two cache instructions. */
void dcbf(uint32_t address);
void dcbi(uint32_t address);
#else
static inline void dcbf(uint32_t address)
{
__asm__ volatile("dcbf 0,%0" ::"r"(address) : "memory");
}
static inline void dcbi(uint32_t address)
{
__asm__ volatile("dcbi 0,%0" ::"r"(address) : "memory");
}
#endif
void hw0d_clear(void)
{
for (uint32_t at = 0; at < STAROS_HW0D_SIZE; at += 4)
write32(AT(STAROS_HW0D_BASE + at), 0);
for (uint32_t at = 0; at < STAROS_HW0D_SIZE; at += STAROS_HW0C_LINE)
dcbf(AT(STAROS_HW0D_BASE + at));
sync();
}
void hw0d_mark(uint32_t kind, uint32_t a, uint32_t b)
{
uint64_t now = timebase();
uint32_t high = (uint32_t)(now >> 32), low = (uint32_t)now;
if (count < STAROS_HW0D_SLOTS) {
kinds[count] = kind;
as[count] = a;
bs[count] = b;
highs[count] = high;
lows[count] = low;
}
last_kind = kind;
last_a = a;
last_b = b;
last_high = high;
last_low = low;
count++;
}
static uint32_t area(uint32_t offset)
{
return read32(AT(STAROS_HW0D_BASE + offset));
}
static void broadway_event(uint32_t kind, uint32_t a, uint32_t b, uint32_t high, uint32_t low)
{
gecko_hex(kind);
gecko_puts(" A ");
gecko_hex(a);
gecko_puts(" B ");
gecko_hex(b);
gecko_puts(" TB ");
gecko_hex(high);
gecko_hex(low);
gecko_puts("\n");
}
/* The word at `offset` of the area, as the last read took it. */
static uint32_t taken(uint32_t offset)
{
return words[(offset / 4u) % STAROS_HW0D_RAW_WORDS];
}
static void starlet_event(uint32_t at)
{
gecko_hex(taken(at));
gecko_puts(" A ");
gecko_hex(taken(at + 4u));
gecko_puts(" B ");
gecko_hex(taken(at + 8u));
gecko_puts(" T ");
gecko_hex(taken(at + 12u));
gecko_puts("\n");
}
/* Reads the area into `words`, at most STAROS_HW0D_ATTEMPTS times: 1 once
* both generations are equal and even. `first`, `last`: the last attempt's. */
static int read_area(uint32_t *first, uint32_t *last, uint32_t *attempts)
{
for (uint32_t attempt = 1; attempt <= STAROS_HW0D_ATTEMPTS; attempt++) {
for (uint32_t at = 0; at < STAROS_HW0D_SIZE; at += STAROS_HW0C_LINE)
dcbi(AT(STAROS_HW0D_BASE + at));
sync();
/* A sync on each side of the words: the generations frame them in the
* bus's order, not only in the program's. */
*first = area(STAROS_HW0D_F_GEN);
sync();
for (uint32_t i = 0; i < STAROS_HW0D_RAW_WORDS; i++)
words[i] = area(4u * i);
sync();
*last = area(STAROS_HW0D_F_GEN);
*attempts = attempt;
if (*first == *last && (*first & 1u) == 0)
return 1;
}
return 0;
}
void hw0d_report(void)
{
uint32_t kept = count < STAROS_HW0D_SLOTS ? count : STAROS_HW0D_SLOTS;
gecko_puts("SBL:HW0D B EVENTS ");
gecko_dec(count);
gecko_puts("\n");
for (uint32_t i = 0; i < kept; i++) {
gecko_puts("SBL:HW0D B ");
gecko_dec(i);
gecko_puts(" ");
broadway_event(kinds[i], as[i], bs[i], highs[i], lows[i]);
}
gecko_puts("SBL:HW0D B LAST ");
broadway_event(last_kind, last_a, last_b, last_high, last_low);
/* The Starlet's record: one coherent read, or INCOMPLETE and the words
* the last attempt took (they are no record then). */
uint32_t first = 0, last = 0, attempts = 0;
int coherent = read_area(&first, &last, &attempts);
gecko_puts("SBL:HW0D S GEN ");
gecko_hex(first);
gecko_puts(" ");
gecko_hex(last);
gecko_puts(" ATTEMPTS ");
gecko_dec(attempts);
gecko_puts(coherent ? " COHERENT\n" : " INCOMPLETE\n");
uint32_t magic = taken(STAROS_HW0D_F_MAGIC), version = taken(STAROS_HW0D_F_VERSION);
if (magic != STAROS_HW0D_MAGIC || version != STAROS_HW0D_VERSION) {
gecko_puts("SBL:HW0D S NONE MAGIC ");
gecko_hex(magic);
gecko_puts(" VERSION ");
gecko_hex(version);
gecko_puts("\n");
} else {
uint32_t events = taken(STAROS_HW0D_F_EVENTS);
uint32_t stored = events < STAROS_HW0D_SLOTS ? events : STAROS_HW0D_SLOTS;
gecko_puts("SBL:HW0D S START ");
gecko_hex(taken(STAROS_HW0D_F_START));
gecko_puts(" POLLS ");
gecko_dec(taken(STAROS_HW0D_F_POLLS));
gecko_puts(" NOW ");
gecko_hex(taken(STAROS_HW0D_F_NOW));
gecko_puts(" LINE ");
gecko_hex(taken(STAROS_HW0D_F_STATE));
gecko_puts(" ");
gecko_hex(taken(STAROS_HW0D_F_SCENARIO));
gecko_puts(" ");
gecko_hex(taken(STAROS_HW0D_F_ROUND));
gecko_puts(" EVENTS ");
gecko_dec(events);
gecko_puts("\n");
for (uint32_t i = 0; i < stored; i++) {
gecko_puts("SBL:HW0D S ");
gecko_dec(i);
gecko_puts(" ");
starlet_event(STAROS_HW0D_F_LOG + i * STAROS_HW0D_EVENT_BYTES);
}
gecko_puts("SBL:HW0D S LAST ");
starlet_event(STAROS_HW0D_F_LAST);
}
/* The Starlet's line, read again now: an answer seen late shows here. */
dcbi(AT(STAROS_HW0C_STARLET));
sync();
gecko_puts("SBL:HW0D B LATE STATE ");
gecko_hex(read32(AT(STAROS_HW0C_STARLET + STAROS_HW0C_C_STATE)));
gecko_puts(" SCENARIO ");
gecko_hex(read32(AT(STAROS_HW0C_STARLET + STAROS_HW0C_C_SCENARIO)));
gecko_puts(" ROUND ");
gecko_hex(read32(AT(STAROS_HW0C_STARLET + STAROS_HW0C_C_ROUND)));
gecko_puts("\nSBL:HW0D END\n");
}