From a8a78d31d955a4c5179050dba49d934fbca20190 Mon Sep 17 00:00:00 2001 From: Dmytro Meleshko Date: Sun, 24 Sep 2023 21:54:48 +0200 Subject: [PATCH] add my own SD card driver! --- src/stm32_hal_patches.h | 45 - src/stm32f4xx_hal_conf.h | 4 +- src/stmes/demos/image_viewer.c | 5 - src/stmes/demos/sd_card_benchmark.c | 88 +- src/stmes/demos/terminal.c | 6 - src/stmes/demos/video_player.c | 11 +- src/stmes/drivers/dma.h | 16 +- src/stmes/drivers/sdmmc.c | 1222 +++++++++++++++++++++++++++ src/stmes/drivers/sdmmc.h | 542 ++++++++++++ src/stmes/fatfs.c | 150 ++-- src/stmes/fatfs.h | 14 +- src/stmes/kernel/crash.c | 3 +- src/stmes/kernel/crash.h | 14 +- src/stmes/kernel/mpu.c | 4 +- src/stmes/kernel/task.c | 10 +- src/stmes/main.c | 6 +- src/stmes/sdio.c | 553 ------------ src/stmes/sdio.h | 45 - src/stmes/utils.h | 31 +- src/stmes/video/console.c | 3 +- 20 files changed, 1942 insertions(+), 830 deletions(-) delete mode 100644 src/stm32_hal_patches.h create mode 100644 src/stmes/drivers/sdmmc.c create mode 100644 src/stmes/drivers/sdmmc.h delete mode 100644 src/stmes/sdio.c delete mode 100644 src/stmes/sdio.h diff --git a/src/stm32_hal_patches.h b/src/stm32_hal_patches.h deleted file mode 100644 index 8e43e58..0000000 --- a/src/stm32_hal_patches.h +++ /dev/null @@ -1,45 +0,0 @@ -// Patching of functions within the STM32 HAL works essentially by declaring -// them weakly linked and providing our own non-weak implementations. To do -// this I abuse the fact that (at least under GCC) a function is marked as a -// weak symbol if any of its declarations (or the definition) have -// `__attribute__((weak))`, or there is a `#pragma weak` preceding the -// definition, which is what I use purely because that latter method requires -// less typing (since the declarations with all the parameters don't need to be -// duplicated here). To reliably insert the pragmas I also exploit the fact -// that the stm32f4xx_hal_conf.h file is indirectly referenced by every C file -// in the HAL, which then includes this file before any of the HAL headers. The -// source files which contain the patched implementations should define an -// appropriate symbol *before including any headers* to turn off the weak -// declarations of the patched functions, so that the linker is certain about -// what to link. -// -// It honestly feels like this should be illegal. - -#ifndef HAL_PATCHES_IMPLEMENT_SDMMC -#pragma weak SDMMC_CmdBlockLength -#pragma weak SDMMC_CmdReadSingleBlock -#pragma weak SDMMC_CmdReadMultiBlock -#pragma weak SDMMC_CmdWriteSingleBlock -#pragma weak SDMMC_CmdWriteMultiBlock -#pragma weak SDMMC_CmdEraseStartAdd -#pragma weak SDMMC_CmdSDEraseStartAdd -#pragma weak SDMMC_CmdEraseEndAdd -#pragma weak SDMMC_CmdSDEraseEndAdd -#pragma weak SDMMC_CmdErase -#pragma weak SDMMC_CmdStopTransfer -#pragma weak SDMMC_CmdSelDesel -#pragma weak SDMMC_CmdGoIdleState -#pragma weak SDMMC_CmdOperCond -#pragma weak SDMMC_CmdAppCommand -#pragma weak SDMMC_CmdAppOperCommand -#pragma weak SDMMC_CmdBusWidth -#pragma weak SDMMC_CmdSendSCR -#pragma weak SDMMC_CmdSendCID -#pragma weak SDMMC_CmdSendCSD -#pragma weak SDMMC_CmdSendEXTCSD -#pragma weak SDMMC_CmdSetRelAdd -#pragma weak SDMMC_CmdSendStatus -#pragma weak SDMMC_CmdStatusRegister -#pragma weak SDMMC_CmdOpCondition -#pragma weak SDMMC_CmdSwitch -#endif diff --git a/src/stm32f4xx_hal_conf.h b/src/stm32f4xx_hal_conf.h index 6a329f4..0e9294d 100644 --- a/src/stm32f4xx_hal_conf.h +++ b/src/stm32f4xx_hal_conf.h @@ -60,7 +60,7 @@ /* #define HAL_RNG_MODULE_ENABLED */ /* #define HAL_RTC_MODULE_ENABLED */ /* #define HAL_SAI_MODULE_ENABLED */ -#define HAL_SD_MODULE_ENABLED +/* #define HAL_SD_MODULE_ENABLED */ /* #define HAL_MMC_MODULE_ENABLED */ /* #define HAL_SPI_MODULE_ENABLED */ #define HAL_TIM_MODULE_ENABLED @@ -271,8 +271,6 @@ * @brief Include module's header file */ -#include "stm32_hal_patches.h" - #ifdef HAL_RCC_MODULE_ENABLED #include "stm32f4xx_hal_rcc.h" #endif /* HAL_RCC_MODULE_ENABLED */ diff --git a/src/stmes/demos/image_viewer.c b/src/stmes/demos/image_viewer.c index 5ddf1f6..ba12211 100644 --- a/src/stmes/demos/image_viewer.c +++ b/src/stmes/demos/image_viewer.c @@ -1,6 +1,5 @@ #include "stmes/demos.h" #include "stmes/fatfs.h" -#include "stmes/sdio.h" #include "stmes/utils.h" #include "stmes/video/framebuf.h" #include "stmes/video/vga.h" @@ -13,10 +12,6 @@ void image_viewer_demo(void) { static FATFS SDFatFS; static FIL SDFile; - while (BSP_SD_Init() != HAL_OK) { - HAL_Delay(500); - } - check_fs_error(f_mount(&SDFatFS, "", 1)); check_fs_error(f_open(&SDFile, "penguins_palette.bin", FA_READ)); diff --git a/src/stmes/demos/sd_card_benchmark.c b/src/stmes/demos/sd_card_benchmark.c index 4cfa82c..1d23641 100644 --- a/src/stmes/demos/sd_card_benchmark.c +++ b/src/stmes/demos/sd_card_benchmark.c @@ -1,7 +1,7 @@ #include "stmes/demos.h" +#include "stmes/drivers/sdmmc.h" #include "stmes/fatfs.h" #include "stmes/kernel/task.h" -#include "stmes/sdio.h" #include "stmes/utils.h" #include "stmes/video/console.h" #include "stmes/video/vga.h" @@ -25,51 +25,81 @@ static struct Notification progress_task_notify; static FATFS SDFatFS; static FIL SDFile; +#if !_FS_READONLY +static FIL SDFile2; +#endif static void test_task_fn(__UNUSED void* user_data) { - while (BSP_SD_Init() != HAL_OK) { - printf("."); - task_sleep(1000); - } - - console_clear_screen(); + static char buf[SDMMC_BLOCK_SIZE * 32]; - check_fs_error(f_mount(&SDFatFS, "", 1)); + FRESULT fres = f_mount(&SDFatFS, "", 1); + if (fres == FR_NO_FILESYSTEM) { +#if !_FS_READONLY && _USE_MKFS + fres = f_mkfs("", FM_ANY, 0, buf, sizeof(buf)); +#endif + } + check_fs_error(fres); + + const struct SdmmcCard* card = sdmmc_get_card(); + printf( + "CID: %08" PRIX32 " %08" PRIX32 " %08" PRIX32 " %08" PRIX32 "\n", + card->cid.words[3], + card->cid.words[2], + card->cid.words[1], + card->cid.words[0] + ); + const struct SdCID* cid = &card->cid.sd; + printf(" Manufacturer ID : 0x%02X\n", cid->manufacturer_id); + printf(" OEM/Application ID : %.2s\n", cid->oem_application_id); + const char* name = cid->product_name; + printf(" Product Name : %c%c%c%c%c\n", name[4], name[3], name[2], name[1], name[0]); + printf( + " Product Revision : %d.%d\n", + (cid->product_revision >> 4) & 0xF, + cid->product_revision & 0xF + ); + printf(" Serial Number : 0x%08" PRIX32 "\n", cid->serial_number); + printf( + " Manufacturing Date : %04d/%02d\n", cid->manufacturing_year + 2000, cid->manufacturing_month + ); + + task_sleep(3000); while (true) { check_fs_error(f_open(&SDFile, "bebop_palette.bin", FA_READ)); +#if !_FS_READONLY + check_fs_error(f_open(&SDFile2, "copy.bin", FA_WRITE | FA_CREATE_ALWAYS)); +#endif printf("loading %lu\n", f_size(&SDFile)); task_yield(); usize total_bytes = 0; - static char buf[BLOCKSIZE * 8]; Systime start_time = systime_now(); while (true) { task_notify(&progress_task_notify); - // yield(); + task_yield(); +#if 1 usize bytes_read = 0; - if (f_read(&SDFile, buf, sizeof(buf), &bytes_read) != FR_OK) { - break; - } - if (bytes_read == 0) { + check_fs_error(f_read(&SDFile, buf, sizeof(buf), &bytes_read)); + if (bytes_read == 0) break; + total_bytes += bytes_read; +#if !_FS_READONLY + check_fs_error(f_write(&SDFile2, buf, bytes_read, &bytes_read)); +#endif +#else + // disk_read(0, (BYTE*)buf, total_bytes / SDMMC_BLOCK_SIZE, sizeof(buf) / SDMMC_BLOCK_SIZE); + sdmmc_read( + (u8*)buf, total_bytes / SDMMC_BLOCK_SIZE, sizeof(buf) / SDMMC_BLOCK_SIZE, NO_DEADLINE + ); + total_bytes += sizeof(buf); + if (total_bytes >= 1024 * 1024) { break; } - total_bytes += bytes_read; +#endif } - // for (u32 i = 0, sectors = sizeof(buf) / BLOCKSIZE; true; i += sectors) { - // task_notify(&progress_task_notify); - // // yield(); - // disk_read(0, (BYTE*)buf, i, sectors); - // u32 bytes_read = BLOCKSIZE * sectors; - // if (total_bytes >= 1024 * 1024) { - // break; - // } - // total_bytes += bytes_read; - // } - task_notify(&progress_task_notify); task_yield(); @@ -84,7 +114,13 @@ static void test_task_fn(__UNUSED void* user_data) { task_sleep(2000); f_close(&SDFile); +#if !_FS_READONLY + f_close(&SDFile2); + break; +#endif } + + check_fs_error(f_mount(0, "", 0)); } static void progress_task_fn(__UNUSED void* user_data) { diff --git a/src/stmes/demos/terminal.c b/src/stmes/demos/terminal.c index 6559a8b..6de33b6 100644 --- a/src/stmes/demos/terminal.c +++ b/src/stmes/demos/terminal.c @@ -1,7 +1,6 @@ #include "stmes/demos.h" #include "stmes/fatfs.h" #include "stmes/kernel/task.h" -#include "stmes/sdio.h" #include "stmes/utils.h" #include "stmes/video/console.h" #include "stmes/video/vga.h" @@ -19,11 +18,6 @@ static void terminal_task_fn(__UNUSED void* user_data) { static FATFS SDFatFS; static DIR SDDir; - while (BSP_SD_Init() != HAL_OK) { - printf("."); - task_sleep(1000); - } - check_fs_error(f_mount(&SDFatFS, "", 1)); check_fs_error(f_opendir(&SDDir, "/")); diff --git a/src/stmes/demos/video_player.c b/src/stmes/demos/video_player.c index 9f0d3d3..53acfa2 100644 --- a/src/stmes/demos/video_player.c +++ b/src/stmes/demos/video_player.c @@ -1,14 +1,15 @@ #include "stmes/demos.h" +#include "stmes/drivers/sdmmc.h" #include "stmes/fatfs.h" #include "stmes/gpio.h" #include "stmes/kernel/crash.h" -#include "stmes/sdio.h" #include "stmes/kernel/task.h" #include "stmes/utils.h" #include "stmes/video/framebuf.h" #include "stmes/video/vga.h" #include "stmes/video/vga_color.h" #include +#include #include #include @@ -22,7 +23,7 @@ static void button_task_fn(void* user_data) { } struct BufferedReader { - u8 buffer[BLOCKSIZE * 8]; + u8 buffer[SDMMC_BLOCK_SIZE * 16]; FSIZE_t offset_start; FSIZE_t offset_end; }; @@ -31,7 +32,7 @@ static u8* buffered_read(struct BufferedReader* self, FIL* file, FSIZE_t pos, FS const FSIZE_t capacity = SIZEOF(self->buffer); ASSERT(len <= capacity); if (unlikely(!(self->offset_start <= pos && pos + len <= self->offset_end))) { - self->offset_start = pos & ~(BLOCKSIZE - 1); // align to 512-byte boundaries + self->offset_start = align_to(pos, SDMMC_BLOCK_SIZE); self->offset_end = self->offset_start; if (f_tell(file) != self->offset_start) { check_fs_error(f_lseek(file, self->offset_start)); @@ -59,10 +60,6 @@ void video_player_demo(void) { static FATFS SDFatFS; static FIL SDFile; - while (BSP_SD_Init() != HAL_OK) { - HAL_Delay(500); - } - check_fs_error(f_mount(&SDFatFS, "", 1)); check_fs_error(f_open(&SDFile, "bebop_palette.bin", FA_READ)); diff --git a/src/stmes/drivers/dma.h b/src/stmes/drivers/dma.h index 4c13cdf..7b2cb14 100644 --- a/src/stmes/drivers/dma.h +++ b/src/stmes/drivers/dma.h @@ -54,27 +54,27 @@ struct DmaConfig { memory_burst : 2; }; -#define DMA_FLAG_FEIF BIT(0) -#define DMA_FLAG_DMEIF BIT(2) -#define DMA_FLAG_TEIF BIT(3) -#define DMA_FLAG_HTIF BIT(4) -#define DMA_FLAG_TCIF BIT(5) +#define DMA_FLAG_FEIF BIT(0) // FIFO error +#define DMA_FLAG_DMEIF BIT(2) // Direct mode error +#define DMA_FLAG_TEIF BIT(3) // Transfer error +#define DMA_FLAG_HTIF BIT(4) // Transfer half-complete +#define DMA_FLAG_TCIF BIT(5) // Transfer complete #define DMA_ALL_INTERRUPT_FLAGS \ (DMA_FLAG_FEIF | DMA_FLAG_DMEIF | DMA_FLAG_TEIF | DMA_FLAG_HTIF | DMA_FLAG_TCIF) -__STATIC_FORCEINLINE DMA_TypeDef* dma_get_base_registers(DMA_Stream_TypeDef* dma) { +__STATIC_FORCEINLINE DMA_TypeDef* dma_get_base_registers(const DMA_Stream_TypeDef* dma) { // RM0383 section 2.3 "Memory map" // return (DMA_TypeDef*)((usize)dma & ~0x3FF); } -__STATIC_FORCEINLINE usize dma_get_stream_index(DMA_Stream_TypeDef* dma) { +__STATIC_FORCEINLINE usize dma_get_stream_index(const DMA_Stream_TypeDef* dma) { // RM0383 section 9.5.11 "DMA register map" // return (((usize)dma & 0xFF) - sizeof(DMA_TypeDef)) / sizeof(DMA_Stream_TypeDef); } -__STATIC_FORCEINLINE u32 dma_get_interrupt_flags(DMA_Stream_TypeDef* dma) { +__STATIC_FORCEINLINE u32 dma_get_interrupt_flags(const DMA_Stream_TypeDef* dma) { DMA_TypeDef* base = dma_get_base_registers(dma); usize idx = dma_get_stream_index(dma); static const u8 flags_offset[] = { 0, 6, 16, 22 }; diff --git a/src/stmes/drivers/sdmmc.c b/src/stmes/drivers/sdmmc.c new file mode 100644 index 0000000..d2a0aef --- /dev/null +++ b/src/stmes/drivers/sdmmc.c @@ -0,0 +1,1222 @@ +// The fastest SD card driver in the west. It was developed to improve the +// performance, reduce the size, expand feature support (such as activating the +// High-Speed mode of compatible cards) and to just generally make the +// implementation more straightforward compared to the stock STM32 HAL driver, +// and on top of that being better integrated into the overall system. And also +// because implementing the SD specification turned out to be way more fun than +// sifting through dry technical PDFs should reasonably be. +// +// Other existing projects were referenced while writing code for this driver: +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// The SD/MMC/SDIO specs can be obtained at: +// +// +// +// +// +// +// And also here are some articles on the matter which I found useful: +// +// +// +// +// + +// TODO: Verify the APP_CMD status bit after CMD55 + +// TODO: Support SDUC cards: +// 1. Parse CSD v3. +// 2. Store block addresses and numbers in u64. +// 3. Send the high bits of the block address with CMD22 before reads/writes. + +// TODO: Verify checksum of CID and CSD. + +// TODO: Add interrupts for card insertion/removal, support hot-plugging. + +#include "stmes/drivers/sdmmc.h" +#include "stmes/drivers/dma.h" +#include "stmes/gpio.h" +#include "stmes/interrupts.h" +#include "stmes/kernel/crash.h" +#include "stmes/kernel/sync.h" +#include "stmes/kernel/task.h" +#include +#include +#include +#include + +#define SDMMC_LOG_COMMANDS 0 + +// The SDIO peripheral has two DMA streams attached to it, DMA2_Stream3 and +// DMA2_Stream6, however, both of them can be used for transferring data in +// both directions, as such, let's use only one since we will only ever be +// either sending or receiving data at a single time anyway. +#define SDIO_DMA DMA2_Stream3 + +// Masks for the SDIO_STA, SDIO_ICR and SDIO_MASK registers (they largely have +// the same bit definitions). +// clang-format off +#define SDIO_CMD_INTERRUPT_FLAGS (SDIO_STA_CCRCFAIL | SDIO_STA_CTIMEOUT | SDIO_STA_CMDREND | SDIO_STA_CMDSENT) +#define SDIO_DATA_ERROR_FLAGS (SDIO_STA_DCRCFAIL | SDIO_STA_DTIMEOUT | SDIO_STA_TXUNDERR | SDIO_STA_RXOVERR | SDIO_STA_STBITERR) +#define SDIO_DATA_INTERRUPT_FLAGS (SDIO_DATA_ERROR_FLAGS | SDIO_STA_DATAEND) +// clang-format on + +#define rca_arg(card) ((u32)(card)->rca << 16) + +static struct SdmmcCard sdmmc_card; + +static struct Mutex sdio_lock; // A global lock for the SDIO peripheral. +static struct Notification sdio_notification; + +struct SdFuncStatus { + bool supported : 1, busy : 1, selected : 1; +}; + +enum SdioTransfer { SDIO_RX, SDIO_TX }; + +static u32 +sd_switch_function(bool do_switch, u32 group, u32 func, struct SdFuncStatus* out_status); +static u32 configure_sdio_bus(u32 freq, u32 bus_width, u32 power_saving); +static void +prepare_sdio_for_transfer(enum SdioTransfer transfer, u8* buffer, u32 blocks, u32 block_size); +static void stop_sdio_transfer(void); +static u32 wait_for_sdio_transfer(Systime deadline); +static u32 sdmmc_command(enum SdmmcCommand cmd, u32 arg, u32 response[4]); + +void sdmmc_init_gpio(void) { + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + + GPIO_InitTypeDef gpio_init = { + .Mode = GPIO_MODE_AF_PP, + .Pull = GPIO_NOPULL, + .Speed = GPIO_SPEED_FREQ_VERY_HIGH, + .Alternate = GPIO_AF12_SDIO, + }; + gpio_init.Pin = SDIO_CLK_PIN; + HAL_GPIO_Init(GPIOB, &gpio_init); + + gpio_init.Pull = GPIO_PULLUP; + gpio_init.Pin = SDIO_D1_PIN | SDIO_D2_PIN | SDIO_CMD_PIN; + HAL_GPIO_Init(GPIOA, &gpio_init); + gpio_init.Pin = SDIO_D0_PIN | SDIO_D3_PIN; + HAL_GPIO_Init(GPIOB, &gpio_init); +} + +bool sdmmc_is_card_inserted(void) { + // My card reader slot simply has a "card detect" pin that is pulled low when + // the card is physically inserted (that is why the result is inverted), but + // it is also possible to use the built-in pull-up resistor on the DAT3 pin + // of the card for card detection: + // + return !LL_GPIO_IsInputPinSet(SDIO_CD_GPIO_PORT, SDIO_CD_PIN); +} + +const struct SdmmcCard* sdmmc_get_card(void) { + if (sdmmc_card.type == SDMMC_UNKNOWN_CARD) { + return NULL; // The card has not been initialized yet + } + return &sdmmc_card; +} + +__STATIC_INLINE u32 physical2logical(u32 blocks, u32 physical_block_size) { + // The ctz() function gives us the log2() of the argument, which for the + // default block size of 512 bytes is 9. The physical block size parameter + // must be given as a power of 2, which the READ_BL_LEN and WRITE_BL_LEN + // fields of the CSD already are. + i32 factor = physical_block_size - __builtin_ctz(SDMMC_BLOCK_SIZE); + // This expression essentially computes: + // blocks * 2^physical / 2^logical = blocks * 2^(physical - logical) + // After all, bitshifting left or right by N either multiplies the operand by + // 2 raised to N or divides it by 2 raised to N, respectively. + return factor >= 0 ? blocks << factor : blocks >> -factor; +} + +u32 sdmmc_get_blocks_count(const struct SdmmcCard* card) { + switch (card->csd.bits.structure_version) { + case SD_CSD_VERSION_1_0: { + const struct SdCSDv1* csd_v1 = &card->csd.sd_v1; + // For SDSC cards the number of blocks is given by: + // BLOCKNR = (C_SIZE + 1) * 2^(C_SIZE_MULT + 2) + // Which can encode at most 4096*512 blocks of size 2^READ_BL_LEN. + u32 blocks = (csd_v1->card_size + 1u) * (1u << (csd_v1->card_size_multiplier + 2u)); + // The spec tells that to indicate a 2 GB card (the maximum capacity for + // an SDSC card) the block size will be set to 1024 bytes instead of the + // normal 512, so the resulting number of logical 512-byte blocks has to + // be adjusted for that. + return physical2logical(blocks, card->csd.bits.max_read_block_length); + } + case SD_CSD_VERSION_2_0: { + const struct SdCSDv2* csd_v2 = &card->csd.sd_v2; + // For SDHC/SDXC cards the number of blocks is defined simply as: + // BLOCKNR = (C_SIZE + 1) * 1024 + // With the block size always fixed to 512 bytes and C_SIZE being + // expanded to 22 bits, the maximum card capacity that can be encoded + // becomes 2 terabytes. + u32 blocks = (csd_v2->card_size + 1u) * 1024u; + // The physical block size is hardcoded as 2^9 = 512 bytes. + return physical2logical(blocks, 9u); + } + default: { + return 0; // Unsupported CSD version + } + } +} + +u32 sdmmc_get_eraseable_sector_size(const struct SdmmcCard* card) { + const struct SdmmcCSDBits* csd = &card->csd.bits; + if (csd->structure_version != SD_CSD_VERSION_1_0) return 1; + if (csd->supports_single_block_erase) return 1; + // Older cards only support erasing (SECTOR_SIZE+1) aligned sectors, in the + // units of 2^WRITE_BL_LEN (see figures 5-1 and 5-2 in the SD spec). + // Additionally, block size on such cards may not necessarily be the same as + // the 512 byte logical blocks. + return physical2logical(csd->eraseable_sector_size + 1, csd->max_write_block_length); +} + +// Returns the maximum supported clock signal frequency reported by the card in +// its CSD. Should almost always be either 25 MHz or 50 MHz. +u32 sdmmc_max_transfer_freq(const struct SdmmcCard* card) { + const struct SdmmcCSDBits* csd = &card->csd.bits; + // TODO: These factors are different for MMC cards + static const u8 transfer_speed_factors[1 << 4] = { + 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, + }; + static const u32 transfer_speed_units[1 << 2] = { 10000, 100000, 1000000, 10000000 }; + // The transfer speed factor is 4 bits long, the array covers its full range. + u32 factor = transfer_speed_factors[csd->transfer_speed_factor & MASK(4)]; + // Even though the transfer speed unit is 3 bits long, values over 3 (4..7) + // are reserved, so the 3rd bit can be simply ignored. + u32 unit = transfer_speed_units[csd->transfer_speed_unit & MASK(2)]; + return unit * factor; +} + +// Determines the supported SD specification version based on the fields in the +// card's SCR. +enum SdSpecVersion sdmmc_sd_spec_version(const struct SdmmcCard* card) { + if (card->type == MMC_CARD) { + return SD_SPEC_UNKNOWN_VERSION; + } + const struct SdSCRv1* scr = &card->scr.v1; + // See table 5-19 "Physical Layer Specification Version" for the combinations + // of the SD_SPEC, SD_SPEC3, SD_SPEC4 and SD_SPECX fields' values. + switch (scr->sd_spec) { + case 0: return SD_SPEC_V1_0; + case 1: return SD_SPEC_V1_1; + case 2: { + if (scr->sd_spec3 != 1) return SD_SPEC_V2_0; + switch (scr->sd_specx) { + // The value of SD_SPEC4 matters only for differentiating v3.00 and + // v4.00, for all other versions it is irrelevant. + case 0: return scr->sd_spec4 != 1 ? SD_SPEC_V3_X : SD_SPEC_V4_X; + case 1: return SD_SPEC_V5_X; + case 2: return SD_SPEC_V6_X; + case 3: return SD_SPEC_V7_X; + case 4: return SD_SPEC_V8_X; + case 5: return SD_SPEC_V9_X; + // Of course, this function can't support every SD card version to ever + // be released because the spec is gonna keep updating, but the newest + // versions of the specification promise that future versions are going + // to be indicated by increasing the value of the SD_SPECX field, so in + // the case of higher values we can at least be sure that the card + // implements functionality of v9.00-and-later. + default: return SD_SPEC_V9_X; + } + } + } + return SD_SPEC_UNKNOWN_VERSION; +} + +u32 sdmmc_init_card(const struct SdmmcHostCapabilities* host_caps) { + u32 err = SDMMC_ERROR_NONE; + mutex_lock(&sdio_lock); + + struct SdmmcCard* card = &sdmmc_card; + // The remaining fields will be automatically zero-initialized. + *card = (struct SdmmcCard){ + .type = SDMMC_UNKNOWN_CARD, + .spec_version = SD_SPEC_UNKNOWN_VERSION, + .rca = 0x0000, // The card starts out with a zero RCA. + }; + + printf("enabling the SDIO peripheral\n"); + + __HAL_RCC_SDIO_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); + + HAL_NVIC_SetPriority(SDIO_IRQn, 4, 0); + HAL_NVIC_EnableIRQ(SDIO_IRQn); + + // TODO: There has to be a better way of abstracting this. + if (!sdmmc_is_card_inserted()) { + err = SDMMC_ERROR_REQUEST_NOT_APPLICABLE; + goto exit; + } + + // TODO: Implement card power control through an external voltage regulator. + // NOTE: The power up procedure is described in section 6.4 "Power scheme". + printf("power cycling the card\n"); + // Disable the SDIO clock and reset all of its parameters. + CLEAR_BIT(SDIO->CLKCR, SDIO_CLKCR_CLKEN | CLKCR_CLEAR_MASK); + // Cut off the power from the SDIO peripheral (the name of this register is a + // bit misleading though, it controls only whether the card is clocked, it + // doesn't actually power off or on the card). + CLEAR_BIT(SDIO->POWER, SDIO_POWER_PWRCTRL); + // Wait for the card to completely shut down. + task_sleep(2); + // Switch on the power (in actuality switch on the SDIO clocking unit). + SET_BIT(SDIO->POWER, SDIO_POWER_PWRCTRL); + // Wait a bit for the supply voltage to reach a stable level of 3.3V. + task_sleep(2); + // Supply a 400 kHz clock signal for the whole duration of the identification phase. + card->clock_freq = configure_sdio_bus(400000, SDIO_BUS_WIDE_1B, SDIO_CLOCK_POWER_SAVE_DISABLE); + // According to the SD spec, the frequency must be in the 100-400 kHz range. + // For real, certain initialization-related commands on certain cards will + // simply refuse to work at higher frequencies: + // + // + ASSERT(100000 <= card->clock_freq && card->clock_freq <= 400000); + // The spec demands that the host waits for the duration of 74 SD clocks + // after powering the card on, which amounts to 185 microseconds, but + // currently the kernel APIs don't give us big enough timer resolution for + // this, and let's generally be safe and wait a little more. + task_sleep(2); + + u32 response[4]; + Systime deadline = NO_DEADLINE; + + // NOTE: Now begins the card initialization and identification procedure. It + // is covered in great detail in the section 4.2 "Card Identification Mode" + // of the SD specification, and an overview is given on figures 4-1 and 4-2. + + printf("starting card initialization\n"); + Systime init_start_time = systime_now(); + + // The card here may be in any state if the MCU has been rebooted. + + printf("resetting the card\n"); + for (u32 attempt = 0; attempt < 10; attempt++) { + if (!(err = sdmmc_command(SDMMC_CMD0_GO_IDLE_STATE, 0, response))) break; + task_sleep(1); + } + if (err) goto exit; + + // After a software reset with CMD0, the card switches to the Idle state. + // Now, CMD8 must be issued to check support for SD spec v2.00-or-later. + + printf("sending interface conditions... "); + bool is_v2_x_card; + union SdmmcIfCond if_cond = { .word = 0 }; + if_cond.bits.check_pattern = 0xAA; // 0b10101010, recommended by the spec + if_cond.bits.host_voltage_supply = SD_VOLTAGE_RANGE_2_7V_TO_3_6V; + if (!(err = sdmmc_command(SD_CMD8_SEND_IF_COND, if_cond.word, response))) { + union SdmmcIfCond resp_if_cond = { .word = response[0] }; + is_v2_x_card = true; + // The spec recommends using the check pattern (in addition to the CRC + // built into the protocol) to test signal integrity. + if (resp_if_cond.bits.check_pattern == if_cond.bits.check_pattern) { + printf("card is at least v2.x\n"); + } else { + printf("check pattern error\n"); + err = SDMMC_ERROR_CMD_CRC_FAIL; + goto exit; + } + } else { + // The card will respond with an error to CMD8 in two cases: either if it + // is a v1.XX card, and thus doesn't support CMD8, or if it can't operate + // under the voltage supplied by the host. Problem is, I have no idea how + // to differentiate the two conditions, since in both cases the card is + // supposed to not return any response. Though, I guess the cards can at + // least turn on and respond to commands at a pretty wide range of + // voltages, so if the execution has reached here, it really must've been + // an illegal command. + is_v2_x_card = false; + printf("card is v1.x\n"); + // Upon receiving an illegal command, the card will set the ILLEGAL_COMMAND + // error bit returned in the R1 response of the next command. This bit is + // reset simply by issuing any other valid command, but in the Idle state + // the choice of legal commands is scarce. + if ((err = sdmmc_command(SDMMC_CMD0_GO_IDLE_STATE, 0, response))) { + if (err != SDMMC_ERROR_ILLEGAL_CMD) goto exit; + } + } + + // Next, the host shall issue ACMD41 to negotiate the supply voltage and + // support for High/Extra Capacity cards, and wait until the card reaches + // the Ready state. + + printf("sending operating conditions...\n"); + // A timeout of 1 second for initialization with ACMD41 is recommended by the spec. + deadline = timeout_to_deadline(1000); + while (true) { + // The parameter and the return value of ACMD41 are so similar that I kinda + // treat the argument as the host's own OCR and the command as negotiating + // the two OCRs. + union SdmmcOCR host_ocr = { .word = 0 }; + // TODO: Deduce the voltage window bits from minimum/maximum voltage + // parameters of SdmmcHostCapabilities. + host_ocr.bits.voltage_window_3_2v_to_3_3v = true; + if (is_v2_x_card) { + // HCS (Host Capacity Support) bit - SDHC/SDXC cards are supported + host_ocr.bits.high_capacity_status = true; + // S18R bit - don't request a switch to the 1.8V signaling level + host_ocr.bits.switching_to_1_8v_status = false; + // XPC bit - don't enable the maximum performance mode of SDXC cards + host_ocr.bits.sdxc_power_control = SDXC_POWER_SAVING; + } + if ((err = sdmmc_command(SDMMC_CMD55_APP_CMD, rca_arg(card), response))) goto exit; + if ((err = sdmmc_command(SD_ACMD41_SD_SEND_OP_COND, host_ocr.word, response))) goto exit; + union SdmmcOCR card_ocr = { .word = response[0] }; + // Check the busy status bit: 1 means that the card is still booting, 0 + // means that the card's internal controller has been initialized. + if (card_ocr.bits.power_up_status) { + if (is_v2_x_card) { + // Check the CCS (Card Capacity Status) bit. + card->type = card_ocr.bits.high_capacity_status ? SDHC_SDXC_CARD : SDSC_V2_X_CARD; + } else { + card->type = SDSC_V1_X_CARD; + } + break; + } + if (systime_now() >= deadline) { + // The most frequent cause for a timeout on ACMD41 is precisely that the + // card can't operate under the supplied voltage. The card will respond + // to the command, but will refuse to perform further initialization. + err = SDMMC_ERROR_INVALID_VOLTRANGE; + goto exit; + } + task_sleep(1); + } + + Systime init_time = systime_now() - init_start_time; + printf("initialization completed in %" PRIu32 " ms\n", (u32)systime_as_millis(init_time)); + + printf("starting card identification\n"); + Systime ident_start_time = systime_now(); + + // The card is now in the Ready state. The host now requests its unique CID + // register by issuing CMD2. + + printf("reading CID data\n"); + if ((err = sdmmc_command(SDMMC_CMD2_ALL_SEND_CID, 0, response))) goto exit; + // The words in the CID must be flipped. The individual bytes are fine though. + card->cid.words[0] = response[3]; + card->cid.words[1] = response[2]; + card->cid.words[2] = response[1]; + card->cid.words[3] = response[0]; + + // After responding to CMD2, the card transitions to the Identification + // state. The host then asks the card to publish its RCA which will be later + // used for addressing it when transferring data. + + printf("requesting RCA... "); + if ((err = sdmmc_command(SD_CMD3_SEND_RELATIVE_ADDR, 0, response))) goto exit; + card->rca = (u16)(response[0] >> 16); // Extract the top 16 bits of the response + printf("0x%04" PRIX16 "\n", card->rca); + + // NOTE: Strictly speaking the identification procedure is complete - the + // card now enters the Stand-by state in the data transfer mode. However, a + // few more steps need to be done in order to complete the initialization + // sequence. The behavior of the SD card's state machine is described in + // section 4.3 "Data Transfer Mode" of the spec and on figure 4-13 "SD Memory + // Card State Diagram". + + printf("reading CSD register\n"); + // CMD9 must be issued before selecting the card. + if ((err = sdmmc_command(SDMMC_CMD9_SEND_CSD, rca_arg(card), response))) goto exit; + // The words of the CSD must be reversed as well. + card->csd.words[0] = response[3]; + card->csd.words[1] = response[2]; + card->csd.words[2] = response[1]; + card->csd.words[3] = response[0]; + + { + char bytes_str[16]; + bytes_str[0] = '\0'; + u32 blocks = sdmmc_get_blocks_count(card); + humanize_bytes(bytes_str, sizeof(bytes_str), (u64)blocks * SDMMC_BLOCK_SIZE); + printf("blocks = %" PRIu32 ", capacity = %sB\n", blocks, bytes_str); + } + + printf("selecting card 0x%04" PRIX16 "\n", card->rca); + // Selecting the card moves it to the Transfer state. + if ((err = sdmmc_command(SDMMC_CMD7_SELECT_CARD, rca_arg(card), response))) goto exit; + + // TODO: Check the CARD_IS_LOCKED bit at this point + + u32 scr_data[2]; + printf("reading SCR data\n"); + if ((err = sdmmc_command(SDMMC_CMD16_SET_BLOCKLEN, sizeof(scr_data), response))) goto exit; + if ((err = sdmmc_command(SDMMC_CMD55_APP_CMD, rca_arg(card), response))) goto exit; + // For some reason the SCR is sent via the DAT lines and not, for example, + // with the long response format R2. + prepare_sdio_for_transfer(SDIO_RX, (u8*)scr_data, 1, sizeof(scr_data)); + deadline = timeout_to_deadline(100); + if (!(err = sdmmc_command(SD_ACMD51_SEND_SCR, 0, response))) { + err = wait_for_sdio_transfer(deadline); + } + stop_sdio_transfer(); + if (err) goto exit; + // The byte order of the whole SCR must be reversed. + card->scr.words[0] = u32_from_be(scr_data[1]); + card->scr.words[1] = u32_from_be(scr_data[0]); + + card->spec_version = sdmmc_sd_spec_version(card); + const char* spec_version_name = "0.00"; + switch (card->spec_version) { + case SD_SPEC_UNKNOWN_VERSION: spec_version_name = "0.00"; break; + case SD_SPEC_V1_0: spec_version_name = "1.01"; break; + case SD_SPEC_V1_1: spec_version_name = "1.10"; break; + case SD_SPEC_V2_0: spec_version_name = "2.00"; break; + case SD_SPEC_V3_X: spec_version_name = "3.00"; break; + case SD_SPEC_V4_X: spec_version_name = "4.00"; break; + case SD_SPEC_V5_X: spec_version_name = "5.00"; break; + case SD_SPEC_V6_X: spec_version_name = "6.00"; break; + case SD_SPEC_V7_X: spec_version_name = "7.00"; break; + case SD_SPEC_V8_X: spec_version_name = "8.00"; break; + case SD_SPEC_V9_X: spec_version_name = "9.00"; break; + } + printf("card implements specification v%s\n", spec_version_name); + + // Here comes the most interesting part of the card initialization sequence + // and what actually makes my driver the fastest: activation of the + // High-Speed mode which allows communication with at frequencies up to 50 + // MHz (25 MB/s transfer rate with a 4 bit bus), instead of the standard 25 + // MHz (12.5 MB/s transfer rate). + if (host_caps->high_speed_mode) { + if (card->spec_version >= SD_SPEC_V1_1 && card->csd.bits.supports_switch_commands) { + const u32 ACCESS_MODE_GROUP = 1; + const u32 ACCESS_MODE_HIGH_SPEED = 1; + + struct SdFuncStatus status; + printf("checking support of High-Speed mode... "); + if ((err = sd_switch_function(false, ACCESS_MODE_GROUP, ACCESS_MODE_HIGH_SPEED, &status))) { + goto exit; + } + if (status.supported && !status.busy && status.selected) { + printf("ok\n"); + printf("switching to High-Speed mode... "); + if ((err = sd_switch_function(true, ACCESS_MODE_GROUP, ACCESS_MODE_HIGH_SPEED, &status))) { + goto exit; + } + if (status.supported && status.selected) { + // The specification requires the host to wait for at least 8 clocks + // after CMD8 before making use of the new functions. + task_sleep(1); + printf("ok\n"); + } else { + printf("fail\n"); + } + + // Confirm the success of the switch by refreshing the CSD register. + // Once the card switches to the HS mode, it is supposed to increase + // the TRAN_SPEED field of the CSD from 25 MHz to 50 MHz. However, CMD9 + // is accepted only in the Stand-by state, so the card must be first + // deselected, and then re-selected again afterwards to be returned to + // the Transfer state. + printf("reading CSD register\n"); + if ((err = sdmmc_command(SDMMC_CMD7_DESELECT_CARD, 0, response))) goto exit; + if ((err = sdmmc_command(SDMMC_CMD9_SEND_CSD, rca_arg(card), response))) goto exit; + card->csd.words[0] = response[3]; + card->csd.words[1] = response[2]; + card->csd.words[2] = response[1]; + card->csd.words[3] = response[0]; + if ((err = sdmmc_command(SDMMC_CMD7_SELECT_CARD, rca_arg(card), response))) goto exit; + } else { + printf("unsupported\n"); + } + } + } + + // This step isn't strictly necessary, but I guess we might want to equalize + // the electrical characteristics of all data pins. + printf("disabling pull-up resistor on CD/DAT3 pin\n"); + if ((err = sdmmc_command(SDMMC_CMD55_APP_CMD, rca_arg(card), response))) goto exit; + if ((err = sdmmc_command(SD_ACMD42_SET_CLR_CARD_DETECT, 0, response))) goto exit; + + u32 bus_width = SDIO_BUS_WIDE_1B; + // Well, technically the SD spec requires all SD cards to support the 4-bit + // bus, but a check beforehand won't hurt. + if (host_caps->use_4bit_data_bus && card->scr.v1.supports_4bit_wide_bus) { + bus_width = SDIO_BUS_WIDE_4B; + printf("switching data bus to 4-bit mode\n"); + if ((err = sdmmc_command(SDMMC_CMD55_APP_CMD, rca_arg(card), response))) goto exit; + if ((err = sdmmc_command(SD_ACMD6_SET_BUS_WIDTH, 2, response))) goto exit; + } + + // Engage the warp drive! Crank the SD clock and bus width to the max, plus + // enable power saving (turns off the clock signal when the bus is idle). + card->clock_freq = + configure_sdio_bus(sdmmc_max_transfer_freq(card), bus_width, SDIO_CLOCK_POWER_SAVE_ENABLE); + + // This might be required for compatibility with the ancient v1.00 cards + // which have variable logical block sizes, with the default not necessarily + // being 512 bytes. Newer cards simply ignore CMD16. + const u32 block_len = SDMMC_BLOCK_SIZE; + printf("setting block length to %" PRIu32 "\n", block_len); + if ((err = sdmmc_command(SDMMC_CMD16_SET_BLOCKLEN, block_len, response))) goto exit; + + Systime ident_time = systime_now() - ident_start_time; + printf( + "indentification sequence completed in %" PRIu32 " ms\n", (u32)systime_as_millis(ident_time) + ); + +exit: + check_sd_error(err); + mutex_unlock(&sdio_lock); + return err; +} + +// Wrapper around the CMD6 command, which is used to query (do_switch=false) +// and switch (do_switch=true) 15 optional SD card functions in 6 function +// groups. Its usage is covered in the section 4.3.10 "Switch Function Command" +// of the SD specification. +static u32 +sd_switch_function(bool do_switch, u32 group, u32 func, struct SdFuncStatus* out_status) { + ASSERT(1 <= group && group <= 6 && func < 15); + // The argument to CMD6 consists of six 4-bit values which designate what to + // do regarding each of the 6 function groups. Each such field may be set to + // either: + // 1. 0xF, signifying that the group should be left as-is, untouched + // 2. 0x0, asking to switch the group to the default function (which is + // always available) + // 3. or any other number, which requests a switch to the respective function + // We start out with an argument with all fields set to 0xF - meaning no + // influence to any of the groups. + u32 func_mask = 0xFFFFFF; + const u32 group_shift = (group - 1) * 4; + CLEAR_BIT(func_mask, MASK(4) << group_shift); // Clear out and change just + SET_BIT(func_mask, func << group_shift); // the requested group. + // The 32nd bit of the argument determines whether to query the presence of + // or actually switch to the function. + const u32 cmd6_arg = func_mask | (do_switch ? BIT(31) : 0); + + // CMD6 also transmits a long table over the DAT lines with the statuses of + // all functions and function groups. + u32 switch_status[512 / (8 * sizeof(u32))]; + + u32 err = SDMMC_ERROR_NONE; + u32 response[4]; + if ((err = sdmmc_command(SDMMC_CMD16_SET_BLOCKLEN, sizeof(switch_status), response))) return err; + + prepare_sdio_for_transfer(SDIO_RX, (u8*)switch_status, 1, sizeof(switch_status)); + Systime deadline = timeout_to_deadline(100); + if (!(err = sdmmc_command(SD_CMD6_SWITCH_FUNC, cmd6_arg, response))) { + err = wait_for_sdio_transfer(deadline); + } + stop_sdio_transfer(); + if (err) return err; + + // The bytes in the entire returned array must be reversed. + const usize len = SIZEOF(switch_status); + u32 *left = &switch_status[0], *right = &switch_status[len - 1], *end = left + len / 2; + for (; left != end; left++, right--) { + u32 a = *left, b = *right; + a = u32_from_be(a), b = u32_from_be(b); + *left = b, *right = a; + } + + const u32 int_bits = sizeof(switch_status[0]) * 8; + + // Fortunately, the fields in the status structure are aligned to byte + // boundaries, so we can use relatively simple bit array code for parsing. + const u32 version_bit = 368; + u32 struct_version = + (switch_status[version_bit / int_bits] >> (version_bit % int_bits)) & MASK(8); + + struct SdFuncStatus tmp_status = { 0 }; + if (struct_version == 0 || struct_version == 1) { + const u32 bit = 400 + (group - 1) * 16 + func; + tmp_status.supported = ((switch_status[bit / int_bits] >> (bit % int_bits)) & 1) != 0; + } + if (struct_version == 1) { + const u32 bit = 272 + (group - 1) * 16 + func; + tmp_status.busy = ((switch_status[bit / int_bits] >> (bit % int_bits)) & 1) != 0; + } + if (struct_version == 0 || struct_version == 1) { + const u32 bit = 376 + (group - 1) * 4; + tmp_status.selected = ((switch_status[bit / int_bits] >> (bit % int_bits)) & MASK(4)) == func; + } + *out_status = tmp_status; + + return err; +} + +u32 sdmmc_get_card_status(union SdmmcCSR* out_status) { + mutex_lock(&sdio_lock); + const struct SdmmcCard* card = sdmmc_get_card(); + u32 response[4]; + u32 err = sdmmc_command(SDMMC_CMD13_SEND_STATUS, rca_arg(card), response); + out_status->word = response[0]; + mutex_unlock(&sdio_lock); + return err; +} + +u32 sdmmc_read(u8* buffer, u32 offset, u32 blocks, Systime deadline) { + u32 err = SDMMC_ERROR_NONE; + mutex_lock(&sdio_lock); + const struct SdmmcCard* card = sdmmc_get_card(); + + if (card->type != SDHC_SDXC_CARD) { + // SDSC cards use byte (instead of block) addressing. + offset *= SDMMC_BLOCK_SIZE; + } + + // NOTE: For block read operations the DPSM must be configured for data + // transfer prior to sending the respective command because the card will + // start streaming data as soon as it has transmitted a response. + prepare_sdio_for_transfer(SDIO_RX, buffer, blocks, SDMMC_BLOCK_SIZE); + u32 response[4]; + enum SdmmcCommand command = + blocks > 1 ? SDMMC_CMD18_READ_MULTIPLE_BLOCK : SDMMC_CMD17_READ_SINGLE_BLOCK; + if (!(err = sdmmc_command(command, offset, response))) { + err = wait_for_sdio_transfer(deadline); + } + stop_sdio_transfer(); + + if (blocks > 1) { + // TODO: Test the following error conditions: + // 1. A sticky error flag being set prior to CMD18/CMD17 (e.g. ILLEGAL_CMD) + // 2. Error while sending CMD18/CMD17, the start command is not sent at all + u32 stop_err = sdmmc_command(SDMMC_CMD12_STOP_TRANSMISSION, 0, response); + if (!err) err = stop_err; + } + + check_sd_error(err); + mutex_unlock(&sdio_lock); + return err; +} + +u32 sdmmc_write(const u8* buffer, u32 offset, u32 blocks, Systime deadline) { + u32 err = SDMMC_ERROR_NONE; + mutex_lock(&sdio_lock); + const struct SdmmcCard* card = sdmmc_get_card(); + + if (card->type != SDHC_SDXC_CARD) { + // SDSC cards use byte (instead of block) addressing. + offset *= SDMMC_BLOCK_SIZE; + } + + u32 response[4]; + enum SdmmcCommand command = + blocks > 1 ? SDMMC_CMD25_WRITE_MULTIPLE_BLOCK : SDMMC_CMD24_WRITE_BLOCK; + if (!(err = sdmmc_command(command, offset, response))) { + // NOTE: For block write operations the DPSM is configured in a different + // order as opposed block read ones, otherwise it will begin transmission + // while a command is still being sent. + prepare_sdio_for_transfer(SDIO_TX, (u8*)buffer, blocks, SDMMC_BLOCK_SIZE); + err = wait_for_sdio_transfer(deadline); + stop_sdio_transfer(); + } + + if (blocks > 1) { + u32 stop_err = sdmmc_command(SDMMC_CMD12_STOP_TRANSMISSION, 0, response); + if (!err) err = stop_err; + } + + check_sd_error(err); + mutex_unlock(&sdio_lock); + return err; +} + +u32 sdmmc_erase(u32 start, u32 end, Systime deadline) { + UNUSED(deadline); + ASSERT(start < end); + u32 err = SDMMC_ERROR_NONE; + mutex_lock(&sdio_lock); + const struct SdmmcCard* card = sdmmc_get_card(); + + if (!card->csd.bits.supports_erase_commands) { + err = SDMMC_ERROR_UNSUPPORTED_FEATURE; + goto exit; + } + + u32 align = sdmmc_get_eraseable_sector_size(card); + ASSERT(start % align == 0); + ASSERT(end % align == 0); + + if (card->type != SDHC_SDXC_CARD) { + // SDSC cards use byte (instead of block) addressing. + start *= SDMMC_BLOCK_SIZE, end *= SDMMC_BLOCK_SIZE; + } + + u32 response[4]; + if ((err = sdmmc_command(SD_CMD32_ERASE_WR_BLK_START, start, response))) goto exit; + if ((err = sdmmc_command(SD_CMD33_ERASE_WR_BLK_END, end, response))) goto exit; + if ((err = sdmmc_command(SDMMC_CMD38_ERASE, 0, response))) goto exit; + +exit: + check_sd_error(err); + mutex_unlock(&sdio_lock); + return err; +} + +// Tries to set the SD clock to at most the given frequency. +static u32 configure_sdio_bus(u32 freq, u32 bus_width, u32 power_saving) { + ASSERT(IS_SDIO_BUS_WIDE(bus_width) && IS_SDIO_CLOCK_POWER_SAVE(power_saving)); + + u32 clkcr = READ_REG(SDIO->CLKCR); + + SET_BIT(clkcr, SDIO_CLKCR_CLKEN); + MODIFY_REG(clkcr, SDIO_CLKCR_WIDBUS, bus_width); + MODIFY_REG(clkcr, SDIO_CLKCR_PWRSAV, power_saving); + // NOTE: Both clock dephasing and hardware flow control options may cause + // data corruption and thus shouldn't be used, see errata ES0287 sections + // 2.7.1 and 2.7.3. + CLEAR_BIT(clkcr, SDIO_CLKCR_NEGEDGE | SDIO_CLKCR_HWFC_EN); + + // The reference manual uses two designations for clock signals related to + // the SDIO peripheral: SDIOCLK, which is the frequency at which the + // peripheral itself is clocked, and SDIO_CK, which is the frequency on the + // SDIO_CK pin, used for clocking the card. The formula for calculating + // SDIOCLK is given in RM0383 section 6.3.2. However, as the SDIO peripheral + // shares a divider at the main PLL with USB_OTG_FS, thus it always operates + // at 48 MHz. On the other hand, the formula for determining SDIO_CK based on + // the parameters in SDIO_CLKCR, is given in RM0383 section 21.9.2. + u32 sdioclk = LL_RCC_GetSDIOClockFreq(LL_RCC_SDIO_CLKSOURCE); + + // The division factor the will get us to the desired frequency or lower. + u32 clkdiv = ceil_div(sdioclk, freq); + u32 sdio_ck; // = the effective card clocking frequency + if (clkdiv >= 2) { + clkdiv = MIN(clkdiv - 2, UINT8_MAX); + sdio_ck = sdioclk / (clkdiv + 2); + CLEAR_BIT(clkcr, SDIO_CLKCR_BYPASS); + MODIFY_REG(clkcr, SDIO_CLKCR_CLKDIV, clkdiv); + } else { + sdio_ck = sdioclk; + SET_BIT(clkcr, SDIO_CLKCR_BYPASS); + MODIFY_REG(clkcr, SDIO_CLKCR_CLKDIV, 0); + } + + u32 bus_bits = 0; + switch (bus_width) { + case SDIO_BUS_WIDE_1B: bus_bits = 1; break; + case SDIO_BUS_WIDE_4B: bus_bits = 4; break; + case SDIO_BUS_WIDE_8B: bus_bits = 8; break; + } + + u32 pclk2 = HAL_RCC_GetPCLK2Freq(); + // RM0383 section 21.3 warns that the clock frequencies must respect the + // following condition: + ASSERT(pclk2 >= 3 * sdio_ck / 8); + + // A check for errata ES0287 section 2.7.5: underrun conditions may occur due + // to limited APB throughput if hardware flow control is disabled (which it + // must be due to a different errata) and clock periods don't respect a + // certain relationship. + if (READ_BIT(clkcr, SDIO_CLKCR_HWFC_EN) == 0) { + // We are dealing with periods here which are inverse of frequencies. I + // don't want to use the FPU for a simple safeguard check, and rewriting + // the equation to not use division operations makes it perform integer + // multiplications which are way outside the range of u32, so instead I use + // fixed-point math by calculating the periods in terms of nanoseconds. + // That doesn't change the meaning of the relationship, is enough to fit + // well within the range of 32-bit ints and can meaningfully represent + // periods at megahertz frequencies. + const u32 ns = 1000000000; + ASSERT(3 * (ns / pclk2) + 3 * (ns / sdioclk) < 32 / bus_bits * (ns / sdio_ck)); + } + + char str[10]; + humanize_units(str, sizeof(str), sdio_ck); + printf("setting SDIO_CK to %sHz\n", str); + WRITE_REG(SDIO->CLKCR, clkcr); + return sdio_ck; +} + +// The error bits in the mapping tables are stored not as 32-bit masks (as they +// are defined in the HAL headers), but as bit positions, thereby giving a 4x +// reduction in size. +struct SdioErrorDef { + u8 response_bit, error_bit; +}; + +// The functions in the STM32 HAL (and some other implementations) use a large +// sequence of else-ifs to check for every error bit in the response and return +// an appropriate error code: +// . +// Needless to say, this generates a lot of machine code for code that is +// normally never even executed. I replace that with a generic function that +// uses a much more compact mapping table between the specific bits in the +// responses and the HAL error codes. +static u32 sdio_check_response_error( + u32 response, const struct SdioErrorDef* error_defs, usize error_defs_len +) { + u32 error_bits = SDMMC_ERROR_NONE; + for (usize i = 0; i < error_defs_len; i++) { + if (response & BIT(error_defs[i].response_bit)) { + error_bits |= BIT(error_defs[i].error_bit); + } + } + return error_bits; +} + +// Extracts the position of a set bit from a bit mask, otherwise (if the mask +// is not a power of 2, i.e. more than one bit is set) triggers a compile-time +// error. +#define ERROR_BIT(mask) \ + (__builtin_choose_expr(__builtin_popcount(mask) == 1, __builtin_ctz(mask), (void)0)) + +static const struct SdioErrorDef SDIO_ERRORS_R1[] = { + // clang-format off + { ERROR_BIT(SDMMC_OCR_ADDR_OUT_OF_RANGE), ERROR_BIT(SDMMC_ERROR_ADDR_OUT_OF_RANGE) }, + { ERROR_BIT(SDMMC_OCR_ADDR_MISALIGNED), ERROR_BIT(SDMMC_ERROR_ADDR_MISALIGNED) }, + { ERROR_BIT(SDMMC_OCR_BLOCK_LEN_ERR), ERROR_BIT(SDMMC_ERROR_BLOCK_LEN_ERR) }, + { ERROR_BIT(SDMMC_OCR_ERASE_SEQ_ERR), ERROR_BIT(SDMMC_ERROR_ERASE_SEQ_ERR) }, + { ERROR_BIT(SDMMC_OCR_BAD_ERASE_PARAM), ERROR_BIT(SDMMC_ERROR_BAD_ERASE_PARAM) }, + { ERROR_BIT(SDMMC_OCR_WRITE_PROT_VIOLATION), ERROR_BIT(SDMMC_ERROR_WRITE_PROT_VIOLATION) }, + { ERROR_BIT(SDMMC_OCR_LOCK_UNLOCK_FAILED), ERROR_BIT(SDMMC_ERROR_LOCK_UNLOCK_FAILED) }, + { ERROR_BIT(SDMMC_OCR_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, + { ERROR_BIT(SDMMC_OCR_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, + { ERROR_BIT(SDMMC_OCR_CARD_ECC_FAILED), ERROR_BIT(SDMMC_ERROR_CARD_ECC_FAILED) }, + { ERROR_BIT(SDMMC_OCR_CC_ERROR), ERROR_BIT(SDMMC_ERROR_CC_ERR) }, + { ERROR_BIT(SDMMC_OCR_GENERAL_UNKNOWN_ERROR), ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR) }, + { ERROR_BIT(SDMMC_OCR_STREAM_READ_UNDERRUN), ERROR_BIT(SDMMC_ERROR_STREAM_READ_UNDERRUN) }, + { ERROR_BIT(SDMMC_OCR_STREAM_WRITE_OVERRUN), ERROR_BIT(SDMMC_ERROR_STREAM_WRITE_OVERRUN) }, + { ERROR_BIT(SDMMC_OCR_CID_CSD_OVERWRITE), ERROR_BIT(SDMMC_ERROR_CID_CSD_OVERWRITE) }, + { ERROR_BIT(SDMMC_OCR_WP_ERASE_SKIP), ERROR_BIT(SDMMC_ERROR_WP_ERASE_SKIP) }, + { ERROR_BIT(SDMMC_OCR_CARD_ECC_DISABLED), ERROR_BIT(SDMMC_ERROR_CARD_ECC_DISABLED) }, + { ERROR_BIT(SDMMC_OCR_ERASE_RESET), ERROR_BIT(SDMMC_ERROR_ERASE_RESET) }, + { ERROR_BIT(SDMMC_OCR_AKE_SEQ_ERROR), ERROR_BIT(SDMMC_ERROR_AKE_SEQ_ERR) }, + // clang-format on +}; + +static const struct SdioErrorDef SDIO_ERRORS_R4[] = { + // clang-format off + { ERROR_BIT(SDMMC_R4_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, + { ERROR_BIT(SDMMC_R4_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, + { ERROR_BIT(SDMMC_R4_INVALID_FUNCTION_NUM), ERROR_BIT(SDMMC_ERROR_REQUEST_NOT_APPLICABLE) }, + { ERROR_BIT(SDMMC_R4_INVALID_PARAMETER), ERROR_BIT(SDMMC_ERROR_INVALID_PARAMETER) }, + { ERROR_BIT(SDMMC_R4_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, + // clang-format on +}; + +static const struct SdioErrorDef SDIO_ERRORS_R5[] = { + // clang-format off + { ERROR_BIT(SDMMC_R5_ARG_OUT_OF_RANGE), ERROR_BIT(SDMMC_ERROR_ADDR_OUT_OF_RANGE) }, + { ERROR_BIT(SDMMC_R5_INVALID_FUNCTION_NUM), ERROR_BIT(SDMMC_ERROR_REQUEST_NOT_APPLICABLE) }, + { ERROR_BIT(SDMMC_R5_GENERAL_UNKNOWN_ERROR), ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR) }, + { ERROR_BIT(SDMMC_R5_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, + { ERROR_BIT(SDMMC_R5_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, + // clang-format on +}; + +static const struct SdioErrorDef SDIO_ERRORS_R6[] = { + // clang-format off + { ERROR_BIT(SDMMC_R6_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, + { ERROR_BIT(SDMMC_R6_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, + { ERROR_BIT(SDMMC_R6_GENERAL_UNKNOWN_ERROR), ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR) }, + // clang-format on +}; + +// Sends an SD/MMC command with a specified argument, waits for a response and +// appropriately handles the errors according to the response format parameter. +// TODO: A deadline parameter. +static u32 sdmmc_command(enum SdmmcCommand cmd, u32 arg, u32 response[4]) { + response[3] = response[2] = response[1] = response[0] = 0; + +#if SDMMC_LOG_COMMANDS + printf("CMD%" PRIu8 "(%08" PRIX32 ") ", (u8)(cmd & SDIO_CMD_CMDINDEX_Msk), arg); +#endif + + // This clears all kinds of completion flags: + WRITE_REG(SDIO->ICR, SDIO_CMD_INTERRUPT_FLAGS); + + // The command constants are defined in such a way that the fields CMDINDEX + // and WAITRESP of SDIO_CMD may simply be extracted out of the constant, this + // trick is explained in the header file. + u32 cmd_params = cmd & (SDIO_CMD_CMDINDEX_Msk | SDIO_CMD_WAITRESP_Msk); + // The argument register must be programmed before writing to SDIO_CMD. + WRITE_REG(SDIO->ARG, arg); + // This will start the CPSM and initiate the transmission of the command: + MODIFY_REG(SDIO->CMD, CMD_CLEAR_MASK, cmd_params | SDIO_CMD_CPSMEN); + + u32 completion_flags = SDIO_STA_CMDSENT; + if (likely(cmd & SDMMC_RESPONSE_IS_PRESENT)) { + // NOTE: All of these are mutually exclusive and need to be checked, only + // one of these flags will be set upon receiving the response. + completion_flags = SDIO_STA_CCRCFAIL | SDIO_STA_CMDREND | SDIO_STA_CTIMEOUT; + } + + Systime deadline = timeout_to_deadline(100); + u32 sdio_sta; + while (true) { + sdio_sta = READ_REG(SDIO->STA); + // Stop spinning if the command transfer is not active and any of the + // completion flags have been set. + if (!(sdio_sta & SDIO_STA_CMDACT) && (sdio_sta & completion_flags)) break; + if (unlikely(systime_now() >= deadline)) return SDMMC_ERROR_TIMEOUT; + task_yield(); + } + + if (unlikely(!(cmd & SDMMC_RESPONSE_IS_PRESENT))) { + return SDMMC_ERROR_NONE; + } + + if (unlikely(sdio_sta & SDIO_STA_CTIMEOUT)) { + return SDMMC_ERROR_CMD_RSP_TIMEOUT; + } else if (unlikely(sdio_sta & SDIO_STA_CCRCFAIL)) { + // The check is a workaround for errata ES0287 2.7.2: the SDIO peripheral + // calculates the CRC of the response even when the SD protocol doesn't + // specify a field in it for the CRC, which makes all commands with such + // response formats invariably fail with a CRC error. Since a CRC error + // can't happen with a response without a CRC by definition, this situation + // is treated as a success condition. + if (likely(cmd & SDMMC_RESPONSE_HAS_CRC)) { + return SDMMC_ERROR_CMD_CRC_FAIL; + } + } + + // Not every response format has a command index field. + if (likely(cmd & SDMMC_RESPONSE_HAS_CMD_INDEX)) { + u8 res_cmd = READ_REG(SDIO->RESPCMD) & SDIO_RESPCMD_RESPCMD_Msk; + if (unlikely(res_cmd != (cmd & SDIO_CMD_CMDINDEX_Msk))) { + return SDMMC_ERROR_CMD_CRC_FAIL; + } + } + + u32 res_r1 = READ_REG(SDIO->RESP1); + response[0] = res_r1; + if (unlikely(cmd & SDMMC_RESPONSE_IS_LONG)) { + response[1] = READ_REG(SDIO->RESP2); + response[2] = READ_REG(SDIO->RESP3); + response[3] = READ_REG(SDIO->RESP4); + } + u32 resp_fmt = (cmd & SDMMC_RESPONSE_FORMAT_Msk) >> SDMMC_RESPONSE_FORMAT_Pos; + +#if SDMMC_LOG_COMMANDS + printf("=> R%" PRIu32 "(", resp_fmt); + if (cmd & SDMMC_RESPONSE_IS_PRESENT) { + if (cmd & SDMMC_RESPONSE_IS_LONG) { + u32* r = response; + printf("%08" PRIX32 ", %08" PRIX32 ", %08" PRIX32 ", %08" PRIX32, r[0], r[1], r[2], r[3]); + } else { + printf("%08" PRIX32, response[0]); + } + } + printf(")\n"); +#endif + + // All responses are first checked against a mask to see if any error bit is + // set and whether the expensive error checking procedure should be entered. + if (likely(resp_fmt == 1)) { + if ((res_r1 & SDMMC_OCR_ERRORBITS) != 0) { + return sdio_check_response_error(res_r1, SDIO_ERRORS_R1, SIZEOF(SDIO_ERRORS_R1)); + } + } else if (unlikely(resp_fmt == 4)) { + if ((res_r1 & SDMMC_R4_ERRORBITS) != 0) { + return sdio_check_response_error(res_r1, SDIO_ERRORS_R4, SIZEOF(SDIO_ERRORS_R4)); + } + } else if (unlikely(resp_fmt == 5)) { + if ((res_r1 & SDMMC_R5_ERRORBITS) != 0) { + return sdio_check_response_error(res_r1, SDIO_ERRORS_R5, SIZEOF(SDIO_ERRORS_R5)); + } + } else if (unlikely(resp_fmt == 6)) { + if ((res_r1 & SDMMC_R6_ERRORBITS) != 0) { + return sdio_check_response_error(res_r1, SDIO_ERRORS_R6, SIZEOF(SDIO_ERRORS_R6)); + } + } + return SDMMC_ERROR_NONE; +} + +static void +prepare_sdio_for_transfer(enum SdioTransfer transfer, u8* buffer, u32 blocks, u32 block_size) { + ASSERT(is_power_of_two(block_size)); + block_size = __builtin_ctz(block_size); // Basically, a cheap log2() for ints + // The peripheral supports transferring blocks of size of at most 2^14 bytes. + ASSERT(block_size <= 14); + + usize length = blocks << block_size; // = blocks * 2^block_size + ASSERT(buffer != NULL); + ASSERT((usize)buffer % 4 == 0); // Check the alignment + ASSERT(length % 4 == 0); + ASSERT(length > 0); + // DMA can perform at most 65535 transfers in a single transaction, and we + // are transferring 4 bytes at a time, so check for that. + ASSERT(length / 4 <= UINT16_MAX); + + // Ensure that the stream is inactive. + ASSERT(!dma_is_stream_enabled(SDIO_DMA)); + // Clear any leftover pending interrupt flags (a requirement for restarting the stream). + dma_clear_interrupt_flags(SDIO_DMA, DMA_ALL_INTERRUPT_FLAGS); + // Clear the SDIO interrupt flags. + WRITE_REG(SDIO->ICR, SDIO_DATA_INTERRUPT_FLAGS); + + const struct DmaConfig config = { + .channel = 4, + .direction = transfer == SDIO_RX ? DMA_PERIPH_TO_MEMORY_DIR : DMA_MEMORY_TO_PERIPH_DIR, + .mode = DMA_PERIPHERAL_FLOW_CONTROL, + .periph_addr_increment = false, + .memory_addr_increment = true, + .periph_data_size = DMA_WORD_DATA, + .memory_data_size = DMA_WORD_DATA, + .priority = DMA_MEDIUM_PRIORITY, + .enable_fifo = true, + .fifo_threshold = DMA_FULL_FIFO_THRESHOLD, + .periph_burst = DMA_BURST_INCR4, + .memory_burst = DMA_BURST_INCR4, + .double_buffer_mode = false, + }; + dma_configure_stream(SDIO_DMA, &config); + + // Disable all DMA interrupts. + CLEAR_BIT(SDIO_DMA->CR, DMA_SxCR_TCIE | DMA_SxCR_TEIE | DMA_SxCR_DMEIE); + CLEAR_BIT(SDIO_DMA->FCR, DMA_SxFCR_FEIE); + + // The length parameter here is apparently ignored - the termination of the + // transfer is managed by the peripheral connected to the flow controller. + dma_configure_transfer(SDIO_DMA, (usize)&SDIO->FIFO, (usize)buffer, length / 4); + dma_enable_stream(SDIO_DMA); + + WRITE_REG(SDIO->DTIMER, UINT32_MAX); // TODO: Calculate read/write timeouts. + WRITE_REG(SDIO->DLEN, length); + + u32 dctrl = READ_REG(SDIO->DCTRL); + CLEAR_BIT(dctrl, DCTRL_CLEAR_MASK); + // Transfer direction: 0 - controller to card, 1 - card to controller. + MODIFY_REG(dctrl, SDIO_DCTRL_DTDIR, (transfer == SDIO_RX ? 1 : 0) << SDIO_DCTRL_DTDIR_Pos); + CLEAR_BIT(dctrl, SDIO_DCTRL_DTMODE); // Block transfer mode + MODIFY_REG(dctrl, SDIO_DCTRL_DBLOCKSIZE, block_size << SDIO_DCTRL_DBLOCKSIZE_Pos); + SET_BIT(dctrl, SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTEN); + WRITE_REG(SDIO->DCTRL, dctrl); +} + +static void stop_sdio_transfer(void) { + dma_disable_stream(SDIO_DMA); + // Confirm that the stream has been completely halted (encouraged by the + // reference manual). + while (dma_is_stream_enabled(SDIO_DMA)) { + task_yield(); + } + CLEAR_BIT(SDIO->DCTRL, SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTEN); +} + +static u32 wait_for_sdio_transfer(Systime deadline) { + while (true) { + SET_BIT(SDIO->MASK, SDIO_DATA_INTERRUPT_FLAGS); + // TODO: Race condition here. What if the interrupt fires before the task goes to sleep? + task_wait(&sdio_notification, deadline); + + u32 sdio_sta = READ_REG(SDIO->STA); + u32 dma_isr = dma_get_interrupt_flags(SDIO_DMA); + u32 error = 0; + if (unlikely(sdio_sta & SDIO_DATA_ERROR_FLAGS)) { + if (sdio_sta & SDIO_STA_DCRCFAIL) error |= SDMMC_ERROR_DATA_CRC_FAIL; + if (sdio_sta & SDIO_STA_DTIMEOUT) error |= SDMMC_ERROR_DATA_TIMEOUT; + if (sdio_sta & SDIO_STA_TXUNDERR) error |= SDMMC_ERROR_TX_UNDERRUN; + if (sdio_sta & SDIO_STA_RXOVERR) error |= SDMMC_ERROR_RX_OVERRUN; + if (sdio_sta & SDIO_STA_STBITERR) error |= SDMMC_ERROR_DATA_TIMEOUT; + } + // NOTE: The FEIF (FIFO error) flag is purposefully ignored here. For some + // reason it is always generated when the flow controller is on (this is + // not documented in the errata). + if (unlikely(dma_isr & (DMA_FLAG_TEIF | DMA_FLAG_DMEIF))) { + error |= SDMMC_ERROR_DMA; + } + if (unlikely(error != 0)) { + return error; + } + + // As far as I understand: + // 1. the DBCKEND flag is set after every single 512 byte block is transferred, + // 2. and the DATAEND flag is set at the very end of the whole transfer. + if (likely(sdio_sta & SDIO_STA_DATAEND)) { + // Confirm that everything has been written to memory by observing the + // transfer-complete flag on the DMA stream. + while (!(dma_isr & DMA_FLAG_TCIF)) { + task_yield(); + dma_isr = dma_get_interrupt_flags(SDIO_DMA); + } + return SDMMC_ERROR_NONE; + } + if (unlikely(systime_now() >= deadline)) { + return SDMMC_ERROR_TIMEOUT; + } + } +} + +void SDIO_IRQHandler(void) { + // A brilliant trick for simplifying the interrupt logic is used here: + // instead of clearing the interrupt flags and backing them up in some + // variable for checking in the normal driver code later, we simply disable + // the interrupt! It was taken from + // + CLEAR_BIT(SDIO->MASK, SDIO_DATA_INTERRUPT_FLAGS | SDIO_CMD_INTERRUPT_FLAGS); + if (task_notify(&sdio_notification)) { + task_yield_from_isr(); + } +} + +__NO_RETURN void crash_on_sd_error(u32 code, const char* file, u32 line) { + static const char* const ERROR_NAMES[] = { + [ERROR_BIT(SDMMC_ERROR_CMD_CRC_FAIL)] = "CMD_CRC_FAIL", + [ERROR_BIT(SDMMC_ERROR_DATA_CRC_FAIL)] = "DATA_CRC_FAIL", + [ERROR_BIT(SDMMC_ERROR_CMD_RSP_TIMEOUT)] = "CMD_RSP_TIMEOUT", + [ERROR_BIT(SDMMC_ERROR_DATA_TIMEOUT)] = "DATA_TIMEOUT", + [ERROR_BIT(SDMMC_ERROR_TX_UNDERRUN)] = "TX_UNDERRUN", + [ERROR_BIT(SDMMC_ERROR_RX_OVERRUN)] = "RX_OVERRUN", + [ERROR_BIT(SDMMC_ERROR_ADDR_MISALIGNED)] = "ADDR_MISALIGNED", + [ERROR_BIT(SDMMC_ERROR_BLOCK_LEN_ERR)] = "BLOCK_LEN_ERR", + [ERROR_BIT(SDMMC_ERROR_ERASE_SEQ_ERR)] = "ERASE_SEQ_ERR", + [ERROR_BIT(SDMMC_ERROR_BAD_ERASE_PARAM)] = "BAD_ERASE_PARAM", + [ERROR_BIT(SDMMC_ERROR_WRITE_PROT_VIOLATION)] = "WRITE_PROT_VIOLATION", + [ERROR_BIT(SDMMC_ERROR_LOCK_UNLOCK_FAILED)] = "LOCK_UNLOCK_FAILED", + [ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED)] = "COM_CRC_FAILED", + [ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD)] = "ILLEGAL_CMD", + [ERROR_BIT(SDMMC_ERROR_CARD_ECC_FAILED)] = "CARD_ECC_FAILED", + [ERROR_BIT(SDMMC_ERROR_CC_ERR)] = "CC_ERR", + [ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR)] = "GENERAL_UNKNOWN_ERR", + [ERROR_BIT(SDMMC_ERROR_STREAM_READ_UNDERRUN)] = "STREAM_READ_UNDERRUN", + [ERROR_BIT(SDMMC_ERROR_STREAM_WRITE_OVERRUN)] = "STREAM_WRITE_OVERRUN", + [ERROR_BIT(SDMMC_ERROR_CID_CSD_OVERWRITE)] = "CID_CSD_OVERWRITE", + [ERROR_BIT(SDMMC_ERROR_WP_ERASE_SKIP)] = "WP_ERASE_SKIP", + [ERROR_BIT(SDMMC_ERROR_CARD_ECC_DISABLED)] = "CARD_ECC_DISABLED", + [ERROR_BIT(SDMMC_ERROR_ERASE_RESET)] = "ERASE_RESET", + [ERROR_BIT(SDMMC_ERROR_AKE_SEQ_ERR)] = "AKE_SEQ_ERR", + [ERROR_BIT(SDMMC_ERROR_INVALID_VOLTRANGE)] = "INVALID_VOLTRANGE", + [ERROR_BIT(SDMMC_ERROR_ADDR_OUT_OF_RANGE)] = "ADDR_OUT_OF_RANGE", + [ERROR_BIT(SDMMC_ERROR_REQUEST_NOT_APPLICABLE)] = "REQUEST_NOT_APPLICABLE", + [ERROR_BIT(SDMMC_ERROR_INVALID_PARAMETER)] = "INVALID_PARAMETER", + [ERROR_BIT(SDMMC_ERROR_UNSUPPORTED_FEATURE)] = "UNSUPPORTED_FEATURE", + [ERROR_BIT(SDMMC_ERROR_BUSY)] = "BUSY", + [ERROR_BIT(SDMMC_ERROR_DMA)] = "DMA", + [ERROR_BIT(SDMMC_ERROR_TIMEOUT)] = "TIMEOUT", + }; + + char msg[64]; + usize msg_pos = 0; + i32 msg_written = snprintf(msg, sizeof(msg), "0x%08" PRIX32, code); + msg_pos += MAX(0, msg_written); + + if (code == 0) { + code = SDMMC_ERROR_GENERAL_UNKNOWN_ERR; + } + while (code != 0) { + u32 bit = 31 - __CLZ(code); + code ^= BIT(bit); + + const char* name = bit < SIZEOF(ERROR_NAMES) ? ERROR_NAMES[bit] : "UNKNOWN"; + msg_written = snprintf(msg + msg_pos, sizeof(msg) - msg_pos, "/SD_%s", name); + msg_pos += MAX(0, msg_written); + } + + crash(msg, file, line); +} diff --git a/src/stmes/drivers/sdmmc.h b/src/stmes/drivers/sdmmc.h new file mode 100644 index 0000000..65677f8 --- /dev/null +++ b/src/stmes/drivers/sdmmc.h @@ -0,0 +1,542 @@ +#pragma once + +#include "stmes/kernel/crash.h" +#include "stmes/kernel/time.h" +#include "stmes/utils.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDMMC_BLOCK_SIZE 512u + +// Some constants that aren't defined in the HAL headers: +// clang-format off +#define SDMMC_R4_ILLEGAL_CMD 0x00000004u +#define SDMMC_R4_COM_CRC_FAILED 0x00000008u +#define SDMMC_R4_INVALID_FUNCTION_NUM 0x00000010u +#define SDMMC_R4_INVALID_PARAMETER 0x00000040u +#define SDMMC_R4_ERRORBITS 0x0000005Cu + +#define SDMMC_R5_ARG_OUT_OF_RANGE 0x00000001u +#define SDMMC_R5_INVALID_FUNCTION_NUM 0x00000002u +#define SDMMC_R5_GENERAL_UNKNOWN_ERROR 0x00000008u +#define SDMMC_R5_ILLEGAL_CMD 0x00000040u +#define SDMMC_R5_COM_CRC_FAILED 0x00000080u +#define SDMMC_R5_ERRORBITS 0x000000CBu + +#define SDMMC_R6_ERRORBITS 0x0000E000u +// clang-format on + +// The bit layout of the command definitions is as follows: +// +// 1. [5:0] - the command index, 0-63 +// 2. [6] - set if the command returns a response +// 3. [7] - 1 if the response format is long, 0 if it is short +// 4. [8] - the response has a CRC field +// 5. [9] - the response has a command index field +// 6. [12:10] - the response format, 0-7 +// +// Notice how the first 8 bits align with the bit definitions of the SDIO_CMD +// register, which are: +// +// 1. [5:0] - CMDINDEX (is exactly the same) +// 2. [7:6] - WAITRESP, with the following variants: +// 1. 00 - no response +// 2. 01 - short response +// 3. 10 - no response +// 4. 11 - long response +// This field may be re-interpreted as a combination of two flags, which can +// be checked separately. +// +#define SDMMC_RESPONSE_HAS_CRC BIT(8) +#define SDMMC_RESPONSE_HAS_CMD_INDEX BIT(9) +#define SDMMC_RESPONSE_FORMAT_Pos 10 +#define SDMMC_RESPONSE_FORMAT_Msk (MASK(3) << SDMMC_RESPONSE_FORMAT_Pos) +#define SDMMC_RESPONSE_FORMAT(n) ((n) << SDMMC_RESPONSE_FORMAT_Pos) +#define SDMMC_SHORT_RESPONSE_FMT(n) (SDMMC_RESPONSE_FORMAT(n) | (1u << SDIO_CMD_WAITRESP_Pos)) +#define SDMMC_LONG_RESPONSE_FMT(n) (SDMMC_RESPONSE_FORMAT(n) | (3u << SDIO_CMD_WAITRESP_Pos)) +#define SDMMC_NO_RESPONSE_FMT(n) (SDMMC_RESPONSE_FORMAT(n) | (0u << SDIO_CMD_WAITRESP_Pos)) +#define SDMMC_RESPONSE_IS_PRESENT SDIO_CMD_WAITRESP_0 +#define SDMMC_RESPONSE_IS_LONG SDIO_CMD_WAITRESP_1 + +enum SdmmcResponse { + SD_CMD_NO_RESP = SDMMC_NO_RESPONSE_FMT(0), + SD_CMD_R1 = SDMMC_SHORT_RESPONSE_FMT(1) | SDMMC_RESPONSE_HAS_CRC | SDMMC_RESPONSE_HAS_CMD_INDEX, + SD_CMD_R1B = SD_CMD_R1, + SD_CMD_R2 = SDMMC_LONG_RESPONSE_FMT(2) | SDMMC_RESPONSE_HAS_CRC, + SD_CMD_R3 = SDMMC_SHORT_RESPONSE_FMT(3), + SD_CMD_R4 = SDMMC_SHORT_RESPONSE_FMT(4), + SD_CMD_R5 = SDMMC_SHORT_RESPONSE_FMT(5) | SDMMC_RESPONSE_HAS_CRC | SDMMC_RESPONSE_HAS_CMD_INDEX, + SD_CMD_R6 = SDMMC_SHORT_RESPONSE_FMT(6) | SDMMC_RESPONSE_HAS_CRC | SDMMC_RESPONSE_HAS_CMD_INDEX, + SD_CMD_R7 = SDMMC_SHORT_RESPONSE_FMT(7) | SDMMC_RESPONSE_HAS_CRC | SDMMC_RESPONSE_HAS_CMD_INDEX, +}; + +// The command definitions were written by combining information from: +// +// +// +// +// +// +// +// +// Command names are prefixed with `SDMMC` if they are supported by both SD and +// MMC cards, with just `SD` or `MMC` if they are implemented by only SD or MMC +// cards (respectively), and with `SDIO` if they are implemented by SDIO cards. +enum SdmmcCommand { + SDMMC_CMD0_GO_IDLE_STATE = 0 | SD_CMD_NO_RESP, + MMC_CMD1_SEND_OP_COND = 1 | SD_CMD_R3, + SDMMC_CMD2_ALL_SEND_CID = 2 | SD_CMD_R2, + SD_CMD3_SEND_RELATIVE_ADDR = 3 | SD_CMD_R6, + MMC_CMD3_SET_RELATIVE_ADDR = 3 | SD_CMD_R1, + SDMMC_CMD4_SET_DSR = 4 | SD_CMD_NO_RESP, + MMC_CMD5_SLEEP_AWAKE = 5 | SD_CMD_R1B, + SDIO_CMD5_IO_SEND_OP_COND = 5 | SD_CMD_R4, + SD_CMD6_SWITCH_FUNC = 6 | SD_CMD_R1, + SD_ACMD6_SET_BUS_WIDTH = 6 | SD_CMD_R1, + MMC_CMD6_SWITCH = 6 | SD_CMD_R1B, + SDMMC_CMD7_SELECT_CARD = 7 | SD_CMD_R1B, + SDMMC_CMD7_DESELECT_CARD = 7 | SD_CMD_NO_RESP, + SD_CMD8_SEND_IF_COND = 8 | SD_CMD_R7, + MMC_CMD8_SEND_EXT_CSD = 8 | SD_CMD_R1, + SDMMC_CMD9_SEND_CSD = 9 | SD_CMD_R2, + SDMMC_CMD10_SEND_CID = 10 | SD_CMD_R2, + SD_CMD11_VOLTAGE_SWITCH = 11 | SD_CMD_R1, + MMC_CMD11_READ_DAT_UNTIL_STOP = 11 | SD_CMD_R1, + SDMMC_CMD12_STOP_TRANSMISSION = 12 | SD_CMD_R1B, + SDMMC_CMD13_SEND_STATUS = 13 | SD_CMD_R1, + SD_ACMD13_SD_STATUS = 13 | SD_CMD_R1, + MMC_CMD14_BUSTEST_R = 14 | SD_CMD_R1, + SDMMC_CMD15_GO_INACTIVE_STATE = 15 | SD_CMD_NO_RESP, + SDMMC_CMD16_SET_BLOCKLEN = 16 | SD_CMD_R1, + SDMMC_CMD17_READ_SINGLE_BLOCK = 17 | SD_CMD_R1, + SDMMC_CMD18_READ_MULTIPLE_BLOCK = 18 | SD_CMD_R1, + SD_CMD19_SEND_TUNING_BLOCK = 19 | SD_CMD_R1, + MMC_CMD19_BUSTEST_W = 19 | SD_CMD_R1, + SD_CMD20_SPEED_CLASS_CONTROL = 20 | SD_CMD_R1B, + MMC_CMD20_WRITE_DAT_UNTIL_STOP = 20 | SD_CMD_R1, + SD_CMD22_ADDRESS_EXTENSION = 22 | SD_CMD_R1, + SD_ACMD22_SEND_NUM_WR_BLOCKS = 22 | SD_CMD_R1, + SDMMC_CMD23_SET_BLOCK_COUNT = 23 | SD_CMD_R1, + SD_ACMD23_SET_WR_BLK_ERASE_COUNT = 23 | SD_CMD_R1, + SDMMC_CMD24_WRITE_BLOCK = 24 | SD_CMD_R1, + SDMMC_CMD25_WRITE_MULTIPLE_BLOCK = 25 | SD_CMD_R1, + SDMMC_CMD26_PROGRAM_CID = 26 | SD_CMD_R1, + SDMMC_CMD27_PROGRAM_CSD = 27 | SD_CMD_R1, + SDMMC_CMD28_SET_WRITE_PROT = 28 | SD_CMD_R1B, + SDMMC_CMD29_CLR_WRITE_PROT = 29 | SD_CMD_R1B, + SDMMC_CMD30_SEND_WRITE_PROT = 30 | SD_CMD_R1, + SD_CMD32_ERASE_WR_BLK_START = 32 | SD_CMD_R1, + MMC_CMD32_TAG_SECTOR_START = 32 | SD_CMD_R1, + SD_CMD33_ERASE_WR_BLK_END = 33 | SD_CMD_R1, + MMC_CMD33_TAG_SECTOR_END = 33 | SD_CMD_R1, + MMC_CMD34_UNTAG_SECTOR = 34 | SD_CMD_R1, + MMC_CMD35_TAG_ERASE_GROUP_START = 35 | SD_CMD_R1, + MMC_CMD36_TAG_ERASE_GROUP_END = 36 | SD_CMD_R1, + MMC_CMD37_UNTAG_ERASE_GROUP = 37 | SD_CMD_R1, + SDMMC_CMD38_ERASE = 38 | SD_CMD_R1B, + SD_CMD39_SELECT_CARD_PARTITION = 39 | SD_CMD_R1B, + MMC_CMD39_FAST_IO = 39 | SD_CMD_R4, + MMC_CMD40_GO_IRQ_STATE = 40 | SD_CMD_R5, + SD_ACMD41_SD_SEND_OP_COND = 41 | SD_CMD_R3, + SD_ACMD42_SET_CLR_CARD_DETECT = 42 | SD_CMD_R1, + SD_CMD42_LOCK_UNLOCK = 42 | SD_CMD_R1, + MMC_CMD42_LOCK_UNLOCK = 42 | SD_CMD_R1B, + SD_CMD48_READ_EXTR_SINGLE = 48 | SD_CMD_R1, + SD_CMD49_WRITE_EXTR_SINGLE = 49 | SD_CMD_R1, + SD_ACMD51_SEND_SCR = 51 | SD_CMD_R1, + SDIO_CMD52_IO_RW_DIRECT = 52 | SD_CMD_R5, + SDIO_CMD53_IO_RW_EXTENDED = 53 | SD_CMD_R5, + SDMMC_CMD55_APP_CMD = 55 | SD_CMD_R1, + SDMMC_CMD56_GEN_CMD = 56 | SD_CMD_R1, + SD_CMD58_READ_EXTR_MULTI = 58 | SD_CMD_R1, + SD_CMD59_WRITE_EXTR_MULTI = 59 | SD_CMD_R1, +}; + +// +// + +// Card Identification register. +union SdmmcCID { + u32 words[4]; + struct __packed SdCID { + u32 : 1; + u32 crc7_checksum : 7; + u32 manufacturing_month : 4; + u32 manufacturing_year : 8; + u32 : 4; + u32 serial_number : 32; + u32 product_revision : 8; + char product_name[5]; + char oem_application_id[2]; + u32 manufacturer_id : 8; + } sd; +}; + +// Card-Specific Data register. +union SdmmcCSD { + u32 words[4]; + + struct __packed SdmmcCSDBits { + u32 : 1; + u32 crc7 : 7; + u32 : 1; + + bool write_protect_until_power_cycle : 1; // not in MMC + + enum { + SD_CSD_HARD_DISK_FS_FORMAT = 0, + SD_CSD_DOS_FAT_FORMAT = 1, + SD_CSD_UNIVERSAL_FILE_FORMAT = 2, + SD_CSD_UNKNOWN_FILE_FORMAT = 3, + } file_format : 2; + + bool temporary_write_protect : 1; + bool permanent_write_protect : 1; + bool contents_were_copied : 1; + + u32 file_format_group : 1; + + u32 : 5; + + bool allows_partial_block_write : 1; + u32 max_write_block_length : 4; + + u32 r2w_factor : 3; + + u32 : 2; + + bool supports_write_protect_groups : 1; + u32 write_protect_group_size : 7; // 5 in MMC + + u32 eraseable_sector_size : 7; // 5 in MMC + bool supports_single_block_erase : 1; // not in MMC + + u32 : 29; + + bool implements_dsr : 1; + + bool allows_misaligned_block_read : 1; + bool allows_misaligned_block_write : 1; + bool allows_partial_block_read : 1; + u32 max_read_block_length : 4; + + bool supports_basic_commands : 1; + bool supports_cmd_queue_commands : 1; + bool supports_block_read_commands : 1; + bool : 1; + bool supports_block_write_commands : 1; + bool supports_erase_commands : 1; + bool supports_write_protect_commands : 1; + bool supports_card_lock_commands : 1; + bool supports_application_commands : 1; + bool supports_io_mode_commands : 1; + bool supports_switch_commands : 1; + bool : 1; + + u32 transfer_speed_unit : 3; + u32 transfer_speed_factor : 4; + u32 : 1; + + u32 nsac : 8; + u32 taac : 8; + + u32 : 6; + + enum { + SD_CSD_VERSION_1_0 = 0, + SD_CSD_VERSION_2_0 = 1, + SD_CSD_VERSION_3_0 = 2, + } structure_version : 2; + } bits; + + struct __packed MmcCSD { + u32 : 8; + u32 ecc : 2; + u32 : 19; + u32 default_ecc : 2; + u32 : 1; + u32 wp_grp_size : 5; + u32 erase_grp_size : 5; + u32 sector_size : 5; + u32 c_size_mult : 3; + u32 vdd_w_curr_max : 3; + u32 vdd_w_curr_min : 3; + u32 vdd_r_curr_max : 3; + u32 vdd_r_curr_min : 3; + u32 c_size : 12; + u32 : 16, : 32; + u32 spec_vers : 4; + u32 : 2; + } mmc; + + struct __packed SdCSDv1 { + u32 : 32, : 15; + u32 card_size_multiplier : 3; + u32 max_write_current_at_max_voltage : 3; + u32 max_write_current_at_min_voltage : 3; + u32 max_read_current_at_max_voltage : 3; + u32 max_read_current_at_min_voltage : 3; + u32 card_size : 12; + u32 : 22, : 32; + } sd_v1; + + struct __packed SdCSDv2 { + u32 : 32, : 16; + u32 card_size : 22; + u32 : 26, : 32; + } sd_v2; + + struct __packed SdCSDv3 { + u32 : 32, : 16; + u32 card_size : 28; + u32 : 20, : 32; + } sd_v3; +}; + +// SD Configuration Register. +union SdmmcSCR { + u32 words[2]; + struct __packed SdSCRv1 { + u32 manufacturer_reserved : 32; + + bool supports_speed_class_control_cmd : 1; + bool supports_set_block_count_cmd : 1; + bool supports_ext_register_single_block_cmds : 1; + bool supports_ext_register_multi_block_cmds : 1; + bool supports_secure_receive_send_cmds : 1; + u32 : 1; + + u32 sd_specx : 4; + u32 sd_spec4 : 1; + + // These are defined in a spec I can't get my hands on, and not that I care + // about DRM functionality anyway. + u32 extended_security_support_bits : 4; + + u32 sd_spec3 : 1; + + bool supports_1bit_wide_bus : 1; + bool : 1; + bool supports_4bit_wide_bus : 1; + bool : 1; + + enum { + SD_NO_SECURITY_SPEC_SUPPORT = 0, + SD_SECURITY_SPEC_NOT_USED = 1, + SD_SECURITY_SPEC_V1 = 2, + SD_SECURITY_SPEC_V2 = 3, + SD_SECURITY_SPEC_V3 = 4, + } security_spec_version : 3; + + // Determines the data stored on the card after an erase operation: 0 if + // the erased blocks will be zeroed out, 1 if they will be filled with ones. + u32 erased_data_state : 1; + + u32 sd_spec : 4; + + enum { + SD_SCR_VERSION_1_0 = 0, + } structure_version : 4; + } v1; +}; + +// SD Status Register. +union SdmmcSSR { + u32 words[16]; + struct __packed SdSSRBits { + u8 manufacturer_reserved[39]; + u32 : 32; + u32 : 32; + u32 : 16; + u32 uhs_au_size : 4; + enum { + SD_UHS_SPEED_LESS_THAN_10MB = 0, + SD_UHS_SPEED_MORE_THAN_10MB = 1, + } uhs_speed_grade : 4; + u32 erase_offset : 2; + u32 erase_timeout : 6; + u32 erase_size : 16; + u32 : 4; + u32 au_size : 4; + u32 performance_move : 8; + enum { + SD_SPEED_CLASS_0 = 0, + SD_SPEED_CLASS_2 = 1, + SD_SPEED_CLASS_4 = 2, + SD_SPEED_CLASS_6 = 3, + SD_SPEED_CLASS_10 = 4, + } speed_class : 8; + u32 size_of_protected_area : 32; + u32 sd_card_type : 16; + u32 : 6; + u32 : 7; + bool secured_mode : 1; + enum { + SD_SSR_BUS_WIDTH_1BIT = 0, + SD_SSR_BUS_WIDTH_4BIT = 2, + } data_bus_width : 2; + } bits; +}; + +enum __packed SdSpecVersion { + SD_SPEC_UNKNOWN_VERSION = 0, + SD_SPEC_V1_0, + SD_SPEC_V1_1, + SD_SPEC_V2_0, + SD_SPEC_V3_X, + SD_SPEC_V4_X, + SD_SPEC_V5_X, + SD_SPEC_V6_X, + SD_SPEC_V7_X, + SD_SPEC_V8_X, + SD_SPEC_V9_X, +}; + +enum __packed SdmmcCardState { + SDMMC_STATE_IDLE = 0, + SDMMC_STATE_READY = 1, + SDMMC_STATE_IDENTIFICATION = 2, + SDMMC_STATE_STANDBY = 3, + SDMMC_STATE_TRANSFER = 4, + SDMMC_STATE_SENDING = 5, + SDMMC_STATE_RECEIVING = 6, + SDMMC_STATE_PROGRAMMING = 7, + SDMMC_STATE_DISCONNECTED = 8, + SDMMC_STATE_ONLY_SDIO = 15, +}; + +// Card Status Register. +union SdmmcCSR { + u32 word; + struct SdmmcCSRBits { + bool : 1; // reserved for a manufacturer test mode + bool : 1; // reserved for a manufacturer test mode as well + bool : 1; // reserved for application-specific commands + bool authentication_sequence_error : 1; + bool : 1; // reserved for SDIO, appears unused + bool application_command : 1; + bool extension_function_event : 1; + bool mmc_switch_error : 1; + bool ready_for_data : 1; + enum SdmmcCardState current_state : 4; + bool erase_sequence_cancelled : 1; + bool internal_ecc_is_disabled : 1; + bool tried_erasing_write_protected : 1; + bool csd_overwrite_error : 1; + bool mmc_stream_write_overrun : 1; + bool mmc_stream_read_underrun : 1; + bool general_unknown_error : 1; + bool internal_controller_error : 1; + bool internal_ecc_failure : 1; + bool prev_command_was_illegal : 1; + bool prev_command_crc_check_failed : 1; + bool lock_unlock_failed : 1; + bool card_is_locked : 1; + bool write_protection_violation : 1; + bool invalid_erase_parameters : 1; + bool incorrect_erase_sequence : 1; + bool block_transfer_length_error : 1; + bool misaligned_block_address_error : 1; + bool out_of_range_block_error : 1; + } bits; +}; + +// Argument of CMD8. +union SdmmcIfCond { + u32 word; + struct SdmmcIfCondBits { + u32 check_pattern : 8; + enum { + SD_VOLTAGE_RANGE_2_7V_TO_3_6V = BIT(0), + SD_LOW_VOLTAGE_RANGE = BIT(1), + } host_voltage_supply : 4; + u32 : 20; + } bits; +}; + +// Operation Conditions Register. +union SdmmcOCR { + u32 word; + struct SdmmcOCRBits { + u32 : 15; + bool voltage_window_2_7v_to_2_8v : 1; + bool voltage_window_2_8v_to_2_9v : 1; + bool voltage_window_2_9v_to_3_0v : 1; + bool voltage_window_3_0v_to_3_1v : 1; + bool voltage_window_3_1v_to_3_2v : 1; + bool voltage_window_3_2v_to_3_3v : 1; + bool voltage_window_3_3v_to_3_4v : 1; + bool voltage_window_3_4v_to_3_5v : 1; + bool voltage_window_3_5v_to_3_6v : 1; + bool switching_to_1_8v_status : 1; + bool : 1; + bool : 1; + bool over_2tb_capacity_status : 1; + enum { + SDXC_POWER_SAVING = 0, + SDXC_MAXIMUM_PERFORMANCE = 1, + } sdxc_power_control : 1; + bool uhs2_status : 1; + bool high_capacity_status : 1; + bool power_up_status : 1; + } bits; +}; + +enum __packed SdmmcCardType { + SDMMC_UNKNOWN_CARD = 0, + SDSC_V1_X_CARD, + SDSC_V2_X_CARD, + SDHC_SDXC_CARD, + MMC_CARD, + SDIO_CARD, +}; + +struct SdmmcCard { + enum SdmmcCardType type; + enum SdSpecVersion spec_version; + u16 rca; + u32 clock_freq; + union SdmmcCID cid; + union SdmmcCSD csd; + union SdmmcSCR scr; +}; + +// Information about the physical and electrical capabilities of the host. +struct SdmmcHostCapabilities { + bool high_speed_mode; + bool use_4bit_data_bus; +}; + +void sdmmc_init_gpio(void); +bool sdmmc_is_card_inserted(void); + +const struct SdmmcCard* sdmmc_get_card(void); +u32 sdmmc_get_blocks_count(const struct SdmmcCard* card); +u32 sdmmc_get_eraseable_sector_size(const struct SdmmcCard* card); +u32 sdmmc_max_transfer_freq(const struct SdmmcCard* card); +enum SdSpecVersion sdmmc_sd_spec_version(const struct SdmmcCard* card); + +u32 sdmmc_init_card(const struct SdmmcHostCapabilities* host_caps); +u32 sdmmc_get_card_status(union SdmmcCSR* out_status); +u32 sdmmc_read(u8* buffer, u32 offset, u32 blocks, Systime deadline); +u32 sdmmc_write(const u8* buffer, u32 offset, u32 blocks, Systime deadline); +u32 sdmmc_erase(u32 start, u32 end, Systime deadline); + +__NO_RETURN void crash_on_sd_error(u32 code, const char* file, u32 line); + +#define check_sd_error(expr) \ + do { \ + u32 __code__ = (expr); \ + if (unlikely(__code__ != SDMMC_ERROR_NONE)) { \ + crash_collect_registers(); \ + crash_on_sd_error(__code__, __FILE__, __LINE__); \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif diff --git a/src/stmes/fatfs.c b/src/stmes/fatfs.c index 8f52159..87f394d 100644 --- a/src/stmes/fatfs.c +++ b/src/stmes/fatfs.c @@ -1,23 +1,20 @@ #include "stmes/fatfs.h" +#include "stmes/drivers/sdmmc.h" #include "stmes/kernel/crash.h" #include "stmes/kernel/sync.h" -#include "stmes/kernel/task.h" -#include "stmes/sdio.h" #include "stmes/utils.h" #include #include #include +#include #include #define SD_TIMEOUT 1000 -#define SD_DEFAULT_BLOCK_SIZE 512 -static u8 dma_scratch[BLOCKSIZE] __ALIGNED(4); +static u8 dma_scratch[SDMMC_BLOCK_SIZE] __ALIGNED(4); static volatile DSTATUS sd_status = STA_NOINIT; -static volatile bool sd_write_done = false, sd_read_done = false; -// static struct Notification sd_notify; void* ff_memalloc(UINT msize) { return ff_malloc(msize); @@ -28,6 +25,7 @@ void ff_memfree(void* mblock) { } int ff_cre_syncobj(BYTE volume, struct Mutex** mutex) { + ASSERT(volume < _VOLUMES); static struct Mutex ff_mutexes[_VOLUMES]; *mutex = &ff_mutexes[volume]; mutex_init(*mutex); @@ -48,35 +46,21 @@ void ff_rel_grant(struct Mutex* mutex) { mutex_unlock(mutex); } -static HAL_StatusTypeDef sd_wait_for_card_state(HAL_SD_CardStateTypedef state, u32 timeout) { - u32 start_time = HAL_GetTick(); +static HAL_StatusTypeDef sd_wait_for_card_state(enum SdmmcCardState state, Systime deadline) { do { - if (HAL_SD_GetCardState(&hsd) == state) { - return HAL_OK; - } - // task_yield(); - } while (HAL_GetTick() - start_time < timeout); - return HAL_TIMEOUT; -} - -static HAL_StatusTypeDef sd_wait_until_flag_set(volatile bool* flag, u32 timeout) { - // TODO: Using notifications instead of a busy loop is apparently slower. Investigate why. - // Instant deadline = systime_now() + timeout; - u32 start_time = HAL_GetTick(); - do { - // task_wait(&sd_notify, deadline); - if (*flag != false) { - return HAL_OK; - } - // task_yield(); - } while (HAL_GetTick() - start_time < timeout); - // } while (systime_now() < deadline); + union SdmmcCSR card_status; + if (sdmmc_get_card_status(&card_status) != SDMMC_ERROR_NONE) return HAL_ERROR; + if (card_status.bits.current_state == state) return HAL_OK; + task_yield(); + } while (systime_now() < deadline); return HAL_TIMEOUT; } static DSTATUS sd_check_status(void) { sd_status = STA_NOINIT; - if (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_TRANSFER) { + union SdmmcCSR card_status; + u32 err = sdmmc_get_card_status(&card_status); + if (err == SDMMC_ERROR_NONE && card_status.bits.current_state == SDMMC_STATE_TRANSFER) { sd_status &= ~STA_NOINIT; } return sd_status; @@ -85,7 +69,12 @@ static DSTATUS sd_check_status(void) { DSTATUS disk_initialize(BYTE pdrv) { UNUSED(pdrv); sd_status = STA_NOINIT; - if (BSP_SD_Init() == HAL_OK) { + sdmmc_init_gpio(); + const struct SdmmcHostCapabilities capabilities = { + .high_speed_mode = true, + .use_4bit_data_bus = true, + }; + if (sdmmc_init_card(&capabilities) == SDMMC_ERROR_NONE) { sd_status = sd_check_status(); } return sd_status; @@ -96,95 +85,78 @@ DSTATUS disk_status(BYTE pdrv) { return sd_check_status(); } -__STATIC_INLINE HAL_StatusTypeDef sd_read_aligned(u8* buf, u32 sector, u32 count) { - HAL_StatusTypeDef res; - sd_read_done = false; - if ((res = HAL_SD_ReadBlocks_DMA(&hsd, buf, sector, count)) != HAL_OK) return res; - if ((res = sd_wait_until_flag_set(&sd_read_done, SD_TIMEOUT)) != HAL_OK) return res; - sd_read_done = false; - return HAL_OK; -} - -DRESULT disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { +DRESULT disk_read(BYTE pdrv, BYTE* buffer, DWORD sector, UINT count) { UNUSED(pdrv); - if (sd_wait_for_card_state(HAL_SD_CARD_TRANSFER, SD_TIMEOUT) != HAL_OK) { - return RES_ERROR; - } + Systime deadline = timeout_to_deadline(SD_TIMEOUT); + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; // Take the fast path if the output buffer is 4-byte aligned - if ((usize)buff % 4 == 0) { - if (sd_read_aligned(buff, sector, count) != HAL_OK) return RES_ERROR; - if (sd_wait_for_card_state(HAL_SD_CARD_TRANSFER, SD_TIMEOUT) != HAL_OK) return RES_ERROR; + if ((usize)buffer % 4 == 0) { + if (sdmmc_read(buffer, sector, count, deadline) != HAL_OK) return RES_ERROR; + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; return RES_OK; } // Otherwise, fetch each sector to an aligned buffer and copy it to the destination for (UINT last = sector + count; sector < last; sector++) { - if (sd_read_aligned(dma_scratch, sector, 1) != HAL_OK) return RES_ERROR; - fast_memcpy_u8(buff, dma_scratch, BLOCKSIZE); - buff += BLOCKSIZE; + if (sdmmc_read(dma_scratch, sector, 1, deadline) != HAL_OK) return RES_ERROR; + fast_memcpy_u8(buffer, dma_scratch, SDMMC_BLOCK_SIZE); + buffer += SDMMC_BLOCK_SIZE; } + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; return RES_OK; } -__STATIC_INLINE HAL_StatusTypeDef sd_write_aligned(const u8* buf, u32 sector, u32 count) { - HAL_StatusTypeDef res; - sd_write_done = false; - if ((res = HAL_SD_WriteBlocks_DMA(&hsd, (u8*)buf, sector, count)) != HAL_OK) return res; - if ((res = sd_wait_until_flag_set(&sd_write_done, SD_TIMEOUT)) != HAL_OK) return res; - sd_write_done = false; - return HAL_OK; -} - -DRESULT disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { +DRESULT disk_write(BYTE pdrv, const BYTE* buffer, DWORD sector, UINT count) { UNUSED(pdrv); - if (sd_wait_for_card_state(HAL_SD_CARD_TRANSFER, SD_TIMEOUT) != HAL_OK) { - return RES_ERROR; - } + Systime deadline = timeout_to_deadline(SD_TIMEOUT); + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; // Take the fast path if the output buffer is 4-byte aligned - if ((usize)buff % 4 == 0) { - if (sd_write_aligned(buff, sector, count) != HAL_OK) return RES_ERROR; - if (sd_wait_for_card_state(HAL_SD_CARD_TRANSFER, SD_TIMEOUT) != HAL_OK) return RES_ERROR; + if ((usize)buffer % 4 == 0) { + if (sdmmc_write(buffer, sector, count, deadline) != HAL_OK) return RES_ERROR; + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; return RES_OK; } // Otherwise, copy each sector to an aligned buffer and write it for (UINT last = sector + count; sector < last; sector++) { - fast_memcpy_u8(dma_scratch, buff, BLOCKSIZE); - buff += BLOCKSIZE; - if (sd_write_aligned(dma_scratch, sector, 1) != HAL_OK) return RES_ERROR; + fast_memcpy_u8(dma_scratch, buffer, SDMMC_BLOCK_SIZE); + buffer += SDMMC_BLOCK_SIZE; + if (sdmmc_write(dma_scratch, sector, 1, deadline) != HAL_OK) return RES_ERROR; } + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; return RES_OK; } -DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff) { +DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* out) { UNUSED(pdrv); if (sd_status & STA_NOINIT) return RES_NOTRDY; switch (cmd) { case CTRL_SYNC: { - if (sd_wait_for_card_state(HAL_SD_CARD_TRANSFER, SD_TIMEOUT) != HAL_OK) { - return RES_ERROR; - } + Systime deadline = timeout_to_deadline(SD_TIMEOUT); + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; return RES_OK; } case GET_SECTOR_COUNT: { - *(DWORD*)buff = hsd.SdCard.LogBlockNbr; + *(DWORD*)out = sdmmc_get_blocks_count(sdmmc_get_card()); return RES_OK; } case GET_SECTOR_SIZE: { - *(WORD*)buff = hsd.SdCard.LogBlockSize; + *(WORD*)out = SDMMC_BLOCK_SIZE; return RES_OK; } case GET_BLOCK_SIZE: { - *(DWORD*)buff = hsd.SdCard.LogBlockSize / SD_DEFAULT_BLOCK_SIZE; + *(DWORD*)out = sdmmc_get_eraseable_sector_size(sdmmc_get_card()); return RES_OK; } case CTRL_TRIM: { - DWORD start = ((DWORD*)buff)[0], end = ((DWORD*)buff)[1]; - if (HAL_SD_Erase(&hsd, start, end) != HAL_OK) { - return RES_ERROR; - } + DWORD start = ((DWORD*)out)[0], end = ((DWORD*)out)[1]; + // TODO: Erasing may take a very long time, calculate the timeout. + Systime deadline = timeout_to_deadline(NO_DEADLINE); + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; + if (sdmmc_erase(start, end, deadline) != HAL_OK) return RES_ERROR; + if (sd_wait_for_card_state(SDMMC_STATE_TRANSFER, deadline) != HAL_OK) return RES_ERROR; return RES_OK; } default: { @@ -197,26 +169,6 @@ DWORD get_fattime(void) { return 0; } -void HAL_SD_TxCpltCallback(SD_HandleTypeDef* hsd) { - UNUSED(hsd); - sd_write_done = true; - // task_notify(&sd_notify); -} - -void HAL_SD_RxCpltCallback(SD_HandleTypeDef* hsd) { - UNUSED(hsd); - sd_read_done = true; - // task_notify(&sd_notify); -} - -void HAL_SD_AbortCallback(SD_HandleTypeDef* hsd) { - UNUSED(hsd); -} - -void HAL_SD_ErrorCallback(SD_HandleTypeDef* hsd) { - UNUSED(hsd); -} - __NO_RETURN void crash_on_fs_error(FRESULT code, const char* file, u32 line) { static const char* const ERROR_NAMES[] = { [FR_OK] = "OK", diff --git a/src/stmes/fatfs.h b/src/stmes/fatfs.h index 8c947a9..f59e53a 100644 --- a/src/stmes/fatfs.h +++ b/src/stmes/fatfs.h @@ -11,13 +11,13 @@ extern "C" { __NO_RETURN void crash_on_fs_error(FRESULT code, const char* file, u32 line); -#define check_fs_error(expr) \ - do { \ - FRESULT code = (expr); \ - if (unlikely(code != FR_OK)) { \ - crash_collect_registers(); \ - crash_on_fs_error(code, __FILE__, __LINE__); \ - } \ +#define check_fs_error(expr) \ + do { \ + FRESULT __code__ = (expr); \ + if (unlikely(__code__ != FR_OK)) { \ + crash_collect_registers(); \ + crash_on_fs_error(__code__, __FILE__, __LINE__); \ + } \ } while (0) #ifdef __cplusplus diff --git a/src/stmes/kernel/crash.c b/src/stmes/kernel/crash.c index b4e8031..a4c64fc 100644 --- a/src/stmes/kernel/crash.c +++ b/src/stmes/kernel/crash.c @@ -592,7 +592,8 @@ __NO_RETURN void enter_crash_screen(void) { // We need at least the VGA interrupts to output something to the screen. __enable_irq(); - console_clear_screen(); + // console_clear_screen(); + console_new_line(); console_set_color(0x97); console_clear_cursor_line(); console_set_color(0x17); diff --git a/src/stmes/kernel/crash.h b/src/stmes/kernel/crash.h index 4b2422c..031a767 100644 --- a/src/stmes/kernel/crash.h +++ b/src/stmes/kernel/crash.h @@ -17,13 +17,13 @@ __NO_RETURN void crash(const char* message, const char* src_file, u32 src_line); __NO_RETURN void crash_on_hal_error(HAL_StatusTypeDef code, const char* file, u32 line); -#define check_hal_error(expr) \ - do { \ - HAL_StatusTypeDef code = (expr); \ - if (unlikely(code != HAL_OK)) { \ - crash_collect_registers(); \ - crash_on_hal_error(code, __FILE__, __LINE__); \ - } \ +#define check_hal_error(expr) \ + do { \ + HAL_StatusTypeDef __code__ = (expr); \ + if (unlikely(__code__ != HAL_OK)) { \ + crash_collect_registers(); \ + crash_on_hal_error(__code__, __FILE__, __LINE__); \ + } \ } while (0) __NO_RETURN void enter_crash_screen(void); diff --git a/src/stmes/kernel/mpu.c b/src/stmes/kernel/mpu.c index 382e53a..a47bdba 100644 --- a/src/stmes/kernel/mpu.c +++ b/src/stmes/kernel/mpu.c @@ -61,8 +61,8 @@ __STATIC_FORCEINLINE void mpu_setup_region(const struct MpuRegionConfig* cfg) { ASSERT(cfg->number < MPU_MAX_REGIONS_NUMBER); usize size = cfg->end_addr - cfg->base_addr + 1; ASSERT(size >= MPU_MIN_REGION_SIZE); - ASSERT((size & (size - 1)) == 0); // Check that the size is a power of 2 - ASSERT((cfg->base_addr & (size - 1)) == 0); // Check the alignment of the base address + ASSERT(is_power_of_two(size)); + ASSERT(is_aligned(cfg->base_addr, size)); u32 size_log2 = 31 - __CLZ(size); u32 attrs = MPU_RASR_ENABLE_Msk; attrs |= (size_log2 - 1) << MPU_RASR_SIZE_Pos; diff --git a/src/stmes/kernel/task.c b/src/stmes/kernel/task.c index 97edbd4..5c72e42 100644 --- a/src/stmes/kernel/task.c +++ b/src/stmes/kernel/task.c @@ -1,8 +1,8 @@ -// The central component of my improvised nanokernel - a preemptive task -// scheduler with a round-robin scheduling policy. Implemented in a more or -// less conventional manner of writing RTOSes for Cortex-M CPUs, it utilizes -// the SVCall interrupt for making syscalls and the PendSV one for preemptively -// entering the scheduler. +// The heart of my improvised nanokernel - a preemptive task scheduler with a +// round-robin scheduling policy. Implemented in a more or less conventional +// manner of writing RTOSes for Cortex-M CPUs, it utilizes the SVCall interrupt +// for making syscalls and the PendSV one for preemptively entering the +// scheduler. // // Context switching is performed with the help of hardware: on exception entry // the CPU pushes a subset of registers onto the stack, which we complement by diff --git a/src/stmes/main.c b/src/stmes/main.c index e805be3..fac15f3 100644 --- a/src/stmes/main.c +++ b/src/stmes/main.c @@ -5,7 +5,6 @@ #include "stmes/kernel/mpu.h" #include "stmes/kernel/task.h" #include "stmes/kernel/time.h" -#include "stmes/sdio.h" #include "stmes/utils.h" #include "stmes/video/console.h" #include "stmes/video/vga.h" @@ -22,7 +21,6 @@ int main(void) { console_init(); gpio_init(); - MX_SDIO_SD_Init(); vga_init(); vga_apply_timings(&VGA_TIMINGS_640x480_57hz); @@ -32,9 +30,9 @@ int main(void) { // mandelbrot_demo(); // pong_demo(); // print_tasks_demo(); - // sd_card_benchmark(); + sd_card_benchmark(); // terminal_demo(); - text_rendering_benchmark(); + // text_rendering_benchmark(); // video_player_demo(); } diff --git a/src/stmes/sdio.c b/src/stmes/sdio.c deleted file mode 100644 index 4332b2b..0000000 --- a/src/stmes/sdio.c +++ /dev/null @@ -1,553 +0,0 @@ -// TODO: Implement our own SDIO driver, the default one is too bloated. -// -// -// -// -// -// -// -// -// -// -// -// -// - -#define HAL_PATCHES_IMPLEMENT_SDMMC - -#include "stmes/sdio.h" -#include "stmes/gpio.h" -#include "stmes/interrupts.h" -#include "stmes/kernel/crash.h" -#include "stmes/kernel/task.h" -#include "stmes/kernel/time.h" -#include -#include - -SD_HandleTypeDef hsd; -static DMA_HandleTypeDef hdma_sdio_rx, hdma_sdio_tx; - -void SDIO_IRQHandler(void) { - HAL_SD_IRQHandler(&hsd); -} - -void DMA2_Stream3_IRQHandler(void) { - HAL_DMA_IRQHandler(&hdma_sdio_rx); -} - -void DMA2_Stream6_IRQHandler(void) { - HAL_DMA_IRQHandler(&hdma_sdio_tx); -} - -void MX_SDIO_SD_Init(void) { - hsd.Instance = SDIO; - hsd.Init = (SDIO_InitTypeDef){ - // NOTE: SDIO_CLOCK_EDGE_FALLING shouldn't be used, see the errata. - .ClockEdge = SDIO_CLOCK_EDGE_RISING, - .ClockBypass = SDIO_CLOCK_BYPASS_DISABLE, - .ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE, - .BusWide = SDIO_BUS_WIDE_1B, - // NOTE: Enabling HW flow control may cause data corruption, see the errata. - .HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE, - .ClockDiv = SDIO_INIT_CLK_DIV, - }; -} - -void HAL_SD_MspInit(SD_HandleTypeDef* hsd) { - if (hsd->Instance == SDIO) { - __HAL_RCC_SDIO_CLK_ENABLE(); - __HAL_RCC_GPIOA_CLK_ENABLE(); - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_DMA2_CLK_ENABLE(); - - GPIO_InitTypeDef gpio_init = { - .Mode = GPIO_MODE_AF_PP, - .Pull = GPIO_NOPULL, - .Speed = GPIO_SPEED_FREQ_VERY_HIGH, - .Alternate = GPIO_AF12_SDIO, - }; - gpio_init.Pin = SDIO_CLK_PIN; - HAL_GPIO_Init(GPIOB, &gpio_init); - - gpio_init.Pull = GPIO_PULLUP; - gpio_init.Pin = SDIO_D1_PIN | SDIO_D2_PIN | SDIO_CMD_PIN; - HAL_GPIO_Init(GPIOA, &gpio_init); - gpio_init.Pin = SDIO_D0_PIN | SDIO_D3_PIN; - HAL_GPIO_Init(GPIOB, &gpio_init); - - hdma_sdio_rx.Instance = DMA2_Stream3; - hdma_sdio_rx.Init = (DMA_InitTypeDef){ - .Channel = DMA_CHANNEL_4, - .Direction = DMA_PERIPH_TO_MEMORY, - .PeriphInc = DMA_PINC_DISABLE, - .MemInc = DMA_MINC_ENABLE, - .PeriphDataAlignment = DMA_PDATAALIGN_WORD, - .MemDataAlignment = DMA_MDATAALIGN_WORD, - .Mode = DMA_PFCTRL, - .Priority = DMA_PRIORITY_MEDIUM, - .FIFOMode = DMA_FIFOMODE_ENABLE, - .FIFOThreshold = DMA_FIFO_THRESHOLD_FULL, - .MemBurst = DMA_MBURST_INC4, - .PeriphBurst = DMA_PBURST_INC4, - }; - check_hal_error(HAL_DMA_Init(&hdma_sdio_rx)); - __HAL_LINKDMA(hsd, hdmarx, hdma_sdio_rx); - - hdma_sdio_tx.Instance = DMA2_Stream6; - hdma_sdio_tx.Init = (DMA_InitTypeDef){ - .Channel = DMA_CHANNEL_4, - .Direction = DMA_MEMORY_TO_PERIPH, - .PeriphInc = DMA_PINC_DISABLE, - .MemInc = DMA_MINC_ENABLE, - .PeriphDataAlignment = DMA_PDATAALIGN_WORD, - .MemDataAlignment = DMA_MDATAALIGN_WORD, - .Mode = DMA_PFCTRL, - .Priority = DMA_PRIORITY_MEDIUM, - .FIFOMode = DMA_FIFOMODE_ENABLE, - .FIFOThreshold = DMA_FIFO_THRESHOLD_FULL, - .MemBurst = DMA_MBURST_INC4, - .PeriphBurst = DMA_PBURST_INC4, - }; - check_hal_error(HAL_DMA_Init(&hdma_sdio_tx)); - __HAL_LINKDMA(hsd, hdmatx, hdma_sdio_tx); - - HAL_NVIC_SetPriority(SDIO_IRQn, 4, 0); - HAL_NVIC_EnableIRQ(SDIO_IRQn); - HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 1, 0); - HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn); - HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 1, 0); - HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn); - } -} - -void HAL_SD_MspDeInit(SD_HandleTypeDef* hsd) { - if (hsd->Instance == SDIO) { - __HAL_RCC_SDIO_CLK_DISABLE(); - HAL_GPIO_DeInit(GPIOA, SDIO_CMD_PIN | SDIO_D1_PIN | SDIO_D2_PIN); - HAL_GPIO_DeInit(GPIOB, SDIO_CLK_PIN | SDIO_D0_PIN | SDIO_D3_PIN); - } -} - -HAL_StatusTypeDef BSP_SD_Init(void) { - if (!BSP_SD_IsDetected()) { - return HAL_ERROR; - } - - HAL_StatusTypeDef error = HAL_OK; - // - // - - hsd.Init.BusWide = SDIO_BUS_WIDE_1B; - hsd.Init.ClockDiv = SDIO_INIT_CLK_DIV; - hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; - if ((error = HAL_SD_Init(&hsd)) != HAL_OK) { - return error; - } - - hsd.Init.BusWide = SDIO_BUS_WIDE_4B; - hsd.Init.ClockDiv = SDIO_TRANSFER_CLK_DIV; - // My board isn't fast enough for this, unfortunately. - // hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_ENABLE; - if ((error = HAL_SD_ConfigWideBusOperation(&hsd, hsd.Init.BusWide)) != HAL_OK) { - return error; - } - - return HAL_OK; -} - -bool BSP_SD_IsDetected(void) { - return HAL_GPIO_ReadPin(SDIO_CD_GPIO_PORT, SDIO_CD_PIN) == GPIO_PIN_RESET; -} - -// Some constants that aren't defined in the HAL headers: - -// clang-format off -#define SDMMC_R4_ILLEGAL_CMD 0x00000004u -#define SDMMC_R4_COM_CRC_FAILED 0x00000008u -#define SDMMC_R4_INVALID_FUNCTION_NUM 0x00000010u -#define SDMMC_R4_INVALID_PARAMETER 0x00000040u -#define SDMMC_R4_ERRORBITS 0x0000005Cu - -#define SDMMC_R5_ARG_OUT_OF_RANGE 0x00000001u -#define SDMMC_R5_INVALID_FUNCTION_NUM 0x00000002u -#define SDMMC_R5_GENERAL_UNKNOWN_ERROR 0x00000008u -#define SDMMC_R5_ILLEGAL_CMD 0x00000040u -#define SDMMC_R5_COM_CRC_FAILED 0x00000080u -#define SDMMC_R5_ERRORBITS 0x000000CBu - -#define SDMMC_R6_ERRORBITS 0x0000E000u -// clang-format on - -// The error bits in the mapping tables are stored not as 32-bit masks (as they -// are defined in the HAL headers), but as bit positions, which gives a 4x -// reduction in size. -struct SdioErrorDef { - u8 response_bit, error_bit; -}; - -// Extracts the position of a set bit from a bit mask, otherwise (if the mask -// is not a power of 2, i.e. more than one bit is set) triggers a compile-time -// error. -#define ERROR_BIT(mask) \ - (__builtin_choose_expr((mask & (mask - 1)) == 0, __builtin_ctz(mask), (void)0)) - -static const struct SdioErrorDef SDIO_ERRORS_R1[] = { - // clang-format off - { ERROR_BIT(SDMMC_OCR_ADDR_OUT_OF_RANGE), ERROR_BIT(SDMMC_ERROR_ADDR_OUT_OF_RANGE) }, - { ERROR_BIT(SDMMC_OCR_ADDR_MISALIGNED), ERROR_BIT(SDMMC_ERROR_ADDR_MISALIGNED) }, - { ERROR_BIT(SDMMC_OCR_BLOCK_LEN_ERR), ERROR_BIT(SDMMC_ERROR_BLOCK_LEN_ERR) }, - { ERROR_BIT(SDMMC_OCR_ERASE_SEQ_ERR), ERROR_BIT(SDMMC_ERROR_ERASE_SEQ_ERR) }, - { ERROR_BIT(SDMMC_OCR_BAD_ERASE_PARAM), ERROR_BIT(SDMMC_ERROR_BAD_ERASE_PARAM) }, - { ERROR_BIT(SDMMC_OCR_WRITE_PROT_VIOLATION), ERROR_BIT(SDMMC_ERROR_WRITE_PROT_VIOLATION) }, - { ERROR_BIT(SDMMC_OCR_LOCK_UNLOCK_FAILED), ERROR_BIT(SDMMC_ERROR_LOCK_UNLOCK_FAILED) }, - { ERROR_BIT(SDMMC_OCR_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, - { ERROR_BIT(SDMMC_OCR_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, - { ERROR_BIT(SDMMC_OCR_CARD_ECC_FAILED), ERROR_BIT(SDMMC_ERROR_CARD_ECC_FAILED) }, - { ERROR_BIT(SDMMC_OCR_CC_ERROR), ERROR_BIT(SDMMC_ERROR_CC_ERR) }, - { ERROR_BIT(SDMMC_OCR_GENERAL_UNKNOWN_ERROR), ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR) }, - { ERROR_BIT(SDMMC_OCR_STREAM_READ_UNDERRUN), ERROR_BIT(SDMMC_ERROR_STREAM_READ_UNDERRUN) }, - { ERROR_BIT(SDMMC_OCR_STREAM_WRITE_OVERRUN), ERROR_BIT(SDMMC_ERROR_STREAM_WRITE_OVERRUN) }, - { ERROR_BIT(SDMMC_OCR_CID_CSD_OVERWRITE), ERROR_BIT(SDMMC_ERROR_CID_CSD_OVERWRITE) }, - { ERROR_BIT(SDMMC_OCR_WP_ERASE_SKIP), ERROR_BIT(SDMMC_ERROR_WP_ERASE_SKIP) }, - { ERROR_BIT(SDMMC_OCR_CARD_ECC_DISABLED), ERROR_BIT(SDMMC_ERROR_CARD_ECC_DISABLED) }, - { ERROR_BIT(SDMMC_OCR_ERASE_RESET), ERROR_BIT(SDMMC_ERROR_ERASE_RESET) }, - { ERROR_BIT(SDMMC_OCR_AKE_SEQ_ERROR), ERROR_BIT(SDMMC_ERROR_AKE_SEQ_ERR) }, - // clang-format on -}; - -static const struct SdioErrorDef SDIO_ERRORS_R4[] = { - // clang-format off - { ERROR_BIT(SDMMC_R4_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, - { ERROR_BIT(SDMMC_R4_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, - { ERROR_BIT(SDMMC_R4_INVALID_FUNCTION_NUM), ERROR_BIT(SDMMC_ERROR_REQUEST_NOT_APPLICABLE) }, - { ERROR_BIT(SDMMC_R4_INVALID_PARAMETER), ERROR_BIT(SDMMC_ERROR_INVALID_PARAMETER) }, - { ERROR_BIT(SDMMC_R4_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, - // clang-format on -}; - -static const struct SdioErrorDef SDIO_ERRORS_R5[] = { - // clang-format off - { ERROR_BIT(SDMMC_R5_ARG_OUT_OF_RANGE), ERROR_BIT(SDMMC_ERROR_ADDR_OUT_OF_RANGE) }, - { ERROR_BIT(SDMMC_R5_INVALID_FUNCTION_NUM), ERROR_BIT(SDMMC_ERROR_REQUEST_NOT_APPLICABLE) }, - { ERROR_BIT(SDMMC_R5_GENERAL_UNKNOWN_ERROR), ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR) }, - { ERROR_BIT(SDMMC_R5_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, - { ERROR_BIT(SDMMC_R5_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, - // clang-format on -}; - -static const struct SdioErrorDef SDIO_ERRORS_R6[] = { - // clang-format off - { ERROR_BIT(SDMMC_R6_ILLEGAL_CMD), ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD) }, - { ERROR_BIT(SDMMC_R6_COM_CRC_FAILED), ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED) }, - { ERROR_BIT(SDMMC_R6_GENERAL_UNKNOWN_ERROR), ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR) }, - // clang-format on -}; - -// The functions in the STM32 HAL use a large sequence of else-ifs to check for -// every error bit in the response and return an appropriate response code: -// . -// Needless to say, this generates a lot of machine code (a branch for every -// case, 32-bit `tst` and `mov` instructions) for code that is normally never -// even executed. I replace that with a generic (for all response types) -// function that uses a much more compact (in terms of flash usage) mapping -// table between the specific bits in the responses and the HAL error codes. -static u32 sdio_check_response_error( - u32 response, const struct SdioErrorDef* error_defs, usize error_defs_len -) { - u32 error_bits = SDMMC_ERROR_NONE; - for (usize i = 0; i < error_defs_len; i++) { - if (response & BIT(error_defs[i].response_bit)) { - error_bits |= error_defs[i].error_bit; - } - } - return error_bits; -} - -// Sends an SD/MMC command with a specified argument, waits for a response and -// appropriately handles the errors according to the response format parameter -// (the reply from the SD card can be read from the SDIO_RESP[1-4] registers -// afterwards). Combines the functionality of the SDIO_SendCommand and -// SDMMC_GetCmdResp[1-4] STM32 HAL functions. Note that the first two arguments -// of this function match the arguments of the other functions which will be -// invoking this one to reduce the number of `mov` instructions. -// -// This function is specialized for different response types using a poor-man's -// templates-in-C technique: the generic function is forcibly inlined into -// wrappers with certain select parameters, but otherwise isn't used directly. -// Specifically, we want a specialized variant only for R1 responses since -// those are the most common kind both in quantity and invocation frequency. -__STATIC_FORCEINLINE u32 -sdio_send_command_impl(SDIO_TypeDef* SDIOx, u32 arg, u8 cmd, enum SdioResponseFormat res) { - // The default implementation leaves it up to the user of SDIO_SendCommand to - // pass the response type, wait type and CPSM-enable (command path state - // machine) bits for the SDIO_CMD register in an options struct, even though - // they can simply be inferred from the response format. - u32 cmd_params = ((u32)cmd << SDIO_CMD_CMDINDEX_Pos) & SDIO_CMD_CMDINDEX_Msk; - cmd_params |= SDIO_WAIT_NO | SDIO_CPSM_ENABLE; - switch (res) { - case SDIO_RESPONSE_NONE: cmd_params |= SDIO_RESPONSE_NO; break; - case SDIO_RESPONSE_R2: cmd_params |= SDIO_RESPONSE_LONG; break; - default: cmd_params |= SDIO_RESPONSE_SHORT; break; - } - WRITE_REG(SDIOx->ARG, arg); - MODIFY_REG(SDIOx->CMD, CMD_CLEAR_MASK, cmd_params); - - // The timeouts used by the library here are something like 63 seconds for - // normal commands and 28 hours for the stop transfer command, not sure if - // such large values are practical. - u32 timeout = cmd == SDMMC_CMD_STOP_TRANSMISSION ? SDIO_STOPTRANSFERTIMEOUT : SDIO_CMDTIMEOUT; - // NOTE: All of these are mutually exclusive and need to be checked, only one - // of these flags will be set upon receiving the response. - u32 completion_flags = SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT; - if (unlikely(res == SDIO_RESPONSE_NONE)) completion_flags = SDIO_FLAG_CMDSENT; - - // The stop transfer command (CMD12) is sometimes sent inside interrupts, - // yielding in that context is wildly unsafe. - // bool should_yield = !in_interrupt_handler(); - u32 start_time = hwtimer_read(); - u32 sdio_sta; - while (true) { - sdio_sta = READ_REG(SDIOx->STA); - // Stop if the command transfer is not active and any of the completion - // flags have been set. - if (!(sdio_sta & SDIO_FLAG_CMDACT) && (sdio_sta & completion_flags)) break; - if (hwtimer_read() - start_time >= timeout) return SDMMC_ERROR_TIMEOUT; - // if (should_yield) task_yield(); - } - // This clears all kinds of completion flags: - __SDIO_CLEAR_FLAG(SDIOx, SDIO_STATIC_CMD_FLAGS); - - if (unlikely(res == SDIO_RESPONSE_NONE)) { - return SDMMC_ERROR_NONE; - } - - if (unlikely(sdio_sta & SDIO_FLAG_CTIMEOUT)) { - return SDMMC_ERROR_CMD_RSP_TIMEOUT; - } else if (unlikely(sdio_sta & SDIO_FLAG_CCRCFAIL)) { - // Workaround for errata 2.7.2: the SDIO peripheral calculates the CRC of - // the response even if it doesn't contain a CRC field, which makes all - // commands with such response formats invariably fail with a CRC error. - // Since this normally can't happen by definition, the CRC error is treated - // as a success condition for responses where the SD protocol doesn't - // specify a field for the CRC. - if (likely(res != SDIO_RESPONSE_R3 && res != SDIO_RESPONSE_R4)) { - return SDMMC_ERROR_CMD_CRC_FAIL; - } - } - - // Not every response format has a command index field. - if (likely(res != SDIO_RESPONSE_R2 && res != SDIO_RESPONSE_R3 && res != SDIO_RESPONSE_R4)) { - u8 res_cmd = (READ_REG(SDIOx->RESPCMD) & SDIO_RESPCMD_RESPCMD_Msk) >> SDIO_RESPCMD_RESPCMD_Pos; - if (unlikely(res_cmd != cmd)) { - return SDMMC_ERROR_CMD_CRC_FAIL; - } - } - - // All responses are first checked against a mask with every error bit set to - // determine if the expensive error checking procedure should be entered. - if (likely(res == SDIO_RESPONSE_R1)) { - u32 res_r1 = READ_REG(SDIOx->RESP1); - if ((res_r1 & SDMMC_OCR_ERRORBITS) != 0) { - return sdio_check_response_error(res_r1, SDIO_ERRORS_R1, SIZEOF(SDIO_ERRORS_R1)); - } - } else if (unlikely(res == SDIO_RESPONSE_R4)) { - u32 res_r1 = READ_REG(SDIOx->RESP1); - if ((res_r1 & SDMMC_R4_ERRORBITS) != 0) { - return sdio_check_response_error(res_r1, SDIO_ERRORS_R4, SIZEOF(SDIO_ERRORS_R4)); - } - } else if (unlikely(res == SDIO_RESPONSE_R5)) { - u32 res_r1 = READ_REG(SDIOx->RESP1); - if ((res_r1 & SDMMC_R5_ERRORBITS) != 0) { - return sdio_check_response_error(res_r1, SDIO_ERRORS_R5, SIZEOF(SDIO_ERRORS_R5)); - } - } else if (unlikely(res == SDIO_RESPONSE_R6)) { - u32 res_r1 = READ_REG(SDIOx->RESP1); - if ((res_r1 & SDMMC_R6_ERRORBITS) != 0) { - return sdio_check_response_error(res_r1, SDIO_ERRORS_R6, SIZEOF(SDIO_ERRORS_R6)); - } - } - - return SDMMC_ERROR_NONE; -} - -u32 sdio_send_command(SDIO_TypeDef* SDIOx, u32 arg, u8 cmd, enum SdioResponseFormat res) { - return sdio_send_command_impl(SDIOx, arg, cmd, res); -} - -u32 sdio_send_command_r1(SDIO_TypeDef* SDIOx, u32 arg, u8 cmd) { - return sdio_send_command_impl(SDIOx, arg, cmd, SDIO_RESPONSE_R1); -} - -// The following functions are patched versions of the STM32 HAL functions for -// sending particular SD/MMC commands, which are necessary for two reasons: -// 1. To yield while in the busy-wait loop, waiting for the response. -// 2. To reduce the flash usage by the compiled code. The default -// implementation bloats the binary a lot by duplicating a large block of -// setup code for every single command. -// - -u32 SDMMC_CmdGoIdleState(SDIO_TypeDef* SDIOx) { - return sdio_send_command(SDIOx, 0, SDMMC_CMD_GO_IDLE_STATE, SDIO_RESPONSE_NONE); -} - -u32 SDMMC_CmdOpCondition(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command(SDIOx, arg, SDMMC_CMD_SEND_OP_COND, SDIO_RESPONSE_R3); -} - -u32 SDMMC_CmdSendCID(SDIO_TypeDef* SDIOx) { - return sdio_send_command(SDIOx, 0, SDMMC_CMD_ALL_SEND_CID, SDIO_RESPONSE_R2); -} - -u32 SDMMC_CmdSetRelAdd(SDIO_TypeDef* SDIOx, u16* rca) { - u32 error = sdio_send_command(SDIOx, 0, SDMMC_CMD_SET_REL_ADDR, SDIO_RESPONSE_R6); - if (error == SDMMC_ERROR_NONE) { - *rca = READ_REG(SDIOx->RESP1) >> 16; - } - return error; -} - -u32 SDMMC_CmdSwitch(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_HS_SWITCH); -} - -u32 SDMMC_CmdSelDesel(SDIO_TypeDef* SDIOx, u64 arg) { - return sdio_send_command_r1(SDIOx, (u32)arg, SDMMC_CMD_SEL_DESEL_CARD); -} - -u32 SDMMC_CmdOperCond(SDIO_TypeDef* SDIOx) { - u32 arg = SDMMC_CHECK_PATTERN; - return sdio_send_command(SDIOx, arg, SDMMC_CMD_HS_SEND_EXT_CSD, SDIO_RESPONSE_R7); -} - -u32 SDMMC_CmdSendEXTCSD(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_HS_SEND_EXT_CSD); -} - -u32 SDMMC_CmdSendCSD(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command(SDIOx, arg, SDMMC_CMD_SEND_CSD, SDIO_RESPONSE_R2); -} - -u32 SDMMC_CmdStopTransfer(SDIO_TypeDef* SDIOx) { - return sdio_send_command_r1(SDIOx, 0, SDMMC_CMD_STOP_TRANSMISSION); -} - -u32 SDMMC_CmdSendStatus(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_SEND_STATUS); -} - -u32 SDMMC_CmdBlockLength(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_SET_BLOCKLEN); -} - -u32 SDMMC_CmdReadSingleBlock(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_READ_SINGLE_BLOCK); -} - -u32 SDMMC_CmdReadMultiBlock(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_READ_MULT_BLOCK); -} - -u32 SDMMC_CmdWriteSingleBlock(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_WRITE_SINGLE_BLOCK); -} - -u32 SDMMC_CmdWriteMultiBlock(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_WRITE_MULT_BLOCK); -} - -u32 SDMMC_CmdSDEraseStartAdd(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_SD_ERASE_GRP_START); -} - -u32 SDMMC_CmdSDEraseEndAdd(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_SD_ERASE_GRP_END); -} - -u32 SDMMC_CmdEraseStartAdd(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_ERASE_GRP_START); -} - -u32 SDMMC_CmdEraseEndAdd(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_ERASE_GRP_END); -} - -u32 SDMMC_CmdErase(SDIO_TypeDef* SDIOx) { - return sdio_send_command_r1(SDIOx, 0, SDMMC_CMD_ERASE); -} - -u32 SDMMC_CmdAppCommand(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_APP_CMD); -} - -u32 SDMMC_CmdBusWidth(SDIO_TypeDef* SDIOx, u32 arg) { - return sdio_send_command_r1(SDIOx, arg, SDMMC_CMD_APP_SD_SET_BUSWIDTH); -} - -u32 SDMMC_CmdStatusRegister(SDIO_TypeDef* SDIOx) { - return sdio_send_command_r1(SDIOx, 0, SDMMC_CMD_SD_APP_STATUS); -} - -u32 SDMMC_CmdAppOperCommand(SDIO_TypeDef* SDIOx, u32 arg) { - arg |= SDMMC_VOLTAGE_WINDOW_SD; - return sdio_send_command(SDIOx, arg, SDMMC_CMD_SD_APP_OP_COND, SDIO_RESPONSE_R3); -} - -u32 SDMMC_CmdSendSCR(SDIO_TypeDef* SDIOx) { - return sdio_send_command_r1(SDIOx, 0, SDMMC_CMD_SD_APP_SEND_SCR); -} - -__NO_RETURN void crash_on_sd_error(u32 code, const char* file, u32 line) { - static const char* const ERROR_NAMES[] = { - [ERROR_BIT(SDMMC_ERROR_CMD_CRC_FAIL)] = "CMD_CRC_FAIL", - [ERROR_BIT(SDMMC_ERROR_DATA_CRC_FAIL)] = "DATA_CRC_FAIL", - [ERROR_BIT(SDMMC_ERROR_CMD_RSP_TIMEOUT)] = "CMD_RSP_TIMEOUT", - [ERROR_BIT(SDMMC_ERROR_DATA_TIMEOUT)] = "DATA_TIMEOUT", - [ERROR_BIT(SDMMC_ERROR_TX_UNDERRUN)] = "TX_UNDERRUN", - [ERROR_BIT(SDMMC_ERROR_RX_OVERRUN)] = "RX_OVERRUN", - [ERROR_BIT(SDMMC_ERROR_ADDR_MISALIGNED)] = "ADDR_MISALIGNED", - [ERROR_BIT(SDMMC_ERROR_BLOCK_LEN_ERR)] = "BLOCK_LEN_ERR", - [ERROR_BIT(SDMMC_ERROR_ERASE_SEQ_ERR)] = "ERASE_SEQ_ERR", - [ERROR_BIT(SDMMC_ERROR_BAD_ERASE_PARAM)] = "BAD_ERASE_PARAM", - [ERROR_BIT(SDMMC_ERROR_WRITE_PROT_VIOLATION)] = "WRITE_PROT_VIOLATION", - [ERROR_BIT(SDMMC_ERROR_LOCK_UNLOCK_FAILED)] = "LOCK_UNLOCK_FAILED", - [ERROR_BIT(SDMMC_ERROR_COM_CRC_FAILED)] = "COM_CRC_FAILED", - [ERROR_BIT(SDMMC_ERROR_ILLEGAL_CMD)] = "ILLEGAL_CMD", - [ERROR_BIT(SDMMC_ERROR_CARD_ECC_FAILED)] = "CARD_ECC_FAILED", - [ERROR_BIT(SDMMC_ERROR_CC_ERR)] = "CC_ERR", - [ERROR_BIT(SDMMC_ERROR_GENERAL_UNKNOWN_ERR)] = "GENERAL_UNKNOWN_ERR", - [ERROR_BIT(SDMMC_ERROR_STREAM_READ_UNDERRUN)] = "STREAM_READ_UNDERRUN", - [ERROR_BIT(SDMMC_ERROR_STREAM_WRITE_OVERRUN)] = "STREAM_WRITE_OVERRUN", - [ERROR_BIT(SDMMC_ERROR_CID_CSD_OVERWRITE)] = "CID_CSD_OVERWRITE", - [ERROR_BIT(SDMMC_ERROR_WP_ERASE_SKIP)] = "WP_ERASE_SKIP", - [ERROR_BIT(SDMMC_ERROR_CARD_ECC_DISABLED)] = "CARD_ECC_DISABLED", - [ERROR_BIT(SDMMC_ERROR_ERASE_RESET)] = "ERASE_RESET", - [ERROR_BIT(SDMMC_ERROR_AKE_SEQ_ERR)] = "AKE_SEQ_ERR", - [ERROR_BIT(SDMMC_ERROR_INVALID_VOLTRANGE)] = "INVALID_VOLTRANGE", - [ERROR_BIT(SDMMC_ERROR_ADDR_OUT_OF_RANGE)] = "ADDR_OUT_OF_RANGE", - [ERROR_BIT(SDMMC_ERROR_REQUEST_NOT_APPLICABLE)] = "REQUEST_NOT_APPLICABLE", - [ERROR_BIT(SDMMC_ERROR_INVALID_PARAMETER)] = "INVALID_PARAMETER", - [ERROR_BIT(SDMMC_ERROR_UNSUPPORTED_FEATURE)] = "UNSUPPORTED_FEATURE", - [ERROR_BIT(SDMMC_ERROR_BUSY)] = "BUSY", - [ERROR_BIT(SDMMC_ERROR_DMA)] = "DMA", - [ERROR_BIT(SDMMC_ERROR_TIMEOUT)] = "TIMEOUT", - }; - - char msg[64]; - usize msg_pos = 0; - i32 msg_written = snprintf(msg, sizeof(msg), "0x%08" PRIX32, code); - msg_pos += MAX(0, msg_written); - - if (code == 0) { - code = SDMMC_ERROR_GENERAL_UNKNOWN_ERR; - } - while (code != 0) { - u32 bit = 31 - __CLZ(code); - code ^= BIT(bit); - - const char* name = bit < SIZEOF(ERROR_NAMES) ? ERROR_NAMES[bit] : "UNKNOWN"; - msg_written = snprintf(msg + msg_pos, sizeof(msg) - msg_pos, "/SD_%s", name); - msg_pos += MAX(0, msg_written); - } - - crash(msg, file, line); -} diff --git a/src/stmes/sdio.h b/src/stmes/sdio.h deleted file mode 100644 index 1011e0b..0000000 --- a/src/stmes/sdio.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "stmes/kernel/crash.h" -#include "stmes/utils.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -extern SD_HandleTypeDef hsd; - -void MX_SDIO_SD_Init(void); - -HAL_StatusTypeDef BSP_SD_Init(void); -bool BSP_SD_IsDetected(void); - -enum SdioResponseFormat { - SDIO_RESPONSE_NONE, - SDIO_RESPONSE_R1, - SDIO_RESPONSE_R2, - SDIO_RESPONSE_R3, - SDIO_RESPONSE_R4, - SDIO_RESPONSE_R5, - SDIO_RESPONSE_R6, - SDIO_RESPONSE_R7, -}; - -u32 sdio_send_command(SDIO_TypeDef* SDIOx, u32 arg, u8 cmd, enum SdioResponseFormat res); -u32 sdio_send_command_r1(SDIO_TypeDef* SDIOx, u32 arg, u8 cmd); - -__NO_RETURN void crash_on_sd_error(u32 code, const char* file, u32 line); - -#define check_sd_error(hsd, expr) \ - do { \ - HAL_StatusTypeDef code = (expr); \ - if (unlikely(code != HAL_OK)) { \ - crash_collect_registers(); \ - crash_on_sd_error((hsd)->ErrorCode, __FILE__, __LINE__); \ - } \ - } while (0) - -#ifdef __cplusplus -} -#endif diff --git a/src/stmes/utils.h b/src/stmes/utils.h index f20efb5..c93b512 100644 --- a/src/stmes/utils.h +++ b/src/stmes/utils.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -28,12 +29,16 @@ extern "C" { #define BITS8(a,b,c,d,e,f,g,h) ((a)<<7 | (b)<<6 | (c)<<5 | (d)<<4 | (e)<<3 | (f)<<2 | (g)<<1 | (h)<<0) // clang-format on +// Poor man's loop unroller macros for fixed repeat counts that just copy-paste +// the given code. Since the provided statement may contain commas, a variadic +// macro parameter must be used to capture it because otherwise the +// preprocessor will split the statement into multiple "arguments". // clang-format off -#define UNROLL_2(x) {x;x;} -#define UNROLL_4(x) {x;x;x;x;} -#define UNROLL_8(x) {x;x;x;x;x;x;x;x;} -#define UNROLL_10(x) {x;x;x;x;x;x;x;x;x;x;} -#define UNROLL_16(x) {x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;x;} +#define UNROLL_2(...) {__VA_ARGS__;__VA_ARGS__;} +#define UNROLL_4(...) {__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;} +#define UNROLL_8(...) {__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;} +#define UNROLL_10(...) {__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;} +#define UNROLL_16(...) {__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;__VA_ARGS__;} // clang-format on #ifdef __GNUC__ @@ -152,6 +157,22 @@ void fast_memcpy_u32(u32* dst, const u32* src, usize n); i32 humanize_units(char* buf, usize buf_size, i64 value); i32 humanize_bytes(char* buf, usize buf_size, i64 bytes); +#define u32_swap_bytes(x) __builtin_bswap32(x) +#if BYTE_ORDER == LITTLE_ENDIAN +#define u32_from_le(x) ((u32)(x)) +#define u32_from_be(x) __builtin_bswap32(x) +#else +#define u32_from_le(x) __builtin_bswap32(x) +#define u32_from_be(x) ((u32)(x)) +#endif + +#define is_power_of_two(x) (((x) & ((x)-1)) == 0) +#define is_aligned(x, align) (((x) & ((align)-1)) == 0) +#define align_to(x, align) ((x) & ~((align)-1)) +#define test_bit(x, bit) (((x) & (bit)) != 0) +#define test_any_bit(x, bit) (((x) & (bit)) != 0) +#define test_all_bits(x, bit) (((x) & (bit)) == bit) + #ifdef __cplusplus } #endif diff --git a/src/stmes/video/console.c b/src/stmes/video/console.c index df26a78..1dba9a1 100644 --- a/src/stmes/video/console.c +++ b/src/stmes/video/console.c @@ -79,20 +79,19 @@ void console_new_line(void) { self->top_line = (self->top_line + 1) % CONSOLE_LINES; console_clear_line(self->cursor_line); } + self->cursor_col = 0; } void console_putchar(char c) { struct ConsoleBuffer* self = &console_buffer; if (c == '\n') { console_new_line(); - self->cursor_col = 0; } else if (c == '\r') { self->cursor_col = 0; } else { console_set_char(self->cursor_line, self->cursor_col, c); self->cursor_col += 1; if (self->cursor_col >= CONSOLE_COLUMNS) { - self->cursor_col = 0; console_new_line(); } }