1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 09:17:35 +00:00

Kernel: Move the Storage directory to be a new directory under Devices

The Storage subsystem, like the Audio and HID subsystems, exposes Unix
device files (for example, in the /dev directory). To ensure consistency
across the repository, we should make the Storage subsystem to reside in
the Kernel/Devices directory like the two other mentioned subsystems.
This commit is contained in:
Liav A 2023-03-18 13:32:12 +02:00 committed by Jelle Raaijmakers
parent f3a58f3a5a
commit 500b7b08d6
59 changed files with 133 additions and 133 deletions

View file

@ -0,0 +1,404 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Kernel::SD {
// Relevant Specifications:
// * (SDHC): SD Host Controller Simplified Specification (https://www.sdcard.org/downloads/pls/)
// * (PLSS) Physical Layer Simplified Specification (https://www.sdcard.org/downloads/pls/)
// PLSS 4.7.4: "Detailed Command Description"
enum class CommandIndex : u8 {
GoIdleState = 0,
AllSendCid = 2,
SendRelativeAddr = 3,
AppSetBusWidth = 6,
SelectCard = 7,
SendIfCond = 8,
SendCsd = 9,
GoInactiveState = 15,
SetBlockLen = 16,
ReadSingleBlock = 17,
ReadMultipleBlock = 18,
WriteSingleBlock = 24,
WriteMultipleBlock = 25,
AppSendOpCond = 41,
AppSendScr = 51,
AppCmd = 55,
};
enum class CommandType : u8 {
Normal,
Suspend,
Resume,
Abort
};
enum class ResponseType : u8 {
NoResponse,
ResponseOf136Bits,
ResponseOf48Bits,
ResponseOf48BitsWithBusy
};
enum class DataTransferDirection : u8 {
HostToCard,
CardToHost
};
enum class SendAutoCommand : u8 {
Disabled,
Command12,
Command23
};
// SDHC 2.2.5 & 2.2.6: "Transfer Mode Register" & "Command Register"
union Command {
u32 raw;
struct {
u32 dma_enable : 1;
u32 block_counter : 1;
SendAutoCommand auto_command : 2;
DataTransferDirection direction : 1;
u32 multiblock : 1;
u32 response_type_r1r5 : 1; // v4.10
u32 response_error_check : 1; // v4.10
u32 response_interrupt_disable : 1; // v4.10
u32 reserved1 : 7;
ResponseType response_type : 2;
u32 sub_command_flag : 1; // v4.10
u32 crc_enable : 1;
u32 idx_enable : 1;
u32 is_data : 1;
CommandType type : 2;
CommandIndex index : 6;
u32 reserved3 : 2;
};
bool requires_dat_line() const
{
return is_data;
}
bool uses_transfer_complete_interrupt() const
{
// FIXME: I don't know how to determine this.
return false;
}
};
static_assert(AssertSize<Command, 4>());
namespace Commands {
constexpr Command go_idle_state = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::NoResponse,
.sub_command_flag = 0,
.crc_enable = 0,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::GoIdleState,
.reserved3 = 0
};
constexpr Command all_send_cid = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf136Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::AllSendCid,
.reserved3 = 0
};
constexpr Command send_relative_addr = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::SendRelativeAddr,
.reserved3 = 0
};
constexpr Command app_set_bus_width = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::AppSetBusWidth,
.reserved3 = 0
};
constexpr Command select_card = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48BitsWithBusy,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::SelectCard,
.reserved3 = 0
};
constexpr Command send_if_cond = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::SendIfCond,
.reserved3 = 0
};
constexpr Command send_csd = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf136Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::SendCsd,
.reserved3 = 0
};
constexpr Command set_block_len = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 0,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::SetBlockLen,
.reserved3 = 0
};
constexpr Command read_single_block = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::CardToHost,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 1,
.type = CommandType::Normal,
.index = CommandIndex::ReadSingleBlock,
.reserved3 = 0
};
constexpr Command read_multiple_block = {
.dma_enable = 0,
.block_counter = 1,
.auto_command = SendAutoCommand::Command12,
.direction = DataTransferDirection::CardToHost,
.multiblock = 1,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 1,
.type = CommandType::Normal,
.index = CommandIndex::ReadMultipleBlock,
.reserved3 = 0
};
constexpr Command write_single_block = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 1,
.type = CommandType::Normal,
.index = CommandIndex::WriteSingleBlock,
.reserved3 = 0
};
constexpr Command write_multiple_block = {
.dma_enable = 0,
.block_counter = 1,
.auto_command = SendAutoCommand::Command12,
.direction = DataTransferDirection::HostToCard,
.multiblock = 1,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 1,
.type = CommandType::Normal,
.index = CommandIndex::WriteMultipleBlock,
.reserved3 = 0
};
constexpr Command app_send_op_cond = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 0,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::AppSendOpCond,
.reserved3 = 0
};
constexpr Command app_send_scr = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::CardToHost,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 0,
.idx_enable = 0,
.is_data = 1,
.type = CommandType::Normal,
.index = CommandIndex::AppSendScr,
.reserved3 = 0
};
constexpr Command app_cmd = {
.dma_enable = 0,
.block_counter = 0,
.auto_command = SendAutoCommand::Disabled,
.direction = DataTransferDirection::HostToCard,
.multiblock = 0,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 0,
.type = CommandType::Normal,
.index = CommandIndex::AppCmd,
.reserved3 = 0
};
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Devices/Storage/SD/PCISDHostController.h>
namespace Kernel {
ErrorOr<NonnullRefPtr<PCISDHostController>> PCISDHostController::try_initialize(PCI::DeviceIdentifier const& device_identifier)
{
auto sdhc = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) PCISDHostController(device_identifier)));
TRY(sdhc->initialize());
PCI::enable_bus_mastering(sdhc->device_identifier());
PCI::enable_memory_space(sdhc->device_identifier());
sdhc->try_enable_dma();
return sdhc;
}
PCISDHostController::PCISDHostController(PCI::DeviceIdentifier const& device_identifier)
: PCI::Device(device_identifier)
, SDHostController()
{
auto slot_information_register = read_slot_information();
if (slot_information_register.slots_available() != 1) {
// TODO: Support multiple slots
dmesgln("SD Host Controller has {} slots, but we currently only support using only one", slot_information_register.slots_available());
}
auto physical_address_of_sdhc_registers = PhysicalAddress {
PCI::get_BAR(device_identifier, static_cast<PCI::HeaderType0BaseRegister>(slot_information_register.first_bar_number))
};
m_registers = Memory::map_typed_writable<SD::HostControlRegisterMap volatile>(physical_address_of_sdhc_registers).release_value_but_fixme_should_propagate_errors();
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/Devices/Storage/SD/SDHostController.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class PCISDHostController : public PCI::Device
, public SDHostController {
public:
static ErrorOr<NonnullRefPtr<PCISDHostController>> try_initialize(PCI::DeviceIdentifier const& device_identifier);
// ^PCI::Device
virtual StringView device_name() const override { return "SD Host Controller"sv; }
protected:
// ^SDHostController
virtual SD::HostControlRegisterMap volatile* get_register_map_base_address() override { return m_registers.ptr(); }
private:
PCISDHostController(PCI::DeviceIdentifier const& device_identifier);
struct [[gnu::packed]] SlotInformationRegister {
u8 first_bar_number : 3;
u8 : 1;
u8 number_of_slots : 3;
u8 : 1;
u8 slots_available() const { return number_of_slots + 1; }
};
static_assert(AssertSize<SlotInformationRegister, 1>());
SlotInformationRegister read_slot_information() const
{
SpinlockLocker locker(device_identifier().operation_lock());
return bit_cast<SlotInformationRegister>(PCI::Access::the().read8_field(device_identifier(), 0x40));
}
Memory::TypedMapping<SD::HostControlRegisterMap volatile> m_registers;
};
}

View file

@ -0,0 +1,329 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Endian.h>
#include <AK/Types.h>
namespace Kernel::SD {
// Relevant Specifications:
// * (SDHC): SD Host Controller Simplified Specification (https://www.sdcard.org/downloads/pls/)
// * (PLSS) Physical Layer Simplified Specification (https://www.sdcard.org/downloads/pls/)
// * (BCM2835) BCM2835 ARM Peripherals (https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf)
enum class HostVersion : u8 {
Version1,
Version2,
Version3,
Unknown
};
enum class ADMAErrorState : u32 {
Stop = 0b00,
FetchDescriptor = 0b01,
Reserved = 0b10,
TransferData = 0b11
};
// SDHC 2.1.1 "SD Host Control Register Map"
// NOTE: The registers must be 32 bits, because of a quirk in the RPI.
struct HostControlRegisterMap {
u32 argument_2;
u32 block_size_and_block_count;
u32 argument_1;
u32 transfer_mode_and_command;
u32 response_0;
u32 response_1;
u32 response_2;
u32 response_3;
u32 buffer_data_port;
u32 present_state;
u32 host_configuration_0;
u32 host_configuration_1;
union InterruptStatus {
struct { // SDHC 2.2.18 Normal Interrupt Status Register (Cat.C Offset 030h)
u32 command_complete : 1;
u32 transfer_complete : 1;
u32 block_gap_event : 1;
u32 dma_interrupt : 1;
u32 buffer_write_ready : 1;
u32 buffer_read_ready : 1;
u32 card_insertion : 1;
u32 card_removal : 1;
u32 card_interrupt : 1;
u32 int_a : 1;
u32 int_b : 1;
u32 int_c : 1;
u32 retuning_event : 1;
u32 fx_event : 1;
u32 : 1;
u32 error_interrupt : 1;
// SDHC 2.2.19 Error Interrupt Status Register (Cat.C Offset 032
u32 command_timeout_error : 1;
u32 command_crc_error : 1;
u32 cammand_index_error : 1;
u32 data_timeout_error : 1;
u32 data_crc_error : 1;
u32 data_end_bit_error : 1;
u32 current_limit_error : 1;
u32 auto_cmd_error : 1;
u32 adma_error : 1;
u32 tuning_error : 1;
u32 response_error : 1;
u32 vendor_specific_error : 1;
};
u32 raw;
} interrupt_status;
u32 interrupt_status_enable;
u32 interrupt_signal_enable;
u32 host_configuration_2;
// SDHC 2.2.26 Capabilities Register (Cat.C Offset 040h)
struct CapabilitesRegister {
u32 timeout_clock_frequency : 6;
u32 : 1;
u32 timeout_clock_unit : 1;
u32 base_clock_frequency : 8;
u32 max_block_length : 2;
u32 eight_bit_support_for_embedded_devices : 1;
u32 adma2 : 1;
u32 : 1;
u32 high_speed : 1;
u32 sdma : 1;
u32 suspend_resume : 1;
u32 three_point_three_volt : 1;
u32 three_point_zero_volt : 1;
u32 one_point_eight_volt : 1;
u32 dma_64_bit_addressing_v4 : 1;
u32 dma_64_bit_addressing_v3 : 1;
u32 async_interrupt : 1;
u32 slot_type : 2;
u32 sdr50 : 1;
u32 sdr140 : 1;
u32 ddr50 : 1;
u32 uhs_ii : 1;
u32 driver_type_A : 1;
u32 driver_type_C : 1;
u32 driver_type_D : 1;
u32 : 1;
u32 timer_count_for_retuning : 4;
u32 : 1;
u32 use_tuning_for_sdr50 : 1;
u32 retuning_modes : 2;
u32 clock_multiplier : 8;
u32 : 3;
u32 adma3 : 1;
u32 one_point_eight_vdd2 : 1;
u32 : 3;
} capabilities;
u32 maximum_current_capabilities;
u32 maximum_current_capabilities_reserved;
u32 force_event_for_auto_cmd_error_status;
struct {
ADMAErrorState state : 2;
u32 length_mismatch_error : 1;
u32 : 5;
u32 : 24;
} adma_error_status;
u32 adma_system_address[2];
u32 preset_value[4];
u32 reserved_0[28];
u32 shared_bus_control;
u32 reserved_1[6];
struct [[gnu::packed]] {
u8 interrupt_signal_for_each_slot;
u8 : 8;
HostVersion specification_version_number;
u8 vendor_version_number;
} slot_interrupt_status_and_version;
};
static_assert(AssertSize<HostControlRegisterMap, 256>());
// SDHC Figure 1-10 : General Descriptor Table Format
enum class DMAAction : u8 {
// ADMA 2
Nop = 0b000,
Rsv0 = 0b010,
Tran = 0b100,
Link = 0b110,
// ADMA 3
CommandDescriptor_SD = 0b001,
CommandDescriptor_UHS_II = 0b011,
Rsv1 = 0b101,
IntegratedDescriptor = 0b111,
};
// Both of these represent the ADMA2 version, ADMA3 might have slight differences
// SDHC 1.13.3.1 ADMA2 Descriptor Format
struct alignas(4) DMADescriptor64 {
u32 valid : 1;
u32 end : 1;
u32 interrupt : 1;
DMAAction action : 3;
u32 length_upper : 10; // Version 4.10+ only
u32 length_lower : 16;
u32 address : 32;
};
static_assert(AssertSize<DMADescriptor64, 8>());
struct alignas(8) DMADescriptor128 {
u32 valid : 1;
u32 end : 1;
u32 interrupt : 1;
DMAAction action : 3;
u32 length_upper : 10; // Version 4.10+ only
u32 length_lower : 16;
u32 address_low : 32;
u32 address_high : 32;
u32 : 32;
};
static_assert(AssertSize<DMADescriptor128, 16>());
// PLSS 5.1: "OCR Register"
union OperatingConditionRegister {
u32 raw;
struct {
u32 : 15;
u32 vdd_voltage_window_27_28 : 1;
u32 vdd_voltage_window_28_29 : 1;
u32 vdd_voltage_window_29_30 : 1;
u32 vdd_voltage_window_30_31 : 1;
u32 vdd_voltage_window_31_32 : 1;
u32 vdd_voltage_window_32_33 : 1;
u32 vdd_voltage_window_33_34 : 1;
u32 vdd_voltage_window_34_35 : 1;
u32 vdd_voltage_window_35_36 : 1;
u32 switching_to_18v_accepted : 1;
u32 : 2;
u32 over_2tb_support_status : 1;
u32 : 1;
u32 uhs2_card_status : 1;
u32 card_capacity_status : 1;
u32 card_power_up_status : 1;
};
};
static_assert(AssertSize<OperatingConditionRegister, 4>());
// PLSS 5.2: "CID Register"
union CardIdentificationRegister {
u32 raw[4];
struct [[gnu::packed]] {
u64 manufacturing_date : 12;
u64 : 4;
u64 product_serial_number : 32;
u64 product_revision : 8;
u64 product_name : 40;
u64 oem_id : 16;
u64 manufacturer_id : 8;
};
};
static_assert(AssertSize<CardIdentificationRegister, 16>());
// PLSS 5.3.2: "CSD Register (CSD Version 1.0)"
union CardSpecificDataRegister {
u64 raw[2];
struct [[gnu::packed]] {
// Note that the physical layer spec says there are 7 bits of checksum and 1 reserved bit here,
// but they are removed
u32 : 1;
u32 write_protection_until_power_cycle : 1;
u32 file_format : 2;
u32 temporary_write_protection : 1;
u32 permanent_write_protection : 1;
u32 copy_flag : 1;
u32 file_format_group : 1;
u32 : 5;
u32 partial_blocks_for_write_allowed : 1;
u32 max_write_data_block_length : 4;
u32 write_speed_factor : 3;
u32 : 2;
u32 write_protect_group_enable : 1;
u32 write_protect_group_size : 7;
u32 erase_sector_size : 7;
u32 erase_single_block_enable : 1;
u32 device_size_multiplier : 3;
u32 max_write_current_at_vdd_max : 3;
u32 max_write_current_at_vdd_min : 3;
u32 max_read_current_at_vdd_max : 3;
u32 max_read_current_at_vdd_min : 3;
u32 device_size : 12;
u32 : 2;
u32 dsr_implemented : 1;
u32 read_block_misalignment : 1;
u32 write_block_misalignment : 1;
u32 partial_blocks_for_read_allowed : 1;
u32 max_read_data_block_length : 4;
u32 card_command_classes : 12;
u32 max_data_transfer_rate : 8;
u32 data_read_access_time2 : 8;
u32 data_read_access_time1 : 8;
u32 : 6;
u32 csd_structure : 2;
};
};
static_assert(AssertSize<CardSpecificDataRegister, 16>());
// PLSS 5.6: "SCR Register"
union SDConfigurationRegister {
u8 raw[8];
struct {
u32 scr_structure : 4;
u32 sd_specification : 4;
u32 data_status_after_erase : 1;
u32 sd_security : 3;
u32 sd_bus_widths : 4;
u32 sd_specification3 : 1;
u32 extended_security : 4;
u32 sd_specification4 : 1;
u32 sd_specification_x : 4;
u32 : 1;
u32 command_support : 5;
u32 : 32;
};
};
static_assert(AssertSize<SDConfigurationRegister, 8>());
// PLSS 4.10.1: "Card Status"
union CardStatus {
u32 raw;
struct {
u32 : 3;
u32 ake_seq_error : 1;
u32 : 1;
u32 app_cmd : 1;
u32 fx_event : 1;
u32 : 1;
u32 ready_for_data : 1;
u32 current_state : 4;
u32 erase_reset : 1;
u32 card_ecc_disabled : 1;
u32 wp_erase_skip : 1;
u32 csd_overwrite : 1;
u32 : 2;
u32 error : 1;
u32 cc_error : 1;
u32 card_ecc_failed : 1;
u32 illegal_command : 1;
u32 com_crc_error : 1;
u32 lock_unlock_failed : 1;
u32 card_is_locked : 1;
u32 wp_violation : 1;
u32 erase_param : 1;
u32 erase_seq_error : 1;
u32 block_len_error : 1;
u32 address_error : 1;
u32 out_of_range : 1;
};
};
static_assert(AssertSize<CardStatus, 4>());
}

View file

@ -0,0 +1,996 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/StdLibExtras.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/Storage/SD/Commands.h>
#include <Kernel/Devices/Storage/SD/SDHostController.h>
#include <Kernel/Devices/Storage/StorageManagement.h>
#include <Kernel/Panic.h>
#include <Kernel/Time/TimeManagement.h>
#if ARCH(AARCH64)
# include <Kernel/Arch/aarch64/RPi/SDHostController.h>
#endif
namespace Kernel {
// Relevant Specifications:
// * (SDHC): SD Host Controller Simplified Specification (https://www.sdcard.org/downloads/pls/)
// * (PLSS) Physical Layer Simplified Specification (https://www.sdcard.org/downloads/pls/)
// * (BCM2835) BCM2835 ARM Peripherals (https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf)
static void delay(i64 nanoseconds)
{
auto start = TimeManagement::the().monotonic_time();
auto end = start + Duration::from_nanoseconds(nanoseconds);
while (TimeManagement::the().monotonic_time() < end)
Processor::pause();
}
constexpr u32 max_supported_sdsc_frequency = 25000000;
constexpr u32 max_supported_sdsc_frequency_high_speed = 50000000;
// In "m_registers->host_configuration_0"
// 2.2.11 Host Control 1 Register
constexpr u32 high_speed_enable = 1 << 2;
constexpr u32 dma_select_adma2_32 = 0b10 << 3;
constexpr u32 dma_select_adma2_64 = 0b11 << 3;
// In "m_registers->host_configuration_1"
// In sub-register "Clock Control"
constexpr u32 internal_clock_enable = 1 << 0;
constexpr u32 internal_clock_stable = 1 << 1;
constexpr u32 sd_clock_enable = 1 << 2;
// In sub-register "Software Reset"
constexpr u32 software_reset_for_all = 0x01000000;
// In Interrupt Status Register
constexpr u32 command_complete = 1 << 0;
constexpr u32 transfer_complete = 1 << 1;
constexpr u32 buffer_write_ready = 1 << 4;
constexpr u32 buffer_read_ready = 1 << 5;
// PLSS 5.1: all voltage windows
constexpr u32 acmd41_voltage = 0x00ff8000;
// PLSS 4.2.3.1: All voltage windows, XPC = 1, SDHC = 1
constexpr u32 acmd41_arg = 0x50ff8000;
constexpr size_t block_len = 512;
SDHostController::SDHostController()
: StorageController(StorageManagement::generate_relative_sd_controller_id({}))
{
}
ErrorOr<void> SDHostController::reset() { return ENOTIMPL; }
ErrorOr<void> SDHostController::shutdown() { return ENOTIMPL; }
void SDHostController::complete_current_request(AsyncDeviceRequest::RequestResult)
{
VERIFY_NOT_REACHED();
}
ErrorOr<void> SDHostController::initialize()
{
m_registers = get_register_map_base_address();
if (!m_registers)
return EIO;
if (host_version() != SD::HostVersion::Version3 && host_version() != SD::HostVersion::Version2)
return ENOTSUP;
TRY(reset_host_controller());
m_registers->interrupt_status_enable = 0xffffffff;
auto card_or_error = try_initialize_inserted_card();
if (card_or_error.is_error() && card_or_error.error().code() != ENODEV) {
dmesgln("SDHostController: Failed to initialize inserted card: {}", card_or_error.error());
} else if (!card_or_error.is_error()) {
m_card = card_or_error.release_value();
}
return {};
}
void SDHostController::try_enable_dma()
{
if (m_registers->capabilities.adma2) {
auto maybe_dma_buffer = MM.allocate_dma_buffer_pages(dma_region_size, "SDHC DMA Buffer"sv, Memory::Region::Access::ReadWrite);
if (maybe_dma_buffer.is_error()) {
dmesgln("Could not allocate DMA pages for SDHC: {}", maybe_dma_buffer.error());
} else {
m_dma_region = maybe_dma_buffer.release_value();
dbgln("Allocated SDHC DMA buffer at {}", m_dma_region->physical_page(0)->paddr());
// FIXME: This check does not seem to work, qemu supports 64 bit addressing, but we don't seem to detect it
// FIXME: Hardcoding to use the 64 bit mode leads to transfer timeouts, without any errors reported from qemu
if (host_version() != SD::HostVersion::Version3 && m_registers->capabilities.dma_64_bit_addressing_v3) {
dbgln("Setting SDHostController to operate using ADMA2 with 64 bit addressing");
m_mode = OperatingMode::ADMA2_64;
m_registers->host_configuration_0 = m_registers->host_configuration_0 | dma_select_adma2_64;
} else {
// FIXME: Use a way that guarantees memory addresses below the 32 bit threshold
VERIFY(m_dma_region->physical_page(0)->paddr().get() >> 32 == 0);
VERIFY(m_dma_region->physical_page(dma_region_size / PAGE_SIZE - 1)->paddr().get() >> 32 == 0);
dbgln("Setting SDHostController to operate using ADMA2 with 32 bit addressing");
m_mode = OperatingMode::ADMA2_32;
m_registers->host_configuration_0 = m_registers->host_configuration_0 | dma_select_adma2_32;
}
}
}
}
ErrorOr<NonnullLockRefPtr<SDMemoryCard>> SDHostController::try_initialize_inserted_card()
{
if (!is_card_inserted())
return ENODEV;
// PLSS 4.2: "Card Identification Mode"
// "After power-on ...the cards are initialized with ... 400KHz clock frequency."
// NOTE: The SDHC might already have been initialized (e.g. by the bootloader), let's reset it to a known configuration
if (is_sd_clock_enabled())
sd_clock_stop();
TRY(sd_clock_supply(400000));
// PLSS 4.2.3: "Card Initialization and Identification Process"
// Also see Figure 4-2 in the PLSS spec for a flowchart of the initialization process.
// Note that the steps correspond to the steps in the flowchart, although I made up the numbering and text
// 1. Send CMD0 (GO_IDLE_STATE) to the card
TRY(issue_command(SD::Commands::go_idle_state, 0));
TRY(wait_for_response());
// 2. Send CMD8 (SEND_IF_COND) to the card
// SD interface condition: 7:0 = check pattern, 11:8 = supply voltage
// 0x1aa: check pattern = 10101010, supply voltage = 1 => 2.7-3.6V
const u32 voltage_window = 0x1aa;
TRY(issue_command(SD::Commands::send_if_cond, voltage_window));
auto interface_condition_response = wait_for_response();
// 3. If the card does not respond to CMD8 it means that (Ver2.00 or later
// SD Memory Card(voltage mismatch) or Ver1.X SD Memory Card or not SD
// Memory Card)
if (interface_condition_response.is_error()) {
// TODO: This is supposed to be the "No Response" branch of the
// flowchart in Figure 4-2 of the PLSS spec
return ENOTSUP;
}
// 4. If the card responds to CMD8, but it's not a valid response then the
// card is not usable
if (interface_condition_response.value().response[0] != voltage_window) {
// FIXME: We should probably try again with a lower voltage window
return ENODEV;
}
// 5. Send ACMD41 (SEND_OP_COND) with HCS=1 to the card, repeat this until the card is ready or timeout
SD::OperatingConditionRegister ocr = {};
bool card_is_usable = true;
if (!retry_with_timeout([&]() {
if (issue_command(SD::Commands::app_cmd, 0).is_error() || wait_for_response().is_error())
return false;
if (issue_command(SD::Commands::app_send_op_cond, acmd41_arg).is_error())
return false;
if (auto acmd41_response = wait_for_response();
!acmd41_response.is_error()) {
// 20. Check if the card supports the voltage windows we requested and SDHC
u32 response = acmd41_response.value().response[0];
if ((response & acmd41_voltage) != acmd41_voltage) {
card_is_usable = false;
return false;
}
ocr.raw = acmd41_response.value().response[0];
}
return ocr.card_power_up_status == 1;
})) {
return card_is_usable ? EIO : ENODEV;
}
// 6. If you requested to switch to 1.8V, and the card accepts, execute a voltage switch sequence
// (we didn't ask it)
// 7. Send CMD2 (ALL_SEND_CID) to the card
TRY(issue_command(SD::Commands::all_send_cid, 0));
auto all_send_cid_response = TRY(wait_for_response());
auto cid = bit_cast<SD::CardIdentificationRegister>(all_send_cid_response.response);
// 8. Send CMD3 (SEND_RELATIVE_ADDR) to the card
TRY(issue_command(SD::Commands::send_relative_addr, 0));
auto send_relative_addr_response = TRY(wait_for_response());
u32 rca = send_relative_addr_response.response[0]; // FIXME: Might need to clear some bits here
// Extra steps:
TRY(issue_command(SD::Commands::send_csd, rca));
auto send_csd_response = TRY(wait_for_response());
auto csd = bit_cast<SD::CardSpecificDataRegister>(send_csd_response.response);
u32 block_count = (csd.device_size + 1) * (1 << (csd.device_size_multiplier + 2));
u32 block_size = (1 << csd.max_read_data_block_length);
u64 capacity = static_cast<u64>(block_count) * block_size;
u64 card_capacity_in_blocks = capacity / block_len;
if (m_registers->capabilities.high_speed) {
dbgln("SDHC: Enabling High Speed mode");
m_registers->host_configuration_0 = m_registers->host_configuration_0 | high_speed_enable;
TRY(sd_clock_frequency_change(max_supported_sdsc_frequency_high_speed));
} else {
TRY(sd_clock_frequency_change(max_supported_sdsc_frequency));
}
TRY(issue_command(SD::Commands::select_card, rca));
TRY(wait_for_response());
// Set block length to 512 if the card is SDSC.
// All other models only support 512 byte blocks so they don't need to be explicitly told
if (!ocr.card_capacity_status) {
TRY(issue_command(SD::Commands::set_block_len, block_len));
TRY(wait_for_response());
}
auto scr = TRY(retrieve_sd_configuration_register(rca));
TRY(issue_command(SD::Commands::app_cmd, rca));
TRY(wait_for_response());
TRY(issue_command(SD::Commands::app_set_bus_width, 0x2)); // 0b00=1 bit bus, 0b10=4 bit bus
TRY(wait_for_response());
return TRY(DeviceManagement::try_create_device<SDMemoryCard>(
*this,
StorageDevice::LUNAddress { controller_id(), 0, 0 },
hardware_relative_controller_id(), block_len,
card_capacity_in_blocks, rca, ocr, cid, scr));
}
bool SDHostController::retry_with_timeout(Function<bool()> f, i64 delay_between_tries)
{
int timeout = 1000;
bool success = false;
while (!success && timeout > 0) {
success = f();
if (!success)
delay(delay_between_tries);
timeout--;
}
return timeout > 0;
}
ErrorOr<void> SDHostController::issue_command(SD::Command const& cmd, u32 argument)
{
// SDHC 3.7.1: "Transaction Control without Data Transfer Using DAT Line"
constexpr u32 command_inhibit = 1 << 1;
// 1. Check Command Inhibit (CMD) in the Present State register.
// Repeat this step until Command Inhibit (CMD) is 0.
// That is, when Command Inhibit (CMD) is 1, the Host Driver
// shall not issue an SD Command.
if (!retry_with_timeout(
[&]() { return !(m_registers->present_state & command_inhibit); })) {
return EIO;
}
// 2. If the Host Driver issues an SD Command using DAT lines
// including busy signal, go to step (3).
// If without using DAT lines including busy signal, go to step (5).
// 3. If the Host Driver is issuing an abort command, go to step (5). In the
// case of non-abort command, go to step (4).
if (cmd.requires_dat_line() && cmd.type != SD::CommandType::Abort) {
// 4. Check Command Inhibit (DAT) in the Present State register. Repeat
// this step until Command Inhibit (DAT) is set to 0.
constexpr u32 data_inhibit = 1 << 2;
if (!retry_with_timeout([&]() { return !(m_registers->present_state & data_inhibit); })) {
return EIO;
}
}
// 5. Set registers as described in Table 1-2 except Command register.
m_registers->argument_1 = argument;
// 6. Set the Command register.
m_registers->transfer_mode_and_command = cmd.raw;
// 7. Perform Command Completion Sequence in accordance with 3.7.1.2.
// Done in wait_for_response()
return {};
}
ErrorOr<SDHostController::Response> SDHostController::wait_for_response()
{
// SDHC 3.7.1.2 The Sequence to Finalize a Command
// 1. Wait for the Command Complete Interrupt. If the Command Complete
// Interrupt has occurred, go to step (2).
if (!retry_with_timeout(
[&]() { return m_registers->interrupt_status.command_complete; })) {
return EIO;
}
// 2. Write 1 to Command Complete in the Normal Interrupt Status register to clear this bit
m_registers->interrupt_status.raw = command_complete;
// 3. Read the Response register(s) to get the response.
// NOTE: We read fewer bits than ResponseType because the missing bits are only
// relevant for the physical layer, and the device filters them before they
// reach us
Response r = {};
auto cmd = last_sent_command();
switch (cmd.response_type) {
case SD::ResponseType::NoResponse:
break;
case SD::ResponseType::ResponseOf136Bits:
r.response[0] = m_registers->response_0;
r.response[1] = m_registers->response_1;
r.response[2] = m_registers->response_2;
r.response[3] = m_registers->response_3;
break;
case SD::ResponseType::ResponseOf48Bits:
r.response[0] = m_registers->response_0;
break;
case SD::ResponseType::ResponseOf48BitsWithBusy:
// FIXME: Idk what to do here
break;
}
// 4. Judge whether the command uses the Transfer Complete Interrupt or not.
// If it uses Transfer Complete, go to step (5). If not, go to step (7).
if (last_sent_command().uses_transfer_complete_interrupt())
TODO();
// 7. Check for errors in Response Data. If there is no error, go to step (8). If there is an error, go to step (9).
if (cmd.response_type != SD::ResponseType::ResponseOf136Bits) {
if (card_status_contains_errors(cmd, r.response[0])) {
return EIO;
}
}
// NOTE: Steps 7, 8 and 9 consist of checking the response for errors, which
// are specific to each command therefore those steps are not fully implemented
// here.
return { r };
}
bool SDHostController::is_sd_clock_enabled()
{
return m_registers->host_configuration_1 & sd_clock_enable;
}
ErrorOr<u32> SDHostController::calculate_sd_clock_divisor(u32 sd_clock_frequency, u32 frequency)
{
// SDHC 2.2.14: "Clock Control Register"
// (1) 10-bit Divisor Mode
// This mode is supported by the Host Controller Version 1.00 and 2.00.
// The frequency is not programmed directly; rather this register holds the divisor of
// the Base Clock Frequency For SD Clock in the Capabilities register. Only
// the following settings are allowed.
//
// +-----+---------------------------+
// | 80h | base clock divided by 256 |
// | 40h | base clock divided by 128 |
// | 20h | base clock divided by 64 |
// | 10h | base clock divided by 32 |
// | 08h | base clock divided by 16 |
// | 04h | base clock divided by 8 |
// | 02h | base clock divided by 4 |
// | 01h | base clock divided by 2 |
// | 00h | Base clock (10MHz-63MHz) |
// +-----+---------------------------+
//
if (host_version() == SD::HostVersion::Version2 || host_version() == SD::HostVersion::Version1) {
for (u32 divisor = 1; divisor <= 256; divisor *= 2) {
if (sd_clock_frequency / divisor <= frequency)
return divisor >> 1;
}
dmesgln("SDHostController: Could not find a suitable divisor for the requested frequency");
return ENOTSUP;
}
// (2) 10-bit Divided Clock Mode
// Host Controller Version 3.00 supports this mandatory mode instead of the
// 8-bit Divided Clock Mode. The length of divider is extended to 10 bits and all
// divider values shall be supported.
//
// +------+-------------------------------+
// | 3FFh | 1/2046 Divided Clock |
// | .... | ............................. |
// | N | 1/2N Divided Clock (Duty 50%) |
// | .... | ............................. |
// | 002h | 1/4 Divided Clock |
// | 001h | 1/2 Divided Clock |
// | 000h | Base Clock (10MHz-255MHz) |
// +------+-------------------------------+
//
if (host_version() == SD::HostVersion::Version3) {
if (frequency == sd_clock_frequency)
return 0;
auto divisor = AK::ceil_div(sd_clock_frequency, 2 * frequency);
if (divisor > 0x3ff) {
dmesgln("SDHostController: Cannot represent the divisor for the requested frequency");
return ENOTSUP;
}
return divisor;
}
VERIFY_NOT_REACHED();
}
ErrorOr<void> SDHostController::sd_clock_supply(u32 frequency)
{
// SDHC 3.2.1: "SD Clock Supply Sequence"
// The *Clock Control* register is in the lower 16 bits of *Host Configuration 1*
VERIFY((m_registers->host_configuration_1 & sd_clock_enable) == 0);
// 1. Find out the divisor to determine the SD Clock Frequency
const u32 sd_clock_frequency = TRY(retrieve_sd_clock_frequency());
u32 divisor = TRY(calculate_sd_clock_divisor(sd_clock_frequency, frequency));
// 2. Set Internal Clock Enable and SDCLK Frequency Select in the Clock Control register
const u32 eight_lower_bits_of_sdclk_frequency_select = (divisor & 0xff) << 8;
u32 sdclk_frequency_select = eight_lower_bits_of_sdclk_frequency_select;
if (host_version() == SD::HostVersion::Version3) {
const u32 two_upper_bits_of_sdclk_frequency_select = (divisor >> 8 & 0x3) << 6;
sdclk_frequency_select |= two_upper_bits_of_sdclk_frequency_select;
}
m_registers->host_configuration_1 = m_registers->host_configuration_1 | internal_clock_enable | sdclk_frequency_select;
// 3. Check Internal Clock Stable in the Clock Control register until it is 1
if (!retry_with_timeout([&] { return m_registers->host_configuration_1 & internal_clock_stable; })) {
return EIO;
}
// 4. Set SD Clock Enable in the Clock Control register to 1
m_registers->host_configuration_1 = m_registers->host_configuration_1 | sd_clock_enable;
return {};
}
void SDHostController::sd_clock_stop()
{
// SDHC 3.2.2: "SD Clock Stop Sequence"
// 1. Set SD Clock Enable in the Clock Control register to 0
m_registers->host_configuration_1 = m_registers->host_configuration_1 & ~sd_clock_enable;
}
ErrorOr<void> SDHostController::sd_clock_frequency_change(u32 new_frequency)
{
// SDHC 3.2.3: "SD Clock Frequency Change Sequence"
// 1. Execute the SD Clock Stop Sequence
sd_clock_stop();
// 2. Execute the SD Clock Supply Sequence
return sd_clock_supply(new_frequency);
}
ErrorOr<void> SDHostController::reset_host_controller()
{
m_registers->host_configuration_0 = 0;
m_registers->host_configuration_1 = m_registers->host_configuration_1 | software_reset_for_all;
if (!retry_with_timeout(
[&] {
return (m_registers->host_configuration_1 & software_reset_for_all) == 0;
})) {
return EIO;
}
return {};
}
ErrorOr<void> SDHostController::transaction_control_with_data_transfer_using_the_dat_line_without_dma(
SD::Command const& command,
u32 argument,
u32 block_count,
u32 block_size,
UserOrKernelBuffer buf,
DataTransferType data_transfer_type)
{
// SDHC 3.7.2: "Transaction Control with Data Transfer Using DAT Line (without DMA)"
// 1. Set the value corresponding to the executed data byte length of one block to Block Size register.
// 2. Set the value corresponding to the executed data block count to Block Count register in accordance with Table 2-8.
m_registers->block_size_and_block_count = (block_count << 16) | block_size;
// 3. Set the argument value to Argument 1 register.
m_registers->argument_1 = argument;
// 4. Set the value to the Transfer Mode register. The host driver
// determines Multi / Single Block
// Select, Block Count Enable, Data Transfer Direction, Auto CMD12 Enable
// and DMA Enable. Multi / Single Block Select and Block Count Enable are
// determined according to Table 2-8. (NOTE: We assume `cmd` already has
// the correct flags set)
// 5. Set the value to Command register.
m_registers->transfer_mode_and_command = command.raw;
// 6. Then, wait for the Command Complete Interrupt.
if (!retry_with_timeout([&]() { return m_registers->interrupt_status.command_complete; })) {
return EIO;
}
// 7. Write 1 to the Command Complete in the Normal Interrupt Status
// register for clearing this bit.
m_registers->interrupt_status.raw = command_complete;
// 8. Read Response register and get necessary information of the issued
// command
// (FIXME: Return the value for better error handling)
// 9. In the case where this sequence is for write to a card, go to step
// (10).
// In case of read from a card, go to step (14).
if (data_transfer_type == DataTransferType::Write) {
for (u32 i = 0; i < block_count; i++) {
// 10. Then wait for Buffer Write Ready Interrupt.
if (!retry_with_timeout(
[&]() {
return m_registers->interrupt_status.buffer_write_ready;
})) {
return EIO;
}
// 11. Write 1 to the Buffer Write Ready in the Normal Interrupt Status register for clearing this bit.
m_registers->interrupt_status.raw = buffer_write_ready;
// 12. Write block data (in according to the number of bytes specified at the step (1)) to Buffer Data Port register.
u32 temp;
for (u32 j = 0; j < block_size / sizeof(u32); j++) {
TRY(buf.read(&temp, i * block_size + sizeof(u32) * j, sizeof(u32)));
m_registers->buffer_data_port = temp;
}
// 13. Repeat until all blocks are sent and then go to step (18).
}
} else {
for (u32 i = 0; i < block_count; i++) {
// 14. Then wait for the Buffer Read Ready Interrupt.
if (!retry_with_timeout([&]() { return m_registers->interrupt_status.buffer_read_ready; })) {
return EIO;
}
// 15. Write 1 to the Buffer Read Ready in the Normal Interrupt Status
// register for clearing this bit.
m_registers->interrupt_status.raw = buffer_read_ready;
// 16. Read block data (in according to the number of bytes specified at
// the step (1)) from the Buffer Data Port register
u32 temp;
for (u32 j = 0; j < block_size / sizeof(u32); j++) {
temp = m_registers->buffer_data_port;
TRY(buf.write(&temp, i * block_size + sizeof(u32) * j, sizeof(u32)));
}
// 17. Repeat until all blocks are received and then go to step (18).
}
}
// 18. If this sequence is for Single or Multiple Block Transfer, go to step
// (19). In case of Infinite Block Transfer, go to step (21)
// 19. Wait for Transfer Complete Interrupt.
if (!retry_with_timeout(
[&]() { return m_registers->interrupt_status.transfer_complete; })) {
return EIO;
}
// 20. Write 1 to the Transfer Complete in the Normal Interrupt Status
// register for clearing this bit
m_registers->interrupt_status.raw = transfer_complete;
return {};
}
u32 SDHostController::make_adma_descriptor_table(u32 block_count)
{
// FIXME: We might be able to write to the destination buffer directly
// Especially with 64 bit addressing enabled
// This might cost us more descriptor entries but avoids the memcpy at the end
// of each read cycle
FlatPtr adma_descriptor_physical = m_dma_region->physical_page(0)->paddr().get();
FlatPtr adma_dma_region_physical = adma_descriptor_physical + PAGE_SIZE;
FlatPtr adma_descriptor_virtual = m_dma_region->vaddr().get();
u32 offset = 0;
u32 blocks_transferred = 0;
u32 blocks_per_descriptor = (1 << 16) / block_len;
using enum OperatingMode;
switch (m_mode) {
case ADMA2_32: {
u32 i = 0;
Array<SD::DMADescriptor64, 64>& command_buffer = *bit_cast<Array<SD::DMADescriptor64, 64>*>(adma_descriptor_virtual);
for (; i < 64; ++i) {
FlatPtr physical_transfer_address = adma_dma_region_physical + offset;
VERIFY(physical_transfer_address >> 32 == 0);
// If the remaining block count is less than the maximum addressable blocks
// we need to set the actual length and break out of the loop
if (block_count - blocks_transferred < blocks_per_descriptor) {
u32 blocks_to_transfer = block_count - blocks_transferred;
command_buffer[i] = SD::DMADescriptor64 {
.valid = 1,
.end = 1,
.interrupt = 0,
.action = SD::DMAAction::Tran,
.length_upper = 0,
.length_lower = static_cast<u32>(blocks_to_transfer * block_len),
.address = static_cast<u32>(physical_transfer_address),
};
blocks_transferred += blocks_to_transfer;
offset += static_cast<size_t>(blocks_to_transfer) * block_len;
break;
}
command_buffer[i] = SD::DMADescriptor64 {
.valid = 1,
.end = 0,
.interrupt = 0,
.action = SD::DMAAction::Tran,
.length_upper = 0,
.length_lower = 0, // length of 0 means 1<<16 bytes
.address = static_cast<u32>(physical_transfer_address),
};
blocks_transferred += blocks_per_descriptor;
offset += (1 << 16);
}
command_buffer[min(i, 63)].end = 1;
break;
}
case ADMA2_64: {
u32 i = 0;
Array<SD::DMADescriptor128, 32>& command_buffer = *bit_cast<Array<SD::DMADescriptor128, 32>*>(adma_descriptor_virtual);
for (; i < 32; ++i) {
FlatPtr physical_transfer_address = adma_dma_region_physical + offset;
VERIFY(physical_transfer_address >> 32 == 0);
// If the remaining block count is less than the maximum addressable blocks
// we need to set the actual length and break out of the loop
if (block_count - blocks_transferred < blocks_per_descriptor) {
u32 blocks_to_read = block_count - blocks_transferred;
command_buffer[i] = SD::DMADescriptor128 {
.valid = 1,
.end = 1,
.interrupt = 0,
.action = SD::DMAAction::Tran,
.length_upper = 0,
.length_lower = static_cast<u32>(blocks_to_read * block_len),
.address_low = static_cast<u32>((physical_transfer_address + offset) & 0xFFFF'FFFF),
.address_high = static_cast<u32>((physical_transfer_address + offset) >> 32),
};
blocks_transferred += blocks_to_read;
offset += static_cast<size_t>(blocks_to_read) * block_len;
break;
}
command_buffer[i] = SD::DMADescriptor128 {
.valid = 1,
.end = 0,
.interrupt = 0,
.action = SD::DMAAction::Tran,
.length_upper = 0,
.length_lower = 0, // length of 0 means 1<<16 bytes
.address_low = static_cast<u32>((physical_transfer_address + offset) & 0xFFFF'FFFF),
.address_high = static_cast<u32>((physical_transfer_address + offset) >> 32),
};
blocks_transferred += blocks_per_descriptor;
offset += (1 << 16);
}
command_buffer[min(i, 31)].end = 1;
break;
}
case PIO:
VERIFY_NOT_REACHED();
}
return blocks_transferred;
}
ErrorOr<void> SDHostController::transfer_blocks_adma2(u32 block_address, u32 block_count, UserOrKernelBuffer out, SD::DataTransferDirection direction)
{
using enum OperatingMode;
FlatPtr adma_descriptor_physical = m_dma_region->physical_page(0)->paddr().get();
FlatPtr adma_descriptor_virtual = m_dma_region->vaddr().get();
FlatPtr adma_dma_region_virtual = adma_descriptor_virtual + PAGE_SIZE;
AK::ArmedScopeGuard abort_guard {
[] {
dbgln("Aborting SDHC ADMA read");
TODO();
}
};
// 3.7.2.3 Using ADMA
u32 blocks_per_descriptor = (1 << 16) / block_len;
u32 addressable_blocks_per_transfer = blocks_per_descriptor * (m_mode == ADMA2_32 ? 64 : 32);
size_t host_offset = 0;
size_t card_offset = 0;
u32 blocks_transferred_total = 0;
while (blocks_transferred_total < block_count) {
// When writing to the card we must prime the transfer buffer with the data we want to write
// FIXME: We might be able to transfer to/from the destination/origin buffer directly
// Especially with 64 bit addressing enabled
// This might cost us more descriptor entries, when the physical range is segmented,
// but avoids the memcpy at the end of each transfer cycle
if (direction == SD::DataTransferDirection::HostToCard)
TRY(out.read(bit_cast<void*>(adma_dma_region_virtual), host_offset, min(block_count - blocks_transferred_total, addressable_blocks_per_transfer) * block_len));
// (1) Create Descriptor table for ADMA in the system memory
u32 blocks_transferred = make_adma_descriptor_table(block_count);
card_offset += blocks_transferred * block_len;
// (2) Set the Descriptor address for ADMA in the ADMA System Address register.
m_registers->adma_system_address[0] = static_cast<u32>(adma_descriptor_physical & 0xFFFF'FFFF);
if (m_mode == ADMA2_64)
m_registers->adma_system_address[1] = static_cast<u32>(adma_descriptor_physical >> 32);
// (3) Set the value corresponding to the executed data byte length of one block in the Block Size
// register.
// (4) Set the value corresponding to the executed data block count in the Block Count register in
// accordance with Table 2-9. Refer to Section 1.15 for more details.
// Note: To avoid the restriction of the 16 bit block count we disable the block counter
// and do not set the block count, resulting in an "Infinite Transfer" (SDHC Table 2-9)
// ADMA has its own way of encoding block counts and to signal transfer termination
m_registers->block_size_and_block_count = block_len;
// (5) Set the argument value to the Argument register.
m_registers->argument_1 = block_address;
// (6) Set the value to the Transfer Mode register. The Host Driver determines Multi / Single Block
// Select, Block Count Enable, Data Transfer Direction, Auto CMD12 Enable and DMA
// Enable. Multi / Single Block Select and Block Count Enable are determined according to
// Table 2-9.
// If response check is enabled (Response Error Check Enable =1), set Response Interrupt
// Disable to 1 and select Response Type R1 / R5
SD::Command command = {
.dma_enable = 1,
.block_counter = 0,
.auto_command = blocks_transferred > 1 ? SD::SendAutoCommand::Command12 : SD::SendAutoCommand::Disabled,
.direction = direction,
.multiblock = blocks_transferred > 1,
.response_type_r1r5 = 0,
.response_error_check = 0,
.response_interrupt_disable = 0,
.reserved1 = 0,
.response_type = SD::ResponseType::ResponseOf48Bits,
.sub_command_flag = 0,
.crc_enable = 1,
.idx_enable = 0,
.is_data = 1,
.type = SD::CommandType::Normal,
.index = direction == SD::DataTransferDirection::HostToCard ? (blocks_transferred > 1 ? SD::CommandIndex::WriteMultipleBlock : SD::CommandIndex::WriteSingleBlock)
: (blocks_transferred > 1 ? SD::CommandIndex::ReadMultipleBlock : SD::CommandIndex::ReadSingleBlock),
.reserved3 = 0
};
// (7) Set the value to the Command register.
// Note: When writing to the upper byte [3] of the Command register, the SD command is issued
// and DMA is started.
m_registers->transfer_mode_and_command = command.raw;
// (8) If response check is enabled, go to stop (11) else wait for the Command Complete Interrupt.
// Note: We never enabled response checking
if (!retry_with_timeout([this]() { return m_registers->interrupt_status.command_complete; })) {
dbgln("SDHC: ADMA2 command response timed out");
}
// (9) Write 1 to the Command Complete in the Normal Interrupt Status register to clear this bit.
// Note: We cannot write to the nit field member directly, due to that also possibly
// setting the already completed `transfer_complete` flag, making the next check time out.
m_registers->interrupt_status.raw = command_complete;
// TODO: (10) Read Response register and get necessary information of the issued command
// (11) Wait for the Transfer Complete Interrupt and ADMA Error Interrupt.
// FIXME: Especially with big transfers this might timeout before the transfer is finished, although
// No error has has happened
// We should set this up so that it actually waits for the interrupts via a designated handler
// Note, that the SDHC has a way to detect transfer timeouts on its own
if (!retry_with_timeout([this]() { return m_registers->interrupt_status.transfer_complete || m_registers->interrupt_status.adma_error; })) {
dbgln("SDHC: ADMA2 transfer timed out");
return EIO;
}
// (12) If Transfer Complete is set to 1, go to Step (13)
if (m_registers->interrupt_status.transfer_complete) {
// (13) Write 1 to the Transfer Complete Status in the Normal Interrupt Status register to clear this bit.
m_registers->interrupt_status.transfer_complete = 1;
}
// else if ADMA Error Interrupt is set to 1, go to Step (14).
else if (m_registers->interrupt_status.adma_error) {
// (14) Write 1 to the ADMA Error Interrupt Status in the Error Interrupt Status register to clear this bit.
m_registers->interrupt_status.adma_error = 1;
// (15) Abort ADMA operation. SD card operation should be stopped by issuing abort command. If
// necessary, the Host Driver checks ADMA Error Status register to detect why ADMA error is
// generated
dmesgln("SDHC transfer failed, ADMA Error Status: {:2b}", AK::to_underlying(m_registers->adma_error_status.state));
// The scope guard will handle the Abort
return EIO;
} else {
VERIFY_NOT_REACHED();
}
// Copy the read data to the correct memory location
// FIXME: As described above, we may be able to target the destination buffer directly
if (direction == SD::DataTransferDirection::CardToHost)
TRY(out.write(bit_cast<void const*>(adma_dma_region_virtual), host_offset, blocks_transferred * block_len));
blocks_transferred_total += blocks_transferred;
host_offset = card_offset;
block_address += card_offset;
card_offset = 0;
}
abort_guard.disarm();
return {};
}
ErrorOr<void> SDHostController::read_block(Badge<SDMemoryCard>, u32 block_address, u32 block_count, UserOrKernelBuffer out)
{
VERIFY(is_card_inserted());
using enum OperatingMode;
switch (m_mode) {
case OperatingMode::ADMA2_32:
case OperatingMode::ADMA2_64:
return transfer_blocks_adma2(block_address, block_count, out, SD::DataTransferDirection::CardToHost);
case PIO: {
if (block_count > 1) {
return transaction_control_with_data_transfer_using_the_dat_line_without_dma(
SD::Commands::read_multiple_block,
block_address,
block_count,
block_len,
out,
DataTransferType::Read);
}
return transaction_control_with_data_transfer_using_the_dat_line_without_dma(
SD::Commands::read_single_block,
block_address,
block_count,
block_len,
out,
DataTransferType::Read);
}
default:
VERIFY_NOT_REACHED();
}
}
ErrorOr<void> SDHostController::write_block(Badge<SDMemoryCard>, u32 block_address, u32 block_count, UserOrKernelBuffer in)
{
VERIFY(is_card_inserted());
using enum OperatingMode;
switch (m_mode) {
case OperatingMode::ADMA2_32:
case OperatingMode::ADMA2_64:
return transfer_blocks_adma2(block_address, block_count, in, SD::DataTransferDirection::HostToCard);
case PIO: {
if (block_count > 1) {
return transaction_control_with_data_transfer_using_the_dat_line_without_dma(
SD::Commands::write_multiple_block,
block_address,
block_count,
block_len,
in,
DataTransferType::Write);
}
return transaction_control_with_data_transfer_using_the_dat_line_without_dma(
SD::Commands::write_single_block,
block_address,
block_count,
block_len,
in,
DataTransferType::Write);
}
default:
VERIFY_NOT_REACHED();
};
}
ErrorOr<SD::SDConfigurationRegister> SDHostController::retrieve_sd_configuration_register(u32 relative_card_address)
{
SD::SDConfigurationRegister scr;
TRY(issue_command(SD::Commands::app_cmd, relative_card_address));
TRY(wait_for_response());
TRY(transaction_control_with_data_transfer_using_the_dat_line_without_dma(
SD::Commands::app_send_scr,
0, 1, 8,
UserOrKernelBuffer::for_kernel_buffer(scr.raw), DataTransferType::Read));
return scr;
}
ErrorOr<u32> SDHostController::retrieve_sd_clock_frequency()
{
if (m_registers->capabilities.base_clock_frequency == 0) {
// Spec says:
// If these bits are all 0, the Host System has to get information via another method
TODO();
}
const i64 one_mhz = 1'000'000;
return { m_registers->capabilities.base_clock_frequency * one_mhz };
}
// PLSS Table 4-43 : Card Status Field/Command
bool SDHostController::card_status_contains_errors(SD::Command const& command, u32 resp)
{
SD::CardStatus status;
// PLSS 4.9.5 R6
if (command.index == SD::CommandIndex::SendRelativeAddr) {
status.raw = (resp & 0x1fff) | ((resp & 0x2000) << 6) | ((resp & 0x4000) << 8) | ((resp & 0x8000) << 8);
} else {
status.raw = resp;
}
bool common_errors = status.error || status.cc_error || status.card_ecc_failed || status.illegal_command || status.com_crc_error || status.lock_unlock_failed || status.card_is_locked || status.wp_violation || status.erase_param || status.csd_overwrite;
bool contains_errors = false;
switch (command.index) {
case SD::CommandIndex::SendRelativeAddr:
if (status.error || status.illegal_command || status.com_crc_error) {
contains_errors = true;
}
break;
case SD::CommandIndex::SelectCard:
if (common_errors) {
contains_errors = true;
}
break;
case SD::CommandIndex::SetBlockLen:
if (common_errors || status.block_len_error) {
contains_errors = true;
}
break;
case SD::CommandIndex::ReadSingleBlock:
case SD::CommandIndex::ReadMultipleBlock:
if (common_errors || status.address_error || status.out_of_range) {
contains_errors = true;
}
break;
case SD::CommandIndex::WriteSingleBlock:
case SD::CommandIndex::WriteMultipleBlock:
if (common_errors || status.block_len_error || status.address_error || status.out_of_range) {
contains_errors = true;
}
break;
case SD::CommandIndex::AppSendScr:
if (common_errors) {
contains_errors = true;
}
break;
case SD::CommandIndex::AppCmd:
if (common_errors) {
contains_errors = true;
}
break;
default:
break;
}
return contains_errors;
}
}

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Badge.h>
#include <AK/Function.h>
#include <AK/Result.h>
#include <AK/Types.h>
#include <Kernel/Devices/Storage/SD/Commands.h>
#include <Kernel/Devices/Storage/SD/Registers.h>
#include <Kernel/Devices/Storage/SD/SDMemoryCard.h>
#include <Kernel/Locking/Mutex.h>
namespace Kernel {
class SDHostController : public StorageController {
public:
SDHostController();
ErrorOr<void> initialize();
virtual ~SDHostController() = default;
virtual LockRefPtr<StorageDevice> device(u32 index) const override { return index == 0 ? m_card : nullptr; }
virtual ErrorOr<void> reset() override;
virtual ErrorOr<void> shutdown() override;
virtual size_t devices_count() const override { return m_card ? 1 : 0; }
virtual void complete_current_request(AsyncDeviceRequest::RequestResult) override;
ErrorOr<void> read_block(Badge<SDMemoryCard>, u32 block_address, u32 block_count, UserOrKernelBuffer out);
ErrorOr<void> write_block(Badge<SDMemoryCard>, u32 block_address, u32 block_count, UserOrKernelBuffer in);
void try_enable_dma();
protected:
virtual SD::HostControlRegisterMap volatile* get_register_map_base_address() = 0;
private:
ErrorOr<NonnullLockRefPtr<SDMemoryCard>> try_initialize_inserted_card();
bool is_card_inserted() const
{
constexpr u32 card_inserted = 1 << 16;
return m_registers->present_state & card_inserted;
}
SD::HostVersion host_version() { return m_registers->slot_interrupt_status_and_version.specification_version_number; }
ErrorOr<void> reset_host_controller();
SD::Command last_sent_command()
{
SD::Command command {};
command.raw = m_registers->transfer_mode_and_command;
return command;
}
bool currently_active_command_uses_transfer_complete_interrupt();
ErrorOr<u32> calculate_sd_clock_divisor(u32 sd_clock_frequency, u32 frequency);
bool is_sd_clock_enabled();
ErrorOr<void> sd_clock_supply(u32 frequency);
void sd_clock_stop();
ErrorOr<void> sd_clock_frequency_change(u32 frequency);
ErrorOr<u32> retrieve_sd_clock_frequency();
struct Response {
u32 response[4];
};
ErrorOr<void> issue_command(SD::Command const&, u32 argument);
ErrorOr<Response> wait_for_response();
bool card_status_contains_errors(SD::Command const&, u32);
bool retry_with_timeout(Function<bool()>, i64 delay_between_tries = 100);
enum class DataTransferType {
Read,
Write
};
enum class OperatingMode {
PIO,
ADMA2_32,
ADMA2_64
};
ErrorOr<void> transaction_control_with_data_transfer_using_the_dat_line_without_dma(SD::Command const&, u32 argument, u32 block_count, u32 block_size, UserOrKernelBuffer, DataTransferType data_transfer_type);
ErrorOr<void> transfer_blocks_adma2(u32 block_address, u32 block_count, UserOrKernelBuffer, SD::DataTransferDirection);
ErrorOr<SD::SDConfigurationRegister> retrieve_sd_configuration_register(u32 relative_card_address);
u32 make_adma_descriptor_table(u32 block_count);
volatile SD::HostControlRegisterMap* m_registers;
LockRefPtr<SDMemoryCard> m_card { nullptr };
u32 m_hardware_relative_controller_id { 0 };
OperatingMode m_mode { OperatingMode::PIO };
Mutex m_lock { "SDHostController"sv };
// For ADMA2
// One page of descriptor tables with 16 bit lengths can address writes of
// Up to 4 MiB ADMA2_32
// Up to 2 MiB ADMA2_64
// To not over allocate we use a buffer of just 16 pages
// FIXME: Investigate the average usage and adjust this
constexpr static size_t dma_rw_buffer_size = 16 * PAGE_SIZE;
constexpr static size_t dma_region_size = PAGE_SIZE + dma_rw_buffer_size;
OwnPtr<Memory::Region> m_dma_region;
};
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/Storage/SD/Commands.h>
#include <Kernel/Devices/Storage/SD/SDHostController.h>
#include <Kernel/Devices/Storage/SD/SDMemoryCard.h>
namespace Kernel {
SDMemoryCard::SDMemoryCard(SDHostController& sdhc, StorageDevice::LUNAddress lun_address, u32 hardware_relative_controller_id, u32 block_len, u64 capacity_in_blocks, u32 relative_card_address, SD::OperatingConditionRegister ocr, SD::CardIdentificationRegister cid, SD::SDConfigurationRegister scr)
: StorageDevice(lun_address, hardware_relative_controller_id, block_len,
capacity_in_blocks)
, m_sdhc(sdhc)
, m_relative_card_address(relative_card_address)
, m_ocr(ocr)
, m_cid(cid)
, m_scr(scr)
{
}
void SDMemoryCard::start_request(AsyncBlockDeviceRequest& request)
{
// FIXME: Make this asynchronous
MutexLocker locker(m_lock);
VERIFY(request.block_size() == block_size());
auto buffer = request.buffer();
u32 block_address = request.block_index();
if (card_addressing_mode() == CardAddressingMode::ByteAddressing) {
block_address *= block_size();
}
if (request.request_type() == AsyncBlockDeviceRequest::RequestType::Write) {
if (m_sdhc.write_block({}, block_address, request.block_count(), buffer).is_error()) {
request.complete(AsyncDeviceRequest::Failure);
return;
}
} else {
if (m_sdhc.read_block({}, block_address, request.block_count(), buffer).is_error()) {
request.complete(AsyncDeviceRequest::Failure);
return;
}
}
request.complete(AsyncDeviceRequest::Success);
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <AK/Result.h>
#include <AK/Types.h>
#include <Kernel/Devices/Storage/SD/Registers.h>
#include <Kernel/Devices/Storage/StorageDevice.h>
#include <Kernel/Locking/Mutex.h>
namespace Kernel {
class SDHostController;
class SDMemoryCard : public StorageDevice {
public:
SDMemoryCard(SDHostController& sdhc, StorageDevice::LUNAddress, u32 hardware_relative_controller_id, u32 block_len, u64 capacity_in_blocks, u32 relative_card_address, SD::OperatingConditionRegister ocr, SD::CardIdentificationRegister cid, SD::SDConfigurationRegister scr);
// ^StorageDevice
virtual CommandSet command_set() const override { return CommandSet::SD; }
// ^BlockDevice
virtual void start_request(AsyncBlockDeviceRequest&) override;
private:
enum class CardAddressingMode {
ByteAddressing,
BlockAddressing
};
CardAddressingMode card_addressing_mode() const
{
return m_ocr.card_capacity_status ? CardAddressingMode::BlockAddressing : CardAddressingMode::ByteAddressing;
}
Mutex m_lock { "SDMemoryCard"sv };
SDHostController& m_sdhc;
u32 m_relative_card_address;
SD::OperatingConditionRegister m_ocr;
SD::CardIdentificationRegister m_cid;
SD::SDConfigurationRegister m_scr;
};
}