diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSD.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceSD.cpp index 0d99f06b11..e064a58eab 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSD.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSD.cpp @@ -8,13 +8,36 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Common/Hash.h" +#include "Common/FileUtil.h" +#include "Common/IOFile.h" #include "Common/Logging/Log.h" namespace ExpansionInterface { CEXISD::CEXISD(Core::System& system) : IEXIDevice(system) { + const std::string filename = File::GetUserPath(D_GCUSER_IDX) + "sdcard.bin"; + 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) @@ -81,30 +104,33 @@ void CEXISD::DoState(PointerWrap& p) { p.Do(inited); p.Do(get_id); - p.Do(m_uPosition); - p.DoArray(cmd); + p.Do(next_is_appcmd); + p.Do(command_position); + p.Do(block_position); + p.DoArray(command_buffer); p.Do(response); + p.DoArray(block_buffer); } void CEXISD::WriteByte(u8 byte) { // TODO: Write-protect inversion(?) - if (m_uPosition == 0) + if (command_position == 0) { if ((byte & 0b11000000) == 0b01000000) { INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI SD command started: {:02x}", byte); - cmd[m_uPosition++] = byte; + command_buffer[command_position++] = byte; } } - else if (m_uPosition < 6) + else if (command_position < 6) { - cmd[m_uPosition++] = byte; + command_buffer[command_position++] = byte; - if (m_uPosition == 6) + if (command_position == 6) { // Buffer now full - m_uPosition = 0; + command_position = 0; if ((byte & 1) != 1) { @@ -114,201 +140,235 @@ void CEXISD::WriteByte(u8 byte) // TODO: Check CRC - INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI SD command received: {:02x}", fmt::join(cmd.begin(), cmd.end(), " ")); + u8 command = command_buffer[0] & 0x3f; + u32 argument = command_buffer[1] << 24 | command_buffer[2] << 16 | command_buffer[3] << 8 | + command_buffer[4]; - if (cmd[0] == 0x40) // GO_IDLE_STATE - { - response.push_back(static_cast(R1::InIdleState)); - } - else if (cmd[0] == 0x41) // SEND_OP_COND - { - // Used by libogc for non-SDHC cards - bool hcs = cmd[1] & 0x40; // Host Capacity Support (for SDHC/SDXC cards) - (void)hcs; - response.push_back(0); // R1 - not idle - } - else if (cmd[0] == 0x48) // SEND_IF_COND - { - // Format R7 - u8 supply_voltage = cmd[4] & 0xF; - u8 check_pattern = cmd[5]; - response.push_back(static_cast(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); - } - else if (cmd[0] == 0x49) // SEND_CSD - { - u64 size = 0x8000000; // TODO + INFO_LOG_FMT(EXPANSIONINTERFACE, "EXI SD command received: {:02x} {:08x}", command, argument); - // 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(0xfe); - // 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 - response.push_back(0x00); - response.push_back(0x07); - response.push_back(0xf0); - response.push_back(0x03); - response.push_back(0x5b); - response.push_back(0x5f); - response.push_back(0x80 | (c_size >> 10)); - response.push_back(c_size >> 2); - response.push_back(0x3f | c_size << 6); - response.push_back(0xfc | (c_size_mult >> 1)); - response.push_back(0x7f | (c_size << 7)); - response.push_back(0x80); - response.push_back(0x07); - response.push_back(0xc0); - response.push_back(0x40); - response.push_back(0x01 | (crc << 1)); - // Hardcoded CRC16 (0x6a74) - response.push_back(0x6a); - response.push_back(0x74); - } - else if (cmd[0] == 0x4A) // SEND_CID + if (next_is_appcmd) { - // R1 - response.push_back(0); - // Data ready token - response.push_back(0xfe); - // The CID -- no idea what the format is, copied from SDIOSlot0 - response.push_back(0x80); - response.push_back(0x11); - response.push_back(0x4d); - response.push_back(0x1c); - response.push_back(0x80); - response.push_back(0x08); - response.push_back(0x00); - response.push_back(0x00); - response.push_back(0x80); - response.push_back(0x07); - response.push_back(0xb5); - response.push_back(0x20); - response.push_back(0x80); - response.push_back(0x08); - response.push_back(0x00); - response.push_back(0x00); - // Hardcoded CRC16 (0x9e3e) - response.push_back(0x9e); - response.push_back(0x3e); - } - else if (cmd[0] == 0x4c) // STOP_TRANSMISSION - { - response.push_back(0); // R1 - // There can be further padding bytes, but it's not needed - } - else if (cmd[0] == 0x50) // SET_BLOCKLEN - { - response.push_back(0); // R1 - } - else if (cmd[0] == 0x77) // APP_CMD - { - // The next command is an appcmd, which requires special treatment (not done here) - response.push_back(0); // R1 - } - else if (cmd[0] == 0x4d) // APP_CMD SD_STATUS - { - response.push_back(0); // R1 - response.push_back(0); // R2 - response.push_back(0xfe); // Data ready token - for (size_t i = 0; i < 64; i++) - { - response.push_back(0); - } - // This CRC16 is 0, probably since the data is all 0 - response.push_back(0); - response.push_back(0); - } - else if (cmd[0] == 0x69) // APP_CMD SD_SEND_OP_COND - { - // Used by Pokémon Channel for all cards, and libogc for SDHC cards - bool hcs = cmd[1] & 0x40; // Host Capacity Support (for SDHC/SDXC cards) - (void)hcs; - response.push_back(0); // R1 - not idle + next_is_appcmd = false; + HandleAppCommand(static_cast(command), argument); } else { - // Don't know it - response.push_back(static_cast(R1::IllegalCommand)); + HandleCommand(static_cast(command), argument); } } } } +void CEXISD::HandleCommand(Command command, u32 argument) +{ + switch (command) + { + case Command::GoIdleState: + response.push_back(static_cast(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) + (void)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(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(0xfe); + // 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 + response.push_back(0x00); + response.push_back(0x07); + response.push_back(0xf0); + response.push_back(0x03); + response.push_back(0x5b); + response.push_back(0x5f); + response.push_back(0x80 | (c_size >> 10)); + response.push_back(c_size >> 2); + response.push_back(0x3f | c_size << 6); + response.push_back(0xfc | (c_size_mult >> 1)); + response.push_back(0x7f | (c_size << 7)); + response.push_back(0x80); + response.push_back(0x07); + response.push_back(0xc0); + response.push_back(0x40); + response.push_back(0x01 | (crc << 1)); + // Hardcoded CRC16 (0x6a74) + response.push_back(0x6a); + response.push_back(0x74); + break; + } + case Command::SendCID: + { + // R1 + response.push_back(0); + // Data ready token + response.push_back(0xfe); + // The CID -- no idea what the format is, copied from SDIOSlot0 + response.push_back(0x80); + response.push_back(0x11); + response.push_back(0x4d); + response.push_back(0x1c); + response.push_back(0x80); + response.push_back(0x08); + response.push_back(0x00); + response.push_back(0x00); + response.push_back(0x80); + response.push_back(0x07); + response.push_back(0xb5); + response.push_back(0x20); + response.push_back(0x80); + response.push_back(0x08); + response.push_back(0x00); + response.push_back(0x00); + // Hardcoded CRC16 (0x9e3e) + response.push_back(0x9e); + response.push_back(0x3e); + break; + } + case Command::StopTransmission: + response.push_back(0); // R1 + // There can be further padding bytes, but it's not needed + 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: + next_is_appcmd = true; + response.push_back(0); // R1 + break; + default: + // Don't know it + WARN_LOG_FMT(EXPANSIONINTERFACE, "Unimplemented SD command {:02x} {:08x}", + static_cast(command), argument); + response.push_back(static_cast(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 + response.push_back(0xfe); // Data ready token + for (size_t i = 0; i < 64; i++) + { + response.push_back(0); + } + // This CRC16 is 0, probably since the data is all 0 + response.push_back(0); + response.push_back(0); + 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) + (void)hcs; + 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(app_command), argument); + response.push_back(static_cast(R1::IllegalCommand)); + } +} + u8 CEXISD::ReadByte() { if (response.empty()) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceSD.h b/Source/Core/Core/HW/EXI/EXI_DeviceSD.h index c45b9d5c8f..8491b55483 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceSD.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceSD.h @@ -9,6 +9,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/IOFile.h" #include "Core/HW/EXI/EXI_Device.h" class PointerWrap; @@ -31,7 +32,98 @@ public: void DoState(PointerWrap& p) override; private: + enum class Command + { + 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, + + WriteBlock = 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-58 Reserved + // 60-63 Reserved for manufacturer + }; + + enum class AppCommand + { + // 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 + }; + void WriteByte(u8 byte); + void HandleCommand(Command command, u32 argument); + void HandleAppCommand(AppCommand app_command, u32 argument); u8 ReadByte(); enum class R1 @@ -57,11 +149,16 @@ private: // OUT_OF_RANGE_OR_CSD_OVERWRITE, not documented in text? }; + File::IOFile m_card; + // STATE_TO_SAVE bool inited = false; bool get_id = false; - u32 m_uPosition = 0; - std::array cmd = {}; + bool next_is_appcmd = false; + u32 command_position = 0; + u32 block_position = 0; + std::array command_buffer = {}; std::deque response; + std::array block_buffer = {}; }; } // namespace ExpansionInterface