Compare commits

...

44 commits

Author SHA1 Message Date
Pokechu22 825a464ef6
Merge d1bf5258cc into 50386c4e39 2024-05-08 19:40:12 -05:00
OatmealDome 50386c4e39
Merge pull request #12740 from mitaclaw/breakpoint-before-fpu-exception
Jit64/JitArm64: Check Breakpoints Before FPU Availability
2024-05-08 01:26:08 -04:00
mitaclaw 756ea81ab2 Jit64: Smaller Instruction Breakpoint Condition
Also some static_asserts in JitArm64.
2024-04-28 15:54:15 -07:00
mitaclaw 307a1e3ab8 Jit64/JitArm64: Check Breakpoints Before FPU Availability
CachedInterpreter already does it in the expected order.
2024-04-26 10:58:16 -07:00
Pokechu22 d1bf5258cc Eliminate hacky setup states
There still probably needs to be another state for CMD0 or such, but that can be handled separately.
2024-03-31 17:18:57 -07:00
Pokechu22 d22f42a08a Only return SD device when CS is 0
This is NOT needed to fix anything, but still seems like a good idea to me.  Though getting a device based on CS still seems less than perfect (e.g. what does the hardware do if multiple chips are selected at the same time?  Probably it doesn't handle it well, but it might still be worth emulating...)
2024-03-31 17:18:57 -07:00
Pokechu22 b5a6fc11bc Call SetCS for all devices
The xor technique doesn't work well when 0 is a legal device, since CS not changing will return device 0, and then reset CS for that device.  This broke memcards; they now work.
2024-03-31 17:18:54 -07:00
Pokechu22 cbe5811833 Print the file SD data is being read from 2024-03-31 16:47:42 -07:00
Pokechu22 0a08247608 Re-add ConfigManager.h include
Dropped in b0f9bb9f13 - I should look at that for how to fix my own changes
2024-03-31 16:47:42 -07:00
Pokechu22 6b7444cdd6 Handle GoIdleState and SendInterfaceCond exceptions 2024-03-31 16:47:42 -07:00
Pokechu22 0766f1c930 More documentation references 2024-03-31 16:47:42 -07:00
Pokechu22 dae01cc143 Fix SD Media Launcher
Datel...
2024-03-31 16:47:42 -07:00
Pokechu22 fb0b86dbf8 Partially implement configuring the SD card path
This doesn't work (changes aren't saved to disk), and I don't fully understand why...
2024-03-31 16:47:42 -07:00
Pokechu22 e372ebfffd Allow configuring SP2
Note that the default was AD16 before, and I've now changed it to None.
2024-03-31 16:47:42 -07:00
Pokechu22 3d32c6b438 NOTE: EXI_DeviceMemoryCard could still use some cleanup 2024-03-31 16:47:42 -07:00
Pokechu22 302b64da17 Actually functional block read
This is enough for Pokémon Channel to save.  Loading should also work, but I'm unable to test since memory cards are still broken...
2024-03-31 16:47:28 -07:00
Pokechu22 98c1826855 WIP fix block reads
Enough for libogc, but not enough for Channel...
2024-03-31 16:47:28 -07:00
Pokechu22 f41d68f9e5 Implement ReadOCR
Libogc needs this now; I'm not sure what I changed from before that made it necessary.
2024-03-31 16:47:28 -07:00
Pokechu22 0c0ad48e7f Undo some test stuff 2024-03-31 16:47:28 -07:00
Pokechu22 89f77a1bd9 More work on multi-block reads; not fully functional 2024-03-31 16:47:27 -07:00
Pokechu22 d30d3cc595 Fix multi-block reads 2024-03-31 16:47:27 -07:00
Pokechu22 0b89a5f8cf Implement SendStatus 2024-03-31 16:47:27 -07:00
Pokechu22 9646fd34c9 Fix CRC handling
2 different CRC bugs, and also an OOB bug.
2024-03-31 16:47:27 -07:00
Pokechu22 749da1e70d Initial implementation of reads and writes
No idea if this actually works
2024-03-31 16:47:27 -07:00
Pokechu22 fe3227da74 WriteBlock -> WriteSingleBlock 2024-03-31 16:47:27 -07:00
Pokechu22 1faab5998c Remove hardcoded CRCs; validate incoming CRC7 2024-03-31 16:47:27 -07:00
Pokechu22 f057491259 Add Common::HashCrc16 and Common::HashCrc7 2024-03-31 16:47:27 -07:00
Pokechu22 fe9162305e Restructure code; start implementing read/write commands 2024-03-31 16:47:27 -07:00
Pokechu22 8474bdc6a1 Use correct CRC16s
Libogc has a bit of a footgun, where __io_gcsda.isInserted() will initialize the card, but not the CRC tables; __io_gcsda.startup() needs to be called first.
2024-03-31 16:47:27 -07:00
Pokechu22 582dd3547f Implement STOP_TRANSMISSION and APP_CMD SD_SEND_OP_COND
Both of these are used by Pok›émon Channel.  However, Pokémon Channel actually DOES care about CRCs, so... yay.
2024-03-31 16:47:27 -07:00
Pokechu22 4a13f8451b Implement last few commands, enough to get it detected by libogc (though not enough to be useful) 2024-03-31 16:47:27 -07:00
Pokechu22 b66b241757 Hardcode data for CMD9 and CMD10
The CRC part still unnerves me
2024-03-31 16:47:27 -07:00
Pokechu22 ce85b22776 CMD9 and CMD10 - early version
(actually like 15 minutes ago)
2024-03-31 16:47:27 -07:00
Pokechu22 5300bde6fd Implement CMD8 2024-03-31 16:47:27 -07:00
Pokechu22 e641198e1d Reply -- make it to the next part at least 2024-03-31 16:47:27 -07:00
Pokechu22 b88811daf8 Basic command detection 2024-03-31 16:47:27 -07:00
Pokechu22 0b050859b3 Adjust SD getid thing 2024-03-31 16:47:27 -07:00
Pokechu22 0d677e6569 HACK to select SD - this will need to be tidied 2024-03-31 16:47:27 -07:00
Pokechu22 f0798d149b EXI_DeviceDummy: Log ImmReadWrite 2024-03-31 16:47:27 -07:00
Pokechu22 32b10ac762 Advance reading
This works, for getting it to detect an SD card, though it then needs to init it and that fails.  But it's a start, even if it'll need to be thrown out when things get further along.
2024-03-31 16:47:27 -07:00
Pokechu22 452740e7f8 Add SetCS - amend dummy 2024-03-31 16:47:27 -07:00
Pokechu22 1afc1deb90 HLE kprintf
Not for this PR, but useful for testing
2024-03-31 16:47:27 -07:00
Pokechu22 a68f4f269d Dummy EXI_DeviceSD 2024-03-31 16:47:14 -07:00
Pokechu22 c6c812654b Create EXI_DeviceSD 2024-03-31 16:22:28 -07:00
37 changed files with 1266 additions and 91 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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];
}

View file

@ -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;

View file

@ -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},

View file

@ -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 =

View file

@ -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"}) {}
};

View file

@ -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:

View file

@ -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:

View file

@ -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) {}

View file

@ -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;
}

View file

@ -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;

View file

@ -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",

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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()

View file

@ -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;

View 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

View 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

View file

@ -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));

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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);

View file

@ -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" />

View file

@ -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(

View file

@ -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();

View file

@ -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"

View file

@ -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()