/* Copyright 2016-2023 melonDS team This file is part of melonDS. melonDS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. melonDS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with melonDS. If not, see http://www.gnu.org/licenses/. */ #include #include #include "Args.h" #include "DSi.h" #include "DSi_SD.h" #include "DSi_NAND.h" #include "DSi_NWifi.h" #include "Platform.h" namespace melonDS { using std::holds_alternative; using std::unique_ptr; using std::get_if; using std::get; using namespace Platform; // observed IRQ behavior during transfers // // during reads: // * bit23 is cleared during the first block, always set otherwise. weird // * bit24 (RXRDY) gets set when the FIFO is full // // during reads with FIFO32: // * FIFO16 drains directly into FIFO32 // * when bit24 is set, FIFO32 is already full (with contents from the other FIFO) // * reading from an empty FIFO just wraps around (and sets bit21) // * FIFO32 starts filling when bit24 would be set? // // // TX: // * when sending command, if current FIFO full // * upon ContinueTransfer(), if current FIFO full // * -> upon DataTX() if current FIFO full // * when filling FIFO #define SD_DESC Num?"SDIO":"SD/MMC" enum { Transfer_TX = 0, Transfer_RX, }; DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional&& sdcard) noexcept : DSi(dsi), Num(0) { DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); Ports[0] = sdcard ? std::make_unique(DSi, this, std::move(*sdcard)) : nullptr; sdcard = std::nullopt; // to ensure that sdcard isn't left with a moved-from object Ports[1] = std::make_unique(DSi, this, std::move(nand)); } // Creates an SDIO host DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi) noexcept : DSi(dsi), Num(1) { DSi.RegisterEventFunc(Event_DSi_SDIOTransfer , Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); DSi.RegisterEventFunc(Event_DSi_SDIOTransfer, Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); Ports[0] = std::make_unique(DSi, this); Ports[1] = nullptr; } DSi_SDHost::~DSi_SDHost() { DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, Transfer_TX); DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, Transfer_RX); // unique_ptr's destructor will clean up the ports } void DSi_SDHost::Reset() { if (Num == 0) { PortSelect = 0x0200; // CHECKME } else { PortSelect = 0x0100; // CHECKME } SoftReset = 0x0007; // CHECKME SDClock = 0; SDOption = 0; Command = 0; Param = 0; memset(ResponseBuffer, 0, sizeof(ResponseBuffer)); DataFIFO[0].Clear(); DataFIFO[1].Clear(); CurFIFO = 0; DataFIFO32.Clear(); IRQStatus = 0; IRQMask = 0x8B7F031D; CardIRQStatus = 0; CardIRQMask = 0xC007; CardIRQCtl = 0; DataCtl = 0; Data32IRQ = 0; DataMode = 0; BlockCount16 = 0; BlockCount32 = 0; BlockCountInternal = 0; BlockLen16 = 0; BlockLen32 = 0; StopAction = 0; TXReq = false; if (Ports[0]) Ports[0]->Reset(); if (Ports[1]) Ports[1]->Reset(); } FATStorage* DSi_SDHost::GetSDCard() noexcept { if (Num != 0) return nullptr; return static_cast(Ports[0].get())->GetSDCard(); } const FATStorage* DSi_SDHost::GetSDCard() const noexcept { if (Num != 0) return nullptr; return static_cast(Ports[0].get())->GetSDCard(); } DSi_NAND::NANDImage* DSi_SDHost::GetNAND() noexcept { if (Num != 0) return nullptr; return static_cast(Ports[1].get())->GetNAND(); } const DSi_NAND::NANDImage* DSi_SDHost::GetNAND() const noexcept { if (Num != 0) return nullptr; return static_cast(Ports[1].get())->GetNAND(); } void DSi_SDHost::SetSDCard(FATStorage&& sdcard) noexcept { if (Num != 0) return; static_cast(Ports[0].get())->SetSDCard(std::move(sdcard)); } void DSi_SDHost::SetSDCard(std::optional&& sdcard) noexcept { if (Num != 0) return; if (sdcard) { if (!Ports[0]) { Ports[0] = std::make_unique(DSi, this, std::move(*sdcard)); } else { static_cast(Ports[0].get())->SetSDCard(std::move(*sdcard)); } } else { Ports[0] = nullptr; } sdcard = std::nullopt; // a moved-from optional isn't empty, it contains a moved-from object } void DSi_SDHost::SetNAND(DSi_NAND::NANDImage&& nand) noexcept { if (Num != 0) return; static_cast(Ports[1].get())->SetNAND(std::move(nand)); } void DSi_SDHost::DoSavestate(Savestate* file) { file->Section(Num ? "SDIO" : "SDMM"); file->Var16(&PortSelect); file->Var16(&SoftReset); file->Var16(&SDClock); file->Var16(&SDOption); file->Var32(&IRQStatus); file->Var32(&IRQMask); file->Var16(&CardIRQStatus); file->Var16(&CardIRQMask); file->Var16(&CardIRQCtl); file->Var16(&DataCtl); file->Var16(&Data32IRQ); file->Var32(&DataMode); file->Var16(&BlockCount16); file->Var16(&BlockCount32); file->Var16(&BlockCountInternal); file->Var16(&BlockLen16); file->Var16(&BlockLen32); file->Var16(&StopAction); file->Var16(&Command); file->Var32(&Param); file->VarArray(ResponseBuffer, 8); file->Var32(&CurFIFO); DataFIFO[0].DoSavestate(file); DataFIFO[1].DoSavestate(file); DataFIFO32.DoSavestate(file); if (Ports[0]) Ports[0]->DoSavestate(file); if (Ports[1]) Ports[1]->DoSavestate(file); } void DSi_SDHost::UpdateData32IRQ() { if (DataMode == 0) return; u32 oldflags = ((Data32IRQ >> 8) & 0x1) | (((~Data32IRQ) >> 8) & 0x2); oldflags &= (Data32IRQ >> 11); Data32IRQ &= ~0x0300; if (DataFIFO32.Level() >= (BlockLen32>>2)) Data32IRQ |= (1<<8); if (!DataFIFO32.IsEmpty()) Data32IRQ |= (1<<9); u32 newflags = ((Data32IRQ >> 8) & 0x1) | (((~Data32IRQ) >> 8) & 0x2); newflags &= (Data32IRQ >> 11); if ((oldflags == 0) && (newflags != 0)) DSi.SetIRQ2(Num ? IRQ2_DSi_SDIO : IRQ2_DSi_SDMMC); } void DSi_SDHost::ClearIRQ(u32 irq) { IRQStatus &= ~(1<IRQ) CardIRQStatus |= (1<<0); else CardIRQStatus &= ~(1<<0); u16 newflags = CardIRQStatus & ~CardIRQMask; if ((oldflags == 0) && (newflags != 0)) // checkme { DSi.SetIRQ2(Num ? IRQ2_DSi_SDIO : IRQ2_DSi_SDMMC); DSi.SetIRQ2(Num ? IRQ2_DSi_SDIO_Data1 : IRQ2_DSi_SD_Data1); } } void DSi_SDHost::UpdateCardIRQ(u16 oldmask) { u16 oldflags = CardIRQStatus & ~oldmask; u16 newflags = CardIRQStatus & ~CardIRQMask; if ((oldflags == 0) && (newflags != 0)) // checkme { DSi.SetIRQ2(Num ? IRQ2_DSi_SDIO : IRQ2_DSi_SDMMC); DSi.SetIRQ2(Num ? IRQ2_DSi_SDIO_Data1 : IRQ2_DSi_SD_Data1); } } void DSi_SDHost::SendResponse(u32 val, bool last) { *(u32*)&ResponseBuffer[6] = *(u32*)&ResponseBuffer[4]; *(u32*)&ResponseBuffer[4] = *(u32*)&ResponseBuffer[2]; *(u32*)&ResponseBuffer[2] = *(u32*)&ResponseBuffer[0]; *(u32*)&ResponseBuffer[0] = val; if (last) SetIRQ(0); } void DSi_SDHost::FinishRX(u32 param) { CheckSwapFIFO(); if (DataMode == 1) UpdateFIFO32(); else SetIRQ(24); } u32 DSi_SDHost::DataRX(u8* data, u32 len) { if (len != BlockLen16) { Log(LogLevel::Warn, "!! BAD BLOCKLEN\n"); len = BlockLen16; } bool last = (BlockCountInternal == 0); u32 f = CurFIFO ^ 1; for (u32 i = 0; i < len; i += 2) DataFIFO[f].Write(*(u16*)&data[i]); //CurFIFO = f; //SetIRQ(24); // TODO: determine what the delay should be! // for now, this is a placeholder // we need a delay because DSi boot2 will send a command and then wait for IRQ0 // but if IRQ24 is thrown instantly, the handler clears IRQ0 before the // send-command function starts polling IRQ status DSi.ScheduleEvent(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, false, 512, Transfer_RX, 0); return len; } void DSi_SDHost::FinishTX(u32 param) { DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (BlockCountInternal == 0) { if (StopAction & (1<<8)) { if (dev) dev->SendCMD(12, 0); } // CHECKME: presumably IRQ2 should not trigger here, but rather // when the data transfer is done //SetIRQ(0); SetIRQ(2); TXReq = false; } else { if (dev) dev->ContinueTransfer(); } } u32 DSi_SDHost::DataTX(u8* data, u32 len) { TXReq = true; u32 f = CurFIFO; if (DataMode == 1) { if ((DataFIFO32.Level() << 2) < len) { if (DataFIFO32.IsEmpty()) { SetIRQ(25); DSi.CheckNDMAs(1, Num ? 0x29 : 0x28); } return 0; } // drain FIFO32 into FIFO16 if (!DataFIFO[f].IsEmpty()) Log(LogLevel::Warn, "VERY BAD!! TRYING TO DRAIN FIFO32 INTO FIFO16 BUT IT CONTAINS SHIT ALREADY\n"); for (;;) { u32 f = CurFIFO; if ((DataFIFO[f].Level() << 1) >= BlockLen16) break; if (DataFIFO32.IsEmpty()) break; u32 val = DataFIFO32.Read(); DataFIFO[f].Write(val & 0xFFFF); DataFIFO[f].Write(val >> 16); } UpdateData32IRQ(); if (BlockCount32 > 1) BlockCount32--; } else { if ((DataFIFO[f].Level() << 1) < len) { if (DataFIFO[f].IsEmpty()) SetIRQ(25); return 0; } } for (u32 i = 0; i < len; i += 2) *(u16*)&data[i] = DataFIFO[f].Read(); CurFIFO ^= 1; BlockCountInternal--; DSi.ScheduleEvent(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, false, 512, Transfer_TX, 0); return len; } u32 DSi_SDHost::GetTransferrableLen(u32 len) { if (len > BlockLen16) len = BlockLen16; // checkme return len; } void DSi_SDHost::CheckRX() { DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); CheckSwapFIFO(); if (BlockCountInternal <= 1) { if (StopAction & (1<<8)) { if (dev) dev->SendCMD(12, 0); } // CHECKME: presumably IRQ2 should not trigger here, but rather // when the data transfer is done //SetIRQ(0); SetIRQ(2); } else { BlockCountInternal--; if (dev) dev->ContinueTransfer(); } } void DSi_SDHost::CheckTX() { if (!TXReq) return; if (DataMode == 1) { if ((DataFIFO32.Level() << 2) < BlockLen32) return; } else { u32 f = CurFIFO; if ((DataFIFO[f].Level() << 1) < BlockLen16) return; } DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev) dev->ContinueTransfer(); } u16 DSi_SDHost::Read(u32 addr) { switch (addr & 0x1FF) { case 0x000: return Command; case 0x002: return PortSelect & 0x030F; case 0x004: return Param & 0xFFFF; case 0x006: return Param >> 16; case 0x008: return StopAction; case 0x00A: return BlockCount16; case 0x00C: return ResponseBuffer[0]; case 0x00E: return ResponseBuffer[1]; case 0x010: return ResponseBuffer[2]; case 0x012: return ResponseBuffer[3]; case 0x014: return ResponseBuffer[4]; case 0x016: return ResponseBuffer[5]; case 0x018: return ResponseBuffer[6]; case 0x01A: return ResponseBuffer[7]; case 0x01C: { u16 ret = (IRQStatus & 0x031D); if (!Num) { if (Ports[0]) // basic check of whether the SD card is inserted { ret |= 0x0020; if (!Ports[0]->ReadOnly) ret |= 0x0080; } } else { // SDIO wifi is always inserted, I guess ret |= 0x00A0; } return ret; } case 0x01E: return ((IRQStatus >> 16) & 0x8B7F); case 0x020: return IRQMask & 0x031D; case 0x022: return (IRQMask >> 16) & 0x8B7F; case 0x024: return SDClock; case 0x026: return BlockLen16; case 0x028: return SDOption; case 0x02C: return 0; // TODO case 0x034: return CardIRQCtl; case 0x036: return CardIRQStatus; case 0x038: return CardIRQMask; case 0x030: return ReadFIFO16(); case 0x0D8: return DataCtl; case 0x0E0: return SoftReset; case 0x0F6: return 0; // MMC write protect (always 0) case 0x100: return Data32IRQ; case 0x102: return 0; case 0x104: return BlockLen32; case 0x108: return BlockCount32; // dunno case 0x106: return 0; case 0x10A: return 0; } Log(LogLevel::Warn, "unknown %s read %08X @ %08X\n", SD_DESC, addr, DSi.GetPC(1)); return 0; } u16 DSi_SDHost::ReadFIFO16() { u32 f = CurFIFO; if (DataFIFO[f].IsEmpty()) { // TODO // on hardware it seems to wrap around. underflow bit is set upon the first 'empty' read. return 0; } u16 ret = DataFIFO[f].Read(); if (DataFIFO[f].IsEmpty()) { CheckRX(); } return ret; } u32 DSi_SDHost::ReadFIFO32() { if (DataMode != 1) return 0; if (DataFIFO32.IsEmpty()) { // TODO return 0; } u32 ret = DataFIFO32.Read(); if (DataFIFO32.IsEmpty()) { CheckRX(); } UpdateData32IRQ(); return ret; } void DSi_SDHost::Write(u32 addr, u16 val) { switch (addr & 0x1FF) { case 0x000: { Command = val; u8 cmd = Command & 0x3F; DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev) { // CHECKME // "Setting Command Type to "ACMD" is automatically sending an APP_CMD prefix prior to the command number" // except DSi boot2 manually sends an APP_CMD prefix AND sets the next command to be ACMD switch ((Command >> 6) & 0x3) { case 0: dev->SendCMD(cmd, Param); break; case 1: /*dev->SendCMD(55, 0);*/ dev->SendCMD(cmd, Param); break; default: Log(LogLevel::Warn, "%s: unknown command type %d, %02X %08X\n", SD_DESC, (Command>>6)&0x3, cmd, Param); break; } } else Log(LogLevel::Debug, "%s: SENDING CMD %04X TO NULL DEVICE\n", SD_DESC, val); } return; case 0x002: PortSelect = (val & 0x040F) | (PortSelect & 0x0300); return; case 0x004: Param = (Param & 0xFFFF0000) | val; return; case 0x006: Param = (Param & 0x0000FFFF) | (val << 16); return; case 0x008: StopAction = val & 0x0101; return; case 0x00A: BlockCount16 = val; BlockCountInternal = val; return; case 0x01C: IRQStatus &= (val | 0xFFFF0000); return; case 0x01E: IRQStatus &= ((val << 16) | 0xFFFF); return; case 0x020: { u32 oldmask = IRQMask; IRQMask = (IRQMask & 0x8B7F0000) | (val & 0x031D); UpdateIRQ(oldmask); } return; case 0x022: { u32 oldmask = IRQMask; IRQMask = (IRQMask & 0x0000031D) | ((val & 0x8B7F) << 16); UpdateIRQ(oldmask); //if (!DataFIFO[CurFIFO]->IsEmpty()) SetIRQ(24); // checkme //if (DataFIFO[CurFIFO]->IsEmpty()) SetIRQ(25); // checkme } return; case 0x024: SDClock = val & 0x03FF; return; case 0x026: BlockLen16 = val & 0x03FF; if (BlockLen16 > 0x200) BlockLen16 = 0x200; return; case 0x028: SDOption = val & 0xC1FF; return; case 0x030: WriteFIFO16(val); return; case 0x034: CardIRQCtl = val & 0x0305; SetCardIRQ(); return; case 0x036: CardIRQStatus &= val; return; case 0x038: { u16 oldmask = CardIRQMask; CardIRQMask = val & 0xC007; UpdateCardIRQ(oldmask); } //CardIRQMask = val & 0xC007; //SetCardIRQ(); return; case 0x0D8: DataCtl = (val & 0x0022); DataMode = ((DataCtl >> 1) & 0x1) & ((Data32IRQ >> 1) & 0x1); return; case 0x0E0: if ((SoftReset & 0x0001) && !(val & 0x0001)) { Log(LogLevel::Debug, "%s: RESET\n", SD_DESC); StopAction = 0; memset(ResponseBuffer, 0, sizeof(ResponseBuffer)); IRQStatus = 0; // TODO: ERROR_DETAIL_STATUS SDClock &= ~0x0500; SDOption = 0x40EE; // TODO: CARD_IRQ_STAT // TODO: FIFO16 shit if (Ports[0]) Ports[0]->Reset(); if (Ports[1]) Ports[1]->Reset(); } SoftReset = 0x0006 | (val & 0x0001); return; case 0x100: Data32IRQ = (val & 0x1802) | (Data32IRQ & 0x0300); if (val & (1<<10)) DataFIFO32.Clear(); DataMode = ((DataCtl >> 1) & 0x1) & ((Data32IRQ >> 1) & 0x1); return; case 0x102: return; case 0x104: BlockLen32 = val & 0x03FF; return; case 0x108: BlockCount32 = val; return; // dunno case 0x106: return; case 0x10A: return; } Log(LogLevel::Warn, "unknown %s write %08X %04X\n", SD_DESC, addr, val); } void DSi_SDHost::WriteFIFO16(u16 val) { u32 f = CurFIFO; if (DataFIFO[f].IsFull()) { // TODO Log(LogLevel::Error, "!!!! %s FIFO (16) FULL\n", SD_DESC); return; } DataFIFO[f].Write(val); CheckTX(); } void DSi_SDHost::WriteFIFO32(u32 val) { if (DataMode != 1) return; if (DataFIFO32.IsFull()) { // TODO Log(LogLevel::Error, "!!!! %s FIFO (32) FULL\n", SD_DESC); return; } DataFIFO32.Write(val); CheckTX(); UpdateData32IRQ(); } void DSi_SDHost::UpdateFIFO32() { // check whether we can drain FIFO32 into FIFO16, or vice versa if (DataMode != 1) return; if (!DataFIFO32.IsEmpty()) Log(LogLevel::Warn, "VERY BAD!! TRYING TO DRAIN FIFO16 INTO FIFO32 BUT IT CONTAINS SHIT ALREADY\n"); for (;;) { u32 f = CurFIFO; if ((DataFIFO32.Level() << 2) >= BlockLen32) break; if (DataFIFO[f].IsEmpty()) break; u32 val = DataFIFO[f].Read(); val |= (DataFIFO[f].Read() << 16); DataFIFO32.Write(val); } UpdateData32IRQ(); if ((DataFIFO32.Level() << 2) >= BlockLen32) { DSi.CheckNDMAs(1, Num ? 0x29 : 0x28); } } void DSi_SDHost::CheckSwapFIFO() { // check whether we can swap the FIFOs u32 f = CurFIFO; bool cur_empty = (DataMode == 1) ? DataFIFO32.IsEmpty() : DataFIFO[f].IsEmpty(); if (cur_empty && ((DataFIFO[f^1].Level() << 1) >= BlockLen16)) { CurFIFO ^= 1; } } #define MMC_DESC (Internal?"NAND":"SDcard") DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept : DSi_SDDevice(host), DSi(dsi), Storage(std::move(nand)) { ReadOnly = false; SetCID(get(Storage).GetEMMCID().data()); } DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept : DSi_SDDevice(host), DSi(dsi), Storage(std::move(sdcard)) { ReadOnly = get(Storage).IsReadOnly(); SetCID(DSiSDCardCID); } // The FATStorage or NANDImage is owned by this object; // std::variant's destructor will clean it up. DSi_MMCStorage::~DSi_MMCStorage() = default; void DSi_MMCStorage::Reset() { // TODO: reset file access???? CSR = 0x00000100; // checkme // TODO: busy bit // TODO: SDHC/SDXC bit OCR = 0x80FF8000; // TODO: customize based on card size etc u8 csd_template[16] = {0x40, 0x40, 0x96, 0xE9, 0x7F, 0xDB, 0xF6, 0xDF, 0x01, 0x59, 0x0F, 0x2A, 0x01, 0x26, 0x90, 0x00}; memcpy(CSD, csd_template, 16); // checkme memset(SCR, 0, 8); *(u32*)&SCR[0] = 0x012A0000; memset(SSR, 0, 64); BlockSize = 0; RWAddress = 0; RWCommand = 0; } void DSi_MMCStorage::DoSavestate(Savestate* file) { file->Section(holds_alternative(Storage) ? "NAND" : "SDCR"); file->VarArray(CID, 16); file->VarArray(CSD, 16); file->Var32(&CSR); file->Var32(&OCR); file->Var32(&RCA); file->VarArray(SCR, 8); file->VarArray(SSR, 64); file->Var32(&BlockSize); file->Var64(&RWAddress); file->Var32(&RWCommand); // TODO: what about the file contents? } void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) { if (CSR & (1<<5)) { CSR &= ~(1<<5); return SendACMD(cmd, param); } switch (cmd) { case 0: // reset/etc Host->SendResponse(CSR, true); return; case 1: // SEND_OP_COND // CHECKME!! // also TODO: it's different for the SD card if (std::holds_alternative(Storage)) { param &= ~(1<<30); OCR &= 0xBF000000; OCR |= (param & 0x40FFFFFF); Host->SendResponse(OCR, true); SetState(0x01); } else { Log(LogLevel::Debug, "CMD1 on SD card!!\n"); } return; case 2: case 10: // get CID Host->SendResponse(*(u32*)&CID[12], false); Host->SendResponse(*(u32*)&CID[8], false); Host->SendResponse(*(u32*)&CID[4], false); Host->SendResponse(*(u32*)&CID[0], true); if (cmd == 2) SetState(0x02); return; case 3: // get/set RCA if (holds_alternative(Storage)) { RCA = param >> 16; Host->SendResponse(CSR|0x10000, true); // huh?? } else { // TODO Log(LogLevel::Debug, "CMD3 on SD card: TODO\n"); Host->SendResponse((CSR & 0x1FFF) | ((CSR >> 6) & 0x2000) | ((CSR >> 8) & 0xC000) | (1 << 16), true); } return; case 6: // MMC: 'SWITCH' // TODO! Host->SendResponse(CSR, true); return; case 7: // select card (by RCA) Host->SendResponse(CSR, true); return; case 8: // set voltage Host->SendResponse(param, true); return; case 9: // get CSD Host->SendResponse(*(u32*)&CSD[12], false); Host->SendResponse(*(u32*)&CSD[8], false); Host->SendResponse(*(u32*)&CSD[4], false); Host->SendResponse(*(u32*)&CSD[0], true); return; case 12: // stop operation SetState(0x04); if (auto* nand = get_if(&Storage)) FileFlush(nand->GetFile()); RWCommand = 0; Host->SendResponse(CSR, true); return; case 13: // get status Host->SendResponse(CSR, true); return; case 16: // set block size BlockSize = param; if (BlockSize > 0x200) { // TODO! raise error Log(LogLevel::Warn, "!! SD/MMC: BAD BLOCK LEN %d\n", BlockSize); BlockSize = 0x200; } SetState(0x04); // CHECKME Host->SendResponse(CSR, true); return; case 17: // read single block case 18: // read multiple blocks //printf("READ_MULTIPLE_BLOCKS addr=%08X size=%08X\n", param, BlockSize); RWAddress = param; if (OCR & (1<<30)) { RWAddress <<= 9; BlockSize = 512; } if (cmd == 18) RWCommand = 18; Host->SendResponse(CSR, true); RWAddress += ReadBlock(RWAddress); SetState(0x05); return; case 24: // write single block case 25: // write multiple blocks //printf("WRITE_MULTIPLE_BLOCKS addr=%08X size=%08X\n", param, BlockSize); RWAddress = param; if (OCR & (1<<30)) { RWAddress <<= 9; BlockSize = 512; } if (cmd == 25) RWCommand = 25; Host->SendResponse(CSR, true); RWAddress += WriteBlock(RWAddress); SetState(0x04); return; case 55: // appcmd prefix CSR |= (1<<5); Host->SendResponse(CSR, true); return; } Log(LogLevel::Warn, "MMC: unknown CMD %d %08X\n", cmd, param); } void DSi_MMCStorage::SendACMD(u8 cmd, u32 param) { switch (cmd) { case 6: // set bus width (TODO?) //printf("SET BUS WIDTH %08X\n", param); Host->SendResponse(CSR, true); return; case 13: // get SSR Host->SendResponse(CSR, true); Host->DataRX(SSR, 64); return; case 41: // set operating conditions // CHECKME: // DSi boot2 sets this to 0x40100000 (hardcoded) // then has two codepaths depending on whether bit30 did get set // is it settable at all on the MMC? probably not. if (holds_alternative(Storage)) param &= ~(1<<30); OCR &= 0xBF000000; OCR |= (param & 0x40FFFFFF); Host->SendResponse(OCR, true); SetState(0x01); return; case 42: // ??? Host->SendResponse(CSR, true); return; case 51: // get SCR Host->SendResponse(CSR, true); Host->DataRX(SCR, 8); return; } Log(LogLevel::Warn, "MMC: unknown ACMD %d %08X\n", cmd, param); } void DSi_MMCStorage::ContinueTransfer() { if (RWCommand == 0) return; u32 len = 0; switch (RWCommand) { case 18: len = ReadBlock(RWAddress); break; case 25: len = WriteBlock(RWAddress); break; } RWAddress += len; } u32 DSi_MMCStorage::ReadBlock(u64 addr) { u32 len = BlockSize; len = Host->GetTransferrableLen(len); u8 data[0x200]; if (auto* sd = std::get_if(&Storage)) { sd->ReadSectors((u32)(addr >> 9), 1, data); } else if (auto* nand = std::get_if(&Storage)) { FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start); FileRead(&data[addr & 0x1FF], 1, len, nand->GetFile()); } return Host->DataRX(&data[addr & 0x1FF], len); } u32 DSi_MMCStorage::WriteBlock(u64 addr) { u32 len = BlockSize; len = Host->GetTransferrableLen(len); u8 data[0x200]; if (len < 0x200) { if (auto* sd = get_if(&Storage)) { sd->ReadSectors((u32)(addr >> 9), 1, data); } } if ((len = Host->DataTX(&data[addr & 0x1FF], len))) { if (!ReadOnly) { if (auto* sd = get_if(&Storage)) { sd->WriteSectors((u32)(addr >> 9), 1, data); } else if (auto* nand = get_if(&Storage)) { FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start); FileWrite(&data[addr & 0x1FF], 1, len, nand->GetFile()); } } } return len; } }