mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-05-20 12:58:05 -04:00
Compare commits
44 commits
b88fa57d13
...
825a464ef6
Author | SHA1 | Date | |
---|---|---|---|
825a464ef6 | |||
50386c4e39 | |||
756ea81ab2 | |||
307a1e3ab8 | |||
d1bf5258cc | |||
d22f42a08a | |||
b5a6fc11bc | |||
cbe5811833 | |||
0a08247608 | |||
6b7444cdd6 | |||
0766f1c930 | |||
dae01cc143 | |||
fb0b86dbf8 | |||
e372ebfffd | |||
3d32c6b438 | |||
302b64da17 | |||
98c1826855 | |||
f41d68f9e5 | |||
0c0ad48e7f | |||
89f77a1bd9 | |||
d30d3cc595 | |||
0b89a5f8cf | |||
9646fd34c9 | |||
749da1e70d | |||
fe3227da74 | |||
1faab5998c | |||
f057491259 | |||
fe9162305e | |||
8474bdc6a1 | |||
582dd3547f | |||
4a13f8451b | |||
b66b241757 | |||
ce85b22776 | |||
5300bde6fd | |||
e641198e1d | |||
b88811daf8 | |||
0b050859b3 | |||
0d677e6569 | |||
f0798d149b | |||
32b10ac762 | |||
452740e7f8 | |||
1afc1deb90 | |||
a68f4f269d | |||
c6c812654b |
|
@ -446,4 +446,48 @@ u32 ComputeCRC32(std::string_view data)
|
|||
{
|
||||
return ComputeCRC32(reinterpret_cast<const u8*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
u8 HashCrc7(const u8* ptr, size_t length)
|
||||
{
|
||||
// Used for SD cards
|
||||
constexpr u8 CRC_POLYNOMIAL = 0x09;
|
||||
|
||||
u8 result = 0;
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
// TODO: Cache in a table
|
||||
result ^= ptr[i];
|
||||
for (auto bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if (result & 0x80)
|
||||
result = (result << 1) ^ (CRC_POLYNOMIAL << 1);
|
||||
else
|
||||
result = result << 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result >> 1;
|
||||
}
|
||||
|
||||
u16 HashCrc16(const u8* ptr, size_t length)
|
||||
{
|
||||
// Specifically CRC-16-CCITT, used for SD cards
|
||||
constexpr u16 CRC_POLYNOMIAL = 0x1021;
|
||||
|
||||
u16 result = 0;
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
// TODO: Cache in a table
|
||||
result ^= (ptr[i] << 8);
|
||||
for (auto bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if (result & 0x8000)
|
||||
result = (result << 1) ^ CRC_POLYNOMIAL;
|
||||
else
|
||||
result = result << 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace Common
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -21,4 +22,19 @@ u32 StartCRC32();
|
|||
u32 UpdateCRC32(u32 crc, const u8* data, size_t len);
|
||||
u32 ComputeCRC32(const u8* data, size_t len);
|
||||
u32 ComputeCRC32(std::string_view data);
|
||||
|
||||
// For SD card emulation
|
||||
u8 HashCrc7(const u8* ptr, size_t length);
|
||||
u16 HashCrc16(const u8* ptr, size_t length);
|
||||
|
||||
template <size_t N>
|
||||
u8 HashCrc7(const std::array<u8, N>& data)
|
||||
{
|
||||
return HashCrc7(data.data(), N);
|
||||
}
|
||||
template <size_t N>
|
||||
u16 HashCrc16(const std::array<u8, N>& data)
|
||||
{
|
||||
return HashCrc16(data.data(), N);
|
||||
}
|
||||
} // namespace Common
|
||||
|
|
|
@ -217,6 +217,8 @@ add_library(core
|
|||
HW/EXI/EXI_DeviceMic.h
|
||||
HW/EXI/EXI_DeviceModem.cpp
|
||||
HW/EXI/EXI_DeviceModem.h
|
||||
HW/EXI/EXI_DeviceSD.cpp
|
||||
HW/EXI/EXI_DeviceSD.h
|
||||
HW/EXI/EXI.cpp
|
||||
HW/EXI/EXI.h
|
||||
HW/GBAPad.cpp
|
||||
|
|
|
@ -111,12 +111,29 @@ const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot
|
|||
|
||||
const Info<int> MAIN_MEMORY_CARD_SIZE{{System::Main, "Core", "MemoryCardSize"}, -1};
|
||||
|
||||
const Info<std::string> MAIN_SLOT_A_SD_CARD_PATH{{System::Main, "Core", "SlotASDCardPath"}, ""};
|
||||
const Info<std::string> MAIN_SLOT_B_SD_CARD_PATH{{System::Main, "Core", "SlotBSDCardPath"}, ""};
|
||||
const Info<std::string> MAIN_SP2_SD_CARD_PATH{{System::Main, "Core", "SP2SDCardPath"}, ""};
|
||||
const Info<std::string>& GetInfoForSDCardPath(ExpansionInterface::Slot slot)
|
||||
{
|
||||
ASSERT(slot != ExpansionInterface::Slot::SP1);
|
||||
static constexpr Common::EnumMap<const Info<std::string>*, ExpansionInterface::MAX_SLOT> infos{
|
||||
&MAIN_SLOT_A_SD_CARD_PATH,
|
||||
&MAIN_SLOT_B_SD_CARD_PATH,
|
||||
nullptr,
|
||||
&MAIN_SP2_SD_CARD_PATH,
|
||||
};
|
||||
return *infos[slot];
|
||||
}
|
||||
|
||||
const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_A{
|
||||
{System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDeviceType::MemoryCardFolder};
|
||||
const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_B{{System::Main, "Core", "SlotB"},
|
||||
ExpansionInterface::EXIDeviceType::None};
|
||||
const Info<ExpansionInterface::EXIDeviceType> MAIN_SERIAL_PORT_1{
|
||||
{System::Main, "Core", "SerialPort1"}, ExpansionInterface::EXIDeviceType::None};
|
||||
const Info<ExpansionInterface::EXIDeviceType> MAIN_SERIAL_PORT_2{
|
||||
{System::Main, "Core", "SerialPort2"}, ExpansionInterface::EXIDeviceType::None};
|
||||
|
||||
const Info<ExpansionInterface::EXIDeviceType>& GetInfoForEXIDevice(ExpansionInterface::Slot slot)
|
||||
{
|
||||
|
@ -126,6 +143,7 @@ const Info<ExpansionInterface::EXIDeviceType>& GetInfoForEXIDevice(ExpansionInte
|
|||
&MAIN_SLOT_A,
|
||||
&MAIN_SLOT_B,
|
||||
&MAIN_SERIAL_PORT_1,
|
||||
&MAIN_SERIAL_PORT_2,
|
||||
};
|
||||
return *infos[slot];
|
||||
}
|
||||
|
|
|
@ -87,9 +87,14 @@ extern const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE;
|
|||
extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE;
|
||||
const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot);
|
||||
extern const Info<int> MAIN_MEMORY_CARD_SIZE;
|
||||
extern const Info<std::string> MAIN_SLOT_A_SD_CARD_PATH;
|
||||
extern const Info<std::string> MAIN_SLOT_B_SD_CARD_PATH;
|
||||
extern const Info<std::string> MAIN_SP2_SD_CARD_PATH;
|
||||
const Info<std::string>& GetInfoForSDCardPath(ExpansionInterface::Slot slot);
|
||||
extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_A;
|
||||
extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SLOT_B;
|
||||
extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SERIAL_PORT_1;
|
||||
extern const Info<ExpansionInterface::EXIDeviceType> MAIN_SERIAL_PORT_2;
|
||||
const Info<ExpansionInterface::EXIDeviceType>& GetInfoForEXIDevice(ExpansionInterface::Slot slot);
|
||||
extern const Info<std::string> MAIN_BBA_MAC;
|
||||
extern const Info<std::string> MAIN_BBA_XLINK_IP;
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace HLE
|
|||
static std::map<u32, u32> s_hooked_addresses;
|
||||
|
||||
// clang-format off
|
||||
constexpr std::array<Hook, 23> os_patches{{
|
||||
constexpr std::array<Hook, 24> os_patches{{
|
||||
// Placeholder, os_patches[0] is the "non-existent function" index
|
||||
{"FAKE_TO_SKIP_0", HLE_Misc::UnimplementedFunction, HookType::Replace, HookFlag::Generic},
|
||||
|
||||
|
|
|
@ -76,6 +76,8 @@ u8 SlotToEXIChannel(Slot slot)
|
|||
return 1;
|
||||
case Slot::SP1:
|
||||
return 0;
|
||||
case Slot::SP2:
|
||||
return 2;
|
||||
default:
|
||||
PanicAlertFmt("Unhandled slot {}", slot);
|
||||
return 0;
|
||||
|
@ -92,6 +94,8 @@ u8 SlotToEXIDevice(Slot slot)
|
|||
return 0;
|
||||
case Slot::SP1:
|
||||
return 2;
|
||||
case Slot::SP2:
|
||||
return 0;
|
||||
default:
|
||||
PanicAlertFmt("Unhandled slot {}", slot);
|
||||
return 0;
|
||||
|
@ -143,7 +147,8 @@ void ExpansionInterfaceManager::Init(const Sram* override_sram)
|
|||
m_channels[0]->AddDevice(EXIDeviceType::MaskROM, 1);
|
||||
m_channels[SlotToEXIChannel(Slot::SP1)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_1),
|
||||
SlotToEXIDevice(Slot::SP1));
|
||||
m_channels[2]->AddDevice(EXIDeviceType::AD16, 0);
|
||||
m_channels[SlotToEXIChannel(Slot::SP2)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_2),
|
||||
SlotToEXIDevice(Slot::SP2));
|
||||
|
||||
m_event_type_change_device = core_timing.RegisterEvent("ChangeEXIDevice", ChangeDeviceCallback);
|
||||
m_event_type_update_interrupts =
|
||||
|
|
|
@ -44,11 +44,12 @@ enum class Slot : int
|
|||
A,
|
||||
B,
|
||||
SP1,
|
||||
SP2,
|
||||
};
|
||||
// Note: using auto here results in a false warning on GCC
|
||||
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80351
|
||||
constexpr std::initializer_list<Slot> SLOTS = {Slot::A, Slot::B, Slot::SP1};
|
||||
constexpr auto MAX_SLOT = Slot::SP1;
|
||||
constexpr std::initializer_list<Slot> SLOTS = {Slot::A, Slot::B, Slot::SP1, Slot::SP2};
|
||||
constexpr auto MAX_SLOT = Slot::SP2;
|
||||
constexpr std::initializer_list<Slot> MEMCARD_SLOTS = {Slot::A, Slot::B};
|
||||
constexpr auto MAX_MEMCARD_SLOT = Slot::B;
|
||||
constexpr bool IsMemcardSlot(Slot slot)
|
||||
|
@ -106,5 +107,5 @@ private:
|
|||
template <>
|
||||
struct fmt::formatter<ExpansionInterface::Slot> : EnumFormatter<ExpansionInterface::MAX_SLOT>
|
||||
{
|
||||
constexpr formatter() : EnumFormatter({"Slot A", "Slot B", "Serial Port 1"}) {}
|
||||
constexpr formatter() : EnumFormatter({"Slot A", "Slot B", "Serial Port 1", "Serial Port 2"}) {}
|
||||
};
|
||||
|
|
|
@ -87,10 +87,16 @@ void CEXIChannel::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
if (m_channel_id == 0)
|
||||
m_status.ROMDIS = new_status.ROMDIS;
|
||||
|
||||
IEXIDevice* device = GetDevice(m_status.CHIP_SELECT ^ new_status.CHIP_SELECT);
|
||||
for (int device = 0; device < NUM_DEVICES; device++)
|
||||
{
|
||||
if (m_devices[device])
|
||||
{
|
||||
bool was_selected = m_status.CHIP_SELECT & (1 << device);
|
||||
bool is_selected = new_status.CHIP_SELECT & (1 << device);
|
||||
m_devices[device]->SetCS(new_status.CHIP_SELECT, was_selected, is_selected);
|
||||
}
|
||||
}
|
||||
m_status.CHIP_SELECT = new_status.CHIP_SELECT;
|
||||
if (device != nullptr)
|
||||
device->SetCS(m_status.CHIP_SELECT);
|
||||
|
||||
system.GetExpansionInterface().UpdateInterrupts();
|
||||
}));
|
||||
|
@ -224,6 +230,11 @@ IEXIDevice* CEXIChannel::GetDevice(const u8 chip_select)
|
|||
{
|
||||
switch (chip_select)
|
||||
{
|
||||
case 0: // SD responds when the CS signal is 0, instead of 1.
|
||||
if (m_devices[0] && m_devices[0]->m_device_type == EXIDeviceType::SD)
|
||||
return m_devices[0].get();
|
||||
else
|
||||
return nullptr;
|
||||
case 1:
|
||||
return m_devices[0].get();
|
||||
case 2:
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceMic.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceModem.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceSD.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
|
@ -82,7 +83,7 @@ bool IEXIDevice::IsPresent() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void IEXIDevice::SetCS(int cs)
|
||||
void IEXIDevice::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -162,6 +163,10 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(Core::System& system, const EXIDevi
|
|||
result = std::make_unique<CEXIAgp>(system, slot);
|
||||
break;
|
||||
|
||||
case EXIDeviceType::SD:
|
||||
result = std::make_unique<CEXISD>(system, channel_num);
|
||||
break;
|
||||
|
||||
case EXIDeviceType::AMBaseboard:
|
||||
case EXIDeviceType::None:
|
||||
default:
|
||||
|
|
|
@ -42,6 +42,7 @@ enum class EXIDeviceType : int
|
|||
EthernetTapServer,
|
||||
EthernetBuiltIn,
|
||||
ModemTapServer,
|
||||
SD,
|
||||
None = 0xFF
|
||||
};
|
||||
|
||||
|
@ -62,7 +63,7 @@ public:
|
|||
|
||||
virtual bool UseDelayedTransferCompletion() const;
|
||||
virtual bool IsPresent() const;
|
||||
virtual void SetCS(int cs);
|
||||
virtual void SetCS(u32 cs, bool was_selected, bool is_selected);
|
||||
virtual void DoState(PointerWrap& p);
|
||||
|
||||
// Is generating interrupt ?
|
||||
|
@ -88,7 +89,7 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(Core::System& system, EXIDeviceType
|
|||
|
||||
template <>
|
||||
struct fmt::formatter<ExpansionInterface::EXIDeviceType>
|
||||
: EnumFormatter<ExpansionInterface::EXIDeviceType::ModemTapServer>
|
||||
: EnumFormatter<ExpansionInterface::EXIDeviceType::SD>
|
||||
{
|
||||
static constexpr array_type names = {
|
||||
_trans("Dummy"),
|
||||
|
@ -106,6 +107,7 @@ struct fmt::formatter<ExpansionInterface::EXIDeviceType>
|
|||
_trans("Broadband Adapter (tapserver)"),
|
||||
_trans("Broadband Adapter (HLE)"),
|
||||
_trans("Modem Adapter (tapserver)"),
|
||||
_trans("SD Adapter"),
|
||||
};
|
||||
|
||||
constexpr formatter() : EnumFormatter(names) {}
|
||||
|
|
|
@ -13,9 +13,9 @@ CEXIAD16::CEXIAD16(Core::System& system) : IEXIDevice(system)
|
|||
{
|
||||
}
|
||||
|
||||
void CEXIAD16::SetCS(int cs)
|
||||
void CEXIAD16::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
if (cs)
|
||||
if (!was_selected && is_selected)
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ class CEXIAD16 : public IEXIDevice
|
|||
{
|
||||
public:
|
||||
explicit CEXIAD16(Core::System& system);
|
||||
void SetCS(int cs) override;
|
||||
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
bool IsPresent() const override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ u32 CEXIDummy::ImmRead(u32 size)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void CEXIDummy::ImmReadWrite(u32& data, u32 size)
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI Dummy {} ImmReadWrite {}", m_name, data);
|
||||
}
|
||||
|
||||
void CEXIDummy::DMAWrite(u32 address, u32 size)
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI DUMMY {} DMAWrite: {:08x} bytes, from {:08x} to device",
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
|
||||
void ImmWrite(u32 data, u32 size) override;
|
||||
u32 ImmRead(u32 size) override;
|
||||
void ImmReadWrite(u32& data, u32 size) override;
|
||||
|
||||
void DMAWrite(u32 address, u32 size) override;
|
||||
void DMARead(u32 address, u32 size) override;
|
||||
|
|
|
@ -103,9 +103,9 @@ CEXIETHERNET::~CEXIETHERNET()
|
|||
m_network_interface->Deactivate();
|
||||
}
|
||||
|
||||
void CEXIETHERNET::SetCS(int cs)
|
||||
void CEXIETHERNET::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
if (cs)
|
||||
if (!was_selected && is_selected)
|
||||
{
|
||||
// Invalidate the previous transfer
|
||||
transfer.valid = false;
|
||||
|
|
|
@ -216,7 +216,7 @@ class CEXIETHERNET : public IEXIDevice
|
|||
public:
|
||||
CEXIETHERNET(Core::System& system, BBADeviceType type);
|
||||
virtual ~CEXIETHERNET();
|
||||
void SetCS(int cs) override;
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
bool IsPresent() const override;
|
||||
bool IsInterruptSet() override;
|
||||
void ImmWrite(u32 data, u32 size) override;
|
||||
|
|
|
@ -246,9 +246,9 @@ void CEXIIPL::LoadFontFile(const std::string& filename, u32 offset)
|
|||
m_fonts_loaded = true;
|
||||
}
|
||||
|
||||
void CEXIIPL::SetCS(int cs)
|
||||
void CEXIIPL::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
if (cs)
|
||||
if (!was_selected && is_selected)
|
||||
{
|
||||
m_command_bytes_received = 0;
|
||||
m_cursor = 0;
|
||||
|
|
|
@ -19,7 +19,7 @@ public:
|
|||
explicit CEXIIPL(Core::System& system);
|
||||
~CEXIIPL() override;
|
||||
|
||||
void SetCS(int cs) override;
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
bool IsPresent() const override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
|
|
|
@ -276,9 +276,9 @@ void CEXIMemoryCard::CmdDoneLater(u64 cycles)
|
|||
core_timing.ScheduleEvent(cycles, s_et_cmd_done[m_card_slot], static_cast<u64>(m_card_slot));
|
||||
}
|
||||
|
||||
void CEXIMemoryCard::SetCS(int cs)
|
||||
void CEXIMemoryCard::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
if (cs) // not-selected to selected
|
||||
if (!was_selected && is_selected)
|
||||
{
|
||||
m_position = 0;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public:
|
|||
CEXIMemoryCard(Core::System& system, Slot slot, bool gci_folder,
|
||||
const Memcard::HeaderData& header_data);
|
||||
~CEXIMemoryCard() override;
|
||||
void SetCS(int cs) override;
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
bool IsInterruptSet() override;
|
||||
bool UseDelayedTransferCompletion() const override;
|
||||
bool IsPresent() const override;
|
||||
|
|
|
@ -254,9 +254,9 @@ bool CEXIMic::IsPresent() const
|
|||
return true;
|
||||
}
|
||||
|
||||
void CEXIMic::SetCS(int cs)
|
||||
void CEXIMic::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
if (cs) // not-selected to selected
|
||||
if (!was_selected && is_selected)
|
||||
m_position = 0;
|
||||
// Doesn't appear to do anything we care about
|
||||
// else if (command == cmdReset)
|
||||
|
|
|
@ -19,7 +19,7 @@ class CEXIMic : public IEXIDevice
|
|||
public:
|
||||
CEXIMic(Core::System& system, const int index);
|
||||
virtual ~CEXIMic();
|
||||
void SetCS(int cs) override;
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
bool IsInterruptSet() override;
|
||||
bool IsPresent() const override;
|
||||
|
||||
|
|
|
@ -53,9 +53,10 @@ bool CEXIModem::IsPresent() const
|
|||
return true;
|
||||
}
|
||||
|
||||
void CEXIModem::SetCS(int cs)
|
||||
void CEXIModem::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR;
|
||||
if (!was_selected && is_selected)
|
||||
m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR;
|
||||
}
|
||||
|
||||
bool CEXIModem::IsInterruptSet()
|
||||
|
|
|
@ -34,7 +34,7 @@ class CEXIModem : public IEXIDevice
|
|||
public:
|
||||
CEXIModem(Core::System& system, ModemDeviceType type);
|
||||
virtual ~CEXIModem();
|
||||
void SetCS(int cs) override;
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
bool IsPresent() const override;
|
||||
bool IsInterruptSet() override;
|
||||
void ImmWrite(u32 data, u32 size) override;
|
||||
|
|
703
Source/Core/Core/HW/EXI/EXI_DeviceSD.cpp
Normal file
703
Source/Core/Core/HW/EXI/EXI_DeviceSD.cpp
Normal file
|
@ -0,0 +1,703 @@
|
|||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceSD.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Hash.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
CEXISD::CEXISD(Core::System& system, int channel_num) : IEXIDevice(system)
|
||||
{
|
||||
ASSERT_MSG(EXPANSIONINTERFACE, 0 <= channel_num && channel_num <= 2,
|
||||
"Trying to create invalid SD card index {}.", channel_num);
|
||||
|
||||
// TODO: I don't like using channel_num here; Slot would be better
|
||||
std::string filename;
|
||||
if (channel_num == 0)
|
||||
filename = Config::Get(Config::MAIN_SLOT_A_SD_CARD_PATH);
|
||||
else if (channel_num == 1)
|
||||
filename = Config::Get(Config::MAIN_SLOT_B_SD_CARD_PATH);
|
||||
else
|
||||
filename = Config::Get(Config::MAIN_SP2_SD_CARD_PATH);
|
||||
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "Reading SD card {}", filename);
|
||||
m_card.Open(filename, "r+b");
|
||||
if (!m_card)
|
||||
{
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE,
|
||||
"Failed to open SD Card image, trying to create a new 128 MB image...");
|
||||
m_card.Open(filename, "wb");
|
||||
// NOTE: Not using Common::SDCardCreate here yet, to test games formatting the card
|
||||
// themselves.
|
||||
if (m_card)
|
||||
{
|
||||
m_card.Resize(0x8000000);
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "Successfully created {}", filename);
|
||||
m_card.Open(filename, "r+b");
|
||||
}
|
||||
if (!m_card)
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE,
|
||||
"Could not open SD Card image or create a new one, are you running "
|
||||
"from a read-only directory?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CEXISD::ImmWrite(u32 data, u32 size)
|
||||
{
|
||||
while (size--)
|
||||
{
|
||||
u8 byte = data >> 24;
|
||||
WriteByte(byte);
|
||||
data <<= 8;
|
||||
}
|
||||
}
|
||||
|
||||
u32 CEXISD::ImmRead(u32 size)
|
||||
{
|
||||
u32 res = 0;
|
||||
u32 position = 0;
|
||||
while (size--)
|
||||
{
|
||||
u8 byte = ReadByte();
|
||||
res |= byte << (24 - (position++ * 8));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void CEXISD::ImmReadWrite(u32& data, u32 size)
|
||||
{
|
||||
ImmWrite(data, size);
|
||||
data = ImmRead(size);
|
||||
}
|
||||
|
||||
void CEXISD::SetCS(u32 cs, bool was_selected, bool is_selected)
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI SD SetCS: {}", cs);
|
||||
}
|
||||
|
||||
bool CEXISD::IsPresent() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CEXISD::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(state);
|
||||
p.Do(command_position);
|
||||
p.DoArray(command_buffer);
|
||||
p.Do(response);
|
||||
p.Do(block_position);
|
||||
p.DoArray(block_buffer);
|
||||
p.Do(address);
|
||||
p.Do(block_crc);
|
||||
}
|
||||
|
||||
void CEXISD::WriteByte(u8 byte)
|
||||
{
|
||||
if (state == State::SingleBlockRead || state == State::MultipleBlockRead)
|
||||
{
|
||||
WriteForBlockRead(byte);
|
||||
}
|
||||
else if (state == State::SingleBlockWrite || state == State::MultipleBlockWrite)
|
||||
{
|
||||
WriteForBlockWrite(byte);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Write-protect inversion(?)
|
||||
if (command_position == 0)
|
||||
{
|
||||
if ((byte & 0b11000000) == 0b01000000)
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI SD command started: {:02x}", byte);
|
||||
command_buffer[command_position++] = byte;
|
||||
}
|
||||
}
|
||||
else if (command_position < 6)
|
||||
{
|
||||
command_buffer[command_position++] = byte;
|
||||
|
||||
if (command_position == 6)
|
||||
{
|
||||
// Buffer now full
|
||||
command_position = 0;
|
||||
|
||||
if ((byte & 1) == 0)
|
||||
{
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE, "EXI SD command invalid, missing end bit: got {:02x}",
|
||||
byte);
|
||||
// Guessed behavior; I'm not sure if this is actually what should be done
|
||||
response.push_back(static_cast<u8>(R1::CommunicationCRCError));
|
||||
return;
|
||||
}
|
||||
|
||||
// Per § 7.2.2 Bus Transfer Protection, the CRC is actually optional in SPI mode and
|
||||
// defaults to disabled (except for the inital GoIdleState command, which is technically in
|
||||
// SD mode, and SendInterfaceCond, which presumably was deemed important enough due to
|
||||
// containing voltage information).
|
||||
|
||||
// Datel, being datel, doesn't include a CRC, instead sending 0xff when a CRC is not
|
||||
// required. Libogc and Nintendo titles also don't enable CRCs, but still send correct ones.
|
||||
u8 hash = (Common::HashCrc7(command_buffer.data(), 5) << 1) | 1;
|
||||
if (byte != hash)
|
||||
{
|
||||
Command command = static_cast<Command>(command_buffer[0] & 0x3f);
|
||||
|
||||
if (byte != 0xff || command == Command::GoIdleState ||
|
||||
command == Command::SendInterfaceCond)
|
||||
{
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE,
|
||||
"EXI SD command invalid, incorrect CRC7: got {:02x}, should be {:02x}",
|
||||
byte, hash);
|
||||
response.push_back(static_cast<u8>(R1::CommunicationCRCError));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE,
|
||||
"EXI SD command invalid, incorrect CRC7: got {:02x}, should be {:02x}"
|
||||
"; ignoring because Datel doesn't include a CRC",
|
||||
byte, hash);
|
||||
}
|
||||
}
|
||||
|
||||
u8 command = command_buffer[0] & 0x3f;
|
||||
u32 argument = command_buffer[1] << 24 | command_buffer[2] << 16 | command_buffer[3] << 8 |
|
||||
command_buffer[4];
|
||||
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI SD command received: {:02x} {:08x}", command,
|
||||
argument);
|
||||
|
||||
if (state == State::ReadyForAppCommand)
|
||||
{
|
||||
state = State::ReadyForCommand;
|
||||
HandleAppCommand(static_cast<AppCommand>(command), argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleCommand(static_cast<Command>(command), argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CEXISD::HandleCommand(Command command, u32 argument)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case Command::GoIdleState:
|
||||
response.push_back(static_cast<u8>(R1::InIdleState));
|
||||
break;
|
||||
case Command::SendOpCond:
|
||||
{
|
||||
// Used by libogc for non-SDHC cards
|
||||
bool hcs = argument & (1 << 30); // Host Capacity Support (for SDHC/SDXC cards)
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "SD Host Capacity Support: {}", hcs);
|
||||
response.push_back(0); // R1 - not idle
|
||||
break;
|
||||
}
|
||||
case Command::SendInterfaceCond:
|
||||
{
|
||||
u8 supply_voltage = (argument >> 8) & 0xf;
|
||||
u8 check_pattern = argument & 0xff;
|
||||
// Format R7
|
||||
response.push_back(static_cast<u8>(R1::InIdleState)); // R1
|
||||
response.push_back(0); // Command version nybble (0), reserved
|
||||
response.push_back(0); // Reserved
|
||||
response.push_back(supply_voltage); // Reserved + voltage
|
||||
response.push_back(check_pattern);
|
||||
break;
|
||||
}
|
||||
case Command::SendCSD:
|
||||
{
|
||||
u64 size = m_card.GetSize();
|
||||
|
||||
// 2048 bytes/sector
|
||||
// We could make this dynamic to support a wider range of file sizes
|
||||
constexpr u32 read_bl_len = 11;
|
||||
|
||||
// size = (c_size + 1) * (1 << (2 + c_size_mult + read_bl_len))
|
||||
u32 c_size_mult = 0;
|
||||
bool invalid_size = false;
|
||||
while (size > 4096)
|
||||
{
|
||||
invalid_size |= size & 1;
|
||||
size >>= 1;
|
||||
if (++c_size_mult >= 8 + 2 + read_bl_len)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_SD, "SD Card is too big!");
|
||||
// Set max values
|
||||
size = 4096;
|
||||
c_size_mult = 7 + 2 + read_bl_len;
|
||||
}
|
||||
}
|
||||
c_size_mult -= 2 + read_bl_len;
|
||||
--size;
|
||||
const u32 c_size(size);
|
||||
|
||||
if (invalid_size)
|
||||
WARN_LOG_FMT(IOS_SD, "SD Card size is invalid");
|
||||
else
|
||||
INFO_LOG_FMT(IOS_SD, "SD C_SIZE = {}, C_SIZE_MULT = {}", c_size, c_size_mult);
|
||||
|
||||
// R1
|
||||
response.push_back(0);
|
||||
// Data ready token
|
||||
response.push_back(START_BLOCK);
|
||||
// CSD
|
||||
// 0b00 CSD_STRUCTURE (SDv1)
|
||||
// 0b000000 reserved
|
||||
// 0b01111111 TAAC (8.0 * 10ms)
|
||||
// 0b00000000 NSAC
|
||||
// 0b00110010 TRAN_SPEED (2.5 * 10 Mbit/s, max operating frequency)
|
||||
|
||||
// 0b010110110101 CCC
|
||||
// 0b1111 READ_BL_LEN (2048 bytes)
|
||||
// 0b1 READ_BL_PARTIAL
|
||||
// 0b0 WRITE_BL_MISALIGN
|
||||
// 0b0 READ_BLK_MISALIGN
|
||||
// 0b0 DSR_IMP (no driver stage register implemented)
|
||||
// 0b00 reserved
|
||||
// 0b?????????? C_SIZE (most significant 10 bits)
|
||||
|
||||
// 0b?? C_SIZE (least significant 2 bits)
|
||||
// 0b111 VDD_R_CURR_MIN (100 mA)
|
||||
// 0b111 VDD_R_CURR_MAX (100 mA)
|
||||
// 0b111 VDD_W_CURR_MIN (100 mA)
|
||||
// 0b111 VDD_W_CURR_MAX (100 mA)
|
||||
// 0b??? C_SIZE_MULT
|
||||
// 0b1 ERASE_BLK_EN (erase unit = 512 bytes)
|
||||
// 0b1111111 SECTOR_SIZE (128 write blocks)
|
||||
// 0b0000000 WP_GRP_SIZE
|
||||
|
||||
// 0b0 WP_GRP_ENABLE (no write protection)
|
||||
// 0b00 reserved
|
||||
// 0b001 R2W_FACTOR (write half as fast as read)
|
||||
// 0b1111 WRITE_BL_LEN (= READ_BL_LEN)
|
||||
// 0b0 WRITE_BL_PARTIAL (no partial block writes)
|
||||
// 0b00000 reserved
|
||||
// 0b0 FILE_FORMAT_GRP (default)
|
||||
// 0b1 COPY (contents are copied)
|
||||
// 0b0 PERM_WRITE_PROTECT (not permanently write protected)
|
||||
// 0b0 TMP_READ_PROTECT (not temporarily write protected)
|
||||
// 0b00 FILE_FORMAT (contains partition table)
|
||||
// 0b00 reserved
|
||||
// 0b??????? CRC
|
||||
// 0b1 reserved
|
||||
|
||||
// TODO: CRC7 (but so far it looks like nobody is actually verifying this)
|
||||
constexpr u32 crc = 0;
|
||||
|
||||
// Form the csd using the description above
|
||||
std::array<u8, 16> csd = {
|
||||
0x00,
|
||||
0x07,
|
||||
0xf0,
|
||||
0x03,
|
||||
0x5b,
|
||||
0x5f,
|
||||
static_cast<u8>(0x80 | (c_size >> 10)),
|
||||
static_cast<u8>(c_size >> 2),
|
||||
static_cast<u8>(0x3f | (c_size << 6)),
|
||||
static_cast<u8>(0xfc | (c_size_mult >> 1)),
|
||||
static_cast<u8>(0x7f | (c_size << 7)),
|
||||
0x80,
|
||||
0x07,
|
||||
0xc0,
|
||||
0x40,
|
||||
static_cast<u8>(0x01 | (crc << 1)),
|
||||
};
|
||||
for (auto byte : csd)
|
||||
response.push_back(byte);
|
||||
|
||||
u16 crc16 = Common::HashCrc16(csd);
|
||||
response.push_back(crc16 >> 8);
|
||||
response.push_back(crc16);
|
||||
break;
|
||||
}
|
||||
case Command::SendCID:
|
||||
{
|
||||
// R1
|
||||
response.push_back(0);
|
||||
// Data ready token
|
||||
response.push_back(START_BLOCK);
|
||||
// The CID -- no idea what the format is, copied from SDIOSlot0
|
||||
std::array<u8, 16> cid = {
|
||||
0x80, 0x11, 0x4d, 0x1c, 0x80, 0x08, 0x00, 0x00,
|
||||
0x80, 0x07, 0xb5, 0x20, 0x80, 0x08, 0x00, 0x00,
|
||||
};
|
||||
for (auto byte : cid)
|
||||
response.push_back(byte);
|
||||
|
||||
u16 crc16 = Common::HashCrc16(cid);
|
||||
response.push_back(crc16 >> 8);
|
||||
response.push_back(crc16);
|
||||
break;
|
||||
}
|
||||
case Command::StopTransmission:
|
||||
response.push_back(0); // R1
|
||||
// There can be further padding bytes, but it's not needed
|
||||
break;
|
||||
case Command::SendStatus:
|
||||
response.push_back(0); // R1
|
||||
response.push_back(0); // R2
|
||||
break;
|
||||
case Command::SetBlockLen:
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "Set blocklen to {}", argument);
|
||||
// TODO: error if blocklen not 512
|
||||
response.push_back(0); // R1
|
||||
break;
|
||||
case Command::AppCmd:
|
||||
state = State::ReadyForAppCommand;
|
||||
response.push_back(0); // R1
|
||||
break;
|
||||
case Command::ReadSingleBlock:
|
||||
state = State::SingleBlockRead;
|
||||
block_state = BlockState::Response;
|
||||
address = argument;
|
||||
break;
|
||||
case Command::ReadMultipleBlock:
|
||||
state = State::MultipleBlockRead;
|
||||
block_state = BlockState::Response;
|
||||
address = argument;
|
||||
break;
|
||||
case Command::WriteSingleBlock:
|
||||
state = State::SingleBlockWrite;
|
||||
block_state = BlockState::Response;
|
||||
address = argument;
|
||||
break;
|
||||
case Command::WriteMultipleBlock:
|
||||
state = State::MultipleBlockWrite;
|
||||
block_state = BlockState::Response;
|
||||
address = argument;
|
||||
break;
|
||||
case Command::ReadOCR:
|
||||
{
|
||||
response.push_back(0); // R1
|
||||
u32 ocr = OCR_DEFAULT.m_hex;
|
||||
response.push_back(ocr >> 24);
|
||||
response.push_back(ocr >> 16);
|
||||
response.push_back(ocr >> 8);
|
||||
response.push_back(ocr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Don't know it
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE, "Unimplemented SD command {:02x} {:08x}",
|
||||
static_cast<u8>(command), argument);
|
||||
response.push_back(static_cast<u8>(R1::IllegalCommand));
|
||||
}
|
||||
}
|
||||
|
||||
void CEXISD::HandleAppCommand(AppCommand app_command, u32 argument)
|
||||
{
|
||||
switch (app_command)
|
||||
{
|
||||
case AppCommand::SDStatus:
|
||||
{
|
||||
response.push_back(0); // R1
|
||||
response.push_back(0); // R2
|
||||
// Data ready token
|
||||
response.push_back(START_BLOCK);
|
||||
// All-zero for now
|
||||
std::array<u8, 64> status = {};
|
||||
for (auto byte : status)
|
||||
{
|
||||
response.push_back(byte);
|
||||
}
|
||||
|
||||
u16 crc16 = Common::HashCrc16(status);
|
||||
response.push_back(crc16 >> 8);
|
||||
response.push_back(crc16);
|
||||
break;
|
||||
}
|
||||
case AppCommand::SDSendOpCond:
|
||||
{
|
||||
// Used by Pokémon Channel for all cards, and libogc for SDHC cards
|
||||
bool hcs = argument & (1 << 30); // Host Capacity Support (for SDHC/SDXC cards)
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "SD Host Capacity Support: {}", hcs);
|
||||
response.push_back(0); // R1 - not idle
|
||||
break;
|
||||
}
|
||||
case AppCommand::AppCmd:
|
||||
// According to the spec, any unknown app command should be treated as a regular command, but
|
||||
// also things should not use this functionality. It also specifically mentions that sending
|
||||
// CMD55 multiple times is the same as sending it only once: the next command that isn't 55 is
|
||||
// treated as an app command.
|
||||
state = State::ReadyForAppCommand;
|
||||
response.push_back(0); // R1 - not idle
|
||||
break;
|
||||
default:
|
||||
// Don't know it
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE, "Unimplemented SD app command {:02x} {:08x}",
|
||||
static_cast<u8>(app_command), argument);
|
||||
response.push_back(static_cast<u8>(R1::IllegalCommand));
|
||||
}
|
||||
}
|
||||
|
||||
u8 CEXISD::ReadByte()
|
||||
{
|
||||
if (state == State::SingleBlockRead || state == State::MultipleBlockRead)
|
||||
{
|
||||
return ReadForBlockRead();
|
||||
}
|
||||
else if (state == State::SingleBlockWrite || state == State::MultipleBlockWrite)
|
||||
{
|
||||
return ReadForBlockWrite();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (response.empty())
|
||||
{
|
||||
// Note that SD cards are detected by trying to read a device ID, and getting 0xffffffff back;
|
||||
// this behavior is required for correct handling.
|
||||
return 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 result = response.front();
|
||||
response.pop_front();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 CEXISD::ReadForBlockRead()
|
||||
{
|
||||
switch (block_state)
|
||||
{
|
||||
case BlockState::Response:
|
||||
{
|
||||
// Would return address error or parameter error here
|
||||
block_state = BlockState::Token;
|
||||
return 0;
|
||||
}
|
||||
case BlockState::Token:
|
||||
// Perform the actual read at this point, as every block needs a start block token or an error
|
||||
// token Oddly, the error token is slightly redundant, since there's an out of bounds response
|
||||
// in R1, which can happen above, but there's also one here.
|
||||
if (!m_card.Seek(address, File::SeekOrigin::Begin))
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "fseeko failed WTF");
|
||||
state = State::ReadyForCommand;
|
||||
block_state = BlockState::Nothing;
|
||||
return DATA_ERROR_OUT_OF_RANGE;
|
||||
}
|
||||
else if (!m_card.ReadBytes(block_buffer.data(), BLOCK_SIZE))
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "SD read failed at {:#x} - error: {}, eof: {}", address,
|
||||
ferror(m_card.GetHandle()), feof(m_card.GetHandle()));
|
||||
state = State::ReadyForCommand;
|
||||
block_state = BlockState::Nothing;
|
||||
return DATA_ERROR_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "SD read succeeded at {:#x}", address);
|
||||
block_position = 0;
|
||||
block_state = BlockState::Block;
|
||||
block_crc = Common::HashCrc16(block_buffer);
|
||||
return START_BLOCK;
|
||||
}
|
||||
case BlockState::Block:
|
||||
{
|
||||
u8 result = block_buffer[block_position++];
|
||||
if (block_position >= BLOCK_SIZE)
|
||||
{
|
||||
block_state = BlockState::Checksum1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case BlockState::Checksum1:
|
||||
block_state = BlockState::Checksum2;
|
||||
return static_cast<u8>(block_crc >> 8);
|
||||
case BlockState::Checksum2:
|
||||
{
|
||||
u8 result = static_cast<u8>(block_crc);
|
||||
if (state == State::MultipleBlockRead)
|
||||
{
|
||||
address += BLOCK_SIZE;
|
||||
block_state = BlockState::Token;
|
||||
}
|
||||
else
|
||||
{
|
||||
address = 0;
|
||||
block_position = 0;
|
||||
block_state = BlockState::Nothing;
|
||||
state = State::ReadyForCommand;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
default:
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Unexpected block_state {} for reading", u32(block_state));
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void CEXISD::WriteForBlockRead(u8 byte)
|
||||
{
|
||||
if (byte != 0xff)
|
||||
{
|
||||
WARN_LOG_FMT(EXPANSIONINTERFACE, "Data written during block read: {:02x}, {}, {}", byte,
|
||||
u32(block_state), block_position);
|
||||
}
|
||||
// TODO: Read the whole command
|
||||
// if (((byte & 0b11000000) == 0b01000000) &&
|
||||
// static_cast<Command>(byte & 0x3f) == Command::StopTransmission)
|
||||
if (byte == 0x61) // CRC for StopTransmission: 4c 00 00 00 00 61
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "Assuming stop transmission");
|
||||
// Can just be done; no block is currently being transmitted
|
||||
if (block_state == BlockState::Token)
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "In token state, marking as finished");
|
||||
address = 0;
|
||||
block_position = 0;
|
||||
block_state = BlockState::Nothing;
|
||||
state = State::ReadyForCommand;
|
||||
}
|
||||
// Finish transmitting the current block and then stop
|
||||
else if (state == State::MultipleBlockRead)
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "Changing from multi to single read to finish");
|
||||
state = State::SingleBlockRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "Already single read; doing nothing");
|
||||
}
|
||||
// JANK (when the block read isn't fully over), but I think this will work right
|
||||
response.push_back(0); // R1 - for later
|
||||
// Libogc expects a busy signal, even though the spec seems to imply that it's optional
|
||||
// It also uses this one (the first one where it's not got the top bit set) as the response, I
|
||||
// think. But I'm not sure why it ignores the first byte.
|
||||
response.push_back(0);
|
||||
// Libogc then wants a 0xff byte, but we automatically give those.
|
||||
}
|
||||
}
|
||||
|
||||
u8 CEXISD::ReadForBlockWrite()
|
||||
{
|
||||
switch (block_state)
|
||||
{
|
||||
case BlockState::Response:
|
||||
block_state = BlockState::Token;
|
||||
// Would return address error or parameter error here
|
||||
return 0;
|
||||
case BlockState::Token:
|
||||
case BlockState::Block:
|
||||
case BlockState::Checksum1:
|
||||
case BlockState::Checksum2:
|
||||
return 0xff;
|
||||
case BlockState::ChecksumWritten:
|
||||
{
|
||||
u16 actual_crc = Common::HashCrc16(block_buffer);
|
||||
u8 result;
|
||||
if (actual_crc != block_crc)
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Bad CRC: was {:04x}, should be {:04x}", actual_crc,
|
||||
block_crc);
|
||||
result = DATA_RESPONSE_BAD_CRC;
|
||||
}
|
||||
else if (!m_card.Seek(address, File::SeekOrigin::Begin))
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "fseeko failed WTF");
|
||||
result = DATA_RESPONSE_WRITE_ERROR;
|
||||
}
|
||||
else if (!m_card.WriteBytes(block_buffer.data(), BLOCK_SIZE))
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "SD write failed at {:#x} - error: {}, eof: {}", address,
|
||||
ferror(m_card.GetHandle()), feof(m_card.GetHandle()));
|
||||
result = DATA_RESPONSE_WRITE_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG_FMT(EXPANSIONINTERFACE, "SD write succeeded at {:#x}", address);
|
||||
result = DATA_RESPONSE_ACCEPTED;
|
||||
}
|
||||
|
||||
if (state == State::SingleBlockWrite)
|
||||
{
|
||||
state = State::ReadyForCommand;
|
||||
block_state = BlockState::Nothing;
|
||||
address = 0;
|
||||
block_position = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
block_state = BlockState::Token;
|
||||
address += BLOCK_SIZE;
|
||||
block_position = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
default:
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Unexpected block_state {} for writing", u32(block_state));
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void CEXISD::WriteForBlockWrite(u8 byte)
|
||||
{
|
||||
switch (block_state)
|
||||
{
|
||||
case BlockState::Response:
|
||||
// Do nothing
|
||||
break;
|
||||
case BlockState::Token:
|
||||
if (byte == START_MULTI_BLOCK)
|
||||
{
|
||||
block_position = 0;
|
||||
block_crc = 0;
|
||||
block_state = BlockState::Block;
|
||||
}
|
||||
else if (byte == END_BLOCK)
|
||||
{
|
||||
state = State::ReadyForCommand;
|
||||
block_state = BlockState::Nothing;
|
||||
}
|
||||
else if (byte != 0xff)
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Unexpected token for block write {:02x}", byte);
|
||||
}
|
||||
break;
|
||||
case BlockState::Block:
|
||||
block_buffer[block_position++] = byte;
|
||||
if (block_position >= BLOCK_SIZE)
|
||||
{
|
||||
block_state = BlockState::Checksum1;
|
||||
}
|
||||
break;
|
||||
case BlockState::Checksum1:
|
||||
block_crc |= byte << 8;
|
||||
block_state = BlockState::Checksum2;
|
||||
break;
|
||||
case BlockState::Checksum2:
|
||||
block_crc |= byte;
|
||||
block_state = BlockState::ChecksumWritten;
|
||||
break;
|
||||
case BlockState::ChecksumWritten:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Unexpected block_state {} for writing", u32(block_state));
|
||||
}
|
||||
}
|
||||
} // namespace ExpansionInterface
|
241
Source/Core/Core/HW/EXI/EXI_DeviceSD.h
Normal file
241
Source/Core/Core/HW/EXI/EXI_DeviceSD.h
Normal file
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Core/HW/EXI/EXI_Device.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
// EXI-SD adapter (DOL-019)
|
||||
// The SD adapter uses SPI mode to communicate with the SD card (SPI and EXI are basically the
|
||||
// same). Refer to the simplified specification, version 3.01, at
|
||||
// https://web.archive.org/web/20131205014133/https://www.sdcard.org/downloads/pls/simplified_specs/archive/part1_301.pdf
|
||||
// for details (SPI information starts on section 7, page 113 (125 in the PDF).
|
||||
class CEXISD final : public IEXIDevice
|
||||
{
|
||||
public:
|
||||
explicit CEXISD(Core::System& system, int channel_num);
|
||||
|
||||
void ImmWrite(u32 data, u32 size) override;
|
||||
u32 ImmRead(u32 size) override;
|
||||
void ImmReadWrite(u32& data, u32 size) override;
|
||||
// TODO: DMA
|
||||
void SetCS(u32 cs, bool was_selected, bool is_selected) override;
|
||||
|
||||
bool IsPresent() const override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
private:
|
||||
enum class Command // § 7.3.1.1, Table 7-3
|
||||
{
|
||||
GoIdleState = 0,
|
||||
SendOpCond = 1,
|
||||
// AllSendCid = 2, // Not SPI
|
||||
// SendRelativeAddr = 3, // Not SPI
|
||||
// SetDSR = 4, // Not SPI
|
||||
// Reserved for SDIO
|
||||
SwitchFunc = 6,
|
||||
// SelectCard = 7, // or Deselect; not SPI
|
||||
SendInterfaceCond = 8,
|
||||
SendCSD = 9,
|
||||
SendCID = 10,
|
||||
// VoltageSwitch = 11, // Not SPI
|
||||
StopTransmission = 12,
|
||||
SendStatus = 13,
|
||||
// 14 Reserved
|
||||
// GoInactiveState = 15, // Not SPI
|
||||
|
||||
SetBlockLen = 16,
|
||||
ReadSingleBlock = 17,
|
||||
ReadMultipleBlock = 18,
|
||||
SendTuningBlock = 19,
|
||||
// SpeedClassControl = 20, // Not SPI
|
||||
// 21 Reserved
|
||||
// 22 Reserved
|
||||
SetBlockCount = 23,
|
||||
|
||||
WriteSingleBlock = 24,
|
||||
WriteMultipleBlock = 25,
|
||||
// 26 Reserved for manufacturer
|
||||
ProgramCSD = 27,
|
||||
|
||||
SetWriteProt = 28,
|
||||
ClearWriteProt = 29,
|
||||
SendWriteProt = 30,
|
||||
// 31 Reserved
|
||||
|
||||
EraseWriteBlockStart = 32,
|
||||
EraseWriteBlockEnd = 33,
|
||||
// 34-37 Reserved for command system set by SwitchFunc
|
||||
Erase = 38,
|
||||
// 39 Reserved
|
||||
// 40 Reserved for security spec
|
||||
// 41 Reserved
|
||||
|
||||
LockUnlock = 42,
|
||||
// 43-49 Reserved
|
||||
// 50 Reserved for command system set by SwitchFunc
|
||||
// 51 Reserved
|
||||
// 52-54 used by SDIO
|
||||
|
||||
AppCmd = 55,
|
||||
GenCmd = 56,
|
||||
// 57 Reserved for command system set by SwitchFunc
|
||||
ReadOCR = 58,
|
||||
CRCOnOff = 59,
|
||||
// 60-63 Reserved for manufacturer
|
||||
};
|
||||
|
||||
enum class AppCommand // § 7.3.1.1, Table 7-4
|
||||
{
|
||||
// 1-5 Reserved
|
||||
// SetBusWidth = 6, // Not SPI
|
||||
// 7-12 Reserved
|
||||
SDStatus = 13,
|
||||
// 14-16 Reserved for security spec
|
||||
// 17 Reserved
|
||||
// 18 Reserved for SD security
|
||||
// 19-21 Reserved
|
||||
SendNumWrittenBlocks = 22,
|
||||
SetWriteBlockEraseCount = 23,
|
||||
// 24 Reserved
|
||||
// 25 Reserved for SD security
|
||||
// 26 Reserved for SD security
|
||||
// 27-28 Reserved for security spec
|
||||
// 29 Reserved
|
||||
// 30-35 Reserved for security spec
|
||||
// 36-37 Reserved
|
||||
// 38 Reserved for SD security
|
||||
// 39-40 Reserved
|
||||
SDSendOpCond = 41,
|
||||
SetClearCardDetect = 42,
|
||||
// 43 Reserved for SD security
|
||||
// 49 Reserved for SD security
|
||||
SendSCR = 51,
|
||||
// 52-54 Reserved for security spec
|
||||
AppCmd = 55,
|
||||
// 56-59 Reserved for security spec
|
||||
};
|
||||
|
||||
enum class OCR : u32 // Operating Conditions Register, § 5.1
|
||||
{
|
||||
// 0-6 reserved
|
||||
// 7 reserved for Low Voltage Range
|
||||
// 8-14 reserved
|
||||
// All of these (including the above reserved bits) are the VDD Voltage Window.
|
||||
// e.g. Vdd27To28 indicates 2.7 to 2.8 volts
|
||||
Vdd27To28 = 1 << 15,
|
||||
Vdd28To29 = 1 << 16,
|
||||
Vdd29To30 = 1 << 17,
|
||||
Vdd30To31 = 1 << 18,
|
||||
Vdd31To32 = 1 << 19,
|
||||
Vdd32To33 = 1 << 20,
|
||||
Vdd33To34 = 1 << 21,
|
||||
Vdd34To35 = 1 << 22,
|
||||
Vdd35To36 = 1 << 23,
|
||||
// "S18A" (not part of VDD Voltage Window)
|
||||
SwitchTo18VAccepted = 1 << 24,
|
||||
// 25-29 reserved
|
||||
// "CCS", only valid after startup done. This bit being set indicates SDHC.
|
||||
CardCapacityStatus = 1 << 30,
|
||||
// 0 if card is still starting up
|
||||
CardPowerUpStatus = 1u << 31,
|
||||
};
|
||||
|
||||
static constexpr Common::Flags<OCR> OCR_DEFAULT{
|
||||
OCR::Vdd27To28, OCR::Vdd28To29, OCR::Vdd29To30, OCR::Vdd30To31, OCR::Vdd31To32,
|
||||
OCR::Vdd32To33, OCR::Vdd33To34, OCR::Vdd34To35, OCR::Vdd35To36, OCR::CardPowerUpStatus};
|
||||
|
||||
// § 7.3.3
|
||||
static constexpr u8 START_BLOCK = 0xfe, START_MULTI_BLOCK = 0xfc, END_BLOCK = 0xfd;
|
||||
// The spec has the first 3 bits of the data responses marked with an x, and doesn't explain why
|
||||
static constexpr u8 DATA_RESPONSE_ACCEPTED = 0b0'010'0;
|
||||
static constexpr u8 DATA_RESPONSE_BAD_CRC = 0b0'101'0;
|
||||
static constexpr u8 DATA_RESPONSE_WRITE_ERROR = 0b0'110'0;
|
||||
// "Same error bits" as in R2, but I guess that only refers to meaning, not the actual bit values
|
||||
static constexpr u8 DATA_ERROR_ERROR = 0x01;
|
||||
static constexpr u8 DATA_ERROR_CONTROLLER = 0x02;
|
||||
static constexpr u8 DATA_ERROR_ECC = 0x04;
|
||||
static constexpr u8 DATA_ERROR_OUT_OF_RANGE = 0x08;
|
||||
|
||||
static constexpr size_t BLOCK_SIZE = 512;
|
||||
|
||||
enum class State
|
||||
{
|
||||
ReadyForCommand,
|
||||
ReadyForAppCommand,
|
||||
SingleBlockRead,
|
||||
MultipleBlockRead,
|
||||
SingleBlockWrite,
|
||||
MultipleBlockWrite,
|
||||
};
|
||||
|
||||
enum class BlockState
|
||||
{
|
||||
Nothing,
|
||||
Response,
|
||||
Token,
|
||||
Block,
|
||||
Checksum1,
|
||||
Checksum2,
|
||||
ChecksumWritten,
|
||||
};
|
||||
|
||||
void WriteByte(u8 byte);
|
||||
void HandleCommand(Command command, u32 argument);
|
||||
void HandleAppCommand(AppCommand app_command, u32 argument);
|
||||
u8 ReadByte();
|
||||
|
||||
u8 ReadForBlockRead();
|
||||
void WriteForBlockRead(u8 byte);
|
||||
u8 ReadForBlockWrite();
|
||||
void WriteForBlockWrite(u8 byte);
|
||||
|
||||
enum class R1 // § 7.3.2.1
|
||||
{
|
||||
InIdleState = 1 << 0,
|
||||
EraseRequest = 1 << 1,
|
||||
IllegalCommand = 1 << 2,
|
||||
CommunicationCRCError = 1 << 3,
|
||||
EraseSequenceError = 1 << 4,
|
||||
AddressError = 1 << 5,
|
||||
ParameterError = 1 << 6,
|
||||
// Top bit 0
|
||||
};
|
||||
enum class R2 // § 7.3.2.3
|
||||
{
|
||||
CardIsLocked = 1 << 0,
|
||||
WriteProtectEraseSkip = 1 << 1, // or lock/unlock command failed
|
||||
Error = 1 << 2,
|
||||
CardControllerError = 1 << 3,
|
||||
CardEccFailed = 1 << 4,
|
||||
WriteProtectViolation = 1 << 5,
|
||||
EraseParam = 1 << 6,
|
||||
// OUT_OF_RANGE_OR_CSD_OVERWRITE, not documented in text?
|
||||
};
|
||||
|
||||
File::IOFile m_card;
|
||||
|
||||
// STATE_TO_SAVE
|
||||
State state = State::ReadyForCommand;
|
||||
BlockState block_state = BlockState::Nothing;
|
||||
u32 command_position = 0;
|
||||
u32 block_position = 0;
|
||||
std::array<u8, 6> command_buffer = {};
|
||||
std::deque<u8> response = {};
|
||||
std::array<u8, BLOCK_SIZE> block_buffer = {};
|
||||
u64 address = 0;
|
||||
u16 block_crc = 0;
|
||||
};
|
||||
} // namespace ExpansionInterface
|
|
@ -1377,6 +1377,11 @@ bool NetPlayServer::SetupNetSettings()
|
|||
// There's no way the BBA is going to sync, disable it
|
||||
device = ExpansionInterface::EXIDeviceType::None;
|
||||
}
|
||||
else if (slot == ExpansionInterface::Slot::SP2)
|
||||
{
|
||||
// SD cards probably won't sync either
|
||||
device = ExpansionInterface::EXIDeviceType::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
device = Config::Get(Config::GetInfoForEXIDevice(slot));
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#endif
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
@ -1035,6 +1036,30 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
}
|
||||
else
|
||||
{
|
||||
auto& cpu = m_system.GetCPU();
|
||||
auto& power_pc = m_system.GetPowerPC();
|
||||
if (m_enable_debugging && power_pc.GetBreakPoints().IsAddressBreakPoint(op.address) &&
|
||||
!cpu.IsStepping())
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
|
||||
MOV(32, PPCSTATE(pc), Imm32(op.address));
|
||||
ABI_PushRegistersAndAdjustStack({}, 0);
|
||||
ABI_CallFunctionP(PowerPC::CheckBreakPointsFromJIT, &power_pc);
|
||||
ABI_PopRegistersAndAdjustStack({}, 0);
|
||||
MOV(64, R(RSCRATCH), ImmPtr(cpu.GetStatePtr()));
|
||||
CMP(32, MatR(RSCRATCH), Imm32(Common::ToUnderlying(CPU::State::Running)));
|
||||
FixupBranch noBreakpoint = J_CC(CC_E);
|
||||
|
||||
Cleanup();
|
||||
MOV(32, PPCSTATE(npc), Imm32(op.address));
|
||||
SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount));
|
||||
JMP(asm_routines.dispatcher_exit, Jump::Near);
|
||||
|
||||
SetJumpTarget(noBreakpoint);
|
||||
}
|
||||
|
||||
if ((opinfo->flags & FL_USE_FPU) && !js.firstFPInstructionFound)
|
||||
{
|
||||
// This instruction uses FPU - needs to add FP exception bailout
|
||||
|
@ -1061,30 +1086,6 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
js.firstFPInstructionFound = true;
|
||||
}
|
||||
|
||||
auto& cpu = m_system.GetCPU();
|
||||
auto& power_pc = m_system.GetPowerPC();
|
||||
if (m_enable_debugging && power_pc.GetBreakPoints().IsAddressBreakPoint(op.address) &&
|
||||
!cpu.IsStepping())
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
|
||||
MOV(32, PPCSTATE(pc), Imm32(op.address));
|
||||
ABI_PushRegistersAndAdjustStack({}, 0);
|
||||
ABI_CallFunctionP(PowerPC::CheckBreakPointsFromJIT, &power_pc);
|
||||
ABI_PopRegistersAndAdjustStack({}, 0);
|
||||
MOV(64, R(RSCRATCH), ImmPtr(cpu.GetStatePtr()));
|
||||
TEST(32, MatR(RSCRATCH), Imm32(0xFFFFFFFF));
|
||||
FixupBranch noBreakpoint = J_CC(CC_Z);
|
||||
|
||||
Cleanup();
|
||||
MOV(32, PPCSTATE(npc), Imm32(op.address));
|
||||
SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount));
|
||||
JMP(asm_routines.dispatcher_exit, Jump::Near);
|
||||
|
||||
SetJumpTarget(noBreakpoint);
|
||||
}
|
||||
|
||||
if (bJITRegisterCacheOff)
|
||||
{
|
||||
gpr.Flush();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <climits>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/JitRegister.h"
|
||||
#include "Common/x64ABI.h"
|
||||
#include "Common/x64Emitter.h"
|
||||
|
@ -105,8 +106,8 @@ void Jit64AsmRoutineManager::Generate()
|
|||
if (enable_debugging)
|
||||
{
|
||||
MOV(64, R(RSCRATCH), ImmPtr(system.GetCPU().GetStatePtr()));
|
||||
TEST(32, MatR(RSCRATCH), Imm32(0xFFFFFFFF));
|
||||
dbg_exit = J_CC(CC_NZ, Jump::Near);
|
||||
CMP(32, MatR(RSCRATCH), Imm32(Common::ToUnderlying(CPU::State::Running)));
|
||||
dbg_exit = J_CC(CC_NE, Jump::Near);
|
||||
}
|
||||
|
||||
SetJumpTarget(skipToRealDispatch);
|
||||
|
@ -236,8 +237,8 @@ void Jit64AsmRoutineManager::Generate()
|
|||
// Check the state pointer to see if we are exiting
|
||||
// Gets checked on at the end of every slice
|
||||
MOV(64, R(RSCRATCH), ImmPtr(system.GetCPU().GetStatePtr()));
|
||||
TEST(32, MatR(RSCRATCH), Imm32(0xFFFFFFFF));
|
||||
J_CC(CC_Z, outerLoop);
|
||||
CMP(32, MatR(RSCRATCH), Imm32(Common::ToUnderlying(CPU::State::Running)));
|
||||
J_CC(CC_E, outerLoop);
|
||||
|
||||
// Landing pad for drec space
|
||||
dispatcher_exit = GetCodePtr();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "Common/Arm64Emitter.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
@ -1239,6 +1240,37 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
}
|
||||
else
|
||||
{
|
||||
if (m_enable_debugging && !cpu.IsStepping() &&
|
||||
m_system.GetPowerPC().GetBreakPoints().IsAddressBreakPoint(op.address))
|
||||
{
|
||||
FlushCarry();
|
||||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
static_assert(PPCSTATE_OFF(pc) <= 252);
|
||||
static_assert(PPCSTATE_OFF(pc) + 4 == PPCSTATE_OFF(npc));
|
||||
|
||||
MOVI2R(DISPATCHER_PC, op.address);
|
||||
STP(IndexType::Signed, DISPATCHER_PC, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc));
|
||||
ABI_CallFunction(&PowerPC::CheckBreakPointsFromJIT, &m_system.GetPowerPC());
|
||||
|
||||
LDR(IndexType::Unsigned, ARM64Reg::W0, ARM64Reg::X0,
|
||||
MOVPage2R(ARM64Reg::X0, cpu.GetStatePtr()));
|
||||
static_assert(Common::ToUnderlying(CPU::State::Running) == 0);
|
||||
FixupBranch no_breakpoint = CBZ(ARM64Reg::W0);
|
||||
|
||||
Cleanup();
|
||||
if (IsProfilingEnabled())
|
||||
{
|
||||
ABI_CallFunction(&JitBlock::ProfileData::EndProfiling, b->profile_data.get(),
|
||||
js.downcountAmount);
|
||||
}
|
||||
DoDownCount();
|
||||
B(dispatcher_exit);
|
||||
|
||||
SetJumpTarget(no_breakpoint);
|
||||
}
|
||||
|
||||
if ((opinfo->flags & FL_USE_FPU) && !js.firstFPInstructionFound)
|
||||
{
|
||||
// This instruction uses FPU - needs to add FP exception bailout
|
||||
|
@ -1268,36 +1300,6 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
js.firstFPInstructionFound = true;
|
||||
}
|
||||
|
||||
if (m_enable_debugging && !cpu.IsStepping() &&
|
||||
m_system.GetPowerPC().GetBreakPoints().IsAddressBreakPoint(op.address))
|
||||
{
|
||||
FlushCarry();
|
||||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
static_assert(PPCSTATE_OFF(pc) <= 252);
|
||||
static_assert(PPCSTATE_OFF(pc) + 4 == PPCSTATE_OFF(npc));
|
||||
|
||||
MOVI2R(DISPATCHER_PC, op.address);
|
||||
STP(IndexType::Signed, DISPATCHER_PC, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc));
|
||||
ABI_CallFunction(&PowerPC::CheckBreakPointsFromJIT, &m_system.GetPowerPC());
|
||||
|
||||
LDR(IndexType::Unsigned, ARM64Reg::W0, ARM64Reg::X0,
|
||||
MOVPage2R(ARM64Reg::X0, cpu.GetStatePtr()));
|
||||
FixupBranch no_breakpoint = CBZ(ARM64Reg::W0);
|
||||
|
||||
Cleanup();
|
||||
if (IsProfilingEnabled())
|
||||
{
|
||||
ABI_CallFunction(&JitBlock::ProfileData::EndProfiling, b->profile_data.get(),
|
||||
js.downcountAmount);
|
||||
}
|
||||
DoDownCount();
|
||||
B(dispatcher_exit);
|
||||
|
||||
SetJumpTarget(no_breakpoint);
|
||||
}
|
||||
|
||||
if (bJITRegisterCacheOff)
|
||||
{
|
||||
FlushCarry();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "Common/Arm64Emitter.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/FloatUtils.h"
|
||||
#include "Common/JitRegister.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
@ -88,6 +89,7 @@ void JitArm64::GenerateAsm()
|
|||
{
|
||||
LDR(IndexType::Unsigned, ARM64Reg::W8, ARM64Reg::X8,
|
||||
MOVPage2R(ARM64Reg::X8, cpu.GetStatePtr()));
|
||||
static_assert(Common::ToUnderlying(CPU::State::Running) == 0);
|
||||
debug_exit = CBNZ(ARM64Reg::W8);
|
||||
}
|
||||
|
||||
|
@ -195,6 +197,7 @@ void JitArm64::GenerateAsm()
|
|||
// Check the state pointer to see if we are exiting
|
||||
// Gets checked on at the end of every slice
|
||||
LDR(IndexType::Unsigned, ARM64Reg::W8, ARM64Reg::X8, MOVPage2R(ARM64Reg::X8, cpu.GetStatePtr()));
|
||||
static_assert(Common::ToUnderlying(CPU::State::Running) == 0);
|
||||
FixupBranch exit = CBNZ(ARM64Reg::W8);
|
||||
|
||||
SetJumpTarget(to_start_of_timing_slice);
|
||||
|
|
|
@ -285,6 +285,7 @@
|
|||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceModem.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceSD.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI.h" />
|
||||
<ClInclude Include="Core\HW\GBACore.h" />
|
||||
<ClInclude Include="Core\HW\GBAPad.h" />
|
||||
|
@ -951,6 +952,7 @@
|
|||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMemoryCard.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceModem.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceSD.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI.cpp" />
|
||||
<ClCompile Include="Core\HW\GBACore.cpp" />
|
||||
<ClCompile Include="Core\HW\GBAPad.cpp" />
|
||||
|
|
|
@ -135,7 +135,7 @@ void GameCubePane::CreateWidgets()
|
|||
// Add slot devices
|
||||
for (const auto device : {EXIDeviceType::None, EXIDeviceType::Dummy, EXIDeviceType::MemoryCard,
|
||||
EXIDeviceType::MemoryCardFolder, EXIDeviceType::Gecko,
|
||||
EXIDeviceType::AGP, EXIDeviceType::Microphone})
|
||||
EXIDeviceType::AGP, EXIDeviceType::Microphone, EXIDeviceType::SD})
|
||||
{
|
||||
const QString name = tr(fmt::format("{:n}", device).c_str());
|
||||
const int value = static_cast<int>(device);
|
||||
|
@ -158,6 +158,14 @@ void GameCubePane::CreateWidgets()
|
|||
static_cast<int>(device));
|
||||
}
|
||||
|
||||
// Add SP2 devices
|
||||
for (const auto device :
|
||||
{EXIDeviceType::None, EXIDeviceType::Dummy, EXIDeviceType::AD16, EXIDeviceType::SD})
|
||||
{
|
||||
m_slot_combos[ExpansionInterface::Slot::SP2]->addItem(tr(fmt::format("{:n}", device).c_str()),
|
||||
static_cast<int>(device));
|
||||
}
|
||||
|
||||
{
|
||||
int row = 0;
|
||||
device_layout->addWidget(new QLabel(tr("Slot A:")), row, 0);
|
||||
|
@ -191,6 +199,11 @@ void GameCubePane::CreateWidgets()
|
|||
device_layout->addWidget(new QLabel(tr("SP1:")), row, 0);
|
||||
device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::SP1], row, 1);
|
||||
device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::SP1], row, 2);
|
||||
|
||||
++row;
|
||||
device_layout->addWidget(new QLabel(tr("SP2:")), row, 0);
|
||||
device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::SP2], row, 1);
|
||||
device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::SP2], row, 2);
|
||||
}
|
||||
|
||||
#ifdef HAS_LIBMGBA
|
||||
|
@ -329,7 +342,8 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
|
|||
has_config = (device == ExpansionInterface::EXIDeviceType::MemoryCard ||
|
||||
device == ExpansionInterface::EXIDeviceType::MemoryCardFolder ||
|
||||
device == ExpansionInterface::EXIDeviceType::AGP ||
|
||||
device == ExpansionInterface::EXIDeviceType::Microphone);
|
||||
device == ExpansionInterface::EXIDeviceType::Microphone ||
|
||||
device == ExpansionInterface::EXIDeviceType::SD);
|
||||
const bool hide_memory_card = device != ExpansionInterface::EXIDeviceType::MemoryCard ||
|
||||
Config::IsDefaultMemcardPathConfigured(slot);
|
||||
const bool hide_gci_path = device != ExpansionInterface::EXIDeviceType::MemoryCardFolder ||
|
||||
|
@ -358,6 +372,9 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
|
|||
device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn ||
|
||||
device == ExpansionInterface::EXIDeviceType::ModemTapServer);
|
||||
break;
|
||||
case ExpansionInterface::Slot::SP2:
|
||||
has_config = (device == ExpansionInterface::EXIDeviceType::SD);
|
||||
break;
|
||||
}
|
||||
|
||||
m_slot_buttons[slot]->setEnabled(has_config);
|
||||
|
@ -379,6 +396,9 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
|
|||
case ExpansionInterface::EXIDeviceType::AGP:
|
||||
BrowseAGPRom(slot);
|
||||
return;
|
||||
case ExpansionInterface::EXIDeviceType::SD:
|
||||
BrowseSDCard(slot);
|
||||
return;
|
||||
case ExpansionInterface::EXIDeviceType::Microphone:
|
||||
{
|
||||
// TODO: convert MappingWindow to use Slot?
|
||||
|
@ -675,6 +695,70 @@ void GameCubePane::SetAGPRom(ExpansionInterface::Slot slot, const QString& filen
|
|||
LoadSettings();
|
||||
}
|
||||
|
||||
void GameCubePane::BrowseSDCard(ExpansionInterface::Slot slot)
|
||||
{
|
||||
ASSERT(slot != ExpansionInterface::Slot::SP1);
|
||||
|
||||
QString filename = DolphinFileDialog::getSaveFileName(
|
||||
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
|
||||
tr("SD Card Image (*.raw)"), 0, QFileDialog::DontConfirmOverwrite);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
QString path_abs = QFileInfo(filename).absoluteFilePath();
|
||||
|
||||
for (ExpansionInterface::Slot other_slot : ExpansionInterface::SLOTS)
|
||||
{
|
||||
if (other_slot == slot || other_slot == ExpansionInterface::Slot::SP1)
|
||||
continue;
|
||||
|
||||
bool other_slot_sd = m_slot_combos[other_slot]->currentData().toInt() ==
|
||||
static_cast<int>(ExpansionInterface::EXIDeviceType::SD);
|
||||
if (other_slot_sd)
|
||||
{
|
||||
QString path_other =
|
||||
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForSDCardPath(other_slot))))
|
||||
.absoluteFilePath();
|
||||
|
||||
if (path_abs == path_other)
|
||||
{
|
||||
ModalMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The same file can't be used in multiple slots; it is already used by %1.")
|
||||
.arg(QString::fromStdString(fmt::to_string(other_slot))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString path_wii =
|
||||
QFileInfo(QString::fromStdString(Config::Get(Config::MAIN_WII_SD_CARD_IMAGE_PATH)))
|
||||
.absoluteFilePath();
|
||||
if (path_abs == path_wii)
|
||||
{
|
||||
ModalMessageBox::critical(this, tr("Error"),
|
||||
tr("The same file can't be used in multiple slots; it is already "
|
||||
"used by the Wii SD slot."));
|
||||
return;
|
||||
}
|
||||
|
||||
QString path_old =
|
||||
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForSDCardPath(slot))))
|
||||
.absoluteFilePath();
|
||||
|
||||
Config::SetBase(Config::GetInfoForSDCardPath(slot), path_abs.toStdString());
|
||||
|
||||
if (Core::IsRunning() && path_abs != path_old)
|
||||
{
|
||||
// ChangeDevice unplugs the device for 1 second, which means that games should notice that
|
||||
// the path has changed and thus the sd card contents have changed
|
||||
// (not sure if anything actually checks for this)
|
||||
Core::System::GetInstance().GetExpansionInterface().ChangeDevice(
|
||||
slot, ExpansionInterface::EXIDeviceType::SD);
|
||||
}
|
||||
}
|
||||
|
||||
void GameCubePane::BrowseGBABios()
|
||||
{
|
||||
QString file = QDir::toNativeSeparators(DolphinFileDialog::getOpenFileName(
|
||||
|
|
|
@ -47,6 +47,7 @@ private:
|
|||
bool SetGCIFolder(ExpansionInterface::Slot slot, const QString& path);
|
||||
void BrowseAGPRom(ExpansionInterface::Slot slot);
|
||||
void SetAGPRom(ExpansionInterface::Slot slot, const QString& filename);
|
||||
void BrowseSDCard(ExpansionInterface::Slot slot);
|
||||
void BrowseGBABios();
|
||||
void BrowseGBARom(size_t index);
|
||||
void SaveRomPathChanged();
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Config/UISettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
|
|
|
@ -513,8 +513,21 @@ void WiiPane::BrowseSDRaw()
|
|||
|
||||
void WiiPane::SetSDRaw(const QString& path)
|
||||
{
|
||||
Config::SetBase(Config::MAIN_WII_SD_CARD_IMAGE_PATH, path.toStdString());
|
||||
SignalBlocking(m_sd_raw_edit)->setText(path);
|
||||
const auto str = path.toStdString();
|
||||
if (str == Config::Get(Config::MAIN_SLOT_A_SD_CARD_PATH) ||
|
||||
str == Config::Get(Config::MAIN_SLOT_B_SD_CARD_PATH) ||
|
||||
str == Config::Get(Config::MAIN_SP2_SD_CARD_PATH))
|
||||
{
|
||||
ModalMessageBox::critical(this, tr("Error"),
|
||||
tr("The same file can't be used in multiple slots."));
|
||||
SignalBlocking(m_sd_raw_edit)
|
||||
->setText(QString::fromStdString(Config::Get(Config::MAIN_WII_SD_CARD_IMAGE_PATH)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Config::SetBase(Config::MAIN_WII_SD_CARD_IMAGE_PATH, str);
|
||||
SignalBlocking(m_sd_raw_edit)->setText(path);
|
||||
}
|
||||
}
|
||||
|
||||
void WiiPane::BrowseSDSyncFolder()
|
||||
|
|
Loading…
Reference in a new issue