Two findings of the review of 2df9dfc909 (StarOS H-1A, D2):
- The report reads the bench window too (hw0c_report(): HW-M4-0D's
area, and H-1A's record if it was never read), but a refused access
there did not fail the run, as one during the run does. It does now:
the failure on stderr, and the run fails.
- --cycles' bound of 2^34 was raised whenever "--broadway-h1a" appeared
among the arguments, even as the value of --ipc-script or --stage-spk
(a file of that name), the run then not in the client's mode.
ClientRequested reads the options with their grammar: the word after
--ipc-script, --stage-spk or --cycles is a value, never an option.
StarletProbeBroadwayTest: ClientRequested.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
146 lines
6.1 KiB
C++
146 lines
6.1 KiB
C++
// Copyright 2026 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "StarOSPanicCheck.h"
|
|
|
|
// StarletBootstrapProbe's --broadway-h1a: the probe stands in for Broadway with StarOS's own bench
|
|
// loader code, so that the H-1A bench (StarOS docs/architecture/h-1a.md, D-H1A-7) runs whole: its
|
|
// Starlet image here, on the ARM core; its Broadway side, StarOS's boot/wii/loader hw0c.c, hw0d.c
|
|
// and h1a.c, vendored unchanged in StarOSLoader/ and built over the host HAL of BroadwayHAL/.
|
|
//
|
|
// The client does what StarOS's loader does around the jump: hw0c_setup() before the Starlet's
|
|
// first instruction, then one hw0c_step() between each two slices, as the loader's monitor calls
|
|
// it, until it says 1; then the report, between the status and END lines main.c prints. It reaches
|
|
// PPC memory only in the bench window, by whole words; it knows nothing of the image and answers
|
|
// nothing for it: what it reads, the ARM code wrote. Dolphin's memory is coherent, so the run says
|
|
// nothing of the console's caches: it rehearses the protocol, its 3000 rounds, the capture and the
|
|
// judge, not the hardware.
|
|
namespace StarletBootstrapProbe
|
|
{
|
|
// The bench window, StarOS boot/contract/staros_hw0c.h's STAROS_HW0C_BASE and _SIZE: the only PPC
|
|
// memory the client reads or writes.
|
|
constexpr u32 BENCH_WINDOW_BASE = 0x13500000;
|
|
// Exclusive.
|
|
constexpr u32 BENCH_WINDOW_END = 0x13510000;
|
|
// Broadway's timebase runs at 243 MHz / 4, the Starlet at 243 MHz: four cycles a tick.
|
|
constexpr u64 CYCLES_PER_TIMEBASE_TICK = 4;
|
|
// The client's first timebase. Any value would do (the loader only subtracts); a nonzero one wider
|
|
// than 32 bits keeps the 64-bit arithmetic honest.
|
|
constexpr u64 CLIENT_TIMEBASE_BASE = u64{1} << 32;
|
|
// The lines StarOS's main.c prints around hw0c_report(): its status line once the Starlet is done
|
|
// or timed out, and its END line. There is no Gecko here, so nothing was dropped.
|
|
constexpr std::string_view CLIENT_STATUS_DONE = "SBL:STARLET DONE";
|
|
constexpr std::string_view CLIENT_STATUS_TIMEOUT = "SBL:TIMEOUT";
|
|
constexpr std::string_view CLIENT_END = "SBL:END GECKO DROPPED AFTER JUMP 0 BEFORE 0";
|
|
|
|
// Whether StarOS's loader would take the Starlet for done, as main.c's monitor does: the SBR1
|
|
// record's state IDLE or after, with EXI given back to Broadway. The client's mode takes
|
|
// --sbr1-record so that the record is there. The trace's own terminal line also counts; it can be
|
|
// missing, the trace holding 1 KiB, which H-1A's image fills before its IDLE line.
|
|
constexpr bool StarletDone(bool trace_terminal, u32 sbr1_state, u32 sbr1_exi_owner)
|
|
{
|
|
return trace_terminal || (sbr1_state >= SBR1_STATE_IDLE && sbr1_exi_owner == SBR1_EXI_PPC);
|
|
}
|
|
|
|
// Whether --broadway-h1a is among the probe's options (argv[2] on), read with their grammar: the
|
|
// word after --ipc-script, --stage-spk or --cycles is that option's value, never an option. It
|
|
// raises --cycles' bound, which is read where it stands, before the other options are known.
|
|
inline bool ClientRequested(int argc, const char* const* argv)
|
|
{
|
|
for (int index = 2; index < argc; ++index)
|
|
{
|
|
const std::string_view option = argv[index];
|
|
if (option == "--ipc-script" || option == "--stage-spk" || option == "--cycles")
|
|
++index;
|
|
else if (option == "--broadway-h1a")
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Whether a whole word at `address` lies in the bench window.
|
|
constexpr bool InBenchWindow(u64 address)
|
|
{
|
|
return address >= BENCH_WINDOW_BASE && address + 4 <= BENCH_WINDOW_END && address % 4 == 0;
|
|
}
|
|
|
|
// What the client reaches: PPC memory, by whole words.
|
|
class BroadwayMemory
|
|
{
|
|
public:
|
|
virtual ~BroadwayMemory() = default;
|
|
virtual u32 LoadWord(u32 address) = 0;
|
|
virtual void StoreWord(u32 address, u32 word) = 0;
|
|
};
|
|
|
|
// The HAL the vendored loader sources call (BroadwayHAL/board.h and gecko.h): the window's bounds,
|
|
// the clock and the capture. A word outside the window is never touched: the first such access
|
|
// fails the client, and a read gives 0.
|
|
class BroadwayHal
|
|
{
|
|
public:
|
|
BroadwayHal(BroadwayMemory& memory, u64 timebase) : m_memory(memory), m_timebase(timebase) {}
|
|
|
|
u32 Read(u32 address);
|
|
void Write(u32 address, u32 value);
|
|
// dcbf and dcbi: a line of the window; nothing to do in Dolphin's coherent memory.
|
|
void CacheLine(u32 address);
|
|
u64 Timebase() const { return m_timebase; }
|
|
// `cycles` Starlet cycles went by.
|
|
void Elapse(u64 cycles) { m_timebase += cycles / CYCLES_PER_TIMEBASE_TICK; }
|
|
void Put(std::string_view text) { m_capture += text; }
|
|
void Hex(u32 value);
|
|
void Dec(u32 value);
|
|
|
|
const std::string& Capture() const { return m_capture; }
|
|
u64 Accesses() const { return m_accesses; }
|
|
bool Failed() const { return !m_failure.empty(); }
|
|
const std::string& Failure() const { return m_failure; }
|
|
|
|
private:
|
|
bool Allowed(u32 address, std::string_view what);
|
|
|
|
BroadwayMemory& m_memory;
|
|
u64 m_timebase;
|
|
u64 m_accesses = 0;
|
|
std::string m_capture;
|
|
std::string m_failure;
|
|
};
|
|
|
|
// The client. The vendored sources keep their state in globals, so a process has one client, and
|
|
// it starts once.
|
|
class BroadwayClient
|
|
{
|
|
public:
|
|
// hw0c_setup() over `memory`, at CLIENT_TIMEBASE_BASE; false, with the reason in Failure(), if a
|
|
// client already started in this process.
|
|
bool Start(BroadwayMemory& memory);
|
|
// `elapsed` Starlet cycles went by: one hw0c_step() at the timebase they make, unless done.
|
|
void Advance(u64 elapsed);
|
|
bool Started() const { return m_started; }
|
|
bool Done() const { return m_done; }
|
|
bool Failed() const;
|
|
std::string Failure() const;
|
|
u64 Steps() const { return m_steps; }
|
|
u64 Timebase() const;
|
|
// The capture main.c would print from its status line on: `status`, hw0c_report()'s lines
|
|
// (hw0d_report()'s and h1a_report()'s among them), the END line. Once.
|
|
std::string Report(std::string_view status);
|
|
// The vendored sources, as built: "<file> <sha256>" lines (CMake computes them at configure).
|
|
static std::string_view Sources();
|
|
|
|
private:
|
|
bool m_started = false;
|
|
bool m_done = false;
|
|
bool m_reported = false;
|
|
u64 m_steps = 0;
|
|
std::string m_refusal;
|
|
};
|
|
} // namespace StarletBootstrapProbe
|