/* * Copyright (c) Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program. If not, see . */ #if defined(ATMOSPHERE_IS_STRATOSPHERE) #include #elif defined(ATMOSPHERE_IS_MESOSPHERE) #include #elif defined(ATMOSPHERE_IS_EXOSPHERE) #include #else #include #endif #include "sdmmc_mmc_device_accessor.hpp" #include "sdmmc_timer.hpp" namespace ams::sdmmc::impl { #if defined(AMS_SDMMC_THREAD_SAFE) #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() std::scoped_lock lk(m_mmc_device.m_device_mutex) #else #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() #endif namespace { constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); constexpr inline u32 OcrAccessMode_Mask = (3 << 29); constexpr inline u32 OcrAccessMode_SectorMode = (2 << 29); constexpr inline u8 ManufacturerId_Toshiba = 0x11; enum DeviceType : u8 { DeviceType_HighSpeed26MHz = (1u << 0), DeviceType_HighSpeed52MHz = (1u << 1), DeviceType_HighSpeedDdr52MHz1_8VOr3_0V = (1u << 2), DeviceType_HighSpeedDdr52MHz1_2V = (1u << 3), DeviceType_Hs200Sdr200MHz1_8V = (1u << 4), DeviceType_Hs200Sdr200MHz1_2V = (1u << 5), DeviceType_Hs400Sdr200MHz1_8V = (1u << 6), DeviceType_Hs400Sdr200MHz1_2V = (1u << 7), }; constexpr bool IsToshibaMmc(const u8 *cid) { /* Check whether the CID's manufacturer id field is Toshiba. */ AMS_ABORT_UNLESS(cid != nullptr); return cid[14] == ManufacturerId_Toshiba; } constexpr bool IsLessThanSpecification4(const u8 *csd) { const u8 spec_vers = ((csd[14] >> 2) & 0xF); return spec_vers < 4; } constexpr bool IsBkopAutoEnable(const u8 *ext_csd) { /* Check the AUTO_EN bit of BKOPS_EN. */ return (ext_csd[163] & (1u << 1)) != 0; } constexpr u8 GetDeviceType(const u8 *ext_csd) { /* Get the DEVICE_TYPE register. */ AMS_ABORT_UNLESS(ext_csd != nullptr); return ext_csd[196]; } constexpr bool IsSupportedHs400(u8 device_type) { return (device_type & DeviceType_Hs400Sdr200MHz1_8V) != 0; } constexpr bool IsSupportedHs200(u8 device_type) { return (device_type & DeviceType_Hs200Sdr200MHz1_8V) != 0; } constexpr bool IsSupportedHighSpeed(u8 device_type) { return (device_type & DeviceType_HighSpeed52MHz) != 0; } constexpr u32 GetMemoryCapacityFromExtCsd(const u32 *ext_csd) { /* Get the SEC_COUNT register. */ AMS_ABORT_UNLESS(ext_csd != nullptr); return ext_csd[212 / sizeof(u32)]; } constexpr u32 GetBootPartitionMemoryCapacityFromExtCsd(const u8 *ext_csd) { /* Get the BOOT_SIZE_MULT register. */ AMS_ABORT_UNLESS(ext_csd != nullptr); return ext_csd[226] * (128_KB / SectorSize); } constexpr Result GetCurrentSpeedModeFromExtCsd(SpeedMode *out, const u8 *ext_csd) { /* Get the HS_TIMING register. */ AMS_ABORT_UNLESS(out != nullptr); AMS_ABORT_UNLESS(ext_csd != nullptr); switch (ext_csd[185] & 0xF) { case 0: *out = SpeedMode_MmcLegacySpeed; break; case 1: *out = SpeedMode_MmcHighSpeed; break; case 2: *out = SpeedMode_MmcHs200; break; case 3: *out = SpeedMode_MmcHs400; break; default: R_THROW(sdmmc::ResultUnexpectedMmcExtendedCsdValue()); } R_SUCCEED(); } } void MmcDevice::SetOcrAndHighCapacity(u32 ocr) { /* Set ocr. */ BaseDevice::SetOcr(ocr); /* Set high capacity. */ BaseDevice::SetHighCapacity((ocr & OcrAccessMode_Mask) == OcrAccessMode_SectorMode); } Result MmcDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const { /* Get the command argument. */ u32 arg = OcrAccessMode_SectorMode; switch (bus_power) { case BusPower_1_8V: arg |= 0x000080; break; case BusPower_3_3V: arg |= 0x03F800; break; AMS_UNREACHABLE_DEFAULT_CASE(); } /* Issue the command. */ constexpr ResponseType CommandResponseType = ResponseType_R3; Command command(CommandIndex_SendOpCond, arg, CommandResponseType, false); IHostController *hc = BaseDeviceAccessor::GetHostController(); R_TRY(hc->IssueCommand(std::addressof(command))); /* Get the response. */ hc->GetLastResponse(out_ocr, sizeof(*out_ocr), CommandResponseType); R_SUCCEED(); } Result MmcDeviceAccessor::IssueCommandSetRelativeAddr() const { /* Get rca. */ const u32 rca = m_mmc_device.GetRca(); AMS_ABORT_UNLESS(rca > 0); /* Issue comamnd. */ const u32 arg = rca << 16; R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_SetRelativeAddr, arg, false, DeviceState_Unknown)); R_SUCCEED(); } Result MmcDeviceAccessor::IssueCommandSwitch(CommandSwitch cs) const { /* Get the command argument. */ const u32 arg = GetCommandSwitchArgument(cs); /* Issue the command. */ R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Switch, arg, true, DeviceState_Unknown)); R_SUCCEED(); } Result MmcDeviceAccessor::IssueCommandSendExtCsd(void *dst, size_t dst_size) const { /* Validate the output buffer. */ AMS_ABORT_UNLESS(dst != nullptr); AMS_ABORT_UNLESS(dst_size >= MmcExtendedCsdSize); /* Issue the command. */ constexpr ResponseType CommandResponseType = ResponseType_R1; Command command(CommandIndex_SendExtCsd, 0, CommandResponseType, false); TransferData xfer_data(dst, MmcExtendedCsdSize, 1, TransferDirection_ReadFromDevice); IHostController *hc = BaseDeviceAccessor::GetHostController(); R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); /* Get the response. */ u32 resp; hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); R_TRY(m_mmc_device.CheckDeviceStatus(resp)); R_SUCCEED(); } Result MmcDeviceAccessor::IssueCommandEraseGroupStart(u32 sector_index) const { /* Get the command argument. */ const u32 arg = m_mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; /* Issue the command. */ R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupStart, arg, false, DeviceState_Unknown)); R_SUCCEED(); } Result MmcDeviceAccessor::IssueCommandEraseGroupEnd(u32 sector_index) const { /* Get the command argument. */ const u32 arg = m_mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; /* Issue the command. */ R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupEnd, arg, false, DeviceState_Tran)); R_SUCCEED(); } Result MmcDeviceAccessor::IssueCommandErase() const { /* Issue the command. */ R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Erase, 0, false, DeviceState_Tran)); R_SUCCEED(); } Result MmcDeviceAccessor::CancelToshibaMmcModel() { /* Special erase sequence done by Nintendo on Toshiba MMCs. */ R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsProductionStateAwarenessEnable)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_TRY(this->IssueCommandSwitch(CommandSwitch_ClearBitsAutoModeEnable)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessNormal)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_SUCCEED(); } Result MmcDeviceAccessor::ChangeToReadyState(BusPower bus_power) { /* Be prepared to wait up to 1.5 seconds to change state. */ ManualTimer timer(1500); while (true) { /* Get the ocr, and check if we're done. */ u32 ocr; R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), bus_power)); if ((ocr & OcrCardPowerUpStatus) != 0) { m_mmc_device.SetOcrAndHighCapacity(ocr); R_SUCCEED(); } /* Check if we've timed out. */ R_UNLESS(timer.Update(), sdmmc::ResultMmcInitializationSoftwareTimeout()); /* Try again in 1ms. */ WaitMicroSeconds(1000); } } Result MmcDeviceAccessor::ExtendBusWidth(BusWidth max_bw) { /* If the maximum bus width is 1bit, we can't extend. */ R_SUCCEED_IF(max_bw == BusWidth_1Bit); /* Determine what bus width to switch to. */ IHostController *hc = BaseDeviceAccessor::GetHostController(); BusWidth target_bw = BusWidth_1Bit; CommandSwitch cs; if (max_bw == BusWidth_8Bit && hc->IsSupportedBusWidth(BusWidth_8Bit)) { target_bw = BusWidth_8Bit; cs = CommandSwitch_WriteBusWidth8Bit; } else if ((max_bw == BusWidth_8Bit || max_bw == BusWidth_4Bit) && hc->IsSupportedBusWidth(BusWidth_4Bit)) { target_bw = BusWidth_4Bit; cs = CommandSwitch_WriteBusWidth4Bit; } else { /* Target bus width is 1bit. */ R_SUCCEED(); } /* Set the bus width. */ R_TRY(this->IssueCommandSwitch(cs)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); hc->SetBusWidth(target_bw); R_SUCCEED(); } Result MmcDeviceAccessor::EnableBkopsAuto() { /* Issue the command. */ R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsBkopsEnAutoEn)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_SUCCEED(); } Result MmcDeviceAccessor::ChangeToHighSpeed(bool check_before) { /* Issue high speed command. */ R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHighSpeed)); /* If we should check status before setting mode, do so. */ if (check_before) { R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); } /* Set the host controller to high speed. */ R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_MmcHighSpeed)); /* If we should check status after setting mode, do so. */ if (!check_before) { R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); } R_SUCCEED(); } Result MmcDeviceAccessor::ChangeToHs200() { /* Issue Hs200 command. */ R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs200)); /* Set the host controller to Hs200. */ IHostController *hc = BaseDeviceAccessor::GetHostController(); R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs200)); /* Perform tuning using command index 21. */ R_TRY(hc->Tuning(SpeedMode_MmcHs200, 21)); /* Check status. */ R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_SUCCEED(); } Result MmcDeviceAccessor::ChangeToHs400() { /* Change first to Hs200. */ R_TRY(this->ChangeToHs200()); /* Save tuning status. */ IHostController *hc = BaseDeviceAccessor::GetHostController(); hc->SaveTuningStatusForHs400(); /* Change to high speed. */ R_TRY(this->ChangeToHighSpeed(false)); /* Issue Hs400 command. */ R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteBusWidth8BitDdr)); R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs400)); /* Set the host controller to Hs400. */ R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs400)); /* Check status. */ R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); R_SUCCEED(); } Result MmcDeviceAccessor::ExtendBusSpeed(u8 device_type, SpeedMode max_sm) { /* We want to switch to the highest speed we can. */ /* Check Hs400/Hs200 first. */ IHostController *hc = BaseDeviceAccessor::GetHostController(); if (hc->IsSupportedTuning() && hc->GetBusPower() == BusPower_1_8V) { if (hc->GetBusWidth() == BusWidth_8Bit && IsSupportedHs400(device_type) && max_sm == SpeedMode_MmcHs400) { R_RETURN(this->ChangeToHs400()); } else if ((hc->GetBusWidth() == BusWidth_8Bit || hc->GetBusWidth() == BusWidth_4Bit) && IsSupportedHs200(device_type) && (max_sm == SpeedMode_MmcHs400 || max_sm == SpeedMode_MmcHs200)) { R_RETURN(this->ChangeToHs200()); } } /* Check if we can switch to high speed. */ if (IsSupportedHighSpeed(device_type)) { R_RETURN(this->ChangeToHighSpeed(true)); } /* We can't, so stay at normal speeds. */ R_SUCCEED(); } Result MmcDeviceAccessor::StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { /* Start up at an appropriate bus power. */ IHostController *hc = BaseDeviceAccessor::GetHostController(); const BusPower bp = hc->IsSupportedBusPower(BusPower_1_8V) ? BusPower_1_8V : BusPower_3_3V; R_TRY(hc->Startup(bp, BusWidth_1Bit, SpeedMode_MmcIdentification, false)); /* Wait 1ms for configuration to take. */ WaitMicroSeconds(1000); /* Wait an additional 74 clocks for configuration to take. */ WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); /* Go to idle state. */ R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); m_current_partition = MmcPartition_UserData; /* Go to ready state. */ R_TRY(this->ChangeToReadyState(bp)); /* Get the CID. */ R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); m_mmc_device.SetCid(wb, wb_size); const bool is_toshiba = IsToshibaMmc(static_cast(wb)); /* Issue set relative addr. */ R_TRY(this->IssueCommandSetRelativeAddr()); /* Get the CSD. */ R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); m_mmc_device.SetCsd(wb, wb_size); const bool spec_under_4 = IsLessThanSpecification4(static_cast(wb)); /* Set the speed mode to legacy. */ R_TRY(hc->SetSpeedMode(SpeedMode_MmcLegacySpeed)); /* Issue select card command. */ R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); /* Set block length to sector size. */ R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); /* If the device SPEC_VERS is less than 4, extended csd/switch aren't supported. */ if (spec_under_4) { R_TRY(m_mmc_device.SetLegacyMemoryCapacity()); m_mmc_device.SetActive(); R_SUCCEED(); } /* Extend the bus width to the largest that we can. */ R_TRY(this->ExtendBusWidth(max_bw)); /* Get the extended csd. */ R_TRY(this->IssueCommandSendExtCsd(wb, wb_size)); AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(wb), alignof(u32))); m_mmc_device.SetMemoryCapacity(GetMemoryCapacityFromExtCsd(static_cast(wb))); /* If the mmc is manufactured by toshiba, try to enable bkops auto. */ if (is_toshiba && !IsBkopAutoEnable(static_cast(wb))) { /* NOTE: Nintendo does not check the result of this. */ this->EnableBkopsAuto(); } /* Extend the bus speed to as fast as we can. */ const u8 device_type = GetDeviceType(static_cast(wb)); R_TRY(this->ExtendBusSpeed(device_type, max_sm)); /* Enable power saving. */ hc->SetPowerSaving(true); R_SUCCEED(); } Result MmcDeviceAccessor::OnActivate() { /* Define the possible startup parameters. */ constexpr const struct { BusWidth bus_width; SpeedMode speed_mode; } StartupParameters[] = { #if defined(AMS_SDMMC_ENABLE_MMC_HS400) { BusWidth_8Bit, SpeedMode_MmcHs400 }, #else { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, #endif { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, { BusWidth_1Bit, SpeedMode_MmcHighSpeed }, }; /* Try to start up with each set of parameters. */ Result result; for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { /* Alias the parameters. */ const auto ¶ms = StartupParameters[i]; /* Set our max bus width/speed mode. */ m_max_bus_width = params.bus_width; m_max_speed_mode = params.speed_mode; /* Try to start up the device. */ result = this->StartupMmcDevice(m_max_bus_width, m_max_speed_mode, m_work_buffer, m_work_buffer_size); if (R_SUCCEEDED(result)) { /* If we previously failed to start up the device, log the error correction. */ if (i != 0) { BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", m_max_bus_width, m_max_speed_mode); BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); } R_SUCCEED(); } /* Log that our startup failed. */ BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", m_max_bus_width, m_max_speed_mode, result.GetValue()); /* Shut down the host controller before we try to start up again. */ BaseDeviceAccessor::GetHostController()->Shutdown(); } /* We failed to start up with all sets of parameters. */ BaseDeviceAccessor::PushErrorTimeStamp(); R_RETURN(result); } Result MmcDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { /* Get the sector index alignment. */ u32 sector_index_alignment = 0; if (!is_read) { constexpr u32 MmcWriteSectorAlignment = 16_KB / SectorSize; sector_index_alignment = MmcWriteSectorAlignment; AMS_ABORT_UNLESS(util::IsAligned(sector_index, MmcWriteSectorAlignment)); } /* Do the read/write. */ R_RETURN(BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, sector_index_alignment, buf, buf_size, is_read)); } Result MmcDeviceAccessor::ReStartup() { /* Shut down the host controller. */ BaseDeviceAccessor::GetHostController()->Shutdown(); /* Perform start up. */ Result result = this->StartupMmcDevice(m_max_bus_width, m_max_speed_mode, m_work_buffer, m_work_buffer_size); if (R_FAILED(result)) { BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", m_max_bus_width, m_max_speed_mode, result.GetValue()); R_RETURN(result); } R_SUCCEED(); } void MmcDeviceAccessor::Initialize() { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* If we've already initialized, we don't need to do anything. */ if (m_is_initialized) { return; } /* Set the base device to our mmc device. */ BaseDeviceAccessor::SetDevice(std::addressof(m_mmc_device)); /* Initialize. */ BaseDeviceAccessor::GetHostController()->Initialize(); m_is_initialized = true; } void MmcDeviceAccessor::Finalize() { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* If we've already finalized, we don't need to do anything. */ if (!m_is_initialized) { return; } m_is_initialized = false; /* Deactivate the device. */ BaseDeviceAccessor::Deactivate(); /* Finalize the host controller. */ BaseDeviceAccessor::GetHostController()->Finalize(); } Result MmcDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { /* Check that we can write to output. */ AMS_ABORT_UNLESS(out_speed_mode != nullptr); /* Get the current speed mode from the ext csd. */ R_TRY(GetMmcExtendedCsd(m_work_buffer, m_work_buffer_size)); R_TRY(GetCurrentSpeedModeFromExtCsd(out_speed_mode, static_cast(m_work_buffer))); R_SUCCEED(); } void MmcDeviceAccessor::PutMmcToSleep() { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* If the device isn't awake, we don't need to do anything. */ if (!m_mmc_device.IsAwake()) { return; } /* Put the device to sleep. */ m_mmc_device.PutToSleep(); /* If necessary, put the host controller to sleep. */ if (m_mmc_device.IsActive()) { BaseDeviceAccessor::GetHostController()->PutToSleep(); } } void MmcDeviceAccessor::AwakenMmc() { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* If the device is awake, we don't need to do anything. */ if (m_mmc_device.IsAwake()) { return; } /* Wake the host controller, if we need to.*/ if (m_mmc_device.IsActive()) { const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); if (R_FAILED(result)) { BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); } } /* Wake the device. */ m_mmc_device.Awaken(); } Result MmcDeviceAccessor::SelectMmcPartition(MmcPartition part) { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* Check that we can access the device. */ R_TRY(m_mmc_device.CheckAccessible()); /* Determine the appropriate SWITCH subcommand. */ CommandSwitch cs; switch (part) { case MmcPartition_UserData: cs = CommandSwitch_WritePartitionAccessDefault; break; case MmcPartition_BootPartition1: cs = CommandSwitch_WritePartitionAccessRwBootPartition1; break; case MmcPartition_BootPartition2: cs = CommandSwitch_WritePartitionAccessRwBootPartition2; break; AMS_UNREACHABLE_DEFAULT_CASE(); } /* Change partition. */ m_current_partition = MmcPartition_Unknown; { R_TRY(this->IssueCommandSwitch(cs)); R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); } m_current_partition = part; R_SUCCEED(); } Result MmcDeviceAccessor::EraseMmc() { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* Check that we can access the device. */ R_TRY(m_mmc_device.CheckAccessible()); /* Get the partition capacity. */ u32 part_capacity; switch (m_current_partition) { case MmcPartition_UserData: part_capacity = m_mmc_device.GetMemoryCapacity(); break; case MmcPartition_BootPartition1: case MmcPartition_BootPartition2: R_TRY(this->GetMmcBootPartitionCapacity(std::addressof(part_capacity))); break; AMS_UNREACHABLE_DEFAULT_CASE(); } /* Begin the erase. */ R_TRY(this->IssueCommandEraseGroupStart(0)); R_TRY(this->IssueCommandEraseGroupEnd(part_capacity - 1)); /* Issue the erase command, allowing 30 seconds for it to complete. */ ManualTimer timer(30000); Result result = this->IssueCommandErase(); R_TRY_CATCH(result) { R_CATCH(sdmmc::ResultDataTimeoutError) { /* Data timeout error is acceptable. */ } R_CATCH(sdmmc::ResultCommandCompleteSoftwareTimeout) { /* Command complete software timeout error is acceptable. */ } R_CATCH(sdmmc::ResultBusySoftwareTimeout) { /* Busy software timeout error is acceptable. */ } } R_END_TRY_CATCH; /* Wait for the erase to finish. */ while (true) { /* Check if we're done. */ result = BaseDeviceAccessor::IssueCommandSendStatus(); if (R_SUCCEEDED(result)) { break; } /* Otherwise, check if we should reject the error. */ if (!sdmmc::ResultUnexpectedDeviceState::Includes(result)) { R_RETURN(result); } /* Check if timeout has been exceeded. */ R_UNLESS(timer.Update(), sdmmc::ResultMmcEraseSoftwareTimeout()); } /* If the partition is user data, check if we need to perform toshiba-specific erase. */ if (m_current_partition == MmcPartition_UserData) { u8 cid[DeviceCidSize]; m_mmc_device.GetCid(cid, sizeof(cid)); if (IsToshibaMmc(cid)) { /* NOTE: Nintendo does not check the result of this operation. */ this->CancelToshibaMmcModel(); } } R_SUCCEED(); } Result MmcDeviceAccessor::GetMmcBootPartitionCapacity(u32 *out_num_sectors) const { /* Get the capacity from the extended csd. */ AMS_ABORT_UNLESS(out_num_sectors != nullptr); R_TRY(this->GetMmcExtendedCsd(m_work_buffer, m_work_buffer_size)); *out_num_sectors = GetBootPartitionMemoryCapacityFromExtCsd(static_cast(m_work_buffer)); R_SUCCEED(); } Result MmcDeviceAccessor::GetMmcExtendedCsd(void *dst, size_t dst_size) const { /* Acquire exclusive access to the device. */ AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); /* Check that we can access the device. */ R_TRY(m_mmc_device.CheckAccessible()); /* Get the csd. */ u8 csd[DeviceCsdSize]; m_mmc_device.GetCsd(csd, sizeof(csd)); /* Check that the card supports ext csd. */ R_UNLESS(!IsLessThanSpecification4(csd), sdmmc::ResultMmcNotSupportExtendedCsd()); /* Get the ext csd. */ R_TRY(this->IssueCommandSendExtCsd(dst, dst_size)); R_SUCCEED(); } }