// Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include "Common/CommonTypes.h" // What StarletBootstrapProbe requires of a finished StarOS panic path (StarOS ADR-0014 R6). The // path sets __panic_state first, then __panic_code, then publishes the SBR1 boot record when // there is one, so the first nonzero guard can show it half done: the probe keeps running until // every condition holds. Words come through a callback, so the check is tested without a CPU. namespace StarletBootstrapProbe { // StarOS's SBR1 boot record in MEM1 (StarOS crates/boot-contract, report.rs). enum class SBR1Mode { None, // --sbr1-record: the probe creates the record as the loader does. Record, // --sbr1-absent: no record; its area must stay zero. Absent, }; constexpr u32 SBR1_BASE = 0x00200000; constexpr u32 SBR1_SIZE = 0x1000; constexpr u32 SBR1_MAGIC = 0x53425231; constexpr u32 SBR1_VERSION = 1; // Word offsets. constexpr u32 SBR1_EXI_OWNER = 0x0c; constexpr u32 SBR1_RELAY_STATUS = 0x10; constexpr u32 SBR1_STARLET_STATE = 0x18; constexpr u32 SBR1_FATAL_REASON = 0x1c; constexpr u32 SBR1_FAULT_CODE = 0x20; constexpr u32 SBR1_RING_LENGTH = 0x2c; constexpr u32 SBR1_RING_DROPPED = 0x30; // Values. constexpr u32 SBR1_EXI_PPC = 1; constexpr u32 SBR1_EXI_STARLET = 2; constexpr u32 SBR1_RELAY_JUMPING = 4; constexpr u32 SBR1_STATE_PANIC = 5; // __panic_state once the path is entered; __panic_code of a Rust panic. constexpr u32 PANIC_ENTERED = 1; constexpr u32 PANIC_CODE_RUST = 1; // The first condition of a finished panic path that does not hold, or an empty string: // - __panic_state 1 and __panic_code 1; // - with --sbr1-record: STARLET_STATE 5 (Panic), FATAL_REASON 0 and EXI_OWNER 1 (PPC); // - with --sbr1-absent: every word of the record area still zero. // read_mem1 returns the big-endian MEM1 word at an address. inline std::string UnmetPanicCondition(u32 panic_state, u32 panic_code, SBR1Mode sbr1, const std::function& read_mem1) { if (panic_state != PANIC_ENTERED) return fmt::format("__panic_state is {}, not {}", panic_state, PANIC_ENTERED); if (panic_code != PANIC_CODE_RUST) return fmt::format("__panic_code is {}, not {}", panic_code, PANIC_CODE_RUST); if (sbr1 == SBR1Mode::Record) { const struct { const char* name; u32 offset; u32 expected; } fields[] = {{"STARLET_STATE", SBR1_STARLET_STATE, SBR1_STATE_PANIC}, {"FATAL_REASON", SBR1_FATAL_REASON, 0}, {"EXI_OWNER", SBR1_EXI_OWNER, SBR1_EXI_PPC}}; for (const auto& field : fields) { const u32 value = read_mem1(SBR1_BASE + field.offset); if (value != field.expected) return fmt::format("SBR1 {} is {}, not {}", field.name, value, field.expected); } } if (sbr1 == SBR1Mode::Absent) { for (u32 offset = 0; offset < SBR1_SIZE; offset += 4) { const u32 value = read_mem1(SBR1_BASE + offset); if (value != 0) return fmt::format("SBR1 area word +{:#x} is {:#x} without a record", offset, value); } } return {}; } } // namespace StarletBootstrapProbe