IOS: boot original BootMii through MINI
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Core/HW/SI/SI.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@@ -151,11 +152,17 @@ void SerialInterfaceManager::RunSIBuffer(u64 user_data, s64 cycles_late)
|
||||
const s32 expected_response_length = ConvertSILengthField(m_com_csr.INLNGTH);
|
||||
|
||||
#if defined(_DEBUG)
|
||||
const std::vector<u8> request_copy(m_si_buffer.data(), m_si_buffer.data() + request_length);
|
||||
const std::vector<u8> request_copy(m_si_output_buffer.data(),
|
||||
m_si_output_buffer.data() + request_length);
|
||||
#endif
|
||||
|
||||
// The communication RAM is bidirectional, but the SI transfer engine retains the bytes written
|
||||
// by the CPU separately from the response it places in the readable RAM. In particular, a new
|
||||
// TSTART without another RAM write retransmits the previous request. BootMii relies on this
|
||||
// when it probes a GameCube controller twice during startup.
|
||||
auto transfer_buffer = m_si_output_buffer;
|
||||
auto* const device = m_channel[m_com_csr.CHANNEL].device.get();
|
||||
const s32 actual_response_length = device->RunBuffer(m_si_buffer.data(), request_length);
|
||||
const s32 actual_response_length = device->RunBuffer(transfer_buffer.data(), request_length);
|
||||
|
||||
DEBUG_LOG_FMT(SERIALINTERFACE,
|
||||
"RunSIBuffer chan: {} request_length: {} expected_response_length: {} "
|
||||
@@ -186,6 +193,11 @@ void SerialInterfaceManager::RunSIBuffer(u64 user_data, s64 cycles_late)
|
||||
// 2) Investigate the timeout period for NOREP0
|
||||
if (actual_response_length != 0)
|
||||
{
|
||||
if (actual_response_length > 0)
|
||||
{
|
||||
const size_t response_length = std::min<size_t>(actual_response_length, m_si_buffer.size());
|
||||
std::copy_n(transfer_buffer.begin(), response_length, m_si_buffer.begin());
|
||||
}
|
||||
m_com_csr.TSTART = 0;
|
||||
m_com_csr.COMERR = actual_response_length < 0;
|
||||
if (actual_response_length < 0)
|
||||
@@ -226,6 +238,7 @@ void SerialInterfaceManager::DoState(PointerWrap& p)
|
||||
p.Do(m_status_reg);
|
||||
p.Do(m_exi_clock_count);
|
||||
p.Do(m_si_buffer);
|
||||
p.Do(m_si_output_buffer);
|
||||
}
|
||||
|
||||
void SerialInterfaceManager::RegisterEvents()
|
||||
@@ -285,6 +298,7 @@ void SerialInterfaceManager::Init()
|
||||
// m_exi_clock_count.LOCK = 1;
|
||||
|
||||
m_si_buffer = {};
|
||||
m_si_output_buffer = {};
|
||||
}
|
||||
|
||||
void SerialInterfaceManager::Shutdown()
|
||||
@@ -312,6 +326,7 @@ void SerialInterfaceManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||
auto& si = system.GetSerialInterface();
|
||||
val = Common::swap32(val);
|
||||
std::memcpy(&si.m_si_buffer[i], &val, sizeof(val));
|
||||
std::memcpy(&si.m_si_output_buffer[i], &val, sizeof(val));
|
||||
}));
|
||||
}
|
||||
for (size_t i = 0; i < m_si_buffer.size(); i += sizeof(u16))
|
||||
@@ -328,6 +343,7 @@ void SerialInterfaceManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||
auto& si = system.GetSerialInterface();
|
||||
val = Common::swap16(val);
|
||||
std::memcpy(&si.m_si_buffer[i], &val, sizeof(val));
|
||||
std::memcpy(&si.m_si_output_buffer[i], &val, sizeof(val));
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ private:
|
||||
USIStatusReg m_status_reg;
|
||||
USIEXIClockCount m_exi_clock_count;
|
||||
std::array<u8, BUFFER_SIZE> m_si_buffer{};
|
||||
std::array<u8, BUFFER_SIZE> m_si_output_buffer{};
|
||||
|
||||
Core::System& m_system;
|
||||
};
|
||||
|
||||
@@ -60,6 +60,24 @@ constexpr bool IsDIAddress(u32 address)
|
||||
return address >= DI_BASE && address < DI_BASE + DI_SIZE;
|
||||
}
|
||||
|
||||
// Broadway sees the EXI register bank at 0x0d006800. Starlet reaches the same
|
||||
// physical controller through Hollywood's 0x0d806800 alias. The 0x40-byte
|
||||
// reset-vector aperture which follows it is separate and remains backed by
|
||||
// StarletMemory for boot2 to populate.
|
||||
constexpr u32 STARLET_EXI_BASE = 0x0d806800;
|
||||
constexpr u32 BROADWAY_EXI_BASE = 0x0d006800;
|
||||
constexpr u32 EXI_REGISTER_SIZE = 0x3c;
|
||||
|
||||
constexpr bool IsStarletEXIAddress(u32 address)
|
||||
{
|
||||
return address >= STARLET_EXI_BASE && address < STARLET_EXI_BASE + EXI_REGISTER_SIZE;
|
||||
}
|
||||
|
||||
constexpr u32 TranslateStarletEXIAddress(u32 address)
|
||||
{
|
||||
return BROADWAY_EXI_BASE + address - STARLET_EXI_BASE;
|
||||
}
|
||||
|
||||
constexpr u32 SHA_BASE = 0x0d030000;
|
||||
constexpr u32 SHA_CMD = SHA_BASE + 0x00;
|
||||
constexpr u32 SHA_SRC = SHA_BASE + 0x04;
|
||||
@@ -316,8 +334,15 @@ constexpr u32 HW_SRNPROT = HW_BASE + 0x60;
|
||||
constexpr u32 HW_AHBPROT = HW_BASE + 0x64;
|
||||
constexpr u32 HW_TIMER = HW_BASE + 0x10;
|
||||
constexpr u32 HW_ALARM = HW_BASE + 0x14;
|
||||
constexpr u32 HW_GPIO_ENABLE = HW_BASE + 0xdc;
|
||||
constexpr u32 HW_GPIO_OUT = HW_BASE + 0xe0;
|
||||
constexpr u32 HW_GPIO_DIR = HW_BASE + 0xe4;
|
||||
constexpr u32 HW_GPIO_IN = HW_BASE + 0xe8;
|
||||
constexpr u32 HW_GPIO_INTLVL = HW_BASE + 0xec;
|
||||
constexpr u32 HW_GPIO_INTFLAG = HW_BASE + 0xf0;
|
||||
constexpr u32 HW_GPIO_INTMASK = HW_BASE + 0xf4;
|
||||
constexpr u32 HW_GPIO_STRAPS = HW_BASE + 0xf8;
|
||||
constexpr u32 HW_GPIO_OWNER = HW_BASE + 0xfc;
|
||||
constexpr u32 HW_DIFLAGS = HW_BASE + 0x180;
|
||||
constexpr u32 HW_SPARE0 = HW_BASE + 0x188;
|
||||
constexpr u32 HW_BOOT0 = HW_BASE + 0x18c;
|
||||
@@ -374,6 +399,7 @@ constexpr u32 GPIO_EEP_CS = 0x400;
|
||||
constexpr u32 GPIO_EEP_CLK = 0x800;
|
||||
constexpr u32 GPIO_EEP_MOSI = 0x1000;
|
||||
constexpr u32 GPIO_EEP_MISO = 0x2000;
|
||||
constexpr u32 GPIO_VALID_MASK = 0x00ffffff;
|
||||
|
||||
constexpr u32 SRNPROT_SRAM_SPLIT_MODE = 1U << 5;
|
||||
constexpr u32 BOOT0_DISABLE = 1U << 12;
|
||||
@@ -523,6 +549,7 @@ void StarletMemory::Reset()
|
||||
m_ddr_seq_address = 0;
|
||||
m_ddr_bist_address = 0;
|
||||
m_gpio_out = 0;
|
||||
m_gpio_interrupt_flags = 0;
|
||||
m_seeprom_command = 0;
|
||||
m_seeprom_output = 0;
|
||||
m_seeprom_input = 0;
|
||||
@@ -663,6 +690,7 @@ void StarletMemory::DoState(PointerWrap& p)
|
||||
p.Do(m_ddr_seq_address);
|
||||
p.Do(m_ddr_bist_address);
|
||||
p.Do(m_gpio_out);
|
||||
p.Do(m_gpio_interrupt_flags);
|
||||
p.Do(m_seeprom_command);
|
||||
p.Do(m_seeprom_output);
|
||||
p.Do(m_seeprom_input);
|
||||
@@ -3029,7 +3057,17 @@ u8 StarletMemory::Read8(u32 address)
|
||||
}
|
||||
if (word_address == HW_GPIO_IN)
|
||||
{
|
||||
const u32 value = m_seeprom_miso ? GPIO_EEP_MISO : 0;
|
||||
const u32 value = GetGPIOInput();
|
||||
return static_cast<u8>(value >> (24 - (address & 3) * 8));
|
||||
}
|
||||
if (word_address == HW_GPIO_INTFLAG)
|
||||
{
|
||||
const u32 value = m_gpio_interrupt_flags;
|
||||
return static_cast<u8>(value >> (24 - (address & 3) * 8));
|
||||
}
|
||||
if (word_address == HW_GPIO_STRAPS)
|
||||
{
|
||||
constexpr u32 value = 0;
|
||||
return static_cast<u8>(value >> (24 - (address & 3) * 8));
|
||||
}
|
||||
if (IsEHCIAddress(word_address))
|
||||
@@ -3064,6 +3102,13 @@ u8 StarletMemory::Read8(u32 address)
|
||||
const u32 value = mmio ? mmio->Read<u32>(m_system, word_address) : 0;
|
||||
return static_cast<u8>(value >> (24 - (address & 3) * 8));
|
||||
}
|
||||
if (IsStarletEXIAddress(word_address))
|
||||
{
|
||||
MMIO::Mapping* const mmio = m_system.GetMemory().GetMMIOMapping();
|
||||
const u32 value =
|
||||
mmio ? mmio->Read<u32>(m_system, TranslateStarletEXIAddress(word_address)) : 0;
|
||||
return static_cast<u8>(value >> (24 - (address & 3) * 8));
|
||||
}
|
||||
if (word_address >= SDHC_BASE && word_address < SDHC_BASE + SDHC_SIZE)
|
||||
{
|
||||
const u32 value = ReadSDHCRegister(word_address);
|
||||
@@ -3136,6 +3181,11 @@ u32 StarletMemory::Read32(u32 address)
|
||||
MMIO::Mapping* const mmio = m_system.GetMemory().GetMMIOMapping();
|
||||
return mmio ? mmio->Read<u32>(m_system, address) : 0;
|
||||
}
|
||||
if ((address & 3) == 0 && IsStarletEXIAddress(address))
|
||||
{
|
||||
MMIO::Mapping* const mmio = m_system.GetMemory().GetMMIOMapping();
|
||||
return mmio ? mmio->Read<u32>(m_system, TranslateStarletEXIAddress(address)) : 0;
|
||||
}
|
||||
|
||||
if (IsMemoryAddress(address) && IsMemoryAddress(address + 3))
|
||||
return m_system.GetMemory().Read_U32(address);
|
||||
@@ -3223,6 +3273,20 @@ void StarletMemory::Write8(u32 address, u8 value)
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (IsStarletEXIAddress(word_address))
|
||||
{
|
||||
WriteMapped8(address, value);
|
||||
if ((address & 3) == 3)
|
||||
{
|
||||
MMIO::Mapping* const mmio = m_system.GetMemory().GetMMIOMapping();
|
||||
if (mmio)
|
||||
{
|
||||
mmio->Write<u32>(m_system, TranslateStarletEXIAddress(word_address),
|
||||
ReadRegister(word_address));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ehci &&
|
||||
(address == EHCI_BASE + EHCI_USB_STATUS || address == EHCI_BASE + EHCI_PORT_STATUS_1 ||
|
||||
address == EHCI_BASE + EHCI_PORT_STATUS_2))
|
||||
@@ -3307,6 +3371,28 @@ void StarletMemory::Write8(u32 address, u8 value)
|
||||
m_sram_split_mode = (word & SRNPROT_SRAM_SPLIT_MODE) != 0;
|
||||
else if (word_address == HW_GPIO_OUT)
|
||||
HandleGPIOWrite(word);
|
||||
else if (word_address == HW_GPIO_INTFLAG)
|
||||
{
|
||||
// Hollywood's GPIO interrupt flag is write-one-to-clear. A level-triggered
|
||||
// flag cannot be cleared while its input still matches the selected active
|
||||
// level. Keep that rule limited to already-latched flags; merely selecting
|
||||
// an active level does not itself invent an edge or a pending interrupt.
|
||||
const u32 pending_before = m_gpio_interrupt_flags;
|
||||
const u32 active = ~(GetGPIOInput() ^ ReadRegister(HW_GPIO_INTLVL)) & GPIO_VALID_MASK;
|
||||
m_gpio_interrupt_flags = (pending_before & ~word) | (pending_before & active);
|
||||
UpdateGPIOInterrupt();
|
||||
}
|
||||
else if (word_address == HW_GPIO_INTMASK || word_address == HW_GPIO_OWNER)
|
||||
{
|
||||
UpdateGPIOInterrupt();
|
||||
}
|
||||
else if (word_address == HW_GPIO_ENABLE || word_address == HW_GPIO_DIR ||
|
||||
word_address == HW_GPIO_INTLVL)
|
||||
{
|
||||
// These GPIO configuration registers are backed by m_registers. Mask off
|
||||
// the unused upper byte exposed by Hollywood's 24-pin bank.
|
||||
WriteMapped8(word_address, 0);
|
||||
}
|
||||
else if (ehci)
|
||||
HandleEHCIWrite(word_address);
|
||||
else if (ohci_controller)
|
||||
@@ -3350,6 +3436,13 @@ void StarletMemory::Write32(u32 address, u32 value)
|
||||
mmio->Write<u32>(m_system, address, value);
|
||||
return;
|
||||
}
|
||||
if ((address & 3) == 0 && IsStarletEXIAddress(address))
|
||||
{
|
||||
MMIO::Mapping* const mmio = m_system.GetMemory().GetMMIOMapping();
|
||||
if (mmio)
|
||||
mmio->Write<u32>(m_system, TranslateStarletEXIAddress(address), value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMemoryAddress(address) && IsMemoryAddress(address + 3))
|
||||
{
|
||||
@@ -4294,4 +4387,20 @@ void StarletMemory::HandleGPIOWrite(u32 value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 StarletMemory::GetGPIOInput() const
|
||||
{
|
||||
// POWER and EJECT are external active-high inputs and remain low until the
|
||||
// emulator grows explicit front-panel button events. The serial EEPROM MISO
|
||||
// line is driven by the emulated SEEPROM state machine.
|
||||
return m_seeprom_miso ? GPIO_EEP_MISO : 0;
|
||||
}
|
||||
|
||||
void StarletMemory::UpdateGPIOInterrupt()
|
||||
{
|
||||
const u32 mask = ReadRegister(HW_GPIO_INTMASK);
|
||||
const u32 owner = ReadRegister(HW_GPIO_OWNER);
|
||||
m_system.GetWiiIPC().SetStarletInterrupt(
|
||||
INT_CAUSE_GPIO_STARLET, (m_gpio_interrupt_flags & mask & ~owner & GPIO_VALID_MASK) != 0);
|
||||
}
|
||||
} // namespace IOS::LLE
|
||||
|
||||
@@ -177,6 +177,8 @@ private:
|
||||
void CompressSHA1(const u8* block);
|
||||
void HandleOTPCommand(u32 command);
|
||||
void HandleGPIOWrite(u32 value);
|
||||
u32 GetGPIOInput() const;
|
||||
void UpdateGPIOInterrupt();
|
||||
u32 GetTimer() const;
|
||||
|
||||
Core::System& m_system;
|
||||
@@ -254,6 +256,7 @@ private:
|
||||
u16 m_ddr_seq_address = 0;
|
||||
u16 m_ddr_bist_address = 0;
|
||||
u32 m_gpio_out = 0;
|
||||
u32 m_gpio_interrupt_flags = 0;
|
||||
u16 m_seeprom_command = 0;
|
||||
u16 m_seeprom_output = 0;
|
||||
u16 m_seeprom_input = 0;
|
||||
|
||||
@@ -178,6 +178,41 @@ TEST(StarletTimer, ZeroDelayAlarmMatchesImmediatelyAndUsesIRQW1C)
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_TIMER, INT_CAUSE_TIMER);
|
||||
}
|
||||
|
||||
TEST(StarletGPIO, InterruptFlagIsWriteOneToClear)
|
||||
{
|
||||
constexpr u32 hardware_base = 0x0d800000;
|
||||
constexpr u32 gpio_interrupt_level = hardware_base + 0xec;
|
||||
constexpr u32 gpio_interrupt_flag = hardware_base + 0xf0;
|
||||
constexpr u32 gpio_interrupt_mask = hardware_base + 0xf4;
|
||||
constexpr u32 gpio_owner = hardware_base + 0xfc;
|
||||
constexpr u32 power = 1;
|
||||
Core::DeclareAsCPUThread();
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetWiiIPC().Reset();
|
||||
StarletMemory memory(system);
|
||||
memory.Reset();
|
||||
const auto read_word = [&memory](u32 address) {
|
||||
return static_cast<u32>(memory.Read8(address)) << 24 |
|
||||
static_cast<u32>(memory.Read8(address + 1)) << 16 |
|
||||
static_cast<u32>(memory.Read8(address + 2)) << 8 | memory.Read8(address + 3);
|
||||
};
|
||||
const auto write_word = [&memory](u32 address, u32 value) {
|
||||
memory.Write8(address, static_cast<u8>(value >> 24));
|
||||
memory.Write8(address + 1, static_cast<u8>(value >> 16));
|
||||
memory.Write8(address + 2, static_cast<u8>(value >> 8));
|
||||
memory.Write8(address + 3, static_cast<u8>(value));
|
||||
};
|
||||
|
||||
// BootMii selects POWER as active-high, waits, then clears the pending flag.
|
||||
// With the emulated front-panel button idle-low this must read back cleared.
|
||||
write_word(gpio_interrupt_level, power);
|
||||
write_word(gpio_interrupt_mask, power);
|
||||
write_word(gpio_owner, 0);
|
||||
write_word(gpio_interrupt_flag, power);
|
||||
EXPECT_EQ(read_word(gpio_interrupt_flag), 0u);
|
||||
EXPECT_EQ(system.GetWiiIPC().ReadStarletRegister(0x38) & INT_CAUSE_GPIO_STARLET, 0u);
|
||||
}
|
||||
|
||||
TEST(StarletAHBPROT, OriginalIOSMaskIsPreservedForBroadway)
|
||||
{
|
||||
constexpr u32 ahbprot = 0x0d800064;
|
||||
|
||||
Reference in New Issue
Block a user