Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9193539fb | ||
|
|
61ebfffa97 | ||
|
|
ba86a7486c | ||
|
|
3d12571127 | ||
|
|
ba5b6de0d0 | ||
|
|
76a133f3a4 | ||
|
|
81ebec368b | ||
|
|
3ce435786c | ||
|
|
09fba9bbb5 | ||
|
|
cd8c8cedca | ||
|
|
78738093ec |
No files matched your search
@@ -9,7 +9,13 @@ Source/Core/Core/Config/MainSettings.cpp -text
|
||||
Source/Core/Core/Config/MainSettings.h -text
|
||||
Source/Core/Core/ConfigManager.cpp -text
|
||||
Source/Core/Core/HW/HW.cpp -text
|
||||
Source/Core/Core/HW/WII_IPC.cpp -text
|
||||
Source/Core/Core/HW/WII_IPC.h -text
|
||||
Source/Core/Core/PowerPC/MMU.cpp -text
|
||||
Source/Core/Core/IOS/Starlet/ARMCore.cpp -text
|
||||
Source/Core/Core/IOS/Starlet/ARMCore.h -text
|
||||
Source/Core/Core/IOS/Starlet/ARMJitX64.cpp -text
|
||||
Source/Core/Core/IOS/Starlet/ARMJitX64.h -text
|
||||
Source/Core/Core/IOS/Starlet/BootstrapELF.cpp -text
|
||||
Source/Core/Core/IOS/Starlet/BootstrapELF.h -text
|
||||
Source/Core/Core/IOS/Starlet/Starlet.cpp -text
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#include "Core/HW/WII_IPC.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
@@ -10,6 +14,7 @@
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/Starlet/Starlet.h"
|
||||
@@ -142,6 +147,149 @@ void WiiIPC::Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// StarletOS phase 11 (proposal L2): with DOLPHIN_IPC_TRACE=1 while the emulated Starlet runs
|
||||
// (the original IOS or a bootstrap ELF), each IPC request Broadway posts (X1) and each reply
|
||||
// Starlet posts (Y1) is logged with its metadata only: the command, the descriptor and the five
|
||||
// argument words (an OPEN's path), then the reply's result. Proposal M1e adds the paths of the
|
||||
// filesystem IOCTLs. No other buffer content is read.
|
||||
bool IPCTraceEnabled()
|
||||
{
|
||||
static const bool enabled = [] {
|
||||
const char* value = std::getenv("DOLPHIN_IPC_TRACE");
|
||||
return value != nullptr && value[0] == '1';
|
||||
}();
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// A NUL-terminated absolute path of printable bytes in a 64-byte field, or "" otherwise.
|
||||
std::string TracePath(Memory::MemoryManager& memory, u32 address)
|
||||
{
|
||||
if (memory.GetPointerForRange(address, 64) == nullptr)
|
||||
return {};
|
||||
const std::string path = memory.GetString(address, 64);
|
||||
if (path.empty() || path.size() >= 64 || path[0] != '/' ||
|
||||
!std::ranges::all_of(path, [](char c) { return c >= 0x20 && c < 0x7f; }))
|
||||
return {};
|
||||
return path;
|
||||
}
|
||||
|
||||
// The paths a filesystem request names, by its input shape: a 64-byte path (DELETE, GET_ATTR),
|
||||
// a 76-byte attribute record with the path at +6 (CREATE_DIR, CREATE_FILE, SET_ATTR), two
|
||||
// 64-byte paths (RENAME), or an IOCTLV whose first input vector is a 64-byte path (READ_DIR,
|
||||
// GET_USAGE). " F -" when the field holds no valid path.
|
||||
std::string TraceFilesystemPaths(Memory::MemoryManager& memory, u32 command, const u32* arg)
|
||||
{
|
||||
const auto field = [&memory](u32 address) {
|
||||
const std::string path = TracePath(memory, address);
|
||||
return path.empty() ? std::string("-") : path;
|
||||
};
|
||||
if (command == 6)
|
||||
{
|
||||
if (arg[2] == 0x40)
|
||||
return " F " + field(arg[1]);
|
||||
if (arg[2] == 0x4c)
|
||||
return " F " + field(arg[1] + 6);
|
||||
if (arg[2] == 0x80)
|
||||
return " F " + field(arg[1]) + " > " + field(arg[1] + 0x40);
|
||||
}
|
||||
else if (command == 7 && arg[1] != 0 && memory.GetPointerForRange(arg[3], 8) != nullptr &&
|
||||
memory.Read_U32(arg[3] + 4) == 0x40)
|
||||
{
|
||||
return " F " + field(memory.Read_U32(arg[3]));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Phase 11 J1: the shutdown request whose reply opens the hardware window, and the window's
|
||||
// access count.
|
||||
u32 s_hardware_trigger = 0;
|
||||
u32 s_hardware_accesses = 0;
|
||||
constexpr u32 HARDWARE_WINDOW_LIMIT = 20000;
|
||||
|
||||
void CloseHardwareWindow(const char* why)
|
||||
{
|
||||
if (!g_starletos_hardware_window)
|
||||
return;
|
||||
g_starletos_hardware_window = false;
|
||||
NOTICE_LOG_FMT(WII_IPC, "HWTRACE close {} after {} accesses", why, s_hardware_accesses);
|
||||
}
|
||||
|
||||
void TraceIPCRequest(Core::System& system, u32 address)
|
||||
{
|
||||
if (StarletOSHardwareTraceRequested())
|
||||
CloseHardwareWindow("at the next request");
|
||||
auto& memory = system.GetMemory();
|
||||
if (memory.GetPointerForRange(address, 0x20) == nullptr)
|
||||
{
|
||||
NOTICE_LOG_FMT(WII_IPC, "IPCTRACE RQ {:08x} unreadable", address);
|
||||
return;
|
||||
}
|
||||
const u32 command = memory.Read_U32(address);
|
||||
const u32 fd = memory.Read_U32(address + 8);
|
||||
u32 arg[5];
|
||||
for (u32 i = 0; i < 5; ++i)
|
||||
arg[i] = memory.Read_U32(address + 0xc + 4 * i);
|
||||
std::string path;
|
||||
if (command == 1 && memory.GetPointerForRange(arg[0], 64) != nullptr)
|
||||
path = " P " + memory.GetString(arg[0], 64);
|
||||
else
|
||||
path = TraceFilesystemPaths(memory, command, arg);
|
||||
NOTICE_LOG_FMT(WII_IPC, "IPCTRACE RQ {:08x} C {} FD {} A {:08x} {:08x} {:08x} {:08x} {:08x}{}",
|
||||
address, command, static_cast<s32>(fd), arg[0], arg[1], arg[2], arg[3], arg[4],
|
||||
path);
|
||||
if (StarletOSHardwareTraceRequested() && command == 6 && arg[0] == 0x28 && arg[2] == 0x20 &&
|
||||
arg[4] == 0x20)
|
||||
{
|
||||
s_hardware_trigger = address;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceIPCReply(Core::System& system, u32 address)
|
||||
{
|
||||
auto& memory = system.GetMemory();
|
||||
if (memory.GetPointerForRange(address, 0x10) == nullptr)
|
||||
{
|
||||
NOTICE_LOG_FMT(WII_IPC, "IPCTRACE RP {:08x} unreadable", address);
|
||||
return;
|
||||
}
|
||||
NOTICE_LOG_FMT(WII_IPC, "IPCTRACE RP {:08x} C {} RC {}", address, memory.Read_U32(address + 8),
|
||||
static_cast<s32>(memory.Read_U32(address + 4)));
|
||||
if (StarletOSHardwareTraceRequested() && s_hardware_trigger != 0 &&
|
||||
address == s_hardware_trigger)
|
||||
{
|
||||
s_hardware_trigger = 0;
|
||||
s_hardware_accesses = 0;
|
||||
g_starletos_hardware_window = true;
|
||||
NOTICE_LOG_FMT(WII_IPC, "HWTRACE open at the reply {:08x}", address);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool g_starletos_hardware_window = false;
|
||||
|
||||
bool StarletOSHardwareTraceRequested()
|
||||
{
|
||||
static const bool requested = [] {
|
||||
const char* value = std::getenv("DOLPHIN_HW_WINDOW");
|
||||
return IPCTraceEnabled() && value != nullptr && value[0] == '1';
|
||||
}();
|
||||
return requested;
|
||||
}
|
||||
|
||||
void TraceStarletOSHardwareAccess(Core::System& system, bool write, u32 address, u32 size,
|
||||
u64 value)
|
||||
{
|
||||
if (++s_hardware_accesses > HARDWARE_WINDOW_LIMIT)
|
||||
{
|
||||
CloseHardwareWindow("at the access limit");
|
||||
return;
|
||||
}
|
||||
NOTICE_LOG_FMT(WII_IPC, "HWTRACE {} {:08x}/{} {:0{}x} PC {:08x}", write ? 'W' : 'R', address,
|
||||
size, value, size * 2, system.GetPPCState().pc);
|
||||
}
|
||||
|
||||
void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||
{
|
||||
// Both IPC message registers are general-purpose read/write registers on
|
||||
@@ -168,6 +316,8 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||
wii_ipc.m_ppc_irq_flags |= INT_CAUSE_IPC_BROADWAY;
|
||||
if (wii_ipc.m_ctrl.X1)
|
||||
{
|
||||
if (system.GetStarlet() && IPCTraceEnabled() && (val >> 0 & 1))
|
||||
TraceIPCRequest(system, wii_ipc.m_ppc_msg);
|
||||
if (system.GetStarlet())
|
||||
wii_ipc.m_arm_irq_flags |= INT_CAUSE_IPC_STARLET;
|
||||
else if (system.GetIOS())
|
||||
@@ -386,6 +536,9 @@ void WiiIPC::WriteStarletRegister(u32 offset, u32 value)
|
||||
break;
|
||||
case IPC_ARMCTRL:
|
||||
m_ctrl.arm(value);
|
||||
// StarletOS phase 11: Y1 (bit 0) posts the reply whose pointer is in ARMMSG.
|
||||
if (IPCTraceEnabled() && (value & 1))
|
||||
TraceIPCReply(m_system, m_arm_msg);
|
||||
break;
|
||||
case PPC_IRQFLAG:
|
||||
m_ppc_irq_flags &= ~value;
|
||||
|
||||
@@ -123,6 +123,14 @@ struct CtrlRegister
|
||||
}
|
||||
};
|
||||
|
||||
// StarletOS phase 11 (J1), emulator only: with DOLPHIN_IPC_TRACE=1 and DOLPHIN_HW_WINDOW=1,
|
||||
// Broadway's hardware register accesses (MMIO) are logged from the reply to a WiiConnect24
|
||||
// shutdown request (IOCTL 0x28, 32-byte input and output) until Broadway's next IPC request.
|
||||
bool StarletOSHardwareTraceRequested();
|
||||
extern bool g_starletos_hardware_window;
|
||||
void TraceStarletOSHardwareAccess(Core::System& system, bool write, u32 address, u32 size,
|
||||
u64 value);
|
||||
|
||||
class WiiIPC
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#if defined(_M_X86_64)
|
||||
// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a
|
||||
@@ -36,9 +37,24 @@ constexpr u32 ModeValue(ARMCore::Mode mode)
|
||||
ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory)
|
||||
: m_bus(bus), m_profile_jit_memory(profile_jit_memory)
|
||||
{
|
||||
m_watch_ranges = m_bus.GetWatchRanges();
|
||||
Reset();
|
||||
}
|
||||
|
||||
void ARMCore::TraceWatchedBlockTransfer(u32 address, u32 size, bool load) const
|
||||
{
|
||||
// StarletOS phase 11 (H1): the virtual range of a register-list transfer and the physical
|
||||
// address the interpreter translates its first word to. A difference shows a transfer that
|
||||
// does not read or write where the program addressed it.
|
||||
const bool overlaps = std::ranges::any_of(m_watch_ranges, [&](const auto& range) {
|
||||
return u64{address} < u64{range.first} + range.second && u64{address} + size > range.first;
|
||||
});
|
||||
if (!overlaps)
|
||||
return;
|
||||
NOTICE_LOG_FMT(IOS, "REVWATCH {} {:08x}->{:08x}/{} PC {:08x}", load ? "LDM" : "STM", address,
|
||||
TranslateVirtualAddress(address), size, m_instruction_address);
|
||||
}
|
||||
|
||||
ARMCore::~ARMCore() = default;
|
||||
|
||||
void ARMCore::SetJitEnabled(bool enabled)
|
||||
@@ -1407,6 +1423,16 @@ void ARMCore::ExecuteARM(u32 instruction)
|
||||
return;
|
||||
}
|
||||
|
||||
// ARMv5TE enhanced DSP instructions (ARM Architecture Reference Manual, ARMv5TE): SMLAxy,
|
||||
// SMLAWy, SMULWy, SMLALxy, SMULxy, then QADD, QSUB, QDADD, QDSUB. They sit in the
|
||||
// data-processing space with a compare opcode and S clear, which ARMv5 reserves for such
|
||||
// miscellaneous instructions; decoded as data processing they would write no register.
|
||||
if ((instruction & 0x0f900090) == 0x01000080 || (instruction & 0x0f900ff0) == 0x01000050)
|
||||
{
|
||||
ExecuteEnhancedDSP(instruction);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((instruction & 0x0f8000f0) == 0x00800090)
|
||||
{
|
||||
ExecuteMultiplyLong(instruction);
|
||||
@@ -2252,6 +2278,8 @@ void ARMCore::ExecuteBlockDataTransfer(u32 instruction)
|
||||
address = preindex ? base + 4 : base;
|
||||
else
|
||||
address = preindex ? base - count * 4 : base - (count - 1) * 4;
|
||||
if (!m_watch_ranges.empty())
|
||||
TraceWatchedBlockTransfer(address, count * 4, load);
|
||||
|
||||
u32 loaded_pc = 0;
|
||||
bool pc_loaded = false;
|
||||
@@ -2309,6 +2337,87 @@ void ARMCore::ExecuteMultiply(u32 instruction)
|
||||
SetNZ(result);
|
||||
}
|
||||
|
||||
void ARMCore::ExecuteEnhancedDSP(u32 instruction)
|
||||
{
|
||||
// StarletOS phase 11 (I1). The ARM926EJ-S implements these; the Q flag (CPSR bit 27) is sticky
|
||||
// and set on signed overflow of an accumulation or on saturation.
|
||||
const auto half = [](u32 value, bool top) {
|
||||
return static_cast<s64>(static_cast<s16>(top ? value >> 16 : value));
|
||||
};
|
||||
const auto saturate = [this](s64 value) {
|
||||
if (value > std::numeric_limits<s32>::max())
|
||||
{
|
||||
m_cpsr |= CPSR_Q;
|
||||
return static_cast<u32>(std::numeric_limits<s32>::max());
|
||||
}
|
||||
if (value < std::numeric_limits<s32>::min())
|
||||
{
|
||||
m_cpsr |= CPSR_Q;
|
||||
return static_cast<u32>(std::numeric_limits<s32>::min());
|
||||
}
|
||||
return static_cast<u32>(static_cast<s32>(value));
|
||||
};
|
||||
const auto accumulate = [this](s64 sum) {
|
||||
if (sum != static_cast<s32>(sum))
|
||||
m_cpsr |= CPSR_Q;
|
||||
return static_cast<u32>(static_cast<s32>(sum));
|
||||
};
|
||||
const u32 op = (instruction >> 21) & 3;
|
||||
const u32 rm = instruction & 0xf;
|
||||
|
||||
if ((instruction & 0xf0) == 0x50)
|
||||
{
|
||||
// QADD (0), QSUB (1), QDADD (2), QDSUB (3): Rd = sat(Rm +/- [sat(2 *)] Rn).
|
||||
const u32 rn = (instruction >> 16) & 0xf;
|
||||
const u32 rd = (instruction >> 12) & 0xf;
|
||||
const s64 m = static_cast<s32>(ReadRegisterOperand(rm));
|
||||
s64 n = static_cast<s32>(ReadRegisterOperand(rn));
|
||||
if ((op & 2) != 0)
|
||||
n = static_cast<s32>(saturate(2 * n));
|
||||
m_registers[rd] = saturate((op & 1) != 0 ? m - n : m + n);
|
||||
return;
|
||||
}
|
||||
|
||||
// SMLAxy (0), SMLAWy/SMULWy (1), SMLALxy (2), SMULxy (3). Rd (RdHi) is bits 19-16, Rn (RdLo)
|
||||
// bits 15-12, Rs bits 11-8; x (bit 5) picks Rm's half, y (bit 6) Rs's half.
|
||||
const u32 rd = (instruction >> 16) & 0xf;
|
||||
const u32 rn = (instruction >> 12) & 0xf;
|
||||
const u32 rs = (instruction >> 8) & 0xf;
|
||||
const bool x = (instruction & (1U << 5)) != 0;
|
||||
const bool y = (instruction & (1U << 6)) != 0;
|
||||
const u32 m = ReadRegisterOperand(rm);
|
||||
const u32 s = ReadRegisterOperand(rs);
|
||||
switch (op)
|
||||
{
|
||||
case 0:
|
||||
m_registers[rd] =
|
||||
accumulate(half(m, x) * half(s, y) + static_cast<s32>(ReadRegisterOperand(rn)));
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
// The top 32 bits of the 48-bit product of Rm and a halfword of Rs.
|
||||
const s64 product = (static_cast<s64>(static_cast<s32>(m)) * half(s, y)) >> 16;
|
||||
if (x) // SMULWy
|
||||
m_registers[rd] = static_cast<u32>(static_cast<s32>(product));
|
||||
else // SMLAWy
|
||||
m_registers[rd] = accumulate(product + static_cast<s32>(ReadRegisterOperand(rn)));
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
const u64 accumulator =
|
||||
(static_cast<u64>(ReadRegisterOperand(rd)) << 32) | ReadRegisterOperand(rn);
|
||||
const u64 result = accumulator + static_cast<u64>(half(m, x) * half(s, y));
|
||||
m_registers[rn] = static_cast<u32>(result);
|
||||
m_registers[rd] = static_cast<u32>(result >> 32);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_registers[rd] = static_cast<u32>(static_cast<s32>(half(m, x) * half(s, y)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ARMCore::ExecuteMultiplyLong(u32 instruction)
|
||||
{
|
||||
const bool signed_multiply = (instruction & (1U << 22)) != 0;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
@@ -59,6 +60,9 @@ public:
|
||||
// Returns Dolphin's 4 GiB physical fastmem view when this bus is backed by Wii RAM. Device,
|
||||
// boot-ROM and SRAM addresses must still use the bus callbacks.
|
||||
virtual u8* GetFastmemBase() const { return nullptr; }
|
||||
// StarletOS phase 11 (F1): physical (start, size) ranges whose accesses must stay on the bus
|
||||
// even under fastmem, so a write watch sees them with their instruction address.
|
||||
virtual std::vector<std::pair<u32, u32>> GetWatchRanges() const { return {}; }
|
||||
// Optional direct Starlet SRAM view. The two state pointers let generated code preserve the
|
||||
// boot0 overlay and SRAM A/B split mapping exactly while avoiding a host call for ordinary SRAM.
|
||||
virtual u8* GetFastmemSRAMBase() const { return nullptr; }
|
||||
@@ -125,6 +129,8 @@ public:
|
||||
u32 GetRegister(size_t index) const;
|
||||
void SetRegister(size_t index, u32 value);
|
||||
u32 GetCPSR() const { return m_cpsr; }
|
||||
// The address of the instruction being executed (interpreter).
|
||||
u32 GetInstructionAddress() const { return m_instruction_address; }
|
||||
void SetCPSR(u32 value);
|
||||
Mode GetMode() const;
|
||||
|
||||
@@ -305,8 +311,12 @@ private:
|
||||
void ExecuteSingleDataTransfer(u32 instruction);
|
||||
void ExecuteHalfwordDataTransfer(u32 instruction);
|
||||
void ExecuteBlockDataTransfer(u32 instruction);
|
||||
// Phase 11 H1: a register-list transfer overlapping a watched range, with its translation.
|
||||
void TraceWatchedBlockTransfer(u32 address, u32 size, bool load) const;
|
||||
void ExecuteMultiply(u32 instruction);
|
||||
void ExecuteMultiplyLong(u32 instruction);
|
||||
// ARMv5TE signed halfword multiplies and saturating arithmetic.
|
||||
void ExecuteEnhancedDSP(u32 instruction);
|
||||
void ExecuteBranch(u32 instruction);
|
||||
void ExecuteCoprocessor(u32 instruction);
|
||||
void ExecutePSRTransfer(u32 instruction);
|
||||
@@ -363,6 +373,8 @@ private:
|
||||
u32 m_memory_poll_address = 0;
|
||||
bool m_pc_written = false;
|
||||
u32 m_instruction_address = 0;
|
||||
// Phase 11 H1: the bus's watched ranges (REV_WATCH), empty when off.
|
||||
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
||||
u32 m_last_undefined_instruction = 0;
|
||||
u64 m_executed_instructions = 0;
|
||||
u64 m_memory_poll_entry_count = 0;
|
||||
|
||||
@@ -56,6 +56,7 @@ SlowMemoryRegion ClassifySlowMemoryAddress(u32 address)
|
||||
ARMJitX64::ARMJitX64(ARMCore& core) : m_core(core)
|
||||
{
|
||||
m_fastmem_base = m_core.m_bus.GetFastmemBase();
|
||||
m_watch_ranges = m_core.m_bus.GetWatchRanges();
|
||||
// SRAM is a plain Hollywood memory array just like MEM1/MEM2. The generated address checks
|
||||
// below still side-exit for boot0 overlays and the unmapped split-window gap, so only actual
|
||||
// SRAM reaches this pointer; MMIO and ROM writes retain the exact bus path.
|
||||
@@ -1163,7 +1164,9 @@ bool ARMJitX64::CanEmitARMDataProcessing(u32 instruction) const
|
||||
if ((instruction & 0x0fff0ff0) == 0x016f0f10 || (instruction & 0x0f8000f0) == 0x00800090 ||
|
||||
(instruction & 0x0fc000f0) == 0x00000090 || (instruction & 0x0fb00ff0) == 0x01000090 ||
|
||||
(instruction & 0x0fbf0fff) == 0x010f0000 || (instruction & 0x0fb0fff0) == 0x0120f000 ||
|
||||
(instruction & 0x0fb0f000) == 0x0320f000 || (instruction & 0x0e000090) == 0x00000090)
|
||||
(instruction & 0x0fb0f000) == 0x0320f000 || (instruction & 0x0e000090) == 0x00000090 ||
|
||||
// ARMv5TE enhanced DSP multiplies and saturating arithmetic: interpreter (StarletOS I1).
|
||||
(instruction & 0x0f900090) == 0x01000080 || (instruction & 0x0f900ff0) == 0x01000050)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1475,6 +1478,15 @@ bool ARMJitX64::EmitARMBlockTransfer(u32 instruction, u32 address, bool* termina
|
||||
}
|
||||
|
||||
std::vector<FixupBranch> slow_paths;
|
||||
// StarletOS phase 11 (H1): a transfer whose virtual range overlaps a watched range runs through
|
||||
// the architectural helper, so the interpreter logs its translation.
|
||||
for (const auto& [start, size] : m_watch_ranges)
|
||||
{
|
||||
MOV(32, R(EDX), R(EAX));
|
||||
SUB(32, R(EDX), Imm32(start - (range_size - 1)));
|
||||
CMP(32, R(EDX), Imm32(size + range_size - 1));
|
||||
slow_paths.push_back(J_CC(CC_B, Jump::Near));
|
||||
}
|
||||
if ((m_core.m_cp15.control & 1U) != 0)
|
||||
{
|
||||
MOV(32, R(EDX), R(EAX));
|
||||
@@ -2255,6 +2267,16 @@ void ARMJitX64::EmitFastmemAddress(std::vector<FixupBranch>* slow_paths, u32 acc
|
||||
slow_paths->push_back(J_CC(CC_NZ, Jump::Near));
|
||||
}
|
||||
|
||||
// StarletOS phase 11 (F1): an access overlapping a watched range takes the bus slow path.
|
||||
// Overlap: start - (range_size - 1) <= address < start + size, as one unsigned comparison.
|
||||
for (const auto& [start, size] : m_watch_ranges)
|
||||
{
|
||||
MOV(32, R(EDX), R(EAX));
|
||||
SUB(32, R(EDX), Imm32(start - (range_size - 1)));
|
||||
CMP(32, R(EDX), Imm32(size + range_size - 1));
|
||||
slow_paths->push_back(J_CC(CC_B, Jump::Near));
|
||||
}
|
||||
|
||||
// Return the resolved host pointer in R11. Wii RAM uses Dolphin's 4 GiB fastmem view.
|
||||
CMP(32, R(EAX), Imm32(Memory::MEM1_SIZE_RETAIL - range_size));
|
||||
FixupBranch in_mem1 = J_CC(CC_BE, Jump::Near);
|
||||
@@ -2384,6 +2406,9 @@ void ARMJitX64::EmitThumbMemorySlowPath(const std::vector<FixupBranch>& slow_pat
|
||||
{
|
||||
for (const FixupBranch& slow_path : slow_paths)
|
||||
SetJumpTarget(slow_path);
|
||||
// Phase 11 F1: the bus write below reports this instruction to the write watch.
|
||||
if (!m_watch_ranges.empty())
|
||||
MOV(32, MInstructionAddress(), Imm32(address));
|
||||
|
||||
// IOS Thumb code repeatedly accesses SRAM and MMIO. These instructions are already decoded:
|
||||
// perform the exact bus transaction without re-entering ExecuteThumb and the block dispatcher.
|
||||
@@ -2478,6 +2503,9 @@ void ARMJitX64::EmitARMMemorySlowPath(const std::vector<FixupBranch>& slow_paths
|
||||
{
|
||||
for (const FixupBranch& slow_path : slow_paths)
|
||||
SetJumpTarget(slow_path);
|
||||
// Phase 11 F1: the bus write below reports this instruction to the write watch.
|
||||
if (!m_watch_ranges.empty())
|
||||
MOV(32, MInstructionAddress(), Imm32(address));
|
||||
|
||||
// A normal ARM single-data transfer that misses RAM/SRAM fastmem is still a fully decoded
|
||||
// instruction. Execute only its physical bus transaction here and stay inside the translated
|
||||
|
||||
@@ -250,6 +250,8 @@ private:
|
||||
std::array<u8, FAST_ENTRY_SET_COUNT> m_fast_entry_next_victim{};
|
||||
std::array<FastTranslationEntry, FAST_TRANSLATION_ENTRY_COUNT> m_fast_translations{};
|
||||
u8* m_fastmem_base = nullptr;
|
||||
// Phase 11 F1: watched physical ranges, routed to the bus slow path.
|
||||
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
||||
u8* m_sram_base = nullptr;
|
||||
const bool* m_boot0_mapped = nullptr;
|
||||
const bool* m_sram_split_mode = nullptr;
|
||||
|
||||
@@ -128,6 +128,17 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
|
||||
}
|
||||
INFO_LOG_FMT(IOS, "Starlet bootstrap keys attached");
|
||||
}
|
||||
// StarletOS phase 11 (M1), emulator only: the emulated Wii Remotes for a Revolution session,
|
||||
// with the identity paired in the attached NAND. Off unless asked for.
|
||||
if (const char* wiimotes = std::getenv("REV_BOOTSTRAP_WIIMOTE"); wiimotes && *wiimotes == '1')
|
||||
{
|
||||
if (!m_memory->EnableBootstrapWiimotes(error))
|
||||
{
|
||||
m_memory.reset();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG_FMT(IOS, "Starlet bootstrap emulated Wii Remotes enabled");
|
||||
}
|
||||
}
|
||||
// ARMCore owns generation-tagged software TLB and instruction caches, so
|
||||
// construct it only after the complete physical bus exists.
|
||||
@@ -139,6 +150,7 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
|
||||
m_core->SetJitEnabled(false);
|
||||
#endif
|
||||
m_core->Reset(m_bootstrap ? m_bootstrap->entry : StarletMemory::BOOT_ROM_BASE);
|
||||
m_memory->SetWatchCore(m_core.get());
|
||||
if (m_bootstrap)
|
||||
m_core->SetBigEndian(true);
|
||||
m_next_jit_diagnostic_instruction = 25'000'000;
|
||||
|
||||
@@ -462,6 +462,153 @@ std::string PathInDirectory(const std::string& directory, const char* filename)
|
||||
|
||||
StarletMemory::StarletMemory(Core::System& system) : m_system(system)
|
||||
{
|
||||
// StarletOS phase 11 (F1): REV_WATCH=start:size[,start:size...], hexadecimal.
|
||||
if (const char* watch = std::getenv("REV_WATCH"); watch && *watch)
|
||||
{
|
||||
for (const std::string& range : SplitString(watch, ','))
|
||||
{
|
||||
const std::vector<std::string> parts = SplitString(range, ':');
|
||||
u32 start = 0;
|
||||
u32 size = 0;
|
||||
if (parts.size() == 2 && TryParse(parts[0], &start) && TryParse(parts[1], &size) && size != 0)
|
||||
m_watch_ranges.emplace_back(start, size);
|
||||
else
|
||||
ERROR_LOG_FMT(IOS, "REV_WATCH: ignoring range '{}'", range);
|
||||
}
|
||||
for (const auto& [start, size] : m_watch_ranges)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS, "REVWATCH range {:08x}+{:x}", start, size);
|
||||
m_watch_shadow.emplace_back(size);
|
||||
m_watch_known.emplace_back(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StarletMemory::SetWatchCore(const ARMCore* core)
|
||||
{
|
||||
m_watch_core = core;
|
||||
}
|
||||
|
||||
bool StarletMemory::WatchHit(u32 address, u32 size) const
|
||||
{
|
||||
for (const auto& [start, length] : m_watch_ranges)
|
||||
{
|
||||
if (u64{address} < u64{start} + length && u64{address} + size > start)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void StarletMemory::WatchCPU(u32 address, u32 size, u32 value)
|
||||
{
|
||||
if (!WatchHit(address, size))
|
||||
return;
|
||||
NOTICE_LOG_FMT(IOS, "REVWATCH CPU {:08x}/{} {:08x} PC {:08x} LR {:08x}", address, size, value,
|
||||
m_watch_core ? m_watch_core->GetInstructionAddress() : 0,
|
||||
m_watch_core ? m_watch_core->GetRegister(14) : 0);
|
||||
// The bus stores scalars big-endian.
|
||||
std::array<u8, 4> bytes{};
|
||||
for (u32 i = 0; i < size; ++i)
|
||||
bytes[i] = static_cast<u8>(value >> (8 * (size - 1 - i)));
|
||||
WatchRecord(address, bytes.data(), size);
|
||||
}
|
||||
|
||||
void StarletMemory::WatchDMA(u32 address, const u8* data, u32 size)
|
||||
{
|
||||
if (!WatchHit(address, size))
|
||||
return;
|
||||
NOTICE_LOG_FMT(IOS, "REVWATCH DMA {:08x}/{:x} PC {:08x}", address, size,
|
||||
m_watch_core ? m_watch_core->GetInstructionAddress() : 0);
|
||||
WatchRecord(address, data, size);
|
||||
}
|
||||
|
||||
void StarletMemory::WatchRecord(u32 address, const u8* data, u32 size)
|
||||
{
|
||||
for (size_t range = 0; range < m_watch_ranges.size(); ++range)
|
||||
{
|
||||
const auto [start, length] = m_watch_ranges[range];
|
||||
for (u32 i = 0; i < size; ++i)
|
||||
{
|
||||
const u32 at = address + i;
|
||||
if (at >= start && at - start < length)
|
||||
{
|
||||
m_watch_shadow[range][at - start] = data[i];
|
||||
m_watch_known[range][at - start] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StarletMemory::WatchRead(u32 address, u32 size)
|
||||
{
|
||||
// Phase 11 H1: a byte the Starlet reads that differs from what the Starlet or a device last
|
||||
// wrote there was changed by another writer. Log it with the reader, then adopt the new value.
|
||||
const u8* memory = m_system.GetMemory().GetPointerForRange(address, size);
|
||||
if (memory == nullptr)
|
||||
return;
|
||||
for (size_t range = 0; range < m_watch_ranges.size(); ++range)
|
||||
{
|
||||
const auto [start, length] = m_watch_ranges[range];
|
||||
u64 read = 0;
|
||||
u64 expected = 0;
|
||||
bool differs = false;
|
||||
for (u32 i = 0; i < size; ++i)
|
||||
{
|
||||
const u32 at = address + i;
|
||||
const bool inside = at >= start && at - start < length;
|
||||
const u8 shadow = inside ? m_watch_shadow[range][at - start] : memory[i];
|
||||
read = (read << 8) | memory[i];
|
||||
expected = (expected << 8) | shadow;
|
||||
if (inside && m_watch_known[range][at - start] && shadow != memory[i])
|
||||
{
|
||||
differs = true;
|
||||
m_watch_shadow[range][at - start] = memory[i];
|
||||
}
|
||||
}
|
||||
if (differs)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS,
|
||||
"REVWATCH DIFF {:08x}/{} read {:0{}x} expected {:0{}x} PC {:08x} LR {:08x}",
|
||||
address, size, read, size * 2, expected, size * 2,
|
||||
m_watch_core ? m_watch_core->GetInstructionAddress() : 0,
|
||||
m_watch_core ? m_watch_core->GetRegister(14) : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StarletMemory::WatchAudit()
|
||||
{
|
||||
// Phase 11 H1, at each IPC reply: every run of written bytes that guest memory no longer
|
||||
// holds (at most 16 runs logged per reply), then adopt the new values.
|
||||
u32 runs = 0;
|
||||
for (size_t range = 0; range < m_watch_ranges.size(); ++range)
|
||||
{
|
||||
const auto [start, length] = m_watch_ranges[range];
|
||||
const u8* memory = m_system.GetMemory().GetPointerForRange(start, length);
|
||||
if (memory == nullptr)
|
||||
continue;
|
||||
auto& shadow = m_watch_shadow[range];
|
||||
const auto& known = m_watch_known[range];
|
||||
for (u32 i = 0; i < length;)
|
||||
{
|
||||
if (!known[i] || shadow[i] == memory[i])
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
const u32 first = i;
|
||||
while (i < length && known[i] && shadow[i] != memory[i])
|
||||
{
|
||||
shadow[i] = memory[i];
|
||||
++i;
|
||||
}
|
||||
if (++runs <= 16)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS, "REVWATCH AUDIT {:08x}+{:x} now {:02x}", start + first, i - first,
|
||||
memory[first]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StarletMemory::Init(const std::string& dump_directory, std::string* error)
|
||||
@@ -792,6 +939,26 @@ bool StarletMemory::AttachDisposableNAND(const std::string& source, const std::s
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StarletMemory::EnableBootstrapWiimotes(std::string* error)
|
||||
{
|
||||
if (!m_nand)
|
||||
{
|
||||
if (error)
|
||||
*error = "REV_BOOTSTRAP_WIIMOTE needs a bootstrap NAND";
|
||||
return false;
|
||||
}
|
||||
// The identities are chosen before the devices exist (see Init).
|
||||
if (!InstallEmulatedWiimotePairings(false))
|
||||
{
|
||||
if (error)
|
||||
*error = "could not read the Wii Remote pairings of the bootstrap NAND";
|
||||
return false;
|
||||
}
|
||||
m_initialized = true;
|
||||
InitializeWiimotes();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StarletMemory::AttachKeys(const std::string& path, std::string* error)
|
||||
{
|
||||
File::IOFile keys_file(path, "rb");
|
||||
@@ -1025,6 +1192,11 @@ u8* StarletMemory::GetFastmemBase() const
|
||||
return m_system.GetMemory().GetPhysicalBase();
|
||||
}
|
||||
|
||||
std::vector<std::pair<u32, u32>> StarletMemory::GetWatchRanges() const
|
||||
{
|
||||
return m_watch_ranges;
|
||||
}
|
||||
|
||||
u8* StarletMemory::GetFastmemSRAMBase() const
|
||||
{
|
||||
// The JIT admits only measured SRAM pages and directions, and checks the live boot0 overlay and
|
||||
@@ -1165,6 +1337,8 @@ u8 StarletMemory::ReadDMA8(u32 address) const
|
||||
|
||||
void StarletMemory::WriteDMA8(u32 address, u8 value)
|
||||
{
|
||||
if (!m_watch_ranges.empty() && !m_watch_in_dma_buffer)
|
||||
WatchDMA(address, &value, 1);
|
||||
if (address >= SRAM_BASE && address < SRAM_BASE + SRAM_WINDOW_SIZE)
|
||||
{
|
||||
const u32 offset = address - SRAM_BASE;
|
||||
@@ -1216,13 +1390,17 @@ void StarletMemory::WriteDMABuffer(u32 address, std::span<const u8> buffer)
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::DMAWrite);
|
||||
if (buffer.empty())
|
||||
return;
|
||||
if (!m_watch_ranges.empty())
|
||||
WatchDMA(address, buffer.data(), static_cast<u32>(buffer.size()));
|
||||
if (u8* memory = GetDMAMemoryPointer(address, buffer.size()))
|
||||
{
|
||||
std::memcpy(memory, buffer.data(), buffer.size());
|
||||
return;
|
||||
}
|
||||
m_watch_in_dma_buffer = true;
|
||||
for (size_t i = 0; i < buffer.size(); ++i)
|
||||
WriteDMA8(address + static_cast<u32>(i), buffer[i]);
|
||||
m_watch_in_dma_buffer = false;
|
||||
}
|
||||
|
||||
void StarletMemory::ClearRegisterCache()
|
||||
@@ -3532,6 +3710,8 @@ void StarletMemory::WriteRegister(u32 address, u32 value)
|
||||
|
||||
u8 StarletMemory::Read8(u32 address)
|
||||
{
|
||||
if (!m_watch_ranges.empty() && WatchHit(address, 1))
|
||||
WatchRead(address, 1);
|
||||
if (IsBootROMAddress(address))
|
||||
return m_boot_rom[GetBootROMOffset(address)];
|
||||
|
||||
@@ -3653,6 +3833,8 @@ u8 StarletMemory::Read8(u32 address)
|
||||
|
||||
u16 StarletMemory::Read16(u32 address)
|
||||
{
|
||||
if (!m_watch_ranges.empty() && WatchHit(address, 2))
|
||||
WatchRead(address, 2);
|
||||
// Aligned reads stay inside one SRAM/boot0 mapping segment. Resolve the live mapping once,
|
||||
// including its holes, before considering unrelated memory and peripheral ranges.
|
||||
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
|
||||
@@ -3687,6 +3869,8 @@ u16 StarletMemory::Read16(u32 address)
|
||||
|
||||
u32 StarletMemory::Read32(u32 address)
|
||||
{
|
||||
if (!m_watch_ranges.empty() && WatchHit(address, 4))
|
||||
WatchRead(address, 4);
|
||||
// ARM IOS accesses the Hollywood timer and interrupt controller almost exclusively as aligned
|
||||
// words. Falling back to ARMBus::Read32 decomposes each access into four virtual Read8 calls;
|
||||
// every byte then repeats the complete device-range decoder and IPC register switch. Preserve
|
||||
@@ -3760,6 +3944,8 @@ u32 StarletMemory::Read32(u32 address)
|
||||
|
||||
void StarletMemory::Write8(u32 address, u8 value)
|
||||
{
|
||||
if (!m_watch_ranges.empty())
|
||||
WatchCPU(address, 1, value);
|
||||
if (IsBootROMAddress(address))
|
||||
return;
|
||||
|
||||
@@ -3973,6 +4159,8 @@ void StarletMemory::Write8(u32 address, u8 value)
|
||||
|
||||
void StarletMemory::Write16(u32 address, u16 value)
|
||||
{
|
||||
if (!m_watch_ranges.empty())
|
||||
WatchCPU(address, 2, value);
|
||||
// An aligned scalar cannot cross an SRAM bank, aperture or boot0-overlay boundary.
|
||||
// Keep this on the bus (including live mapping/protection), but resolve it only once.
|
||||
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
|
||||
@@ -4010,6 +4198,8 @@ void StarletMemory::Write16(u32 address, u16 value)
|
||||
|
||||
void StarletMemory::Write32(u32 address, u32 value)
|
||||
{
|
||||
if (!m_watch_ranges.empty())
|
||||
WatchCPU(address, 4, value);
|
||||
// SRAM is ordinary byte-addressed storage, not MMIO. Aligned words stay inside one
|
||||
// mapping segment, so there is no need to decode every device or map all four bytes.
|
||||
// Unaligned/boundary accesses below still use the original byte-exact bus path.
|
||||
@@ -4039,6 +4229,9 @@ void StarletMemory::Write32(u32 address, u32 value)
|
||||
{
|
||||
WriteRegister(address, value);
|
||||
m_system.GetWiiIPC().WriteStarletRegister(address - HW_BASE, value);
|
||||
// Phase 11 H1: an IPC reply (ARMCTRL Y1) audits the watched ranges.
|
||||
if (!m_watch_ranges.empty() && address == HW_BASE + 0x0c && (value & 1) != 0)
|
||||
WatchAudit();
|
||||
if (address == HW_BASE + 0x0c && (value & 0x09) != 0)
|
||||
{
|
||||
if (Starlet* const starlet = m_system.GetStarlet())
|
||||
@@ -4259,7 +4452,7 @@ bool StarletMemory::CommitNANDPages(const NANDJournal::Pages& pages)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
bool StarletMemory::InstallEmulatedWiimotePairings(bool allow_nand_rewrite)
|
||||
{
|
||||
using NANDSuperblock = DiscIO::NANDImporter::NANDSuperblock;
|
||||
using NANDFSTEntry = DiscIO::NANDImporter::NANDFSTEntry;
|
||||
@@ -4499,6 +4692,12 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
trusted_count);
|
||||
return true;
|
||||
}
|
||||
if (!allow_nand_rewrite)
|
||||
{
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "No paired Wii Remote identity in the raw NAND; keeping the "
|
||||
"default identities without modifying NAND");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ranges::copy(pairing, sysconf.begin() + static_cast<ptrdiff_t>(*pairing_offset));
|
||||
|
||||
|
||||
@@ -72,6 +72,16 @@ public:
|
||||
// Revolution IOS Phase 04C: bootstrap mode loads no keys, so the OTP reads zero. This
|
||||
// loads a 1024-byte BootMii keys.bin so OTP reads return the console's own words.
|
||||
bool AttachKeys(const std::string& path, std::string* error);
|
||||
// StarletOS phase 11 (M1), emulator only: give a bootstrap-ELF session the emulated Wii
|
||||
// Remotes of the original-IOS mode, with the identities already paired in the attached NAND's
|
||||
// SYSCONF (keys needed to decrypt it). Never writes the NAND: without a paired identity the
|
||||
// remotes keep their default addresses. Call after AttachKeys and a NAND attach.
|
||||
bool EnableBootstrapWiimotes(std::string* error);
|
||||
// StarletOS phase 11 (F1), emulator only: REV_WATCH=start:size[,start:size...] (hex) logs
|
||||
// every Starlet CPU store (with the instruction's address and LR) and every device DMA write
|
||||
// that touches one of those physical ranges. The ARM JIT sends the accesses that hit a watched
|
||||
// range to its bus slow path, with the instruction address. `core` gives that address.
|
||||
void SetWatchCore(const ARMCore* core);
|
||||
void Reset();
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
@@ -85,6 +95,7 @@ public:
|
||||
bool IsIdlePollAddress(u32 address, u32 size) const override;
|
||||
bool IsSliceStablePollAddress(u32 address, u32 size) const override;
|
||||
u8* GetFastmemBase() const override;
|
||||
std::vector<std::pair<u32, u32>> GetWatchRanges() const override;
|
||||
u8* GetFastmemSRAMBase() const override;
|
||||
const bool* GetFastmemBoot0Mapped() const override;
|
||||
const bool* GetFastmemSRAMSplitMode() const override;
|
||||
@@ -202,7 +213,7 @@ private:
|
||||
void CompleteNANDCommand(u32 command);
|
||||
bool ReadRawNANDPage(u32 page, NANDPage* raw);
|
||||
bool CommitNANDPages(const NANDJournal::Pages& pages);
|
||||
bool InstallEmulatedWiimotePairings();
|
||||
bool InstallEmulatedWiimotePairings(bool allow_nand_rewrite = true);
|
||||
bool ReadNANDPage(u32 command);
|
||||
bool ReadNANDID(u32 command);
|
||||
bool ReadNANDStatus(u32 command);
|
||||
@@ -337,6 +348,20 @@ private:
|
||||
u64 m_arm_cycles = 0;
|
||||
u32 m_timer_offset = 0;
|
||||
bool m_initialized = false;
|
||||
// Phase 11 F1 write watch (REV_WATCH); empty when off.
|
||||
std::vector<std::pair<u32, u32>> m_watch_ranges;
|
||||
const ARMCore* m_watch_core = nullptr;
|
||||
bool m_watch_in_dma_buffer = false;
|
||||
// Phase 11 H1: each watched range as the Starlet and the devices wrote it, and which of its
|
||||
// bytes they wrote. Starlet reads and IPC replies compare guest memory with it.
|
||||
std::vector<std::vector<u8>> m_watch_shadow;
|
||||
std::vector<std::vector<u8>> m_watch_known;
|
||||
bool WatchHit(u32 address, u32 size) const;
|
||||
void WatchCPU(u32 address, u32 size, u32 value);
|
||||
void WatchDMA(u32 address, const u8* data, u32 size);
|
||||
void WatchRecord(u32 address, const u8* data, u32 size);
|
||||
void WatchRead(u32 address, u32 size);
|
||||
void WatchAudit();
|
||||
bool m_nand_read_only = false;
|
||||
bool m_boot0_mapped = true;
|
||||
bool m_sram_split_mode = false;
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "Core/HW/WII_IPC.h"
|
||||
#include "Core/IOS/Starlet/Starlet.h"
|
||||
#include "Core/IOS/Starlet/StarletMemory.h"
|
||||
#include "Core/HW/WII_IPC.h"
|
||||
#include "Core/PowerPC/GDBStub.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
@@ -270,8 +271,12 @@ T MMU::ReadFromHardware(u32 em_address)
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<T>(
|
||||
m_memory.GetMMIOMapping()->Read<std::make_unsigned_t<T>>(m_system, em_address));
|
||||
const auto value =
|
||||
m_memory.GetMMIOMapping()->Read<std::make_unsigned_t<T>>(m_system, em_address);
|
||||
// StarletOS phase 11 (J1): Broadway's hardware reads in the IPC trace's window.
|
||||
if (IOS::g_starletos_hardware_window) [[unlikely]]
|
||||
IOS::TraceStarletOSHardwareAccess(m_system, false, em_address, sizeof(T), value);
|
||||
return static_cast<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +464,10 @@ void MMU::WriteToHardware(u32 em_address, const u32 data, const u32 size)
|
||||
return;
|
||||
}
|
||||
|
||||
// StarletOS phase 11 (J1): Broadway's hardware writes in the IPC trace's window.
|
||||
if (IOS::g_starletos_hardware_window) [[unlikely]]
|
||||
IOS::TraceStarletOSHardwareAccess(m_system, true, em_address, size, data);
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case 1:
|
||||
@@ -1485,6 +1494,10 @@ u32 MMU::IsOptimizableMMIOAccess(u32 address, u32 access_size) const
|
||||
if (m_power_pc.GetMemChecks().HasAny())
|
||||
return 0;
|
||||
|
||||
// StarletOS phase 11 (J1): no inlined MMIO reads while the hardware window may open.
|
||||
if (IOS::StarletOSHardwareTraceRequested())
|
||||
return 0;
|
||||
|
||||
if (!m_ppc_state.msr.DR)
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -1755,6 +1755,83 @@ TEST(StarletARMCore, ARMJitCompilesLongMultiplyFamily)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, ARMv5TEEnhancedDSPInstructions)
|
||||
{
|
||||
// StarletOS phase 11 (I1): encodings from GNU as (-march=armv5te); results from the ARM
|
||||
// Architecture Reference Manual. r0 = Rm, r1 = Rs or Rn, r7 = accumulator, r3/r4 results.
|
||||
struct Case
|
||||
{
|
||||
u32 instruction;
|
||||
u32 r0;
|
||||
u32 r1;
|
||||
u32 r3;
|
||||
u32 r4;
|
||||
u32 r7;
|
||||
u32 expected_r3;
|
||||
u32 expected_r4;
|
||||
bool expected_q;
|
||||
};
|
||||
constexpr std::array cases = {
|
||||
// smlabb r4, r0, r1, r7: a 88-byte entry 9 of a table (Revolution's RAM layer).
|
||||
Case{0xe1047180, 9, 88, 0, 0, 0x13780000, 0, 0x13780318, false},
|
||||
Case{0xe1640180, 0xffff0003, 0x0002fffe, 0, 0, 0, 0, 0xfffffffa, false}, // smulbb
|
||||
Case{0xe16401a0, 0xffff0003, 0x0002fffe, 0, 0, 0, 0, 2, false}, // smultb
|
||||
// smlatt r4, r0, r1, r7: 0x3fff0001 + 0x7fffffff overflows, wraps and sets Q.
|
||||
Case{0xe10471e0, 0x7fff0000, 0x7fff0000, 0, 0, 0x7fffffff, 0, 0xbfff0000, true},
|
||||
Case{0xe1247180, 0x00020000, 0x00000003, 0, 0, 10, 0, 16, false}, // smlawb
|
||||
Case{0xe12401e0, 0xfffe0000, 0x00030000, 0, 0, 0, 0, 0xfffffffa, false}, // smulwt
|
||||
// smlalbb r3, r4, r0, r1: 0x00000000_ffffffff + 1 carries into RdHi.
|
||||
Case{0xe1443180, 1, 1, 0xffffffff, 0, 0, 0, 1, false},
|
||||
Case{0xe1014050, 0x7fffffff, 1, 0, 0, 0, 0, 0x7fffffff, true}, // qadd
|
||||
Case{0xe1214050, 0x80000000, 1, 0, 0, 0, 0, 0x80000000, true}, // qsub
|
||||
Case{0xe1414050, 1, 0x40000000, 0, 0, 0, 0, 0x7fffffff, true}, // qdadd
|
||||
Case{0xe1614050, 0, 3, 0, 0, 0, 0, 0xfffffffa, false}, // qdsub
|
||||
};
|
||||
|
||||
for (const Case& test : cases)
|
||||
{
|
||||
TestBus interpreter_bus;
|
||||
ARMCore interpreter(interpreter_bus);
|
||||
#if defined(_M_X86_64)
|
||||
TestBus jit_bus;
|
||||
ARMCore jit(jit_bus);
|
||||
std::array<ARMCore*, 2> cores = {&interpreter, &jit};
|
||||
jit_bus.WriteARM(0, test.instruction);
|
||||
jit_bus.WriteARM(4, 0xeafffffe); // b .
|
||||
#else
|
||||
std::array<ARMCore*, 1> cores = {&interpreter};
|
||||
#endif
|
||||
interpreter_bus.WriteARM(0, test.instruction);
|
||||
interpreter_bus.WriteARM(4, 0xeafffffe); // b .
|
||||
for (ARMCore* core : cores)
|
||||
{
|
||||
core->SetCPSR(static_cast<u32>(ARMCore::Mode::System));
|
||||
core->SetRegister(0, test.r0);
|
||||
core->SetRegister(1, test.r1);
|
||||
core->SetRegister(3, test.r3);
|
||||
core->SetRegister(4, test.r4);
|
||||
core->SetRegister(7, test.r7);
|
||||
core->SetRegister(15, 0);
|
||||
}
|
||||
#if defined(_M_X86_64)
|
||||
jit.SetJitEnabled(true);
|
||||
#endif
|
||||
for (ARMCore* core : cores)
|
||||
{
|
||||
ASSERT_EQ(core->RunCycles(2), 2u);
|
||||
EXPECT_EQ(core->GetRegister(4), test.expected_r4) << std::hex << test.instruction;
|
||||
if (test.instruction == 0xe1443180)
|
||||
EXPECT_EQ(core->GetRegister(3), test.expected_r3);
|
||||
EXPECT_EQ((core->GetCPSR() & ARMCore::CPSR_Q) != 0, test.expected_q)
|
||||
<< std::hex << test.instruction;
|
||||
// The DSP instruction must not touch the condition flags (it is not a compare).
|
||||
EXPECT_EQ(core->GetCPSR() & (ARMCore::CPSR_N | ARMCore::CPSR_Z | ARMCore::CPSR_C |
|
||||
ARMCore::CPSR_V),
|
||||
0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StarletARMCore, FCSESwitchPreservesTaggedTLBTranslations)
|
||||
{
|
||||
TestBus bus(0x10000);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/WII_IPC.h"
|
||||
#include "Core/IOS/Starlet/ARMCore.h"
|
||||
#include "Core/IOS/Starlet/BootstrapELF.h"
|
||||
@@ -25,6 +28,18 @@ enum class ExpectedOutcome
|
||||
Phase2
|
||||
};
|
||||
|
||||
// Exit status of a run stopped by a Dolphin alert (1: failed expectation, 2: usage).
|
||||
constexpr int ALERT_EXIT_STATUS = 3;
|
||||
|
||||
// Dolphin's default handler shows an alert in a modal MessageBox on Windows, which blocks the
|
||||
// probe until someone closes it. Report the alert on stderr and fail the run at once instead.
|
||||
bool FailOnAlert(const char* caption, const char* text, bool, Common::MsgType)
|
||||
{
|
||||
std::cout.flush();
|
||||
std::cerr << "Dolphin alert (" << caption << "): " << text << std::endl;
|
||||
std::_Exit(ALERT_EXIT_STATUS);
|
||||
}
|
||||
|
||||
bool Run(const std::string& path, bool jit, ExpectedOutcome expected)
|
||||
{
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
@@ -132,6 +147,7 @@ bool Run(const std::string& path, bool jit, ExpectedOutcome expected)
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Common::RegisterMsgAlertHandler(FailOnAlert);
|
||||
if (argc < 2 || argc > 4)
|
||||
{
|
||||
std::cerr << "Usage: StarletBootstrapProbe <ELF> [--jit] "
|
||||
@@ -166,5 +182,14 @@ int main(int argc, char** argv)
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return Run(argv[1], jit, expected) ? 0 : 1;
|
||||
|
||||
// The Starlet shares MEM1 and MEM2 with Broadway. Back them as on a retail Wii (24 and 64 MiB,
|
||||
// zeroed): without them, any Starlet access there, such as StarOS reading its SBR1 record
|
||||
// header at 0x00200000, is an access to unmapped memory and raises an alert.
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.SetIsWii(true);
|
||||
system.GetMemory().Init();
|
||||
const bool passed = Run(argv[1], jit, expected);
|
||||
system.GetMemory().Shutdown();
|
||||
return passed ? 0 : 1;
|
||||
}
|
||||
@@ -46,10 +46,35 @@
|
||||
"sha256": "7e9bcfff87f526510aad149e781ae57140e32f7b32beafec00271fbcdb152848",
|
||||
"bytes": 4526
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/HW/WII_IPC.cpp",
|
||||
"sha256": "a2244dabdd1c9334a63c8ddd3f09ce3abe22e86198b1fd45b092cd238379a6c0",
|
||||
"bytes": 24790
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/HW/WII_IPC.h",
|
||||
"sha256": "886a0ca83ff839013a170efd5d3bb9e32c2a8ad327ca5a78985da27b58ae7cf9",
|
||||
"bytes": 4770
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||
"sha256": "40d45aed7c5edb4540105dcdee4dd8dcde88c21ba7f3279eff0a9564f2d02a29",
|
||||
"bytes": 75597
|
||||
"sha256": "2559d69e1ec654734f788312cf5f3dca4de63e22e0880626306e8d15a4585648",
|
||||
"bytes": 80026
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||
"sha256": "c3263bebbbdb7cd1119cc74ad328a63ad969136c1755ccf8e06e6549f23da3aa",
|
||||
"bytes": 14758
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||
"sha256": "724382f4f66442d51382d3249bea68620d7417a6c7f48b0c19efa60e942a183c",
|
||||
"bytes": 109615
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/ARMJitX64.h",
|
||||
"sha256": "7e99ddee84b738bbb1e72aae0c8e2ee31335499c9eeb9585dfe5725688ad8c9e",
|
||||
"bytes": 13979
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/BootstrapELF.cpp",
|
||||
@@ -63,8 +88,8 @@
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/Starlet.cpp",
|
||||
"sha256": "78e3b006501af06fbef220d95213690f8a31ed5b3c36699c6e07ba58be3ba852",
|
||||
"bytes": 26039
|
||||
"sha256": "2d10d03f2434f03a58f0a8e5f6cdab93ee8e8028d94ad519d137e739b3a99467",
|
||||
"bytes": 26556
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/Starlet.h",
|
||||
@@ -73,13 +98,18 @@
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||
"sha256": "34900b6c340e412f99ddb3ce6ba17bdb4d85ba7b27b2adc522315b158b675fca",
|
||||
"bytes": 189757
|
||||
"sha256": "f46cb37943c0feb9695e5d0541864e06fb47c2cbed7e6d48b03df64006181afd",
|
||||
"bytes": 196308
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/IOS/Starlet/StarletMemory.h",
|
||||
"sha256": "cfa3aee13f260590ae9f7cdd48f8f08d1c67e4e6d647ef3eca6e01d472dbdd29",
|
||||
"bytes": 14873
|
||||
"sha256": "6d58d81d94b32a647dbc3babea3e943aee307c02d9f1fb8904d8ebaccd22cf05",
|
||||
"bytes": 16542
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/Core/PowerPC/MMU.cpp",
|
||||
"sha256": "25c302b6cf1365abca38f4bcf88666dc25c5799a93534da1f55ae0a0c18dbb68",
|
||||
"bytes": 85125
|
||||
},
|
||||
{
|
||||
"path": "Source/Core/DolphinNoGUI/PlatformHeadless.cpp",
|
||||
@@ -122,5 +152,71 @@
|
||||
"unpublishedHistoricalFileVersionsScanned": 67,
|
||||
"privateFindings": 0,
|
||||
"newFullBuildOrHardwareTest": "NOT RUN: publication of existing qualified bytes only; see companion phase reports"
|
||||
}
|
||||
},
|
||||
"updates": [
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal L2: IPC request/reply metadata trace while the original IOS runs (DOLPHIN_IPC_TRACE=1); no behavior change otherwise",
|
||||
"files": [
|
||||
"Source/Core/Core/HW/WII_IPC.cpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal M1: REV_BOOTSTRAP_WIIMOTE=1 gives a bootstrap-ELF (Revolution) session the emulated Wii Remotes with the identity paired in the attached NAND; never writes the NAND; off by default",
|
||||
"files": [
|
||||
"Source/Core/Core/IOS/Starlet/Starlet.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal M1e: the IPC trace also prints the paths named by filesystem IOCTLs (64-byte path, 76-byte record at +6, rename pair, first IOCTLV vector of 64 bytes); paths only",
|
||||
"files": [
|
||||
"Source/Core/Core/HW/WII_IPC.cpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal F1: REV_WATCH=start:size[,...] logs Starlet CPU stores (instruction address, LR) and device DMA writes in those physical ranges; the ARM JIT routes accesses in watched ranges to its bus slow path and records the instruction address there; off by default",
|
||||
"files": [
|
||||
"Source/Core/Core/IOS/Starlet/Starlet.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.h",
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal H1: with REV_WATCH, a shadow of each watched range (Starlet stores and device DMA); Starlet reads that differ from it (REVWATCH DIFF) and IPC-reply audits (REVWATCH AUDIT) reveal other writers; register-list transfers whose virtual range overlaps a watched range run through the architectural helper and log their translation (REVWATCH LDM/STM)",
|
||||
"files": [
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/StarletMemory.h"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal I1: the ARM interpreter executes the ARMv5TE enhanced DSP instructions (SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy, QADD, QSUB, QDADD, QDSUB, Q flag), previously decoded as compare data processing that wrote no register; the JIT leaves them to the interpreter; test ARMv5TEEnhancedDSPInstructions",
|
||||
"files": [
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.cpp",
|
||||
"Source/Core/Core/IOS/Starlet/ARMCore.h",
|
||||
"Source/Core/Core/IOS/Starlet/ARMJitX64.cpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordedAt": "2026-10-06",
|
||||
"purpose": "StarletOS Phase 11 proposal J1: with DOLPHIN_IPC_TRACE=1 and DOLPHIN_HW_WINDOW=1, Broadway hardware register accesses (MMU MMIO reads and writes, PC) are logged as HWTRACE from the reply to an IOCTL 0x28 32/32 request until the next IPC request; no inlined MMIO reads while requested",
|
||||
"files": [
|
||||
"Source/Core/Core/HW/WII_IPC.cpp",
|
||||
"Source/Core/Core/HW/WII_IPC.h",
|
||||
"Source/Core/Core/PowerPC/MMU.cpp"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -43,3 +43,209 @@ Read AGENTS.md. Record the starting commit, changed behavior, actual tests and
|
||||
remaining work. Update this handoff and any changed file hashes, commit to this
|
||||
fork, then update the pinned dependency in StarletOS. Never label a build, a
|
||||
source comparison or a fake emulator device as a physical Wii observation.
|
||||
|
||||
|
||||
## Linux qualification — 2026-10-03
|
||||
|
||||
Starting checkout: master `54f5a74bbf4e3be8418a3e98d006436c16a23e40`, clean;
|
||||
fetch and fast-forward-only pull found no divergence. All recursive submodules
|
||||
were initialized at their recorded commits. No runtime/source bytes changed;
|
||||
this addition records the first build on the destination Linux PC.
|
||||
|
||||
- Debian 13 x86_64, GCC 14.2.0, CMake 3.31.6, Ninja 1.12.1. Release build in
|
||||
`.starlet_linux`: `StarletBootstrapProbe`, `dolphin-nogui`, `tests` succeeded.
|
||||
Qt, X11/EGL, desktop audio/input, LLVM disassembly and optional integrations
|
||||
were disabled; headless and tests enabled. The complete configuration is in
|
||||
the companion `docs/LINUX_QUALIFICATION.md`. This is not a GUI qualification.
|
||||
- 95 selected tests of bootstrap ELF loading, ARM execution, SRAM, DMA,
|
||||
timer, register and IPC behavior passed. Google Test also reports two
|
||||
existing disabled tests; they were not enabled. The rest of Dolphin's
|
||||
unit suite was not run.
|
||||
- Ten standalone bootstrap runs passed: idle, panic, fatal data, undefined
|
||||
instruction and Phase 02 self-test, each in interpreter and JIT. Rust used
|
||||
the companion's pinned nightly-2026-09-20. The Phase 02 trace still says
|
||||
IRQ delivery deferred and timer flag relatch untested; no hardware inference.
|
||||
- SHA-256 of Linux `StarletBootstrapProbe`:
|
||||
`a536e5125c193d3ae23f3a5028e2f34df0359785d86191500c65ce7bcd9610f9`.
|
||||
Tests: `6acf5f26206e5afee2b3fbe3fd149b7c7c4dcd1f035d4dc12b89193df1d13a6f`.
|
||||
NoGUI: `bd0f9420428f07a1c8785c131f430cc0f9eed88a5723e2e56546291fa3b72544`.
|
||||
- Logs are private/local in companion `target/linux-qualification-20261003/`;
|
||||
their hashes and commands are summarized in its public traceability report.
|
||||
All 22 pinned integration file hashes still match. The private Menu profile
|
||||
and NAND were not used or modified by these standalone tests. No full Menu
|
||||
replay or physical Wii test is established by this build.
|
||||
|
||||
Next: use the companion handoff. Stock-IOS H02B has now been copied to the
|
||||
identified SD and verified after remount; the console result is pending.
|
||||
Rebuilding its PPC binary under Linux remains unqualified (the exact devkitPro
|
||||
packages could not be downloaded: HTTP 403). Preserve the archived probe.
|
||||
This documentation commit changes the dependency pin, not the built code;
|
||||
the executables above still report their build's original commit.
|
||||
|
||||
|
||||
## Phase 11 IPC trace for the original IOS — 2026-10-06
|
||||
|
||||
Starting checkout: master `78738093ece433e9198213f91d43a3a0d60e3fa2`, clean;
|
||||
fetch found no divergence. Companion: StarletOS Phase 11 proposal L2
|
||||
(`docs/PHASE11_PROPOSAL.md`), approved by the user.
|
||||
|
||||
- `Source/Core/Core/HW/WII_IPC.cpp`: with the environment variable
|
||||
`DOLPHIN_IPC_TRACE=1` and the original IOS running on the emulated Starlet
|
||||
(`WiiIOSLLE`), every request Broadway posts (PPCCTRL X1) is logged as
|
||||
`IPCTRACE RQ <addr> C <command> FD <fd> A <five argument words>` (an OPEN
|
||||
adds ` P <path>`), and every reply Starlet posts (ARMCTRL Y1) as
|
||||
`IPCTRACE RP <addr> C <original command> RC <result>`. Notice level, `WII_IPC`
|
||||
log type. Metadata only: buffer contents are never read. Without the
|
||||
variable, or in HLE mode, nothing changes.
|
||||
- The file is now an exact integration file (`.gitattributes -text`, hash in
|
||||
`docs/StarletOS_Checkpoint.json`, 23 files).
|
||||
- Build: `.starlet_linux` `dolphin-emu-nogui` and `tests` rebuilt; the 95
|
||||
selected Starlet tests (filter in the companion `docs/LINUX_QUALIFICATION.md`)
|
||||
passed.
|
||||
- Emulation smoke run (supporting evidence only, private log in the companion
|
||||
`target/phase11/l2/`): headless, Null video, the companion's Phase 10C NAND
|
||||
read-only behind a private profile's COW journal, original boot chain to the
|
||||
System Menu for 5 minutes: 74342 requests and 74337 replies traced (5
|
||||
asynchronous requests still pending at the stop).
|
||||
- Limitation: this Linux build has no window platform (X11 disabled; the
|
||||
system lacks the `xi` development package), so no one can point and click in
|
||||
it yet. A separate X11 build directory is the next step once `libxi-dev` is
|
||||
installed; the qualified headless build stays unchanged.
|
||||
|
||||
Next: the companion's Phase 11 L2c (the user launches the Mii Channel from the
|
||||
Menu in a window with the trace on); analysis stays in the companion.
|
||||
|
||||
## X11 window build and the reference launch — 2026-10-06
|
||||
|
||||
No source change. The user installed `libxi-dev` and `libxrandr-dev`; a
|
||||
separate build directory `.starlet_linux_x11` (the qualified options with
|
||||
`ENABLE_X11=ON`, tests off) built `dolphin-nogui` in 2 min 53 s
|
||||
(SHA-256 `a7d372f47e7265a4ebe20c71bde6f8dd63f22416b422551b960f526a604ebda4`).
|
||||
The qualified headless `.starlet_linux` build is unchanged. On KDE Plasma
|
||||
Wayland the window runs through XWayland (`-p x11`, Vulkan); the emulated
|
||||
Wii Remote reads the mouse through XInput2.
|
||||
|
||||
Companion session L2c (emulation, supporting only): original boot chain and
|
||||
IOS80, the companion's Phase 10C NAND read-only (hash unchanged after), no
|
||||
Ethernet NAT; the user launched the Mii Channel from the System Menu and it
|
||||
started. The `IPCTRACE` log shows ES LAUNCH right after the WiiConnect24
|
||||
shutdown request; analysis is in the companion's Phase 11 STATUS (finding 9).
|
||||
|
||||
Known limit for the next companion step: in bootstrap (Revolution) mode the
|
||||
emulated Wii Remotes are not initialized (`m_initialized` stays false), so
|
||||
nobody can point at a channel there. Changing that is proposed in the
|
||||
companion (proposal M1), not done.
|
||||
|
||||
## Emulated Wii Remotes for a Revolution session — 2026-10-06
|
||||
|
||||
Companion proposal M1 (approved by the user): replay the reference launch
|
||||
with Revolution instead of the original IOS, to see where Broadway stops.
|
||||
|
||||
- `REV_BOOTSTRAP_WIIMOTE=1` (bootstrap-ELF mode, after the NAND and keys
|
||||
attaches): `StarletMemory::EnableBootstrapWiimotes` selects the identities
|
||||
paired in the attached NAND's SYSCONF (`BT.DINF`, decrypted with the keys)
|
||||
and creates the emulated Wii Remotes exactly as the original-IOS mode does.
|
||||
`InstallEmulatedWiimotePairings(false)` never rewrites SYSCONF: without a
|
||||
paired identity the remotes keep their default addresses. Without the
|
||||
variable nothing changes (the remotes stay absent, as before).
|
||||
- Checkpoint hashes updated for `Starlet.cpp`, `StarletMemory.cpp`,
|
||||
`StarletMemory.h`.
|
||||
- Build: `.starlet_linux` (`dolphin-emu-nogui`, `tests`) and
|
||||
`.starlet_linux_x11`; the 95 selected Starlet tests passed.
|
||||
- Without the variable, the companion's headless P11-H06 Menu regression
|
||||
(`tools/regression/phase11_menu.py --step H06 --outcome Phase9Running
|
||||
--timeout 600`) still passes on this build, NAND source and disposable
|
||||
copy identical.
|
||||
|
||||
Next: the companion's M1b (smoke test with the variable, no window) and M1c
|
||||
(the user launches the Mii Channel under Revolution in the X11 window).
|
||||
|
||||
## Filesystem paths in the IPC trace — 2026-10-06
|
||||
|
||||
Companion proposal M1e (approved): under Revolution in the window the System
|
||||
Menu's save before a launch got -106 on its second CREATE_DIR, and no trace
|
||||
had the path. `IPCTRACE RQ` lines now end with ` F <path>` for IOCTLs whose
|
||||
input is a 64-byte path (DELETE, GET_ATTR), a 76-byte attribute record (path
|
||||
at +6: CREATE_DIR, CREATE_FILE, SET_ATTR) or two paths (` F a > b`, RENAME),
|
||||
and for IOCTLVs whose first input vector is 64 bytes (READ_DIR, GET_USAGE);
|
||||
` F -` when that field holds no valid path. Read from memory when X1 is
|
||||
rung, like the OPEN path; nothing else of the buffers. Checked headless under
|
||||
Revolution: `/shared2/test2`, `/title/00000001/00000002/data/cache.dat`, the
|
||||
GET_USAGE directories. 95 Starlet tests passed.
|
||||
|
||||
## Write watch for Starlet memory — 2026-10-06
|
||||
|
||||
Companion proposal F1 (approved): find what changes Revolution's RAM layer
|
||||
table between two filesystem requests (the companion's finding 11).
|
||||
|
||||
- `REV_WATCH=start:size[,start:size...]` (hexadecimal, physical): every
|
||||
Starlet CPU store (`StarletMemory::Write8/16/32`) and every device DMA
|
||||
write (`WriteDMABuffer`, `WriteDMA8`) touching a range is logged as
|
||||
`REVWATCH CPU addr/size value PC <instruction> LR <r14>` or
|
||||
`REVWATCH DMA addr/size PC <instruction>`. Off without the variable.
|
||||
- The ARM JIT keeps fastmem; `ARMBus::GetWatchRanges` gives it the ranges,
|
||||
`EmitFastmemAddress` sends any access overlapping one to the existing bus
|
||||
slow path, and the ARM and Thumb slow paths store the instruction address
|
||||
first (only while watching). Speed under watch is the JIT's (companion
|
||||
smoke run: the Menu's main menu at 74 s, as without the watch).
|
||||
- `ARMCore.h` (`GetInstructionAddress`, `GetWatchRanges`), `ARMJitX64.cpp`,
|
||||
`ARMJitX64.h` are now exact integration files (26 in the checkpoint).
|
||||
- The 95 selected Starlet tests passed (`.starlet_linux`); `.starlet_linux_x11`
|
||||
rebuilt. Smoke run with the companion's Revolution P11-H06: 1519 watched
|
||||
writes on its layer table during start-up, attributed by instruction.
|
||||
|
||||
## Shadow comparison for the write watch — 2026-10-06
|
||||
|
||||
Companion proposal H1 (approved). The companion's diagnostic image showed
|
||||
its kernel reading back, from its RAM layer table, an entry different from
|
||||
the one it had just written, with no other Starlet store or device DMA seen.
|
||||
|
||||
- With `REV_WATCH`, `StarletMemory` keeps a shadow of each range, updated by
|
||||
the Starlet stores and device DMA it logs. A Starlet read whose bytes differ
|
||||
from the shadow logs `REVWATCH DIFF addr/size read … expected … PC … LR …`
|
||||
(another writer changed them: Broadway, its GPU, the DSP…); each IPC reply
|
||||
(ARMCTRL Y1) audits the ranges (`REVWATCH AUDIT addr+len now …`, at most 16
|
||||
runs per reply). The shadow then adopts the new bytes.
|
||||
- Register-list transfers whose virtual range overlaps a watched range now
|
||||
take the JIT's architectural helper, and the interpreter logs
|
||||
`REVWATCH LDM|STM virtual->physical/size PC …` (its own translation).
|
||||
- `ARMCore.cpp` keeps its CRLF bytes. 95 Starlet tests passed. Companion
|
||||
smoke run (P11-H06D, 150 s): no DIFF, no AUDIT, 9 LDM with identical
|
||||
virtual and physical addresses.
|
||||
|
||||
## ARMv5TE enhanced DSP instructions — 2026-10-06
|
||||
|
||||
Companion proposal I1 (approved). The companion's Revolution kernel computes
|
||||
table addresses with `smlabb`; the ARM926EJ-S executes it, but
|
||||
`ARMCore::ExecuteARM` had no case for the ARMv5TE enhanced DSP instructions,
|
||||
so `smlabb` fell into data processing as `TST` (no destination written, flags
|
||||
changed). The kernel then read the wrong RAM-layer entry and refused a
|
||||
creation -106 ("system files corrupted" in the System Menu), in the fork only.
|
||||
|
||||
- `ARMCore::ExecuteEnhancedDSP`: SMLAxy, SMLAWy, SMULWy, SMLALxy, SMULxy,
|
||||
QADD, QSUB, QDADD, QDSUB, with the sticky Q flag (ARM Architecture
|
||||
Reference Manual semantics), decoded before the generic decoders.
|
||||
- `CanEmitARMDataProcessing` excludes these encodings, so the JIT leaves them
|
||||
to the interpreter.
|
||||
- Test `StarletARMCore.ARMv5TEEnhancedDSPInstructions`: eleven encodings from
|
||||
GNU as (`-march=armv5te`), interpreter and JIT, results, Q and NZCV
|
||||
untouched. 96 selected Starlet tests passed.
|
||||
- The companion's images use, besides these (16 `smlabb`, 4 `smulbb`), only
|
||||
`blx`, `clz`, `ldrd`, `strd` among the ARMv5TE additions; those were
|
||||
already handled.
|
||||
|
||||
## Broadway hardware window after the WiiConnect24 shutdown — 2026-10-06
|
||||
|
||||
Companion proposal J1 (approved). With the DSP fix, the companion's System
|
||||
Menu under Revolution reaches ES LAUNCH in the fork, while on the console it
|
||||
stops right after the WiiConnect24 shutdown reply (IOCTL 0x28), with no IPC
|
||||
until then. This lists what Broadway touches in that gap.
|
||||
|
||||
- `DOLPHIN_HW_WINDOW=1` (with `DOLPHIN_IPC_TRACE=1`): the IPC trace remembers
|
||||
an IOCTL 0x28 with 32-byte input and output; its reply opens a window, and
|
||||
`MMU::ReadFromHardware`/`WriteToHardware` log each Broadway MMIO access as
|
||||
`HWTRACE R|W address/size value PC` until Broadway's next IPC request (or
|
||||
20000 accesses). `IsOptimizableMMIOAccess` returns 0 while requested, so the
|
||||
JIT inlines no MMIO read. Nothing changes without the variables.
|
||||
- `MMU.cpp` and `WII_IPC.h` are now exact integration files (28).
|
||||
- 96 Starlet tests passed; both builds rebuilt.
|
||||
Reference in new issue
Block a user