sdmmc: implement BaseDeviceAccessor

This commit is contained in:
Michael Scire 2020-10-22 15:49:51 -07:00
parent 631de13133
commit 3981527bb8
9 changed files with 1166 additions and 2 deletions

View file

@ -40,6 +40,25 @@ namespace ams::sdmmc {
R_DEFINE_ERROR_RESULT(AutoCommandResponseTimeoutError, 44);
R_DEFINE_ERROR_RESULT(CommandCompleteSoftwareTimeout, 45);
R_DEFINE_ERROR_RESULT(TransferCompleteSoftwareTimeout, 46);
R_DEFINE_ERROR_RANGE(DeviceStatusHasError, 48, 70);
R_DEFINE_ERROR_RESULT(DeviceStatusAddressOutOfRange, 49);
R_DEFINE_ERROR_RESULT(DeviceStatusAddressMisaligned, 50);
R_DEFINE_ERROR_RESULT(DeviceStatusBlockLenError, 51);
R_DEFINE_ERROR_RESULT(DeviceStatusEraseSeqError, 52);
R_DEFINE_ERROR_RESULT(DeviceStatusEraseParam, 53);
R_DEFINE_ERROR_RESULT(DeviceStatusWpViolation, 54);
R_DEFINE_ERROR_RESULT(DeviceStatusLockUnlockFailed, 55);
R_DEFINE_ERROR_RESULT(DeviceStatusComCrcError, 56);
R_DEFINE_ERROR_RESULT(DeviceStatusIllegalCommand, 57);
R_DEFINE_ERROR_RESULT(DeviceStatusDeviceEccFailed, 58);
R_DEFINE_ERROR_RESULT(DeviceStatusCcError, 59);
R_DEFINE_ERROR_RESULT(DeviceStatusError, 60);
R_DEFINE_ERROR_RESULT(DeviceStatusCidCsdOverwrite, 61);
R_DEFINE_ERROR_RESULT(DeviceStatusWpEraseSkip, 62);
R_DEFINE_ERROR_RESULT(DeviceStatusEraseReset, 63);
R_DEFINE_ERROR_RESULT(DeviceStatusSwitchError, 64);
R_DEFINE_ERROR_RESULT(UnexpectedDeviceState, 72);
R_DEFINE_ERROR_RESULT(UnexpectedDeviceCsdValue, 73);
R_DEFINE_ERROR_RESULT(AbortTransactionSoftwareTimeout, 74);
R_DEFINE_ERROR_RESULT(CommandInhibitCmdSoftwareTimeout, 75);
R_DEFINE_ERROR_RESULT(CommandInhibitDatSoftwareTimeout, 76);

View file

@ -28,6 +28,7 @@
//#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS
//#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL
//#define AMS_SDMMC_USE_DEVICE_DETECTOR
//#define AMS_SDMMC_USE_LOGGER
//#define AMS_SDMMC_USE_OS_EVENTS
//#define AMS_SDMMC_USE_OS_TIMER
#define AMS_SDMMC_USE_UTIL_TIMER
@ -38,6 +39,7 @@
//#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS
//#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL
//#define AMS_SDMMC_USE_DEVICE_DETECTOR
//#define AMS_SDMMC_USE_LOGGER
//#define AMS_SDMMC_USE_OS_EVENTS
//#define AMS_SDMMC_USE_OS_TIMER
#define AMS_SDMMC_USE_UTIL_TIMER
@ -48,6 +50,7 @@
#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS
#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL
#define AMS_SDMMC_USE_DEVICE_DETECTOR
#define AMS_SDMMC_USE_LOGGER
#define AMS_SDMMC_USE_OS_EVENTS
#define AMS_SDMMC_USE_OS_TIMER
//#define AMS_SDMMC_USE_UTIL_TIMER

View file

@ -72,6 +72,8 @@ namespace ams::sdmmc {
using DeviceDetectionEventCallback = void (*)(void *);
constexpr inline size_t SectorSize = 0x200;
constexpr inline size_t DeviceCidSize = 0x10;
constexpr inline size_t DeviceCsdSize = 0x10;

View file

@ -0,0 +1,573 @@
/*
* Copyright (c) 2018-2020 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 <http://www.gnu.org/licenses/>.
*/
#include <vapours.hpp>
#include "sdmmc_base_device_accessor.hpp"
namespace ams::sdmmc::impl {
#if defined(AMS_SDMMC_THREAD_SAFE)
#define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() std::scoped_lock lk(this->base_device->device_mutex)
#else
#define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX()
#endif
void BaseDevice::GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const {
AMS_ABORT_UNLESS(out_c_size_mult != nullptr);
AMS_ABORT_UNLESS(out_read_bl_len != nullptr);
/* Extract C_SIZE_MULT and READ_BL_LEN from the CSD. */
*out_c_size_mult = static_cast<u8>((this->csd[2] >> 7) & 0x7);
*out_read_bl_len = static_cast<u8>((this->csd[4] >> 8) & 0xF);
}
Result BaseDevice::SetLegacyMemoryCapacity() {
/* Get csize from the csd. */
const u32 c_size = ((this->csd[3] >> 6) & 0x3FF) | ((this->csd[4] & 0x3) << 10);
/* Get c_size_mult and read_bl_len. */
u8 c_size_mult, read_bl_len;
this->GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len));
/* Validate the parameters. */
R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue());
/* Set memory capacity. */
this->memory_capacity = (c_size + 1) << ((read_bl_len + c_size_mult + 2) - 9);
this->is_valid_memory_capacity = true;
return ResultSuccess();
}
Result BaseDevice::CheckDeviceStatus(u32 r1_resp) const {
/* Check if there are any errors at all. */
R_SUCCEED_IF((r1_resp & DeviceStatus_ErrorMask) == 0);
/* Check errors individually. */
#define AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(__ERROR__) R_UNLESS((r1_resp & DeviceStatus_##__ERROR__) == 0, sdmmc::ResultDeviceStatus##__ERROR__())
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(ComCrcError);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(DeviceEccFailed);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CcError);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(Error);
if (this->GetDeviceType() == DeviceType_Mmc) {
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(SwitchError);
}
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressMisaligned);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(BlockLenError);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseSeqError);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseParam);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpViolation);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(LockUnlockFailed);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CidCsdOverwrite);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseReset);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(IllegalCommand);
AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressOutOfRange);
#undef AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR
return ResultSuccess();
}
DeviceState BaseDevice::GetDeviceState(u32 r1_resp) const {
return static_cast<DeviceState>((r1_resp & DeviceStatus_CurrentStateMask) >> DeviceStatus_CurrentStateShift);
}
Result BaseDeviceAccessor::IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const {
/* Issue the command. */
constexpr ResponseType CommandResponseType = ResponseType_R1;
Command command(command_index, command_arg, CommandResponseType, is_busy);
R_TRY(this->host_controller->IssueCommand(std::addressof(command)));
/* Get the response. */
AMS_ABORT_UNLESS(out_response != nullptr);
this->host_controller->GetLastResponse(out_response, sizeof(u32), CommandResponseType);
/* Mask out the ignored status bits. */
if (status_ignore_mask != 0) {
*out_response &= ~status_ignore_mask;
}
/* Check the r1 response for errors. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
R_TRY(this->base_device->CheckDeviceStatus(*out_response));
/* Check the device state. */
if (expected_state != DeviceState_Unknown) {
R_UNLESS(this->base_device->GetDeviceState(*out_response) == expected_state, sdmmc::ResultUnexpectedDeviceState());
}
return ResultSuccess();
}
Result BaseDeviceAccessor::IssueCommandGoIdleState() const {
/* Issue the command. */
Command command(CommandIndex_GoIdleState, 0, ResponseType_R0, false);
return this->host_controller->IssueCommand(std::addressof(command));
}
Result BaseDeviceAccessor::IssueCommandAllSendCid(void *dst, size_t dst_size) const {
/* Issue the command. */
constexpr ResponseType CommandResponseType = ResponseType_R2;
Command command(CommandIndex_AllSendCid, 0, CommandResponseType, false);
R_TRY(this->host_controller->IssueCommand(std::addressof(command)));
/* Copy the data out. */
AMS_ABORT_UNLESS(dst != nullptr);
AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(dst), alignof(u32)));
AMS_ABORT_UNLESS(dst_size >= DeviceCidSize);
this->host_controller->GetLastResponse(static_cast<u32 *>(dst), DeviceCidSize, CommandResponseType);
return ResultSuccess();
}
Result BaseDeviceAccessor::IssueCommandSelectCard() const {
/* Get the command argument. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
const u32 arg = static_cast<u32>(this->base_device->GetRca()) << 16;
/* Issue the command. */
return this->IssueCommandAndCheckR1(CommandIndex_SelectCard, arg, true, DeviceState_Unknown);
}
Result BaseDeviceAccessor::IssueCommandSendCsd(void *dst, size_t dst_size) const {
/* Get the command argument. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
const u32 arg = static_cast<u32>(this->base_device->GetRca()) << 16;
/* Issue the command. */
constexpr ResponseType CommandResponseType = ResponseType_R2;
Command command(CommandIndex_SendCsd, arg, CommandResponseType, false);
R_TRY(this->host_controller->IssueCommand(std::addressof(command)));
/* Copy the data out. */
AMS_ABORT_UNLESS(dst != nullptr);
AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(dst), alignof(u32)));
AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize);
this->host_controller->GetLastResponse(static_cast<u32 *>(dst), DeviceCsdSize, CommandResponseType);
return ResultSuccess();
}
Result BaseDeviceAccessor::IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const {
/* Get the command argument. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
const u32 arg = static_cast<u32>(this->base_device->GetRca()) << 16;
/* Issue the command. */
return this->IssueCommandAndCheckR1(out_device_status, CommandIndex_SendStatus, arg, false, DeviceState_Tran, status_ignore_mask);
}
Result BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize() const {
/* Issue the command. */
return this->IssueCommandAndCheckR1(CommandIndex_SetBlockLen, SectorSize, false, DeviceState_Tran);
}
Result BaseDeviceAccessor::IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const {
/* Get the argument. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
const u32 arg = this->base_device->IsHighCapacity() ? sector_index : sector_index * SectorSize;
/* Get the command index and transfer direction. */
const u32 command_index = is_read ? CommandIndex_ReadMultipleBlock : CommandIndex_WriteMultipleBlock;
const auto xfer_direction = is_read ? TransferDirection_ReadFromDevice : TransferDirection_WriteToDevice;
/* Issue the command. */
constexpr ResponseType CommandResponseType = ResponseType_R1;
Command command(command_index, arg, CommandResponseType, false);
TransferData xfer_data(buf, SectorSize, num_sectors, xfer_direction, true, true);
Result result = this->host_controller->IssueCommand(std::addressof(command), std::addressof(xfer_data), out_num_transferred_blocks);
/* Handle the failure case. */
if (R_FAILED(result)) {
/* Check if we were removed. */
R_TRY(this->CheckRemoved());
/* By default, we'll want to return the result we just got. */
Result result_to_return = result;
/* Stop transmission. */
u32 resp = 0;
result = this->host_controller->IssueStopTransmissionCommand(std::addressof(resp));
if (R_SUCCEEDED(result)) {
result = this->base_device->CheckDeviceStatus(resp & (~DeviceStatus_IllegalCommand));
if (R_FAILED(result)) {
result_to_return = result;
}
}
/* Check if we were removed. */
R_TRY(this->CheckRemoved());
/* Get the device status. */
u32 device_status = 0;
result = this->IssueCommandSendStatus(std::addressof(device_status), DeviceStatus_IllegalCommand);
/* If there's a device status error we don't already have, we prefer to return it. */
if (!sdmmc::ResultDeviceStatusHasError::Includes(result_to_return) && sdmmc::ResultDeviceStatusHasError::Includes(result)) {
result_to_return = result;
}
/* Return the result we chose. */
return result_to_return;
}
/* Get the responses. */
u32 resp, st_resp;
this->host_controller->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType);
this->host_controller->GetLastStopTransmissionResponse(std::addressof(st_resp), sizeof(st_resp));
/* Check the device status. */
R_TRY(this->base_device->CheckDeviceStatus(resp));
/* Decide on what errors to ignore. */
u32 status_ignore_mask = 0;
if (is_read) {
AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr);
if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) {
status_ignore_mask = DeviceStatus_AddressOutOfRange;
}
}
/* Check the device status. */
R_TRY(this->base_device->CheckDeviceStatus(st_resp & ~status_ignore_mask));
return ResultSuccess();
}
Result BaseDeviceAccessor::ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const {
/* Issue the read/write command. */
AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr);
R_TRY(this->IssueCommandMultipleBlock(out_num_transferred_blocks, sector_index, num_sectors, buf, is_read));
/* Decide on what errors to ignore. */
u32 status_ignore_mask = 0;
if (is_read) {
AMS_ABORT_UNLESS(this->base_device != nullptr);
if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) {
status_ignore_mask = DeviceStatus_AddressOutOfRange;
}
}
/* Get and check the status. */
u32 device_status;
R_TRY(this->IssueCommandSendStatus(std::addressof(device_status), status_ignore_mask));
return ResultSuccess();
}
Result BaseDeviceAccessor::ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read) {
/* Verify that we can send the command. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
/* If we want to read zero sectors, there's no work for us to do. */
R_SUCCEED_IF(num_sectors == 0);
/* Check that the buffer is big enough for the sectors we're reading. */
AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors);
/* Read sectors repeatedly until we've read all the ones we want. */
u32 cur_sector_index = sector_index;
u32 remaining_sectors = num_sectors;
u8 *cur_buf = static_cast<u8 *>(buf);
while (remaining_sectors > 0) {
/* Determine how many sectors we can read in this iteration. */
u32 cur_sectors = remaining_sectors;
if (sector_index_alignment > 0) {
AMS_ABORT_UNLESS((cur_sector_index % sector_index_alignment) == 0);
const u32 max_sectors = this->host_controller->GetMaxTransferNumBlocks();
if (remaining_sectors > max_sectors) {
cur_sectors = max_sectors - (max_sectors % sector_index_alignment);
}
}
/* Try to perform the read/write. */
u32 num_transferred_blocks = 0;
Result result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read);
if (R_FAILED(result)) {
/* Check if we were removed. */
R_TRY(this->CheckRemoved());
/* Log that we failed to read/write. */
this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue());
/* Retry the read/write. */
num_transferred_blocks = 0;
result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read);
if (R_FAILED(result)) {
/* Check if we were removed. */
R_TRY(this->CheckRemoved());
/* Log that we failed to read/write. */
this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue());
/* Re-startup the connection, to see if that helps. */
R_TRY(this->ReStartup());
/* Retry the read/write a third time. */
num_transferred_blocks = 0;
result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read);
if (R_FAILED(result)) {
/* Log that we failed after a re-startup. */
this->PushErrorLog(true, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue());
return result;
}
/* Log that we succeeded after a retry. */
this->PushErrorLog(true, "%s %X %X:0", is_read ? "R" : "W", cur_sector_index, cur_sectors);
/* Increment the number of error corrections we've done. */
++this->num_read_write_error_corrections;
}
}
/* Update our tracking variables. */
AMS_ABORT_UNLESS(remaining_sectors >= num_transferred_blocks);
remaining_sectors -= num_transferred_blocks;
cur_sector_index += num_transferred_blocks;
cur_buf += num_transferred_blocks * SectorSize;
}
return ResultSuccess();
}
#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS)
void BaseDeviceAccessor::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Register the address. */
return this->host_controller->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address);
}
void BaseDeviceAccessor::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Register the address. */
return this->host_controller->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address);
}
#endif
Result BaseDeviceAccessor::Activate() {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is awake. */
R_UNLESS(this->base_device->IsAwake(), sdmmc::ResultNotAwakened());
/* If the device is already active, we don't need to do anything. */
R_SUCCEED_IF(this->base_device->IsActive());
/* Activate the base device. */
auto activate_guard = SCOPE_GUARD { ++this->num_activation_failures; };
R_TRY(this->OnActivate());
/* We successfully activated the device. */
activate_guard.Cancel();
this->base_device->SetActive();
return ResultSuccess();
}
void BaseDeviceAccessor::Deactivate() {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Deactivate the base device. */
if (this->base_device->IsActive()) {
this->host_controller->Shutdown();
this->base_device->Deactivate();
}
}
Result BaseDeviceAccessor::ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Perform the read/write. */
auto rw_guard = SCOPE_GUARD { ++this->num_read_write_failures; };
R_TRY(this->OnReadWrite(sector_index, num_sectors, buffer, buffer_size, is_read));
/* We successfully performed the read/write. */
rw_guard.Cancel();
return ResultSuccess();
}
Result BaseDeviceAccessor::CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the current speed mode/bus width. */
*out_speed_mode = this->host_controller->GetSpeedMode();
*out_bus_width = this->host_controller->GetBusWidth();
/* Verify that we can get the status. */
R_TRY(this->host_controller->GetInternalStatus());
return ResultSuccess();
}
Result BaseDeviceAccessor::GetMemoryCapacity(u32 *out_sectors) const {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the capacity. */
AMS_ABORT_UNLESS(out_sectors != nullptr);
*out_sectors = this->base_device->GetMemoryCapacity();
return ResultSuccess();
}
Result BaseDeviceAccessor::GetDeviceStatus(u32 *out) const {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the status. */
R_TRY(this->IssueCommandSendStatus(out, 0));
return ResultSuccess();
}
Result BaseDeviceAccessor::GetOcr(u32 *out) const {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the ocr. */
AMS_ABORT_UNLESS(out != nullptr);
*out = this->base_device->GetOcr();
return ResultSuccess();
}
Result BaseDeviceAccessor::GetRca(u16 *out) const {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the rca. */
AMS_ABORT_UNLESS(out != nullptr);
*out = this->base_device->GetRca();
return ResultSuccess();
}
Result BaseDeviceAccessor::GetCid(void *out, size_t size) const {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the cid. */
this->base_device->GetCid(out, size);
return ResultSuccess();
}
Result BaseDeviceAccessor::GetCsd(void *out, size_t size) const {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Check that the device is accessible. */
R_TRY(this->base_device->CheckAccessible());
/* Get the csd. */
this->base_device->GetCsd(out, size);
return ResultSuccess();
}
void BaseDeviceAccessor::GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) {
/* Lock exclusive access of the base device. */
AMS_ABORT_UNLESS(this->base_device != nullptr);
AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX();
/* Set the output error info. */
AMS_ABORT_UNLESS(out_error_info != nullptr);
out_error_info->num_activation_failures = this->num_activation_failures;
out_error_info->num_activation_error_corrections = this->num_activation_error_corrections;
out_error_info->num_read_write_failures = this->num_read_write_failures;
out_error_info->num_read_write_error_corrections = this->num_read_write_error_corrections;
this->ClearErrorInfo();
/* Check if we should write logs. */
if (out_log_size == nullptr) {
return;
}
/* Check if we can write logs. */
if (out_log_buffer == nullptr || log_buffer_size == 0) {
*out_log_size = 0;
return;
}
/* Get and clear our logs. */
#if defined(AMS_SDMMC_USE_LOGGER)
{
if (this->error_logger.HasLog()) {
this->PushErrorTimeStamp();
*out_log_size = this->error_logger.GetAndClearLogs(out_log_buffer, log_buffer_size);
} else {
*out_log_size = 0;
}
}
#else
{
*out_log_size = 0;
}
#endif
}
}

View file

@ -0,0 +1,512 @@
/*
* Copyright (c) 2018-2020 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#include "sdmmc_i_host_controller.hpp"
#include "sdmmc_i_device_accessor.hpp"
namespace ams::sdmmc::impl {
#if defined(AMS_SDMMC_USE_LOGGER)
class Logger {
private:
static constexpr size_t LogLengthMax = 0x20;
static constexpr size_t LogCountMax = 0x10;
private:
char logs[LogCountMax][LogLengthMax];
int log_index;
private:
void Clear() {
for (size_t i = 0; i < LogCountMax; ++i) {
this->logs[i][0] = '\0';
}
this->log_index = 0;
}
size_t Pop(char *dst, size_t dst_size) {
/* Decrease log index. */
if ((--this->log_index) < 0) {
this->log_index = LogCountMax - 1;
}
/* Check if we have a log. */
if (this->logs[this->log_index][0] == '\0') {
return 0;
}
/* Copy log to output. */
const int len = ::ams::util::Strlcpy(dst, this->logs[this->log_index], dst_size);
/* Clear the log we copied. */
this->logs[this->log_index][0] = '\0';
return static_cast<size_t>(len);
}
public:
Logger() {
this->Clear();
}
void Push(const char *fmt, std::va_list vl) {
/* Format the log into the current buffer. */
::ams::util::TVSNPrintf(this->logs[this->log_index], LogLengthMax, fmt, vl);
/* Update our log index. */
if ((++this->log_index) >= static_cast<int>(LogCountMax)) {
this->log_index = 0;
}
}
void Push(const char *fmt, ...) {
std::va_list vl;
va_start(vl, fmt);
this->Push(fmt, vl);
va_end(vl);
}
bool HasLog() const {
const int index = this->log_index > 0 ? this->log_index - 1 : static_cast<int>(LogCountMax - 1);
return this->logs[index][0] != '\0';
}
size_t GetAndClearLogs(char *dst, size_t dst_size) {
AMS_ABORT_UNLESS(dst != nullptr);
AMS_ABORT_UNLESS(dst_size > 0);
/* Pop logs until we run out of them. */
size_t total_len = 0;
while (true) {
/* Pop the current log. */
const size_t cur_len = this->Pop(dst + total_len, dst_size - total_len);
if (cur_len == 0) {
break;
}
/* Check if the log exceeded the buffer size. */
if (total_len + cur_len + 1 >= dst_size) {
break;
}
/* Advance the total length. */
total_len += cur_len;
/* Check if there's space for our separator. */
if (total_len + 3 >= dst_size) {
break;
}
dst[total_len + 0] = ',';
dst[total_len + 1] = ' ';
total_len += 2;
}
/* Ensure that the complete log fits in the buffer. */
if (total_len >= dst_size) {
total_len = dst_size - 1;
}
/* Ensure null termination. */
dst[total_len] = '\0';
/* Clear any remaining logs. */
this->Clear();
/* Return the length of the logs, including null terminator. */
return total_len + 1;
}
};
#endif
enum DeviceType {
DeviceType_Mmc = 0,
DeviceType_SdCard = 1,
DeviceType_GcAsic = 2,
};
enum DeviceState {
DeviceState_Idle = 0,
DeviceState_Ready = 1,
DeviceState_Ident = 2,
DeviceState_Stby = 3,
DeviceState_Tran = 4,
DeviceState_Data = 5,
DeviceState_Rcv = 6,
DeviceState_Prg = 7,
DeviceState_Dis = 8,
DeviceState_Rsvd0 = 9,
DeviceState_Rsvd1 = 10,
DeviceState_Rsvd2 = 11,
DeviceState_Rsvd3 = 12,
DeviceState_Rsvd4 = 13,
DeviceState_Rsvd5 = 14,
DeviceState_RsvdIoMode = 15,
DeviceState_Unknown = 16,
};
enum DeviceStatus : u32 {
DeviceStatus_AkeSeqError = (1u << 3),
DeviceStatus_AppCmd = (1u << 5),
DeviceStatus_SwitchError = (1u << 7),
DeviceStatus_EraseReset = (1u << 13),
DeviceStatus_WpEraseSkip = (1u << 15),
DeviceStatus_CidCsdOverwrite = (1u << 16),
DeviceStatus_Error = (1u << 19),
DeviceStatus_CcError = (1u << 20),
DeviceStatus_DeviceEccFailed = (1u << 21),
DeviceStatus_IllegalCommand = (1u << 22),
DeviceStatus_ComCrcError = (1u << 23),
DeviceStatus_LockUnlockFailed = (1u << 24),
DeviceStatus_WpViolation = (1u << 26),
DeviceStatus_EraseParam = (1u << 27),
DeviceStatus_EraseSeqError = (1u << 28),
DeviceStatus_BlockLenError = (1u << 29),
DeviceStatus_AddressMisaligned = (1u << 30),
DeviceStatus_AddressOutOfRange = (1u << 31),
DeviceStatus_CurrentStateShift = 9,
DeviceStatus_CurrentStateMask = (0b1111u << DeviceStatus_CurrentStateShift),
DeviceStatus_ErrorMask = (DeviceStatus_SwitchError |
DeviceStatus_EraseReset |
DeviceStatus_WpEraseSkip |
DeviceStatus_CidCsdOverwrite |
DeviceStatus_Error |
DeviceStatus_CcError |
DeviceStatus_DeviceEccFailed |
DeviceStatus_IllegalCommand |
DeviceStatus_ComCrcError |
DeviceStatus_LockUnlockFailed |
DeviceStatus_WpViolation |
DeviceStatus_EraseParam |
DeviceStatus_EraseSeqError |
DeviceStatus_BlockLenError |
DeviceStatus_AddressMisaligned |
DeviceStatus_AddressOutOfRange),
};
class BaseDevice {
private:
u32 ocr;
u8 cid[DeviceCidSize];
u16 csd[DeviceCsdSize / sizeof(u16)];
u32 memory_capacity;
bool is_high_capacity;
bool is_valid_ocr;
bool is_valid_cid;
bool is_valid_csd;
bool is_valid_high_capacity;
bool is_valid_memory_capacity;
bool is_active;
bool is_awake;
public:
#if defined(AMS_SDMMC_THREAD_SAFE)
mutable os::Mutex device_mutex;
public:
BaseDevice() : device_mutex(true)
#else
BaseDevice()
#endif
{
this->is_awake = true;
this->ocr = 0;
this->memory_capacity = 0;
this->is_high_capacity = false;
this->OnDeactivate();
}
void OnDeactivate() {
this->is_active = false;
this->is_valid_ocr = false;
this->is_valid_cid = false;
this->is_valid_csd = false;
this->is_valid_high_capacity = false;
this->is_valid_memory_capacity = false;
}
bool IsAwake() const {
return this->is_awake;
}
void Awaken() {
this->is_awake = true;
}
void PutToSleep() {
this->is_awake = false;
}
bool IsActive() const {
return this->is_active;
}
void SetActive() {
this->is_active = true;
}
virtual void Deactivate() {
this->OnDeactivate();
}
#if defined(AMS_SDMMC_USE_OS_EVENTS)
virtual os::EventType *GetRemovedEvent() const = 0;
#endif
virtual DeviceType GetDeviceType() const = 0;
virtual u16 GetRca() const = 0;
#if defined(AMS_SDMMC_USE_OS_EVENTS)
void InitializeRemovedEvent() {
if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) {
os::InitializeEvent(removed_event, false, os::EventClearMode_ManualClear);
}
}
void FinalizeRemovedEvent() {
if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) {
os::FinalizeEvent(removed_event);
}
}
void SignalRemovedEvent() {
if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) {
os::SignalEvent(removed_event);
}
}
void ClearRemovedEvent() {
if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) {
os::ClearEvent(removed_event);
}
}
bool IsRemoved() const {
if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) {
return os::TryWaitEvent(removed_event);
}
return false;
}
#endif
Result CheckRemoved() const {
#if defined(AMS_SDMMC_USE_OS_EVENTS)
R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved());
#endif
return ResultSuccess();
}
Result CheckAccessible() const {
/* Check awake. */
R_UNLESS(this->IsAwake(), sdmmc::ResultNotAwakened());
/* Check active. */
R_UNLESS(this->IsActive(), sdmmc::ResultNotActivated());
/* Check removed. */
R_TRY(this->CheckRemoved());
return ResultSuccess();
}
void SetHighCapacity(bool en) {
this->is_high_capacity = en;
this->is_valid_high_capacity = true;
}
bool IsHighCapacity() const {
AMS_ABORT_UNLESS(this->is_valid_high_capacity);
return this->is_high_capacity;
}
void SetOcr(u32 o) {
this->ocr = o;
this->is_valid_ocr = true;
}
u32 GetOcr() const {
AMS_ABORT_UNLESS(this->is_valid_ocr);
return this->ocr;
}
void SetCid(const void *src, size_t src_size) {
AMS_ABORT_UNLESS(src != nullptr);
AMS_ABORT_UNLESS(src_size >= DeviceCidSize);
std::memcpy(this->cid, src, DeviceCidSize);
this->is_valid_cid = true;
}
void GetCid(void *dst, size_t dst_size) const {
AMS_ABORT_UNLESS(this->is_valid_cid);
AMS_ABORT_UNLESS(dst != nullptr);
AMS_ABORT_UNLESS(dst_size >= DeviceCidSize);
std::memcpy(dst, this->cid, DeviceCidSize);
}
void SetCsd(const void *src, size_t src_size) {
AMS_ABORT_UNLESS(src != nullptr);
AMS_ABORT_UNLESS(src_size >= DeviceCsdSize);
std::memcpy(this->csd, src, DeviceCsdSize);
this->is_valid_csd = true;
}
void GetCsd(void *dst, size_t dst_size) const {
AMS_ABORT_UNLESS(this->is_valid_csd);
AMS_ABORT_UNLESS(dst != nullptr);
AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize);
std::memcpy(dst, this->csd, DeviceCsdSize);
}
void SetMemoryCapacity(u32 num_sectors) {
this->memory_capacity = num_sectors;
this->is_valid_memory_capacity = true;
}
u32 GetMemoryCapacity() const {
AMS_ABORT_UNLESS(this->is_valid_memory_capacity);
return this->memory_capacity;
}
void GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const;
Result SetLegacyMemoryCapacity();
Result CheckDeviceStatus(u32 r1_resp) const;
DeviceState GetDeviceState(u32 r1_resp) const;
};
class BaseDeviceAccessor : public IDeviceAccessor {
private:
IHostController *host_controller;
BaseDevice *base_device;
u32 num_activation_failures;
u32 num_activation_error_corrections;
u32 num_read_write_failures;
u32 num_read_write_error_corrections;
#if defined(AMS_SDMMC_USE_LOGGER)
Logger error_logger;
#endif
private:
void ClearErrorInfo() {
this->num_activation_failures = 0;
this->num_activation_error_corrections = 0;
this->num_read_write_failures = 0;
this->num_read_write_error_corrections = 0;
}
protected:
explicit BaseDeviceAccessor(IHostController *hc) : host_controller(hc), base_device(nullptr) {
this->ClearErrorInfo();
}
IHostController *GetHostController() const {
return this->host_controller;
}
void SetDevice(BaseDevice *bd) {
this->base_device = bd;
}
Result CheckRemoved() const {
return this->base_device->CheckRemoved();
}
Result IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const;
Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const {
u32 dummy;
return this->IssueCommandAndCheckR1(std::addressof(dummy), command_index, command_arg, is_busy, expected_state, status_ignore_mask);
}
Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state) const {
return this->IssueCommandAndCheckR1(command_index, command_arg, is_busy, expected_state, 0);
}
Result IssueCommandGoIdleState() const;
Result IssueCommandAllSendCid(void *dst, size_t dst_size) const;
Result IssueCommandSelectCard() const;
Result IssueCommandSendCsd(void *dst, size_t dst_size) const;
Result IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const;
Result IssueCommandSendStatus(u32 status_ignore_mask) const {
u32 dummy;
return this->IssueCommandSendStatus(std::addressof(dummy), status_ignore_mask);
}
Result IssueCommandSendStatus() const {
return this->IssueCommandSendStatus(0);
}
Result IssueCommandSetBlockLenToSectorSize() const;
Result IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const;
Result ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const;
Result ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read);
void IncrementNumActivationErrorCorrections() {
++this->num_activation_error_corrections;
}
void PushErrorTimeStamp() {
#if defined(AMS_SDMMC_USE_LOGGER)
{
this->error_logger.Push("%u", static_cast<u32>(os::ConvertToTimeSpan(os::GetSystemTick()).GetSeconds()));
}
#endif
}
void PushErrorLog(bool with_timestamp, const char *fmt, ...) {
#if defined(AMS_SDMMC_USE_LOGGER)
{
std::va_list vl;
va_start(vl, fmt);
this->error_logger.Push(fmt, vl);
va_end(vl);
if (with_timestamp) {
this->PushErrorTimeStamp();
}
}
#else
{
AMS_UNUSED(with_timestamp, fmt);
}
#endif
}
virtual Result OnActivate() = 0;
virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_sie, bool is_read) = 0;
virtual Result ReStartup() = 0;
public:
#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS)
virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override;
virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override;
#endif
virtual Result Activate() = 0;
virtual void Deactivate() = 0;
virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) = 0;
virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) = 0;
virtual Result GetMemoryCapacity(u32 *out_sectors) const override;
virtual Result GetDeviceStatus(u32 *out) const override;
virtual Result GetOcr(u32 *out) const override;
virtual Result GetRca(u16 *out) const override;
virtual Result GetCid(void *out, size_t size) const override;
virtual Result GetCsd(void *out, size_t size) const override;
virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) override;
};
}

View file

@ -38,11 +38,12 @@ namespace ams::sdmmc::impl {
virtual Result GetSpeedMode(SpeedMode *out) const = 0;
virtual Result GetMemoryCapacity(u32 *out_sectors) const = 0;
virtual Result GetDeviceStatus(u32 *out) const = 0;
virtual Result GetOcr(u32 *out) const = 0;
virtual Result GetRca(u16 *out) const = 0;
virtual Result GetCid(void *out, size_t size) const = 0;
virtual Result GetCsd(void *out, size_t size) const = 0;
virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) = 0;
/* TODO */
};
}

View file

@ -36,6 +36,59 @@ namespace ams::sdmmc::impl {
TransferDirection_WriteToDevice = 1,
};
enum CommandIndex {
/* Generic commands. */
CommandIndex_GoIdleState = 0,
CommandIndex_AllSendCid = 2,
CommandIndex_SendRelativeAddr = 3,
CommandIndex_SetDsr = 4,
CommandIndex_SelectCard = 7,
CommandIndex_DeselectCard = 7,
CommandIndex_SendIfCond = 8,
CommandIndex_SendCsd = 9,
CommandIndex_SendCid = 10,
CommandIndex_VoltageSwitch = 11,
CommandIndex_StopTransmission = 12,
CommandIndex_SendStatus = 13,
CommandIndex_SendTaskStatus = 13,
CommandIndex_GoInactiveState = 15,
CommandIndex_SetBlockLen = 16,
CommandIndex_ReadSingleBlock = 17,
CommandIndex_ReadMultipleBlock = 18,
CommandIndex_SendTuningBlock = 19,
CommandIndex_SpeedClassControl = 20,
CommandIndex_AddressExtension = 22,
CommandIndex_SetBlockCount = 23,
CommandIndex_WriteBlock = 24,
CommandIndex_WriteMultipleBlock = 25,
CommandIndex_ProgramCsd = 27,
CommandIndex_SetWriteProt = 28,
CommandIndex_ClearWriteProt = 29,
CommandIndex_SendWriteProt = 30,
CommandIndex_EraseWriteBlockStart = 32,
CommandIndex_EraseWriteBlockEnd = 33,
CommandIndex_Erase = 38,
CommandIndex_LockUnlock = 42,
CommandIndex_AppCmd = 55,
CommandIndex_GenCmd = 56,
/* Nintendo specific vendor commands for lotus3. */
CommandIndex_GcAsicWriteOperation = 60,
CommandIndex_GcAsicFinishOperation = 61,
CommandIndex_GcAsicSleep = 62,
CommandIndex_GcAsicUpdateKey = 63,
};
struct Command {
u32 command_index;
u32 command_argument;

View file

@ -16,6 +16,7 @@
#include <vapours.hpp>
#include "sdmmc_port_mmc0.hpp"
#include "sdmmc_select_sdmmc_controller.hpp"
#include "sdmmc_base_device_accessor.hpp"
namespace ams::sdmmc::impl {

View file

@ -721,7 +721,7 @@ namespace ams::sdmmc::impl {
ON_SCOPE_EXIT { this->DisableInterruptStatus(); };
/* Set the command. */
Command command(12, 0, StopTransmissionCommandResponseType, true);
Command command(CommandIndex_StopTransmission, 0, StopTransmissionCommandResponseType, true);
this->SetCommand(std::addressof(command), false);
/* Wait for the command to complete. */