Restructure code; start implementing read/write commands

This commit is contained in:
Pokechu22 2020-08-23 20:01:47 -07:00
parent d98be4bdc9
commit 4f6cd4f535
2 changed files with 352 additions and 195 deletions

View File

@ -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<u8>(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<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);
}
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<AppCommand>(command), argument);
}
else
{
// Don't know it
response.push_back(static_cast<u8>(R1::IllegalCommand));
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)
(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<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(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<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
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<u8>(app_command), argument);
response.push_back(static_cast<u8>(R1::IllegalCommand));
}
}
u8 CEXISD::ReadByte()
{
if (response.empty())

View File

@ -9,6 +9,7 @@
#include <string>
#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<u8, 6> cmd = {};
bool next_is_appcmd = false;
u32 command_position = 0;
u32 block_position = 0;
std::array<u8, 6> command_buffer = {};
std::deque<u8> response;
std::array<u8, 512> block_buffer = {};
};
} // namespace ExpansionInterface