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]>
186 lines
4.0 KiB
C++
186 lines
4.0 KiB
C++
// Copyright 2026 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "ProbeBroadway.h"
|
|
|
|
#include <cstdint>
|
|
#include <optional>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include "ProbeBroadwaySources.h"
|
|
|
|
extern "C" {
|
|
#include "hw0c.h"
|
|
}
|
|
|
|
namespace StarletBootstrapProbe
|
|
{
|
|
namespace
|
|
{
|
|
// The one HAL of the process: the vendored sources' functions are global, so is their HAL.
|
|
std::optional<BroadwayHal> s_hal;
|
|
} // namespace
|
|
|
|
bool BroadwayHal::Allowed(u32 address, std::string_view what)
|
|
{
|
|
++m_accesses;
|
|
if (InBenchWindow(address))
|
|
return true;
|
|
if (m_failure.empty())
|
|
{
|
|
m_failure = fmt::format("{} at {:#010x}, outside the bench window {:#010x}..{:#010x}", what,
|
|
address, BENCH_WINDOW_BASE, BENCH_WINDOW_END);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
u32 BroadwayHal::Read(u32 address)
|
|
{
|
|
return Allowed(address, "a read") ? m_memory.LoadWord(address) : 0;
|
|
}
|
|
|
|
void BroadwayHal::Write(u32 address, u32 value)
|
|
{
|
|
if (Allowed(address, "a write"))
|
|
m_memory.StoreWord(address, value);
|
|
}
|
|
|
|
void BroadwayHal::CacheLine(u32 address)
|
|
{
|
|
// The line's first word stands for it: the loader passes line-aligned window addresses.
|
|
Allowed(address & ~u32{3}, "a cache line");
|
|
}
|
|
|
|
void BroadwayHal::Hex(u32 value)
|
|
{
|
|
m_capture += fmt::format("0x{:08x}", value);
|
|
}
|
|
|
|
void BroadwayHal::Dec(u32 value)
|
|
{
|
|
m_capture += fmt::format("{}", value);
|
|
}
|
|
|
|
bool BroadwayClient::Start(BroadwayMemory& memory)
|
|
{
|
|
if (s_hal)
|
|
{
|
|
m_refusal = "a Broadway client already started in this process: the loader's state is global";
|
|
return false;
|
|
}
|
|
s_hal.emplace(memory, CLIENT_TIMEBASE_BASE);
|
|
m_started = true;
|
|
// As the loader before the jump: the window's sentinel and control lines, HW-M4-0D's area, H-1A's
|
|
// pages and record, the plan and the header's magic last.
|
|
hw0c_setup();
|
|
return true;
|
|
}
|
|
|
|
void BroadwayClient::Advance(u64 elapsed)
|
|
{
|
|
if (!m_started || m_done || Failed())
|
|
return;
|
|
s_hal->Elapse(elapsed);
|
|
++m_steps;
|
|
m_done = hw0c_step(s_hal->Timebase()) != 0;
|
|
}
|
|
|
|
bool BroadwayClient::Failed() const
|
|
{
|
|
return !m_refusal.empty() || (m_started && s_hal->Failed());
|
|
}
|
|
|
|
std::string BroadwayClient::Failure() const
|
|
{
|
|
if (!m_refusal.empty())
|
|
return m_refusal;
|
|
return m_started ? s_hal->Failure() : std::string{};
|
|
}
|
|
|
|
u64 BroadwayClient::Timebase() const
|
|
{
|
|
return m_started ? s_hal->Timebase() : 0;
|
|
}
|
|
|
|
std::string BroadwayClient::Report(std::string_view status)
|
|
{
|
|
if (!m_started || m_reported)
|
|
return {};
|
|
m_reported = true;
|
|
const size_t mark = s_hal->Capture().size();
|
|
hw0c_report();
|
|
return fmt::format("{}\n{}{}\n", status, s_hal->Capture().substr(mark), CLIENT_END);
|
|
}
|
|
|
|
std::string_view BroadwayClient::Sources()
|
|
{
|
|
return STAROS_LOADER_SHA256;
|
|
}
|
|
} // namespace StarletBootstrapProbe
|
|
|
|
// The HAL's C functions (BroadwayHAL/board.h and gecko.h, and the cache and special-register
|
|
// functions the vendored sources declare under their _HOST macros).
|
|
extern "C" {
|
|
uint32_t read32(uint32_t address)
|
|
{
|
|
return StarletBootstrapProbe::s_hal ? StarletBootstrapProbe::s_hal->Read(address) : 0;
|
|
}
|
|
|
|
void write32(uint32_t address, uint32_t value)
|
|
{
|
|
if (StarletBootstrapProbe::s_hal)
|
|
StarletBootstrapProbe::s_hal->Write(address, value);
|
|
}
|
|
|
|
void sync(void)
|
|
{
|
|
}
|
|
|
|
uint64_t timebase(void)
|
|
{
|
|
return StarletBootstrapProbe::s_hal ? StarletBootstrapProbe::s_hal->Timebase() : 0;
|
|
}
|
|
|
|
void dcbf(uint32_t address)
|
|
{
|
|
if (StarletBootstrapProbe::s_hal)
|
|
StarletBootstrapProbe::s_hal->CacheLine(address);
|
|
}
|
|
|
|
void dcbi(uint32_t address)
|
|
{
|
|
if (StarletBootstrapProbe::s_hal)
|
|
StarletBootstrapProbe::s_hal->CacheLine(address);
|
|
}
|
|
|
|
// Not a Broadway: HID0 and L2CR, which hw0c.c only reports, read as 0.
|
|
uint32_t hid0(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint32_t l2cr(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void gecko_puts(const char* text)
|
|
{
|
|
if (StarletBootstrapProbe::s_hal)
|
|
StarletBootstrapProbe::s_hal->Put(text);
|
|
}
|
|
|
|
void gecko_hex(uint32_t value)
|
|
{
|
|
if (StarletBootstrapProbe::s_hal)
|
|
StarletBootstrapProbe::s_hal->Hex(value);
|
|
}
|
|
|
|
void gecko_dec(uint32_t value)
|
|
{
|
|
if (StarletBootstrapProbe::s_hal)
|
|
StarletBootstrapProbe::s_hal->Dec(value);
|
|
}
|
|
}
|