/*
* 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 .
*/
#pragma once
#include
#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(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(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(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(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_size, 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() override;
virtual void Deactivate() override;
virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) override;
virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) override;
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;
};
}