Skip to content

SD - SD card protocol stack

The SD component is the software above the SD host controller driver (SDHC): the SD protocol stack that initializes the card, the disk interface that reads and writes the card by sector, and the fs_* interface that puts a FAT filesystem on the card. All three layers can be used on their own.

This code ships with Zephyr upstream, and the F101 fork does not change it. What the F101 side provides is the SDHC host controller driver (drivers/sdhc/sdhc_sunxi.c), and the disk child node named SD under smhc0 in the board devicetree. This page describes how to use this stack on yuzukineko, and every interface comes with its output on the board.

Source locations (all under ~/f101-zephyr/):

  • SD protocol stack: zephyr/subsys/sd/ (sd.c, sdmmc.c, sd_ops.c, sdio.c, mmc.c), headers zephyr/include/zephyr/sd/{sd.h,sdmmc.h,sd_spec.h,sdio.h,mmc.h}.
  • Disk: zephyr/subsys/disk/disk_access.c is the disk interface, zephyr/drivers/disk/sdmmc_subsys.c attaches the SD protocol stack as a disk named SD; headers zephyr/include/zephyr/storage/disk_access.h, zephyr/include/zephyr/drivers/disk.h.
  • Filesystem: zephyr/subsys/fs/fs.c is the virtual filesystem layer, zephyr/subsys/fs/fat_fs.c is the FAT adaptation layer, modules/fs/fatfs/ is ELM FatFs; header zephyr/include/zephyr/fs/fs.h.
  • Host controller: zephyr/drivers/sdhc/sdhc_sunxi.c.

A few prerequisites on yuzukineko: the SD disk has only SDMMC_STACK enabled, not the MMC (eMMC) stack; the SDIO stack is used by the WiFi/Bluetooth chip, see AIC8800 WIFI. The bus is 4 bit, and the 1.8 V UHS mode and SPI mode are not supported. The filesystem is FAT12/16/32, plus exFAT when CONFIG_FS_FATFS_EXFAT is enabled, with long file name support. The disk name SD corresponds to the FatFs mount point /SD:.

flowchart TB
    APP["Application<br/>fs_open / fs_read / fs_write ...<br/>disk_access_read / disk_access_write ...<br/>sd_init / sdmmc_read_blocks ..."]
    APP --> VFS["fs.c virtual filesystem<br/>mount table, file and directory objects"]
    VFS --> FATA["fat_fs.c<br/>Zephyr to FatFs adaptation"]
    FATA --> FF["ELM FatFs<br/>ff.c: FAT table, directories, long file names"]
    FF --> DISKIF["diskio.c<br/>the FatFs disk functions"]
    DISKIF --> DA["disk_access.c<br/>look up by disk name, reference counting"]
    APP --> DA
    DA --> DRV["sdmmc_subsys.c<br/>disk driver named SD<br/>zephyr,sdmmc-disk"]
    DRV --> SDM["sdmmc.c / sd_ops.c<br/>sdmmc_read_blocks / write_blocks / ioctl"]
    APP --> SDC["sd.c<br/>sd_init / sd_is_card_present"]
    DRV --> SDC
    SDC --> SDM
    SDM --> HOST["sdhc.h<br/>sdhc_request / sdhc_set_io / sdhc_card_present"]
    HOST --> SUNXI["sdhc_sunxi.c<br/>smhc0: DMA, bounce buffer"]
    SUNXI --> CARD["TF card<br/>4 bit bus, PF0..PF5, card detect PF6"]

The arrows in the diagram above are the direction of function calls, and each of the three layers handles one thing:

LayerWhat it handlesEntry points
SD protocol stackPower-up, card identification, bus width and clock negotiation, block read and write, card ioctl. Internally a struct sd_card records everything about this cardsd_init, sd_is_card_present, sdmmc_read_blocks, sdmmc_write_blocks, sdmmc_ioctl
Disk interfaceGives each storage device a name and unifies it into “initialize, status, read and write by sector, ioctl”. The same name can be initialized by several users, with reference counting internallydisk_access_init, disk_access_status, disk_access_read, disk_access_write, disk_access_ioctl
FilesystemPuts FAT on the disk, and provides paths, files and directoriesfs_mount, fs_open, fs_read, fs_write, fs_readdir, and so on

An application can use any one of the layers: if it only wants files, use fs_*; to manage partitions itself and do raw read and write, use disk_access_*; to hold a struct sd_card and decide itself when to initialize (for example, not through the disk node in the devicetree), use sd_init directly. fs_* goes down to disk_access_*, and disk_access_* goes down to sdmmc_*; the three layers can be used at the same time, but each records the card state on its own: disk_access_* uses the struct sd_card inside the disk driver, while one the application initializes itself with sd_init is another, and neither knows about the other.

flowchart TB
    A["sd_init(sdhc, &card)"] --> B["sdhc_get_host_props<br/>read the controller capabilities"]
    B --> C["sd_init_io<br/>power off 500 ms, power on 500 ms<br/>1 bit bus, 400 kHz"]
    C --> D["CMD0 + CMD8<br/>voltage check, retry SD_RETRY_COUNT times on failure"]
    D --> E["ACMD41<br/>probe, then initialize with the voltage window and the HCS bit<br/>read the OCR, learn the SDHC"]
    E --> F["CMD2 reads the CID<br/>CMD3 asks for the RCA<br/>CMD9 reads the CSD, computes the block count"]
    F --> G["CMD7 selects the card<br/>clock raised to 25 MHz"]
    G --> H["ACMD51 reads the SCR<br/>CMD6 checks the functions"]
    H --> I["sdmmc_init_hs<br/>ACMD6 switches to a 4 bit bus<br/>clock raised to the highest the host can give, 50 MHz"]
    I --> J["card.status = CARD_INITIALIZED"]

The initialization uses the same commands demonstrated under “manual CMD” on the SDHC page; the difference is that here the protocol stack does it automatically, and handles the various retries and the SDSC/SDHC differences. On yuzukineko the whole sd_init takes about 1016 ms, of which 1000 ms is the two 500 ms power-on delays (power-up delay, from the controller’s power_delay); the actual command interaction is only a dozen milliseconds.

flowchart LR
    subgraph File
        F1["fs_read / fs_write"] --> F2["FatFs<br/>cluster chain, FAT table<br/>512 byte window cache"]
        F2 --> F3["disk_access_read / write<br/>sector number + sector count"]
    end
    subgraph Disk
        F3 --> D1["sdmmc_read_blocks / write_blocks"]
        R1["application directly<br/>disk_access_read / write"] --> D1
    end
    D1 --> D2{"buffer aligned?<br/>CONFIG_SDHC_BUFFER_ALIGNMENT = 1"}
    D2 -->|"always satisfied, use it directly"| D3["card_read / card_write<br/>CMD17 / CMD18 / CMD24 / CMD25"]
    D3 --> D4["sdhc_request<br/>DMA descriptors"]
    D4 --> D5{"buffer cache-line aligned?"}
    D5 -->|yes| D6["the controller DMAs directly"]
    D5 -->|no| D7["the bounce buffer in the driver<br/>CPU copy"]
    D6 --> D8["SD card"]
    D7 --> D8
  • Alignment: the buffer alignment requirement of the SD protocol stack is determined by CONFIG_SDHC_BUFFER_ALIGNMENT. The sunxi driver does not change this value and keeps the default 1, so the SD protocol stack does not copy anything itself and hands any address to the controller driver; a buffer that is not cache-line (64 byte) aligned is copied by sdhc_sunxi.c through the bounce buffer. One DMA transfer by the controller driver moves at most SDHC_SUNXI_MAX_DESC (128) descriptors, each 8 KiB, 1 MiB in total; a request larger than that does not use DMA and the CPU reads and writes the FIFO instead. A buffer that is not 64 byte aligned goes through the 64 KiB bounce buffer (SDHC_SUNXI_BOUNCE_KB), and a request larger than it is likewise moved by the CPU. In the demonstration the aligned 1 MiB read uses DMA and takes 44 ms; the 1 MiB read at an offset of 4 bytes exceeds the bounce buffer, is moved by the CPU, and shows 199 ms in the disk_access_read output below.
  • Read and write failure: when a data transfer fails, the stack retries SD_DATA_RETRIES (default 3) times.
  • Concurrency: there is a mutex in struct sd_card, and both sdmmc_* and sdmmc_ioctl take it; when it cannot be taken, they return -EBUSY after SD_DATA_TIMEOUT.

The SD protocol stack has no device of its own; it hangs below the node of the SDHC host controller. The smhc0 node of yuzukineko is already written, and the application does not need to change it.

FilePurpose
dts/bindings/sd/zephyr,sdmmc-disk.yamlThe zephyr,sdmmc-disk binding: requires the disk node to have disk-name
dts/bindings/sd/sd-device.yamlThe common part of devices on the SD bus (on-bus: sd)
dts/bindings/sdhc/allwinner,sunxi-smhc.yamlThe binding of the host controller, see SDHC
boards/yuzukihd/yuzukineko/yuzukineko.dtsThe board devicetree: the pins, bus width and card detect of smhc0, and the sdmmc disk child node
&smhc0 {
/* Pins: PF0..PF5, multiplexing function 2, defined on the SDHC page */
pinctrl-0 = <&smhc0_pins>;
pinctrl-names = "default";
/* The TF card slot has 4 data lines wired */
bus-width = <4>;
/* Card detect: PF6, low when a card is present. This is what sd_is_card_present reads */
cd-gpios = <&gpiof 6 GPIO_ACTIVE_LOW>;
status = "okay";
/* Child node: a disk on the SD bus. The node name is arbitrary; compatible must be zephyr,sdmmc-disk */
sdmmc {
/* Matches DT_DRV_COMPAT zephyr_sdmmc_disk in drivers/disk/sdmmc_subsys.c */
compatible = "zephyr,sdmmc-disk";
/* Disk name: the first argument of disk_access_*, and also the FatFs disk name.
* The SD in the FatFs mount point "/SD:" must match it */
disk-name = "SD";
status = "okay";
};
};

The driver sdmmc_subsys.c does three things for each zephyr,sdmmc-disk node: it keeps its own struct sd_card (sdmmc_data.card), finds the parent node (the host controller) with DT_INST_PARENT, and registers the disk name with disk_access_register during system initialization (priority CONFIG_SD_INIT_PRIORITY, 90). Registration does not access the card; the first disk_access_init is what actually initializes the card.

description: |
Zephyr SDMMC disk.
A binding with this compatible present within an SD
host controller device node indicates that an SDMMC disk is attached to that
SD bus. This binding will enable that disk to be used with the disk driver
API and any subsystems that utilize it.
compatible: "zephyr,sdmmc-disk"
include: [sd-device.yaml]
properties:
disk-name:
type: string
required: true
description: |
Disk name.
PropertyRequiredMeaning
disk-nameYesDisk name. disk_access_* uses it to look up the disk; with FatFs it must also equal the string between / and : in the mount point
statusNoThe disk device is created only when okay

The Kconfig of all three parts, the SD protocol stack, the disk and FatFs, is upstream. The table below is in the order “which layer to enable, how each layer is tuned”, one option per row; the default values are the values actually in effect for the build on yuzukineko, and options that are not listed are not selected.

OptionDefaultMeaning
CONFIG_SDMMC_STACKy (selected by CONFIG_SDMMC_SUBSYS)SD memory card protocol support
CONFIG_MMC_STACKnMMC (eMMC) protocol support, not needed when only SD cards are attached
CONFIG_SDIO_STACKnSDIO card protocol support; selected automatically when the WiFi driver uses it
CONFIG_SD_STACKy (selected when any of the three above is enabled)Hidden option, enables the whole SD subsystem and selects SDHC
CONFIG_SD_LOG_LEVEL3 (INF), determined by CONFIG_SD_LOG_LEVEL_DEFAULTSD protocol stack log level, module name sd
CONFIG_SD_INIT_TIMEOUT1500Longest time sd_init waits for the card mutex (milliseconds)
CONFIG_SD_RETRY_COUNT10Number of retries when an initialization command (such as CMD8) fails
CONFIG_SD_OCR_RETRY_COUNT1000Number of retries when reading the OCR (ACMD41); a card may power up very slowly
CONFIG_SD_CMD_TIMEOUT200Command timeout (milliseconds)
CONFIG_SD_DATA_TIMEOUT10000Data transfer timeout, which is also the longest wait for taking the sd_card mutex (milliseconds)
CONFIG_SD_BUFFER_SIZE64Number of bytes of the internal buffer in struct sd_card; 512 when SDHC_BUFFER_ALIGNMENT is not 1, or when MMC or SDIO is enabled
CONFIG_SD_CMD_RETRIES0Number of retries after a single command fails
CONFIG_SD_DATA_RETRIES3Number of retries after a data transfer fails
CONFIG_SD_UHS_PROTOCOLnUltra high speed (1.8 V UHS) protocol. Requires the host driver to select SDHC_SUPPORTS_UHS, which the sunxi driver does not, so it is unavailable
CONFIG_MMC_RCA2Relative address of the MMC card; has no effect when MMC_STACK is off
CONFIG_SDHC_BUFFER_ALIGNMENT1Number of buffer alignment bytes required by the host driver; sunxi uses the default 1
OptionDefaultMeaning
CONFIG_DISK_ACCESSset to y in the application; FAT_FILESYSTEM_ELM also selects itThe disk interface disk_access_*
CONFIG_DISK_DRIVER_SDMMCy (when the devicetree has a zephyr,sdmmc-disk node)Builds sdmmc_subsys.c, attaching the SD protocol stack as a disk
CONFIG_SDMMC_SUBSYSyAccess the disk through the SD protocol stack, and selects SDMMC_STACK
CONFIG_SD_INIT_PRIORITY90System initialization priority of the disk driver, which must be later than the host controller (SDHC_INIT_PRIORITY, 85)
CONFIG_SDMMC_LOG_LEVEL3Disk driver log level, module name sdmmc
CONFIG_DISK_LOG_LEVEL3Disk interface log level, module name disk
OptionDefaultMeaning
CONFIG_FILE_SYSTEMset to y in the applicationThe virtual filesystem layer fs_*
CONFIG_FAT_FILESYSTEM_ELMset to y in the applicationELM FatFs, and selects DISK_ACCESS
CONFIG_FILE_SYSTEM_MAX_TYPES2Maximum number of filesystem types to register
CONFIG_FILE_SYSTEM_MAX_FILE_NAME-1Maximum file name length; a non-positive value means take the largest among the enabled filesystems
CONFIG_FILE_SYSTEM_INIT_PRIORITY99Filesystem initialization priority
CONFIG_FILE_SYSTEM_MKFSnProvides fs_mkfs, to format a storage device
CONFIG_FS_FATFS_READ_ONLYnRemoves all write code, the card is read-only
CONFIG_FS_FATFS_MKFSnFatFs with format code (selected when FILE_SYSTEM_MKFS or FS_FATFS_MOUNT_MKFS is enabled)
CONFIG_FS_FATFS_MOUNT_MKFSdefault y, set to n in the demonstrationAutomatically format when the mount fails (the card has no filesystem). It erases the whole card, so the application must turn it off; after turning it off the card must be formatted in advance, otherwise fs_mount returns an error
CONFIG_FS_FATFS_MAX_ROOT_ENTRIES512Number of root directory entries when formatting, present only when FS_FATFS_MKFS is enabled
CONFIG_FS_FATFS_EXFATn, set to y in the demonstrationexFAT support, which selects long file names; cards larger than 32 GB are often exFAT
CONFIG_FS_FATFS_NUM_FILES4Number of files open at the same time, each FIL preallocated at build time
CONFIG_FS_FATFS_NUM_DIRS4Number of directories open at the same time, each DIR preallocated at build time
CONFIG_FS_FATFS_HAS_RTCnFile timestamps. When off all files use a fixed date; when on the application must implement get_fattime
CONFIG_FS_FATFS_EXTRA_NATIVE_APInExposes f_getlabel, f_setlabel, f_expand, f_findfirst, f_findnext
CONFIG_FS_FATFS_LFNn, set to y in the demonstrationLong file names. When off only 8.3 short file names are supported
CONFIG_FS_FATFS_LFN_MODE_BSSy (the default when LFN is on)The long file name work area is in static memory and is not reentrant
CONFIG_FS_FATFS_LFN_MODE_STACKn, set to y in the demonstrationThe long file name work area is on the stack, and the main stack must be large enough; in the demonstration MAIN_STACK_SIZE is 8192
CONFIG_FS_FATFS_LFN_MODE_HEAPnThe long file name work area is on the heap, requiring HEAP_MEM_POOL_SIZE to be greater than 0
CONFIG_FS_FATFS_MAX_LFN255Maximum number of characters of a long file name
CONFIG_FS_FATFS_CODEPAGE437OEM code page for short file names
CONFIG_FS_FATFS_MAX_SS512Maximum supported sector size; at 512 all volumes are treated as 512
CONFIG_FS_FATFS_MIN_SS512Expected minimum sector size
CONFIG_FS_FATFS_WINDOW_ALIGNMENT1Number of alignment bytes of the window buffer in FATFS
CONFIG_FS_FATFS_REENTRANTnOne mutex per volume; enable it when several threads access the same volume at the same time
CONFIG_FS_FATFS_LBA64n64 bit sector numbers, depends on FS_FATFS_EXFAT
CONFIG_FS_FATFS_MULTI_PARTITIONnSeveral partitions on one physical disk. When off one disk is one volume, using the first partition on the card (or the whole disk is one volume)
CONFIG_FS_FATFS_CUSTOM_MOUNT_POINT_COUNT0Number of custom mount points; by default only built-in names such as /SD: exist
CONFIG_FS_FATFS_CUSTOM_MOUNT_POINTSnoneList of custom mount point names, present only when the count is greater than 0
CONFIG_FS_FATFS_FSTAB_AUTOMOUNTnMount automatically according to the fstab node in the devicetree
CONFIG_FS_LOG_LEVEL3Filesystem layer log level, module name fs. On errors it prints <err> fs: ..., see the logs below

When fs_mount fails, check the items below. They are all already written in sd_api, and a different one missing gives a different symptom.

LocationWhat it needsWhat happens if it is missing
Board devicetree &smhc0status = "okay", with pinctrl-0, bus-width = <4> and cd-gpios all writtenNo smhc0 device, and DEVICE_DT_GET(DT_NODELABEL(smhc0)) does not build
Board devicetree &smhc0 { sdmmc { ... }; }A child node with compatible = "zephyr,sdmmc-disk", disk-name = "SD" and status = "okay"No disk named SD, so disk_access_* returns -EINVAL and fs_mount fails
Mount pointmnt_point = "/SD:", with the SD before the colon matching disk-nameThe disk name does not match and fs_mount cannot find the disk
struct fs_mount_t.type = FS_FATFS, .fs_data pointing at a FATFS object that always exists (global or static, not on a stack that returns), .mnt_point as aboveA wrong type returns -ENOENT; a FATFS object on the stack is destroyed after the function returns
prj.confCONFIG_FILE_SYSTEM=y, CONFIG_FAT_FILESYSTEM_ELM=y (which selects DISK_ACCESS); CONFIG_DISK_DRIVER_SDMMC is automatically y when the devicetree has a disk nodeWithout the filesystem options fs_* does not link; without the FAT driver fs_mount returns -ENOENT (the FAT type is not registered)
Long file names in prj.confCONFIG_FS_FATFS_LFN=y is needed when the card has long file names or Chinese names; cards larger than 32 GB are often exFAT and need CONFIG_FS_FATFS_EXFAT=yLong file names cannot be read, and an exFAT card fails to mount (-ENODEV, no recognizable filesystem on the card)
Stack in prj.confCONFIG_MAIN_STACK_SIZE=8192; when long file names are on the stack (FS_FATFS_LFN_MODE_STACK) the fs_* call depth is fairly deepMain thread stack overflow, showing up as a crash or garbage when mounting or listing a directory
CardThe card is already formatted as FAT/exFAT, and the first partition in the partition table (or the whole disk) is the filesystemfs_mount returns -ENODEV; without FS_MOUNT_FLAG_NO_FORMAT and with FS_FATFS_MOUNT_MKFS=y (the default), a failed mount formats the card
PinsPF0..PF5 are shared with JTAG, and card detect is PF6When this group of pins is taken by JTAG, the card slot does not read the card or the card does not respond

The default value of CONFIG_FS_FATFS_MOUNT_MKFS is y: when mounting, if the filesystem is not recognized and fs_mount_t.flags does not have FS_MOUNT_FLAG_NO_FORMAT, FatFs formats the card directly. sd_api sets it to n in prj.conf, and also adds FS_MOUNT_FLAG_NO_FORMAT to flags.

When a mount fails, first try the disk layer alone with disk_access_init("SD"): if it returns 0 then the card and the devicetree are fine and the problem is on the filesystem side; if it returns a negative number then the problem is the card or the disk node. On a filesystem layer error, <err> fs: ... is printed to the console.

The demonstration is on yuzukineko, with an 8 GB SDHC card in the card slot, and the card already has a FAT32 filesystem and some files on it. Each interface has its own heading, below which is its prototype, its behavior, a complete example function, and the output of that function on the board.

The demonstration does not change what is already on the card: a raw sector write writes back its original content, and the files and directories it creates are all deleted at the end.

Headers, macros and helper functions shared by the example functions:

#include <errno.h>
#include <ff.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/disk.h>
#include <zephyr/drivers/sdhc.h>
#include <zephyr/fs/fs.h>
#include <zephyr/kernel.h>
#include <zephyr/sd/sd.h>
#include <zephyr/sd/sdmmc.h>
#include <zephyr/storage/disk_access.h>
#include <zephyr/sys/printk.h>
/* the host controller, and the disk named in the sdmmc node of the board (disk-name = "SD") */
static const struct device *const sdhc = DEVICE_DT_GET(DT_NODELABEL(smhc0));
#define DISK_NAME "SD"
#define MOUNT_POINT "/SD:"
#define TEST_FILE MOUNT_POINT "/F101API.TXT"
#define TEST_FILE2 MOUNT_POINT "/F101NEW.TXT"
#define TEST_DIR MOUNT_POINT "/F101DIR"
static const char *err(int ret)
{
switch (ret) {
case 0: return "0";
case -EIO: return "-EIO";
case -EINVAL: return "-EINVAL";
case -ENOENT: return "-ENOENT";
case -ENODEV: return "-ENODEV";
case -ENOTSUP: return "-ENOTSUP";
case -EBUSY: return "-EBUSY";
case -ETIMEDOUT: return "-ETIMEDOUT";
case -EEXIST: return "-EEXIST";
case -ENOTDIR: return "-ENOTDIR";
case -EROFS: return "-EROFS";
case -EACCES: return "-EACCES";
default: return "?";
}
}

DISK_NAME and MOUNT_POINT are the disk name SD in the devicetree and the FatFs mount point /SD:. err() simply prints a few common error numbers as names.

big is the 1 MiB buffer shared by the examples:

static uint8_t big[1024 * 1024 + 64] __aligned(64);
#include <zephyr/sd/sd.h>
bool sd_is_card_present(const struct device *sdhc_dev);

Asks the host controller whether there is a card in the slot. Internally it is sdhc_card_present(dev) == 1, so it reads the cd-gpios pin in the devicetree (PF6); when the controller is NULL it returns false.

ReturnMeaning
trueThere is a card
falseThere is no card, or sdhc_dev is NULL
static void demo_sd_is_card_present(void)
{
printk("[sd_is_card_present] controller = %d (1 = a card is in the slot)\n",
sd_is_card_present(sdhc));
printk("[sd_is_card_present] NULL controller = %d\n", sd_is_card_present(NULL));
}
[sd_is_card_present] controller = 1 (1 = a card is in the slot)
[sd_is_card_present] NULL controller = 0
#include <zephyr/sd/sd.h>
int sd_init(const struct device *sdhc_dev, struct sd_card *card);

Initializes a card: power-up, identification, bus width and clock negotiation; on success card holds all the information about this card, and only card is needed to access it afterwards. The process is in the “Initializing the card” flowchart above. The memory for card is provided by the caller and must not be freed after the call, until the card is no longer accessed.

ReturnMeaning
0Success, card->status is CARD_INITIALIZED (2)
-ENODEVsdhc_dev is NULL
-ETIMEDOUTCard initialization timed out (card absent, not responding)
-EBUSYThe card is busy
-EIOAn I/O error occurred during initialization
-ENOTSUPThe protocol stack for the card type is not enabled (for example an MMC is attached but MMC_STACK is off)

struct sd_card fields that can be read after initialization:

FieldMeaningValue in the demonstration
statusCARD_UNINITIALIZED 0, CARD_ERROR 1, CARD_INITIALIZED 22
typeCARD_SDMMC 0, CARD_SDIO 1, CARD_COMBO 2, CARD_MMC 30
ocrThe OCR register of the card; bit 31 is power-up complete, bit 30 (CCS) being 1 means a block-addressed SDHC/SDXCc0ff8000
relative_addrThe relative address obtained from CMD30001
sd_versionSpecification version, SD_SPEC_VER1_0 (bit 0), 1_1 (bit 1), 2_0 (bit 2), 3_0 (bit 3)4, that is 2.0
block_count, block_sizeNumber of blocks and bytes per block15728640, 512, 7680 MiB in total
bus_io.bus_width, bus_io.clockCurrent bus width and clock4 bit, 50 MHz
bus_io.timingCurrent timing, the values are enum sdhc_timing_mode: SDHC_TIMING_LEGACY 1, SDHC_TIMING_HS 21

For a card whose SD specification version is below 3.0 (sd_version less than SD_SPEC_VER3_0), the upstream sdmmc_read_switch records the bus speed bits the card supports (switch_caps.bus_speed) only at 3.0 and above. sdmmc_select_bus_speed cannot read the high speed bit and picks the default speed (CMD6 selects the default function, timing SDHC_TIMING_LEGACY); when sdmmc_set_bus_speed takes the clock it uses MIN(the highest clock of the host, hs_max_dtr), and hs_max_dtr is another high speed limit of 50 MHz filled in from the CMD6 check result. The two together make this card “default speed timing, 50 MHz clock”. The SD specification says the default speed mode is at most 25 MHz, so 50 MHz is outside the specification.

This is the behavior of the upstream subsys/sd, not a problem with the F101 SDHC driver or the attachment layer: the driver sets the controller to the clock and timing the protocol stack asks for, and whatever it is set to is what it is. This card reads and writes 1 MiB of data at 50 MHz, and repeatedly reading and writing the last sector and the FAT files all match what was written, with no read or write errors; but with another 2.0 card, if read or write errors or CRC errors appear, this is the first thing to suspect. This page does not change the upstream code.

static struct sd_card card;
static void demo_sd_init(void)
{
int64_t t0 = k_uptime_get();
int ret = sd_init(sdhc, &card);
printk("[sd_init] sd_init = %s, %lld ms\n", err(ret), k_uptime_get() - t0);
printk("[sd_init] status %d (2 = initialized), type %d (0 = SD memory card)\n",
card.status, card.type);
printk("[sd_init] OCR %08x, RCA %04x, SD spec version flag %u\n", card.ocr,
card.relative_addr, card.sd_version);
printk("[sd_init] %u blocks of %u bytes, bus width %u bit, clock %u Hz, timing mode %u\n",
card.block_count, card.block_size, card.bus_io.bus_width, card.bus_io.clock,
card.bus_io.timing);
printk("[sd_init] sd_init with a NULL controller = %s\n", err(sd_init(NULL, &card)));
}
[sd_init] sd_init = 0, 1016 ms
[sd_init] status 2 (2 = initialized), type 0 (0 = SD memory card)
[sd_init] OCR c0ff8000, RCA 0001, SD spec version flag 4
[sd_init] 15728640 blocks of 512 bytes, bus width 4 bit, clock 50000000 Hz, timing mode 1
[sd_init] sd_init with a NULL controller = -ENODEV
#include <zephyr/sd/sdmmc.h>
int sdmmc_read_blocks(struct sd_card *card, uint8_t *rbuf, uint32_t start_block, uint32_t num_blocks);

Reads num_blocks blocks from the card (each card->block_size bytes, 512 for SDHC) into rbuf, starting at block number start_block. One block uses CMD17, several blocks use CMD18, and the controller sends CMD12 automatically at the end.

ReturnMeaning
0Success
-EINVALstart_block + num_blocks exceeds card->block_count
-ENOTSUPThe card is an SDIO card and has no storage
-EBUSYThe card mutex cannot be taken (another thread is accessing it, for longer than SD_DATA_TIMEOUT)
-ETIMEDOUTRead timeout
-EIOI/O error

The buffer has no alignment requirement (SDHC_BUFFER_ALIGNMENT is 1), but a buffer that is not 64 byte aligned goes through the bounce buffer of the driver and is slower.

static void demo_sdmmc_read_blocks(void)
{
uint32_t n = 1024 * 1024 / card.block_size;
int64_t t0;
int ret;
ret = sdmmc_read_blocks(&card, big, 0, 1);
printk("[sdmmc_read_blocks] block 0 = %s, boot signature %02x %02x\n", err(ret),
big[510], big[511]);
t0 = k_uptime_get();
ret = sdmmc_read_blocks(&card, big, 0, n);
printk("[sdmmc_read_blocks] %u blocks (1 MiB) = %s, %lld ms\n", n, err(ret),
k_uptime_get() - t0);
ret = sdmmc_read_blocks(&card, big + 4, 0, 1);
printk("[sdmmc_read_blocks] buffer + 4 bytes = %s\n", err(ret));
ret = sdmmc_read_blocks(&card, big, card.block_count - 1, 1);
printk("[sdmmc_read_blocks] the last block (%u) = %s\n", card.block_count - 1, err(ret));
ret = sdmmc_read_blocks(&card, big, card.block_count, 1);
printk("[sdmmc_read_blocks] one block beyond the end = %s\n", err(ret));
}
[sdmmc_read_blocks] block 0 = 0, boot signature 55 aa
[sdmmc_read_blocks] 2048 blocks (1 MiB) = 0, 44 ms
[sdmmc_read_blocks] buffer + 4 bytes = 0
[sdmmc_read_blocks] the last block (15728639) = 0
[sdmmc_read_blocks] one block beyond the end = -EINVAL

The last two bytes of block 0 are 55 aa, showing that what was read is the MBR or the FAT boot sector. The aligned 1 MiB read takes 44 ms, about 23 MiB/s, and one request exactly uses up 128 DMA descriptors. When the buffer is offset by 4 bytes the request exceeds the bounce buffer and is moved by the CPU instead, which is 199 ms in the disk_access_read output below. See SDHC_SUNXI_MAX_DESC and SDHC_SUNXI_BOUNCE_KB in SDHC.

#include <zephyr/sd/sdmmc.h>
int sdmmc_write_blocks(struct sd_card *card, const uint8_t *wbuf, uint32_t start_block, uint32_t num_blocks);

Writes num_blocks blocks from wbuf to the card, starting at block number start_block. One block uses CMD24, several blocks use CMD25. The return values are the same as sdmmc_read_blocks; on a write timeout or write error they are -ETIMEDOUT, -EIO. The card may still be programming internally after the function returns; to make sure the data is on the card, call sdmmc_ioctl(card, DISK_IOCTL_CTRL_SYNC, NULL) afterwards, which waits for the card to become idle.

Writing raw blocks destroys the filesystem on the card, so the demonstration only reads the last block out and writes it back as it is, syncs after writing, and reads it once more to compare.

/* writes the last block back with the content it has: the card is not changed */
static void demo_sdmmc_write_blocks(void)
{
static uint8_t before[512] __aligned(64), after[512] __aligned(64);
uint32_t last = card.block_count - 1;
int ret;
ret = sdmmc_read_blocks(&card, before, last, 1);
printk("[sdmmc_write_blocks] read the last block (%u) = %s\n", last, err(ret));
ret = sdmmc_write_blocks(&card, before, last, 1);
printk("[sdmmc_write_blocks] write it back = %s\n", err(ret));
ret = sdmmc_ioctl(&card, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[sdmmc_write_blocks] sync = %s\n", err(ret));
ret = sdmmc_read_blocks(&card, after, last, 1);
printk("[sdmmc_write_blocks] read again = %s, %s\n", err(ret),
memcmp(before, after, sizeof(before)) == 0 ? "same data" : "DIFFERENT data");
ret = sdmmc_write_blocks(&card, before, card.block_count, 1);
printk("[sdmmc_write_blocks] one block beyond the end = %s\n", err(ret));
}
[sdmmc_write_blocks] read the last block (15728639) = 0
[sdmmc_write_blocks] write it back = 0
[sdmmc_write_blocks] sync = 0
[sdmmc_write_blocks] read again = 0, same data
[sdmmc_write_blocks] one block beyond the end = -EINVAL
#include <zephyr/sd/sdmmc.h>
#include <zephyr/drivers/disk.h>
int sdmmc_ioctl(struct sd_card *card, uint8_t cmd, void *buf);

Sends an I/O control to the card. Supported cmd:

cmdbufBehavior
DISK_IOCTL_GET_SECTOR_COUNTuint32_t *Number of blocks
DISK_IOCTL_GET_SECTOR_SIZEuint32_t *Block size (bytes)
DISK_IOCTL_GET_ERASE_BLOCK_SZuint32_t *Erase block size. The SD protocol stack returns the value of the block size here (512), not the real erase block size of the card
DISK_IOCTL_CTRL_SYNCnot used, can be NULLWaits for the card to not be busy. The protocol stack has no write cache, so there is no cache to flush
DISK_IOCTL_CTRL_DEINITnot usedWaits for the card to not be busy, then powers the card off. Accessing this card afterwards requires a new sd_init
anything else-ENOTSUP

When the card mutex cannot be taken it returns an error such as -EBUSY.

static void demo_sdmmc_ioctl(void)
{
uint32_t v = 0;
int ret;
ret = sdmmc_ioctl(&card, DISK_IOCTL_GET_SECTOR_COUNT, &v);
printk("[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = %s: %u\n", err(ret), v);
ret = sdmmc_ioctl(&card, DISK_IOCTL_GET_SECTOR_SIZE, &v);
printk("[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = %s: %u\n", err(ret), v);
ret = sdmmc_ioctl(&card, DISK_IOCTL_GET_ERASE_BLOCK_SZ, &v);
printk("[sdmmc_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = %s: %u\n", err(ret), v);
ret = sdmmc_ioctl(&card, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[sdmmc_ioctl] DISK_IOCTL_CTRL_SYNC = %s\n", err(ret));
ret = sdmmc_ioctl(&card, DISK_IOCTL_RESERVED, NULL);
printk("[sdmmc_ioctl] DISK_IOCTL_RESERVED (not handled) = %s\n", err(ret));
}
/* the card is powered off; the disk layer below identifies it again */
static void demo_sdmmc_deinit(void)
{
int ret = sdmmc_ioctl(&card, DISK_IOCTL_CTRL_DEINIT, NULL);
printk("[sdmmc_ioctl] DISK_IOCTL_CTRL_DEINIT = %s\n", err(ret));
}
[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = 0: 15728640
[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = 0: 512
[sdmmc_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = 0: 512
[sdmmc_ioctl] DISK_IOCTL_CTRL_SYNC = 0
[sdmmc_ioctl] DISK_IOCTL_RESERVED (not handled) = -ENOTSUP
[sdmmc_ioctl] DISK_IOCTL_CTRL_DEINIT = 0
#include <zephyr/storage/disk_access.h>
int disk_access_init(const char *pdrv);

Initializes the disk named pdrv. Internally: first look up the disk by name and return -EINVAL if there is none; the first time the disk is initialized it calls the init of the driver (for SD this is sd_is_card_present first, then sd_init), and on success increments the reference count; later calls only increment the reference count and do not initialize the card again. The card is really powered off only after the reference count reaches 0 (through that many DISK_IOCTL_CTRL_DEINIT).

New code should use disk_access_ioctl(pdrv, DISK_IOCTL_CTRL_INIT, NULL); this function is kept only for compatibility with old code.

ReturnMeaning
0Success
-EINVALThere is no disk with this name
DISK_STATUS_NOMEDIA (2, positive)There is no card in the slot. Note that this is a positive number
other negative numbersErrors of sd_init, for example -ETIMEDOUT, -EIO

Initialization takes as long as sd_init, about 1015 ms.

static void demo_disk_access_init(void)
{
int64_t t0 = k_uptime_get();
int ret = disk_access_init(DISK_NAME);
printk("[disk_access_init] \"%s\" = %s, %lld ms\n", DISK_NAME, err(ret), k_uptime_get() - t0);
ret = disk_access_init("NODISK");
printk("[disk_access_init] \"NODISK\" = %s\n", err(ret));
}
[disk_access_init] "SD" = 0, 1015 ms
[disk_access_init] "NODISK" = -EINVAL
#include <zephyr/storage/disk_access.h>
int disk_access_status(const char *pdrv);

Reads the disk status.

ReturnMeaning
DISK_STATUS_OK (0)The card is initialized and can be read and written
DISK_STATUS_UNINIT (1)The card is not initialized yet, or has been deinitialized
DISK_STATUS_NOMEDIA (2)There is no card in the slot (every call reads the card detect pin once)
-EINVALThere is no disk with this name

What is returned is a status value rather than an error code, so 0 means normal, -EINVAL is negative, and the other statuses are positive.

static void demo_disk_access_status(void)
{
printk("[disk_access_status] \"%s\" = %d (0 = DISK_STATUS_OK)\n", DISK_NAME,
disk_access_status(DISK_NAME));
printk("[disk_access_status] \"NODISK\" = %d\n", disk_access_status("NODISK"));
}
[disk_access_status] "SD" = 0 (0 = DISK_STATUS_OK)
[disk_access_status] "NODISK" = -22
#include <zephyr/storage/disk_access.h>
#include <zephyr/drivers/disk.h>
int disk_access_ioctl(const char *pdrv, uint8_t cmd, void *buff);

Gets disk parameters or sends a control request. For the SD disk:

cmdbuffBehavior
DISK_IOCTL_GET_SECTOR_COUNTuint32_t *Number of sectors
DISK_IOCTL_GET_SECTOR_SIZEuint32_t *Sector size (bytes)
DISK_IOCTL_GET_ERASE_BLOCK_SZuint32_t *Erase block size, the SD protocol stack returns 512 (see sdmmc_ioctl)
DISK_IOCTL_CTRL_SYNCnot usedWaits for the card to not be busy
DISK_IOCTL_CTRL_INITnot usedSame as disk_access_init, increments the reference count
DISK_IOCTL_CTRL_DEINITnot used, or bool * (true means force, regardless of the reference count)Decrements the reference count and, at 0, powers the card off and returns the disk status to DISK_STATUS_UNINIT
anything else-ENOTSUP

For a disk that does not have this name it returns -EINVAL.

static uint32_t sector_count, sector_size;
static void demo_disk_access_ioctl(void)
{
uint32_t erase = 0;
int ret;
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_GET_SECTOR_COUNT, &sector_count);
printk("[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = %s: %u\n", err(ret),
sector_count);
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_GET_SECTOR_SIZE, &sector_size);
printk("[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = %s: %u\n", err(ret), sector_size);
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_GET_ERASE_BLOCK_SZ, &erase);
printk("[disk_access_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = %s: %u sectors\n", err(ret),
erase);
printk("[disk_access_ioctl] capacity %u MiB\n",
(uint32_t)(((uint64_t)sector_count * sector_size) >> 20));
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[disk_access_ioctl] DISK_IOCTL_CTRL_SYNC = %s\n", err(ret));
}
/* a deinit powers the card off; the next init identifies it again */
static void demo_disk_access_deinit(void)
{
int ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_DEINIT, NULL);
printk("[disk_access_ioctl] DISK_IOCTL_CTRL_DEINIT = %s\n", err(ret));
printk("[disk_access_status] after the deinit = %d (1 = DISK_STATUS_UNINIT)\n",
disk_access_status(DISK_NAME));
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_INIT, NULL);
printk("[disk_access_ioctl] DISK_IOCTL_CTRL_INIT = %s\n", err(ret));
ret = disk_access_read(DISK_NAME, big, 0, 1);
printk("[disk_access_read] sector 0 after the init = %s, %02x %02x\n", err(ret),
big[510], big[511]);
}
[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = 0: 15728640
[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = 0: 512
[disk_access_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = 0: 512 sectors
[disk_access_ioctl] capacity 7680 MiB
[disk_access_ioctl] DISK_IOCTL_CTRL_SYNC = 0
[disk_access_ioctl] DISK_IOCTL_CTRL_DEINIT = 0
[disk_access_status] after the deinit = 1 (1 = DISK_STATUS_UNINIT)
[disk_access_ioctl] DISK_IOCTL_CTRL_INIT = 0
[disk_access_read] sector 0 after the init = 0, 55 aa
#include <zephyr/storage/disk_access.h>
int disk_access_read(const char *pdrv, uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector);

Reads num_sector sectors from the disk into data_buf. The disk must be initialized first, otherwise the read is an error. The return values are the same as sdmmc_read_blocks: out of range is -EINVAL, and a missing disk name is also -EINVAL.

static void demo_disk_access_read(void)
{
uint32_t n = 1024 * 1024 / sector_size;
int64_t t0;
int ret;
ret = disk_access_read(DISK_NAME, big, 0, 1);
printk("[disk_access_read] sector 0 = %s, boot signature %02x %02x\n", err(ret),
big[510], big[511]);
t0 = k_uptime_get();
ret = disk_access_read(DISK_NAME, big, 0, n);
printk("[disk_access_read] %u sectors (1 MiB), aligned buffer = %s, %lld ms\n", n,
err(ret), k_uptime_get() - t0);
t0 = k_uptime_get();
ret = disk_access_read(DISK_NAME, big + 4, 0, n);
printk("[disk_access_read] %u sectors (1 MiB), buffer + 4 bytes = %s, %lld ms\n", n,
err(ret), k_uptime_get() - t0);
ret = disk_access_read(DISK_NAME, big, sector_count - 1, 1);
printk("[disk_access_read] the last sector (%u) = %s\n", sector_count - 1, err(ret));
ret = disk_access_read(DISK_NAME, big, sector_count, 1);
printk("[disk_access_read] one sector beyond the end = %s\n", err(ret));
}
[disk_access_read] sector 0 = 0, boot signature 55 aa
[disk_access_read] 2048 sectors (1 MiB), aligned buffer = 0, 44 ms
[disk_access_read] 2048 sectors (1 MiB), buffer + 4 bytes = 0, 199 ms
[disk_access_read] the last sector (15728639) = 0
[disk_access_read] one sector beyond the end = -EINVAL
#include <zephyr/storage/disk_access.h>
int disk_access_write(const char *pdrv, const uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector);

Writes num_sector sectors from data_buf to the disk. The return values are the same as sdmmc_write_blocks. After writing, use DISK_IOCTL_CTRL_SYNC to wait for the card to finish programming. As above, the demonstration only reads the last sector out and writes it back as it is.

/* writes the last sector back with the data it has: the card is not changed */
static void demo_disk_access_write(void)
{
static uint8_t before[512] __aligned(64), after[512] __aligned(64);
uint32_t last = sector_count - 1;
int ret;
ret = disk_access_read(DISK_NAME, before, last, 1);
printk("[disk_access_write] read the last sector (%u) = %s\n", last, err(ret));
ret = disk_access_write(DISK_NAME, before, last, 1);
printk("[disk_access_write] write it back = %s\n", err(ret));
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[disk_access_write] sync = %s\n", err(ret));
ret = disk_access_read(DISK_NAME, after, last, 1);
printk("[disk_access_write] read again = %s, %s\n", err(ret),
memcmp(before, after, sizeof(before)) == 0 ? "same data" : "DIFFERENT data");
ret = disk_access_write(DISK_NAME, before, sector_count, 1);
printk("[disk_access_write] one sector beyond the end = %s\n", err(ret));
}
[disk_access_write] read the last sector (15728639) = 0
[disk_access_write] write it back = 0
[disk_access_write] sync = 0
[disk_access_write] read again = 0, same data
[disk_access_write] one sector beyond the end = -EINVAL
#include <zephyr/fs/fs.h>
static inline void fs_file_t_init(struct fs_file_t *zfp);

Zeroes the file object. It must be called before fs_open, otherwise random values on the stack are treated as an already open file.

static void demo_fs_file_t_init(void)
{
struct fs_file_t f;
memset(&f, 0xff, sizeof(f));
fs_file_t_init(&f);
printk("[fs_file_t_init] mp = %p, filep = %p, flags = %u\n", f.mp, f.filep, f.flags);
}
[fs_file_t_init] mp = 0, filep = 0, flags = 0
#include <zephyr/fs/fs.h>
static inline void fs_dir_t_init(struct fs_dir_t *zdp);

Zeroes the directory object. It must be called before fs_opendir.

static void demo_fs_dir_t_init(void)
{
struct fs_dir_t dir;
memset(&dir, 0xff, sizeof(dir));
fs_dir_t_init(&dir);
printk("[fs_dir_t_init] mp = %p, dirp = %p\n", dir.mp, dir.dirp);
}
[fs_dir_t_init] mp = 0, dirp = 0
#include <zephyr/fs/fs.h>
#include <ff.h>
int fs_mount(struct fs_mount_t *mp);

Mounts a filesystem at the mount point. The contents of struct fs_mount_t:

FieldMeaning
typeFilesystem type, FS_FATFS for FAT
mnt_pointMount point. ELM FatFs only allows /RAM:, /NAND:, /CF:, /SD:, /SD2:, /USB:, /USB2:, /USB3:, or a single digit such as /0:; the name here (without / and :) is the disk name, so the SD disk can only be mounted at /SD:
fs_dataThe volume object of FatFs, of type FATFS (from <ff.h>); the memory is provided by the application and must exist until fs_unmount
flagsFS_MOUNT_FLAG_NO_FORMAT: do not format; FS_MOUNT_FLAG_READ_ONLY: read-only; FS_MOUNT_FLAG_USE_DISK_ACCESS: set by the FAT driver itself after a successful mount, the application does not set it (so fs_mount modifies flags). FS_MOUNT_FLAG_AUTOMOUNT: for automatic mounting from the devicetree

When mounting, FatFs initializes the SD disk through the disk interface (so disk_access_init does not have to be called by yourself before fs_mount), reads the boot sector, and recognizes FAT12/16/32 or exFAT. The structure passed in must not be modified until fs_unmount succeeds.

Without FS_MOUNT_FLAG_NO_FORMAT and with CONFIG_FS_FATFS_MOUNT_MKFS enabled, a card with no recognizable filesystem is formatted automatically, that is, the whole card is erased. The demonstration turns FS_FATFS_MOUNT_MKFS off in prj.conf and also adds FS_MOUNT_FLAG_NO_FORMAT, two safeguards.

ReturnMeaning
0Success
-EBUSYThis mount point is already mounted
-ENOENTThat filesystem type is not registered
-ENOTSUPThe filesystem driver does not support it (or USE_DISK_ACCESS is set and the driver does not support it)
-EROFSFormatting is needed, but the read-only flag is set
-ENODEVThere is no recognizable filesystem on the card, and formatting is not allowed
-EIOReading the card failed, or the card did not respond
static FATFS fat_fs;
static struct fs_mount_t mp = {
.type = FS_FATFS,
.fs_data = &fat_fs,
.mnt_point = MOUNT_POINT,
/* never format a card that does not mount */
.flags = FS_MOUNT_FLAG_NO_FORMAT,
};
static void demo_fs_mount(void)
{
int ret = fs_mount(&mp);
printk("[fs_mount] %s = %s\n", MOUNT_POINT, err(ret));
ret = fs_mount(&mp);
printk("[fs_mount] the same mount point again = %s\n", err(ret));
}
[fs_mount] /SD: = 0
[fs_mount] the same mount point again = -EBUSY

On the second mount the filesystem layer prints <err> fs: file system already mounted!!, in the full log below.

#include <zephyr/fs/fs.h>
int fs_readmount(int *index, const char **name);

Iterates over the current mount points. index starts at 0 and is incremented by 1 on each success, and name returns the mount point name; when there are no more mount points it returns -ENOENT and sets name to NULL.

static void demo_fs_readmount(void)
{
const char *name;
int index = 0;
while (fs_readmount(&index, &name) == 0) {
printk("[fs_readmount] mount point %d: %s\n", index - 1, name);
}
}
[fs_readmount] mount point 0: /SD:
#include <zephyr/fs/fs.h>
int fs_statvfs(const char *path, struct fs_statvfs *stat);

Reads the total and free space of the volume. path is the mount point or a path under the mount point. The FAT result: f_frsize is the cluster size, f_blocks is the total number of clusters, f_bfree is the number of free clusters; f_bsize is the sector size (512). The total number of bytes is f_blocks * f_frsize.

ReturnMeaning
0Success
-EINVALWrong path, not mounted
-ENOTSUPThe filesystem driver does not support it
-EIOError reading the FAT table
static void demo_fs_statvfs(void)
{
struct fs_statvfs sv;
int ret = fs_statvfs(MOUNT_POINT, &sv);
printk("[fs_statvfs] %s = %s: block size %lu, fragment size %lu, %lu blocks, %lu free\n",
MOUNT_POINT, err(ret), sv.f_bsize, sv.f_frsize, sv.f_blocks, sv.f_bfree);
printk("[fs_statvfs] %u MiB total, %u MiB free\n",
(uint32_t)(((uint64_t)sv.f_blocks * sv.f_frsize) >> 20),
(uint32_t)(((uint64_t)sv.f_bfree * sv.f_frsize) >> 20));
}
[fs_statvfs] /SD: = 0: block size 512, fragment size 32768, 245631 blocks, 188896 free
[fs_statvfs] 7675 MiB total, 5903 MiB free

The cluster size is 32 KiB, there are 245631 clusters in total, and 188896 are free.

#include <zephyr/fs/fs.h>
int fs_opendir(struct fs_dir_t *zdp, const char *path);

Opens a directory. zdp must be fs_dir_t_inited first. The number of directories open at the same time is limited by CONFIG_FS_FATFS_NUM_DIRS.

ReturnMeaning
0Success
-ENOENTThe directory does not exist, or the mount point in the path does not exist
-ENOMEMDirectory objects are used up (FS_FATFS_NUM_DIRS)
-ENODEVThere is no recognizable filesystem on the volume
static void demo_fs_opendir(void)
{
struct fs_dir_t dir;
int ret;
fs_dir_t_init(&dir);
ret = fs_opendir(&dir, MOUNT_POINT);
printk("[fs_opendir] %s = %s\n", MOUNT_POINT, err(ret));
if (ret == 0) {
fs_closedir(&dir);
}
ret = fs_opendir(&dir, MOUNT_POINT "/NODIR");
printk("[fs_opendir] a directory that is not there = %s\n", err(ret));
}
[fs_opendir] /SD: = 0
[fs_opendir] a directory that is not there = -ENOENT
#include <zephyr/fs/fs.h>
int fs_readdir(struct fs_dir_t *zdp, struct fs_dirent *entry);

Reads the next directory entry into entry (name, type, size). type is FS_DIR_ENTRY_FILE or FS_DIR_ENTRY_DIR; the size of a directory is 0. At the end of the directory it returns 0 and entry->name[0] is '\0', so the end condition is the name, not the return value. FAT directory entries have no fixed order; it is the order in which they are stored on the disk.

/* the first entries of the root directory; an empty name ends the listing */
static void demo_fs_readdir(void)
{
struct fs_dir_t dir;
struct fs_dirent ent;
int ret, n = 0;
fs_dir_t_init(&dir);
ret = fs_opendir(&dir, MOUNT_POINT);
if (ret != 0) {
printk("[fs_readdir] fs_opendir = %s\n", err(ret));
return;
}
while (n < 6) {
ret = fs_readdir(&dir, &ent);
if (ret != 0 || ent.name[0] == '\0') {
printk("[fs_readdir] end of the directory = %s\n", err(ret));
break;
}
printk("[fs_readdir] %-4s %10u %s\n",
ent.type == FS_DIR_ENTRY_DIR ? "dir" : "file", (uint32_t)ent.size, ent.name);
n++;
}
fs_closedir(&dir);
}
[fs_readdir] dir 0 System Volume Information
[fs_readdir] file 8689914 badapple.h264
[fs_readdir] dir 0 arduino
[fs_readdir] file 0 swap.bin
[fs_readdir] file 44907671 video720p.h264
[fs_readdir] file 32768 Pokemon-Red.sav

The demonstration reads only the first 6 entries.

#include <zephyr/fs/fs.h>
int fs_closedir(struct fs_dir_t *zdp);

Closes the directory and frees the directory object. Returns 0 on success.

static void demo_fs_closedir(void)
{
struct fs_dir_t dir;
int ret;
fs_dir_t_init(&dir);
ret = fs_opendir(&dir, MOUNT_POINT);
if (ret != 0) {
printk("[fs_closedir] fs_opendir = %s\n", err(ret));
return;
}
ret = fs_closedir(&dir);
printk("[fs_closedir] = %s\n", err(ret));
}
[fs_closedir] = 0
#include <zephyr/fs/fs.h>
int fs_mkdir(const char *path);

Creates a directory, only the last level; the parent directory must already exist.

ReturnMeaning
0Success
-EEXISTA file or directory with the same name already exists
-ENOENTThe parent directory does not exist
static void demo_fs_mkdir(void)
{
struct fs_dirent st;
int ret;
ret = fs_mkdir(TEST_DIR);
printk("[fs_mkdir] %s = %s\n", TEST_DIR, err(ret));
ret = fs_stat(TEST_DIR, &st);
printk("[fs_mkdir] fs_stat = %s: %s, type %s\n", err(ret), st.name,
st.type == FS_DIR_ENTRY_DIR ? "dir" : "file");
ret = fs_mkdir(TEST_DIR);
printk("[fs_mkdir] the same directory again = %s\n", err(ret));
}
[fs_mkdir] /SD:/F101DIR = 0
[fs_mkdir] fs_stat = 0: F101DIR, type dir
[fs_mkdir] the same directory again = -EEXIST
#include <zephyr/fs/fs.h>
int fs_open(struct fs_file_t *zfp, const char *file_name, fs_mode_t flags);

Opens a file. zfp must be fs_file_t_inited first; the number of files open at the same time is limited by CONFIG_FS_FATFS_NUM_FILES. flags:

FlagMeaning
FS_O_READRead-only
FS_O_WRITEWrite-only
FS_O_RDWRRead and write (`FS_O_READ
FS_O_CREATECreate the file if it does not exist
FS_O_APPENDMove to the end of the file before each write
FS_O_TRUNCTruncate the file to 0 bytes when opening
ReturnMeaning
0Success
-ENOENTThe file does not exist (no FS_O_CREATE), or the mount point in the path does not exist
-ENOMEMFile objects are used up (FS_FATFS_NUM_FILES)
-EROFSA write is requested on a volume mounted with FS_MOUNT_FLAG_READ_ONLY
-ENODEVThere is no recognizable filesystem on the volume
static void demo_fs_open(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_CREATE | FS_O_RDWR);
printk("[fs_open] %s, FS_O_CREATE | FS_O_RDWR = %s\n", TEST_FILE, err(ret));
if (ret == 0) {
fs_close(&f);
}
ret = fs_open(&f, MOUNT_POINT "/NOFILE.TXT", FS_O_READ);
printk("[fs_open] a file that is not there, FS_O_READ = %s\n", err(ret));
ret = fs_open(&f, "/NOMOUNT:/A.TXT", FS_O_READ);
printk("[fs_open] a path with no mount point = %s\n", err(ret));
}
[fs_open] /SD:/F101API.TXT, FS_O_CREATE | FS_O_RDWR = 0
[fs_open] a file that is not there, FS_O_READ = -ENOENT
[fs_open] a path with no mount point = -ENOENT

For a path with no mount point, the filesystem layer prints <err> fs: mount point not found!!.

#include <zephyr/fs/fs.h>
ssize_t fs_write(struct fs_file_t *zfp, const void *ptr, size_t size);

Writes size bytes from the current position, and the position moves forward. Returns the number of bytes actually written, which is less than size when the card is full; on error it returns a negative error number. The data may still be in the FatFs cache; to get it onto the card use fs_sync or fs_close.

A write to a file opened with FS_O_READ returns -EACCES (the “access denied” of FatFs).

static const char text[] = "Written by sd_api on the YuzukiNeko\n";
static void demo_fs_write(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_WRITE | FS_O_TRUNC);
if (ret != 0) {
printk("[fs_write] fs_open = %s\n", err(ret));
return;
}
ret = fs_write(&f, text, sizeof(text) - 1);
printk("[fs_write] %u bytes = %d\n", (unsigned int)sizeof(text) - 1, ret);
ret = fs_write(&f, text, sizeof(text) - 1);
printk("[fs_write] the same text again = %d\n", ret);
fs_close(&f);
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret == 0) {
ret = fs_write(&f, text, 1);
printk("[fs_write] on a file opened with FS_O_READ = %s\n", err(ret));
fs_close(&f);
}
}
[fs_write] 36 bytes = 36
[fs_write] the same text again = 36
[fs_write] on a file opened with FS_O_READ = -EACCES
#include <zephyr/fs/fs.h>
int fs_sync(struct fs_file_t *zfp);

Writes the cached file data and the directory entry (file size) to the card, and the file stays open. In the demonstration, after fs_sync the new size is visible through fs_stat while the file is still open.

static void demo_fs_sync(void)
{
struct fs_file_t f;
struct fs_dirent st;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_WRITE | FS_O_APPEND);
if (ret != 0) {
printk("[fs_sync] fs_open = %s\n", err(ret));
return;
}
fs_write(&f, text, sizeof(text) - 1);
ret = fs_sync(&f);
printk("[fs_sync] = %s\n", err(ret));
/* the size is on the card now, while the file is still open */
fs_stat(TEST_FILE, &st);
printk("[fs_sync] fs_stat while the file is open: %u bytes (3 * %u)\n",
(uint32_t)st.size, (unsigned int)sizeof(text) - 1);
fs_close(&f);
}
[fs_sync] = 0
[fs_sync] fs_stat while the file is open: 108 bytes (3 * 36)
#include <zephyr/fs/fs.h>
int fs_seek(struct fs_file_t *zfp, off_t offset, int whence);

Moves the read/write position. whence is FS_SEEK_SET (from the start), FS_SEEK_CUR (from the current position), FS_SEEK_END (from the end, the offset is usually negative). Returns 0 on success.

static void demo_fs_seek(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_seek] fs_open = %s\n", err(ret));
return;
}
ret = fs_seek(&f, 10, FS_SEEK_SET);
printk("[fs_seek] 10 from the start = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
ret = fs_seek(&f, 5, FS_SEEK_CUR);
printk("[fs_seek] 5 from here = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
ret = fs_seek(&f, -4, FS_SEEK_END);
printk("[fs_seek] -4 from the end = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
fs_close(&f);
}
[fs_seek] 10 from the start = 0, now at 10
[fs_seek] 5 from here = 0, now at 15
[fs_seek] -4 from the end = 0, now at 104

The file is 108 bytes, and going back 4 bytes from the end is 104.

#include <zephyr/fs/fs.h>
off_t fs_tell(struct fs_file_t *zfp);

Returns the current read/write position, in bytes, counted from the start of the file.

static void demo_fs_tell(void)
{
struct fs_file_t f;
char buf[8];
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_tell] fs_open = %s\n", err(ret));
return;
}
printk("[fs_tell] after fs_open = %ld\n", (long)fs_tell(&f));
fs_read(&f, buf, sizeof(buf));
printk("[fs_tell] after reading %u bytes = %ld\n", (unsigned int)sizeof(buf),
(long)fs_tell(&f));
fs_close(&f);
}
[fs_tell] after fs_open = 0
[fs_tell] after reading 8 bytes = 8
#include <zephyr/fs/fs.h>
ssize_t fs_read(struct fs_file_t *zfp, void *ptr, size_t size);

Reads up to size bytes from the current position, and the position moves forward. Returns the number of bytes actually read; at the end of the file it returns 0; on error it returns a negative error number.

static void demo_fs_read(void)
{
struct fs_file_t f;
char back[sizeof(text)] = { 0 };
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_read] fs_open = %s\n", err(ret));
return;
}
ret = fs_read(&f, back, sizeof(text) - 1);
printk("[fs_read] %d bytes: %s", ret, back);
fs_seek(&f, 0, FS_SEEK_END);
ret = fs_read(&f, back, sizeof(back) - 1);
printk("[fs_read] at the end of the file = %d\n", ret);
fs_close(&f);
}
[fs_read] 36 bytes: Written by sd_api on the YuzukiNeko
[fs_read] at the end of the file = 0
#include <zephyr/fs/fs.h>
int fs_truncate(struct fs_file_t *zfp, off_t length);

Changes the file to length bytes: shorter than before truncates, longer than before extends, and the extended part is filled with 0 (the FatFs adaptation layer writes 0 byte by byte). After the call the read/write position is at length; when there is not enough space on the volume, the file is extended only as far as it can be. The file must be opened in a writable way.

static void demo_fs_truncate(void)
{
struct fs_file_t f;
struct fs_dirent st;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_RDWR);
if (ret != 0) {
printk("[fs_truncate] fs_open = %s\n", err(ret));
return;
}
ret = fs_truncate(&f, 10);
printk("[fs_truncate] to 10 bytes = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
fs_sync(&f);
fs_stat(TEST_FILE, &st);
printk("[fs_truncate] size %u bytes\n", (uint32_t)st.size);
ret = fs_truncate(&f, 100);
fs_sync(&f);
fs_stat(TEST_FILE, &st);
printk("[fs_truncate] extended to 100 bytes = %s, size %u bytes\n", err(ret),
(uint32_t)st.size);
fs_close(&f);
}
[fs_truncate] to 10 bytes = 0, now at 10
[fs_truncate] size 10 bytes
[fs_truncate] extended to 100 bytes = 0, size 100 bytes
#include <zephyr/fs/fs.h>
int fs_close(struct fs_file_t *zfp);

Closes the file, the cached data is written to the card, and the directory entry is updated. After closing, zfp->mp is cleared, so closing the same file object again is a no-op and returns 0.

static void demo_fs_close(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_close] fs_open = %s\n", err(ret));
return;
}
ret = fs_close(&f);
printk("[fs_close] = %s\n", err(ret));
ret = fs_close(&f);
printk("[fs_close] the same file again = %s\n", err(ret));
}
[fs_close] = 0
[fs_close] the same file again = 0
#include <zephyr/fs/fs.h>
int fs_stat(const char *path, struct fs_dirent *entry);

Reads the information of a file or directory: name, type (FS_DIR_ENTRY_FILE or FS_DIR_ENTRY_DIR), size. The file does not need to be opened first.

ReturnMeaning
0Success
-ENOENTDoes not exist
-EINVALWrong path
static void demo_fs_stat(void)
{
struct fs_dirent st;
int ret;
ret = fs_stat(TEST_FILE, &st);
printk("[fs_stat] %s = %s: name %s, type %s, size %u\n", TEST_FILE, err(ret), st.name,
st.type == FS_DIR_ENTRY_DIR ? "dir" : "file", (uint32_t)st.size);
ret = fs_stat(MOUNT_POINT "/NOFILE.TXT", &st);
printk("[fs_stat] a file that is not there = %s\n", err(ret));
}
[fs_stat] /SD:/F101API.TXT = 0: name F101API.TXT, type file, size 100
[fs_stat] a file that is not there = -ENOENT
#include <zephyr/fs/fs.h>
int fs_rename(const char *from, const char *to);

Renames or moves; from and to must be on the same mount point. In the FAT implementation, if to already exists, it is deleted first and then the rename happens, and -EEXIST is not returned. Note: this deletion happens before from is checked, so when from does not exist, to is deleted as well and the function returns -ENOENT. The demonstration verifies a rename, overwriting an existing name, and renaming a file that does not exist, in that order.

static void demo_fs_rename(void)
{
struct fs_file_t f;
struct fs_dirent st;
int ret;
ret = fs_rename(TEST_FILE, TEST_FILE2);
printk("[fs_rename] %s to %s = %s\n", TEST_FILE, TEST_FILE2, err(ret));
ret = fs_stat(TEST_FILE, &st);
printk("[fs_rename] old name: fs_stat = %s\n", err(ret));
ret = fs_stat(TEST_FILE2, &st);
printk("[fs_rename] new name: fs_stat = %s, %u bytes\n", err(ret), (uint32_t)st.size);
/* a new 4 byte file renamed onto a name that exists: the old file there is replaced */
fs_file_t_init(&f);
if (fs_open(&f, TEST_FILE, FS_O_CREATE | FS_O_WRITE) == 0) {
fs_write(&f, "abcd", 4);
fs_close(&f);
}
ret = fs_rename(TEST_FILE, TEST_FILE2);
printk("[fs_rename] onto a name that exists = %s\n", err(ret));
ret = fs_stat(TEST_FILE2, &st);
printk("[fs_rename] the new name now has %u bytes (fs_stat = %s)\n", (uint32_t)st.size,
err(ret));
ret = fs_rename(TEST_FILE, MOUNT_POINT "/F101OLD.TXT");
printk("[fs_rename] a file that is not there = %s\n", err(ret));
}
[fs_rename] /SD:/F101API.TXT to /SD:/F101NEW.TXT = 0
[fs_rename] old name: fs_stat = -ENOENT
[fs_rename] new name: fs_stat = 0, 100 bytes
[fs_rename] onto a name that exists = 0
[fs_rename] the new name now has 4 bytes (fs_stat = 0)
[fs_rename] a file that is not there = -ENOENT
#include <zephyr/fs/fs.h>
int fs_unlink(const char *path);

Deletes a file or an empty directory.

ReturnMeaning
0Success
-ENOENTDoes not exist
-EACCESThe directory is not empty, or the file has the read-only attribute (the “access denied” of FatFs)
static void demo_fs_unlink(void)
{
struct fs_dirent st;
int ret;
ret = fs_unlink(TEST_FILE2);
printk("[fs_unlink] %s = %s\n", TEST_FILE2, err(ret));
ret = fs_stat(TEST_FILE2, &st);
printk("[fs_unlink] fs_stat afterwards = %s\n", err(ret));
ret = fs_unlink(TEST_DIR);
printk("[fs_unlink] the empty directory %s = %s\n", TEST_DIR, err(ret));
ret = fs_unlink(TEST_FILE2);
printk("[fs_unlink] a file that is not there = %s\n", err(ret));
}
[fs_unlink] /SD:/F101NEW.TXT = 0
[fs_unlink] fs_stat afterwards = -ENOENT
[fs_unlink] the empty directory /SD:/F101DIR = 0
[fs_unlink] a file that is not there = -ENOENT
#include <zephyr/fs/fs.h>
int fs_unmount(struct fs_mount_t *mp);

Unmounts the filesystem; what is passed in is the same structure that fs_mount used. All files and directories must be closed before unmounting. Only after unmounting may mp and fs_data be freed or modified. When unmounting, the FAT driver also powers the disk off once (equivalent to DISK_IOCTL_CTRL_DEINIT); accessing the card afterwards requires a new disk_access_init or a new fs_mount.

ReturnMeaning
0Success
-EINVALThis mount point is not mounted
-ENOTSUPThe filesystem driver does not support it
static void demo_fs_unmount(void)
{
int ret = fs_unmount(&mp);
printk("[fs_unmount] %s = %s\n", MOUNT_POINT, err(ret));
ret = fs_unmount(&mp);
printk("[fs_unmount] the same mount point again = %s\n", err(ret));
}
[fs_unmount] /SD: = 0
[fs_unmount] the same mount point again = -EINVAL

File read and write and raw read and write are the same path; here the FatFs file read and write speed is measured separately: write a 1 MiB file (64 KiB at a time), then read it back and compare byte by byte. Writing takes 90 ms (about 11 MiB/s), reading 59 ms (about 17 MiB/s), and the data read back matches what was written. File read and write is 16 requests of 64 KiB, with the buffer 64 byte aligned, going straight to DMA; writing is slower than reading, and includes the time the card spends programming internally plus the updates of the FAT table and the directory entry.

static uint8_t pattern[64 * 1024] __aligned(64);
static void demo_fs_speed(void)
{
struct fs_file_t f;
int64_t t0;
int ret, bad = 0;
for (size_t i = 0; i < sizeof(pattern); i++) {
pattern[i] = (uint8_t)(i * 7 + 3);
}
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_CREATE | FS_O_RDWR | FS_O_TRUNC);
if (ret != 0) {
printk("[speed] fs_open = %s\n", err(ret));
return;
}
t0 = k_uptime_get();
for (int i = 0; i < 16; i++) {
ret = fs_write(&f, pattern, sizeof(pattern));
if (ret != sizeof(pattern)) {
break;
}
}
fs_sync(&f);
printk("[speed] wrote 1 MiB in 64 KiB pieces = %d, %lld ms\n", ret, k_uptime_get() - t0);
fs_seek(&f, 0, FS_SEEK_SET);
t0 = k_uptime_get();
for (int i = 0; i < 16; i++) {
memset(big, 0, sizeof(pattern));
ret = fs_read(&f, big, sizeof(pattern));
if (ret != sizeof(pattern) || memcmp(big, pattern, sizeof(pattern)) != 0) {
bad++;
}
}
printk("[speed] read it back, %d piece(s) differ, %lld ms\n", bad, k_uptime_get() - t0);
fs_close(&f);
fs_unlink(TEST_FILE);
}
[speed] wrote 1 MiB in 64 KiB pieces = 65536, 90 ms
[speed] read it back, 0 piece(s) differ, 59 ms

Below, item by item: what was verified, how it was verified, and what was seen on the board. All the output is in the full log below.

ItemHow it was verifiedWhat was seen on the board
Card detectsd_is_card_present called with smhc0 and with NULLReturns 1 with a card, returns 0 with NULL
Card initializationsd_init, then reading the fields of struct sd_card; a NULL controllerReturns 0, taking 1016 ms; OCR c0ff8000, block addressing; 7680 MiB; 4 bit bus, 50 MHz; with NULL it returns -ENODEV
Block read and writesdmmc_read_blocks reading block 0, 1 MiB, the last block, one block beyond the end; sdmmc_write_blocks reading the last block out and writing it back as it is, then reading and comparing after a syncThe signature of block 0 is 55 aa; the aligned 1 MiB read takes 44 ms; out of range returns -EINVAL; the data is the same after writing it back
Block ioctlsdmmc_ioctl getting the block count, block size and erase block size, syncing, sending a command that is not handled, and finally DEINITThe values match sd_init; the unhandled command returns -ENOTSUP; DEINIT returns 0
Disk initialization and statusdisk_access_init, disk_access_status, with the disk names SD and the nonexistent NODISKSD returns 0, status 0; NODISK returns -EINVAL
Disk read and writedisk_access_read reading sector 0, 1 MiB aligned and offset by 4 bytes, the last sector, and out of range; disk_access_write writing the last sector back as it isThe aligned 1 MiB takes 44 ms, the 4 byte offset 199 ms; out of range returns -EINVAL; the data is the same after writing it back
Disk ioctldisk_access_ioctl getting the sector count, sector size and erase block size, and syncing; after DEINIT checking the status, then INIT, then reading sector 0The status becomes 1 (DISK_STATUS_UNINIT), and after INIT 55 aa can be read
Mountfs_mount mounting /SD:, mounting it once more; fs_readmount iterating; fs_unmount twiceThe first is 0, the second -EBUSY; the iteration reaches /SD:; the second unmount returns -EINVAL
Volume informationfs_statvfsClusters of 32768 bytes, 245631 clusters, 188896 free, 7675 MiB in total
Directoriesfs_opendir, fs_readdir, fs_closedir, fs_mkdir, fs_unlink (deleting the empty directory)Lists the first 6 entries of the root directory; a directory that does not exist returns -ENOENT; creating it again returns -EEXIST; deleting the empty directory returns 0
File read and writefs_open to create, fs_write twice, fs_stat after fs_sync while the file is still open, fs_seek with the three whence values, fs_tell, fs_read, fs_truncate shrinking and extending, fs_closeWrites 36 bytes twice; the size after the sync is 108; the fs_seek positions are 10, 15, 104; the original text is read back; after truncation 10 bytes, after extension 100 bytes
File managementfs_stat, fs_rename (an ordinary rename, overwriting an existing name, a missing source), fs_unlinkAfter the rename the old name is -ENOENT; after the overwrite the size of the new name becomes 4 bytes; a missing source returns -ENOENT
Error pathsWriting after opening read-only, a path with no mount point, a file that does not exist-EACCES, -ENOENT, -ENOENT, and the console also has <err> fs logs
Large block DMAOne 1 MiB read on the aligned buffer (128 descriptors)From 199 ms down to 44 ms
File read and write speed and data consistencyWriting 1 MiB (64 KiB at a time) and reading it back for a byte by byte comparisonWrite 90 ms, read 59 ms, 0 pieces differ
Object initializationfs_file_t_init, fs_dir_t_init on an object filled with 0xffThe pointers and flags are all cleared
ItemWhy it was not verified
fs_mkfsIt formats the storage device and erases the whole card; FILE_SYSTEM_MKFS is not enabled either
The path when there is no card in the slotThere is always a card on the board. In the code, when sd_is_card_present is 0, main prints no card in the slot, nothing more to do and exits; this one has not been run
Card removal and insertion eventsThey need a manual action and are not in the automatic flow
Other cards (SDSC, SDXC, SD 3.0 and above, exFAT)Only this one SDHC SD 2.0 card is on hand
fs_register, fs_unregisterInterfaces to register a new filesystem type; they belong to filesystem drivers and applications do not need them
The interfaces of sdio.h, mmc.hSDIO is used by the WiFi/Bluetooth chip, see AIC8800 WIFI; yuzukineko has no eMMC and MMC_STACK is not enabled
1.8 V UHS mode, SPI modeThe controller driver does not support them, and the related protocol stack code is not compiled in
Other filesystems such as LittleFSOnly FAT on the SD card is covered here

All the examples and logs above come from this one program. Create a directory, put the files below into it, and it can be built like any other Zephyr application.

sd_api/
├── CMakeLists.txt
├── prj.conf
└── src/
└── main.c
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(sd_api)
target_sources(app PRIVATE src/main.c)
# SD protocol stack: the sdmmc disk node of the board pulls in SDMMC_STACK
CONFIG_SDHC=y
CONFIG_DISK_ACCESS=y
CONFIG_DISK_DRIVER_SDMMC=y
# FAT file system (ELM FatFs), with long file names and exFAT
CONFIG_FILE_SYSTEM=y
CONFIG_FAT_FILESYSTEM_ELM=y
CONFIG_FS_FATFS_EXFAT=y
CONFIG_FS_FATFS_LFN=y
CONFIG_FS_FATFS_LFN_MODE_STACK=y
# never format a card on a failed mount
CONFIG_FS_FATFS_MOUNT_MKFS=n
CONFIG_PRINTK=y
CONFIG_MAIN_STACK_SIZE=8192
CONFIG_SHELL=n
CONFIG_DEVMEM_SHELL=n
CONFIG_LOG_PRINTK=n

Lines such as CONFIG_SDHC=y and CONFIG_DISK_ACCESS=y are selected automatically when the board devicetree has a zephyr,sdmmc-disk node; they are written out here to make the dependencies obvious at a glance. CONFIG_MAIN_STACK_SIZE=8192 is because long file names are on the stack (FS_FATFS_LFN_MODE_STACK) and the fs_* calls are fairly deep.

/*
* SD card protocol stack on the TF slot of the YuzukiNeko (SMHC0). Every interface of the three
* layers is called once:
* - the SD subsystem: <zephyr/sd/sd.h> and <zephyr/sd/sdmmc.h>
* - the disk access layer: <zephyr/storage/disk_access.h>
* - the FAT file system through <zephyr/fs/fs.h>
* Nothing on the card is changed in the end: sectors are written back with the data they had, and
* the files and the directory that are created are deleted again.
*/
#include <errno.h>
#include <ff.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/disk.h>
#include <zephyr/drivers/sdhc.h>
#include <zephyr/fs/fs.h>
#include <zephyr/kernel.h>
#include <zephyr/sd/sd.h>
#include <zephyr/sd/sdmmc.h>
#include <zephyr/storage/disk_access.h>
#include <zephyr/sys/printk.h>
/* the host controller, and the disk named in the sdmmc node of the board (disk-name = "SD") */
static const struct device *const sdhc = DEVICE_DT_GET(DT_NODELABEL(smhc0));
#define DISK_NAME "SD"
#define MOUNT_POINT "/SD:"
#define TEST_FILE MOUNT_POINT "/F101API.TXT"
#define TEST_FILE2 MOUNT_POINT "/F101NEW.TXT"
#define TEST_DIR MOUNT_POINT "/F101DIR"
static const char *err(int ret)
{
switch (ret) {
case 0: return "0";
case -EIO: return "-EIO";
case -EINVAL: return "-EINVAL";
case -ENOENT: return "-ENOENT";
case -ENODEV: return "-ENODEV";
case -ENOTSUP: return "-ENOTSUP";
case -EBUSY: return "-EBUSY";
case -ETIMEDOUT: return "-ETIMEDOUT";
case -EEXIST: return "-EEXIST";
case -ENOTDIR: return "-ENOTDIR";
case -EROFS: return "-EROFS";
case -EACCES: return "-EACCES";
default: return "?";
}
}
/* a 1 MiB buffer aligned to the cache line, and one that is used on purpose unaligned */
static uint8_t big[1024 * 1024 + 64] __aligned(64);
/* ---------------------------------------------------------------- SD subsystem */
/* the structure of the subsystem: one per card. sd_init fills it in. */
static struct sd_card card;
static void demo_sd_is_card_present(void)
{
printk("[sd_is_card_present] controller = %d (1 = a card is in the slot)\n",
sd_is_card_present(sdhc));
printk("[sd_is_card_present] NULL controller = %d\n", sd_is_card_present(NULL));
}
static void demo_sd_init(void)
{
int64_t t0 = k_uptime_get();
int ret = sd_init(sdhc, &card);
printk("[sd_init] sd_init = %s, %lld ms\n", err(ret), k_uptime_get() - t0);
printk("[sd_init] status %d (2 = initialized), type %d (0 = SD memory card)\n",
card.status, card.type);
printk("[sd_init] OCR %08x, RCA %04x, SD spec version flag %u\n", card.ocr,
card.relative_addr, card.sd_version);
printk("[sd_init] %u blocks of %u bytes, bus width %u bit, clock %u Hz, timing mode %u\n",
card.block_count, card.block_size, card.bus_io.bus_width, card.bus_io.clock,
card.bus_io.timing);
printk("[sd_init] sd_init with a NULL controller = %s\n", err(sd_init(NULL, &card)));
}
static void demo_sdmmc_ioctl(void)
{
uint32_t v = 0;
int ret;
ret = sdmmc_ioctl(&card, DISK_IOCTL_GET_SECTOR_COUNT, &v);
printk("[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = %s: %u\n", err(ret), v);
ret = sdmmc_ioctl(&card, DISK_IOCTL_GET_SECTOR_SIZE, &v);
printk("[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = %s: %u\n", err(ret), v);
ret = sdmmc_ioctl(&card, DISK_IOCTL_GET_ERASE_BLOCK_SZ, &v);
printk("[sdmmc_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = %s: %u\n", err(ret), v);
ret = sdmmc_ioctl(&card, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[sdmmc_ioctl] DISK_IOCTL_CTRL_SYNC = %s\n", err(ret));
ret = sdmmc_ioctl(&card, DISK_IOCTL_RESERVED, NULL);
printk("[sdmmc_ioctl] DISK_IOCTL_RESERVED (not handled) = %s\n", err(ret));
}
static void demo_sdmmc_read_blocks(void)
{
uint32_t n = 1024 * 1024 / card.block_size;
int64_t t0;
int ret;
ret = sdmmc_read_blocks(&card, big, 0, 1);
printk("[sdmmc_read_blocks] block 0 = %s, boot signature %02x %02x\n", err(ret),
big[510], big[511]);
t0 = k_uptime_get();
ret = sdmmc_read_blocks(&card, big, 0, n);
printk("[sdmmc_read_blocks] %u blocks (1 MiB) = %s, %lld ms\n", n, err(ret),
k_uptime_get() - t0);
ret = sdmmc_read_blocks(&card, big + 4, 0, 1);
printk("[sdmmc_read_blocks] buffer + 4 bytes = %s\n", err(ret));
ret = sdmmc_read_blocks(&card, big, card.block_count - 1, 1);
printk("[sdmmc_read_blocks] the last block (%u) = %s\n", card.block_count - 1, err(ret));
ret = sdmmc_read_blocks(&card, big, card.block_count, 1);
printk("[sdmmc_read_blocks] one block beyond the end = %s\n", err(ret));
}
/* writes the last block back with the content it has: the card is not changed */
static void demo_sdmmc_write_blocks(void)
{
static uint8_t before[512] __aligned(64), after[512] __aligned(64);
uint32_t last = card.block_count - 1;
int ret;
ret = sdmmc_read_blocks(&card, before, last, 1);
printk("[sdmmc_write_blocks] read the last block (%u) = %s\n", last, err(ret));
ret = sdmmc_write_blocks(&card, before, last, 1);
printk("[sdmmc_write_blocks] write it back = %s\n", err(ret));
ret = sdmmc_ioctl(&card, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[sdmmc_write_blocks] sync = %s\n", err(ret));
ret = sdmmc_read_blocks(&card, after, last, 1);
printk("[sdmmc_write_blocks] read again = %s, %s\n", err(ret),
memcmp(before, after, sizeof(before)) == 0 ? "same data" : "DIFFERENT data");
ret = sdmmc_write_blocks(&card, before, card.block_count, 1);
printk("[sdmmc_write_blocks] one block beyond the end = %s\n", err(ret));
}
/* the card is powered off; the disk layer below identifies it again */
static void demo_sdmmc_deinit(void)
{
int ret = sdmmc_ioctl(&card, DISK_IOCTL_CTRL_DEINIT, NULL);
printk("[sdmmc_ioctl] DISK_IOCTL_CTRL_DEINIT = %s\n", err(ret));
}
/* ---------------------------------------------------------------- disk access */
static uint32_t sector_count, sector_size;
static void demo_disk_access_init(void)
{
int64_t t0 = k_uptime_get();
int ret = disk_access_init(DISK_NAME);
printk("[disk_access_init] \"%s\" = %s, %lld ms\n", DISK_NAME, err(ret), k_uptime_get() - t0);
ret = disk_access_init("NODISK");
printk("[disk_access_init] \"NODISK\" = %s\n", err(ret));
}
static void demo_disk_access_status(void)
{
printk("[disk_access_status] \"%s\" = %d (0 = DISK_STATUS_OK)\n", DISK_NAME,
disk_access_status(DISK_NAME));
printk("[disk_access_status] \"NODISK\" = %d\n", disk_access_status("NODISK"));
}
static void demo_disk_access_ioctl(void)
{
uint32_t erase = 0;
int ret;
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_GET_SECTOR_COUNT, &sector_count);
printk("[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = %s: %u\n", err(ret),
sector_count);
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_GET_SECTOR_SIZE, &sector_size);
printk("[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = %s: %u\n", err(ret), sector_size);
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_GET_ERASE_BLOCK_SZ, &erase);
printk("[disk_access_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = %s: %u sectors\n", err(ret),
erase);
printk("[disk_access_ioctl] capacity %u MiB\n",
(uint32_t)(((uint64_t)sector_count * sector_size) >> 20));
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[disk_access_ioctl] DISK_IOCTL_CTRL_SYNC = %s\n", err(ret));
}
static void demo_disk_access_read(void)
{
uint32_t n = 1024 * 1024 / sector_size;
int64_t t0;
int ret;
ret = disk_access_read(DISK_NAME, big, 0, 1);
printk("[disk_access_read] sector 0 = %s, boot signature %02x %02x\n", err(ret),
big[510], big[511]);
t0 = k_uptime_get();
ret = disk_access_read(DISK_NAME, big, 0, n);
printk("[disk_access_read] %u sectors (1 MiB), aligned buffer = %s, %lld ms\n", n,
err(ret), k_uptime_get() - t0);
t0 = k_uptime_get();
ret = disk_access_read(DISK_NAME, big + 4, 0, n);
printk("[disk_access_read] %u sectors (1 MiB), buffer + 4 bytes = %s, %lld ms\n", n,
err(ret), k_uptime_get() - t0);
ret = disk_access_read(DISK_NAME, big, sector_count - 1, 1);
printk("[disk_access_read] the last sector (%u) = %s\n", sector_count - 1, err(ret));
ret = disk_access_read(DISK_NAME, big, sector_count, 1);
printk("[disk_access_read] one sector beyond the end = %s\n", err(ret));
}
/* writes the last sector back with the data it has: the card is not changed */
static void demo_disk_access_write(void)
{
static uint8_t before[512] __aligned(64), after[512] __aligned(64);
uint32_t last = sector_count - 1;
int ret;
ret = disk_access_read(DISK_NAME, before, last, 1);
printk("[disk_access_write] read the last sector (%u) = %s\n", last, err(ret));
ret = disk_access_write(DISK_NAME, before, last, 1);
printk("[disk_access_write] write it back = %s\n", err(ret));
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_SYNC, NULL);
printk("[disk_access_write] sync = %s\n", err(ret));
ret = disk_access_read(DISK_NAME, after, last, 1);
printk("[disk_access_write] read again = %s, %s\n", err(ret),
memcmp(before, after, sizeof(before)) == 0 ? "same data" : "DIFFERENT data");
ret = disk_access_write(DISK_NAME, before, sector_count, 1);
printk("[disk_access_write] one sector beyond the end = %s\n", err(ret));
}
/* a deinit powers the card off; the next init identifies it again */
static void demo_disk_access_deinit(void)
{
int ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_DEINIT, NULL);
printk("[disk_access_ioctl] DISK_IOCTL_CTRL_DEINIT = %s\n", err(ret));
printk("[disk_access_status] after the deinit = %d (1 = DISK_STATUS_UNINIT)\n",
disk_access_status(DISK_NAME));
ret = disk_access_ioctl(DISK_NAME, DISK_IOCTL_CTRL_INIT, NULL);
printk("[disk_access_ioctl] DISK_IOCTL_CTRL_INIT = %s\n", err(ret));
ret = disk_access_read(DISK_NAME, big, 0, 1);
printk("[disk_access_read] sector 0 after the init = %s, %02x %02x\n", err(ret),
big[510], big[511]);
}
/* ---------------------------------------------------------------- file system */
static FATFS fat_fs;
static struct fs_mount_t mp = {
.type = FS_FATFS,
.fs_data = &fat_fs,
.mnt_point = MOUNT_POINT,
/* never format a card that does not mount */
.flags = FS_MOUNT_FLAG_NO_FORMAT,
};
static void demo_fs_mount(void)
{
int ret = fs_mount(&mp);
printk("[fs_mount] %s = %s\n", MOUNT_POINT, err(ret));
ret = fs_mount(&mp);
printk("[fs_mount] the same mount point again = %s\n", err(ret));
}
static void demo_fs_readmount(void)
{
const char *name;
int index = 0;
while (fs_readmount(&index, &name) == 0) {
printk("[fs_readmount] mount point %d: %s\n", index - 1, name);
}
}
static void demo_fs_statvfs(void)
{
struct fs_statvfs sv;
int ret = fs_statvfs(MOUNT_POINT, &sv);
printk("[fs_statvfs] %s = %s: block size %lu, fragment size %lu, %lu blocks, %lu free\n",
MOUNT_POINT, err(ret), sv.f_bsize, sv.f_frsize, sv.f_blocks, sv.f_bfree);
printk("[fs_statvfs] %u MiB total, %u MiB free\n",
(uint32_t)(((uint64_t)sv.f_blocks * sv.f_frsize) >> 20),
(uint32_t)(((uint64_t)sv.f_bfree * sv.f_frsize) >> 20));
}
static void demo_fs_dir_t_init(void)
{
struct fs_dir_t dir;
memset(&dir, 0xff, sizeof(dir));
fs_dir_t_init(&dir);
printk("[fs_dir_t_init] mp = %p, dirp = %p\n", dir.mp, dir.dirp);
}
static void demo_fs_file_t_init(void)
{
struct fs_file_t f;
memset(&f, 0xff, sizeof(f));
fs_file_t_init(&f);
printk("[fs_file_t_init] mp = %p, filep = %p, flags = %u\n", f.mp, f.filep, f.flags);
}
static void demo_fs_opendir(void)
{
struct fs_dir_t dir;
int ret;
fs_dir_t_init(&dir);
ret = fs_opendir(&dir, MOUNT_POINT);
printk("[fs_opendir] %s = %s\n", MOUNT_POINT, err(ret));
if (ret == 0) {
fs_closedir(&dir);
}
ret = fs_opendir(&dir, MOUNT_POINT "/NODIR");
printk("[fs_opendir] a directory that is not there = %s\n", err(ret));
}
/* the first entries of the root directory; an empty name ends the listing */
static void demo_fs_readdir(void)
{
struct fs_dir_t dir;
struct fs_dirent ent;
int ret, n = 0;
fs_dir_t_init(&dir);
ret = fs_opendir(&dir, MOUNT_POINT);
if (ret != 0) {
printk("[fs_readdir] fs_opendir = %s\n", err(ret));
return;
}
while (n < 6) {
ret = fs_readdir(&dir, &ent);
if (ret != 0 || ent.name[0] == '\0') {
printk("[fs_readdir] end of the directory = %s\n", err(ret));
break;
}
printk("[fs_readdir] %-4s %10u %s\n",
ent.type == FS_DIR_ENTRY_DIR ? "dir" : "file", (uint32_t)ent.size, ent.name);
n++;
}
fs_closedir(&dir);
}
static void demo_fs_closedir(void)
{
struct fs_dir_t dir;
int ret;
fs_dir_t_init(&dir);
ret = fs_opendir(&dir, MOUNT_POINT);
if (ret != 0) {
printk("[fs_closedir] fs_opendir = %s\n", err(ret));
return;
}
ret = fs_closedir(&dir);
printk("[fs_closedir] = %s\n", err(ret));
}
static void demo_fs_mkdir(void)
{
struct fs_dirent st;
int ret;
ret = fs_mkdir(TEST_DIR);
printk("[fs_mkdir] %s = %s\n", TEST_DIR, err(ret));
ret = fs_stat(TEST_DIR, &st);
printk("[fs_mkdir] fs_stat = %s: %s, type %s\n", err(ret), st.name,
st.type == FS_DIR_ENTRY_DIR ? "dir" : "file");
ret = fs_mkdir(TEST_DIR);
printk("[fs_mkdir] the same directory again = %s\n", err(ret));
}
static const char text[] = "Written by sd_api on the YuzukiNeko\n";
static void demo_fs_open(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_CREATE | FS_O_RDWR);
printk("[fs_open] %s, FS_O_CREATE | FS_O_RDWR = %s\n", TEST_FILE, err(ret));
if (ret == 0) {
fs_close(&f);
}
ret = fs_open(&f, MOUNT_POINT "/NOFILE.TXT", FS_O_READ);
printk("[fs_open] a file that is not there, FS_O_READ = %s\n", err(ret));
ret = fs_open(&f, "/NOMOUNT:/A.TXT", FS_O_READ);
printk("[fs_open] a path with no mount point = %s\n", err(ret));
}
static void demo_fs_write(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_WRITE | FS_O_TRUNC);
if (ret != 0) {
printk("[fs_write] fs_open = %s\n", err(ret));
return;
}
ret = fs_write(&f, text, sizeof(text) - 1);
printk("[fs_write] %u bytes = %d\n", (unsigned int)sizeof(text) - 1, ret);
ret = fs_write(&f, text, sizeof(text) - 1);
printk("[fs_write] the same text again = %d\n", ret);
fs_close(&f);
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret == 0) {
ret = fs_write(&f, text, 1);
printk("[fs_write] on a file opened with FS_O_READ = %s\n", err(ret));
fs_close(&f);
}
}
static void demo_fs_sync(void)
{
struct fs_file_t f;
struct fs_dirent st;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_WRITE | FS_O_APPEND);
if (ret != 0) {
printk("[fs_sync] fs_open = %s\n", err(ret));
return;
}
fs_write(&f, text, sizeof(text) - 1);
ret = fs_sync(&f);
printk("[fs_sync] = %s\n", err(ret));
/* the size is on the card now, while the file is still open */
fs_stat(TEST_FILE, &st);
printk("[fs_sync] fs_stat while the file is open: %u bytes (3 * %u)\n",
(uint32_t)st.size, (unsigned int)sizeof(text) - 1);
fs_close(&f);
}
static void demo_fs_seek(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_seek] fs_open = %s\n", err(ret));
return;
}
ret = fs_seek(&f, 10, FS_SEEK_SET);
printk("[fs_seek] 10 from the start = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
ret = fs_seek(&f, 5, FS_SEEK_CUR);
printk("[fs_seek] 5 from here = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
ret = fs_seek(&f, -4, FS_SEEK_END);
printk("[fs_seek] -4 from the end = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
fs_close(&f);
}
static void demo_fs_tell(void)
{
struct fs_file_t f;
char buf[8];
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_tell] fs_open = %s\n", err(ret));
return;
}
printk("[fs_tell] after fs_open = %ld\n", (long)fs_tell(&f));
fs_read(&f, buf, sizeof(buf));
printk("[fs_tell] after reading %u bytes = %ld\n", (unsigned int)sizeof(buf),
(long)fs_tell(&f));
fs_close(&f);
}
static void demo_fs_read(void)
{
struct fs_file_t f;
char back[sizeof(text)] = { 0 };
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_read] fs_open = %s\n", err(ret));
return;
}
ret = fs_read(&f, back, sizeof(text) - 1);
printk("[fs_read] %d bytes: %s", ret, back);
fs_seek(&f, 0, FS_SEEK_END);
ret = fs_read(&f, back, sizeof(back) - 1);
printk("[fs_read] at the end of the file = %d\n", ret);
fs_close(&f);
}
static void demo_fs_truncate(void)
{
struct fs_file_t f;
struct fs_dirent st;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_RDWR);
if (ret != 0) {
printk("[fs_truncate] fs_open = %s\n", err(ret));
return;
}
ret = fs_truncate(&f, 10);
printk("[fs_truncate] to 10 bytes = %s, now at %ld\n", err(ret), (long)fs_tell(&f));
fs_sync(&f);
fs_stat(TEST_FILE, &st);
printk("[fs_truncate] size %u bytes\n", (uint32_t)st.size);
ret = fs_truncate(&f, 100);
fs_sync(&f);
fs_stat(TEST_FILE, &st);
printk("[fs_truncate] extended to 100 bytes = %s, size %u bytes\n", err(ret),
(uint32_t)st.size);
fs_close(&f);
}
static void demo_fs_close(void)
{
struct fs_file_t f;
int ret;
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_READ);
if (ret != 0) {
printk("[fs_close] fs_open = %s\n", err(ret));
return;
}
ret = fs_close(&f);
printk("[fs_close] = %s\n", err(ret));
ret = fs_close(&f);
printk("[fs_close] the same file again = %s\n", err(ret));
}
static void demo_fs_stat(void)
{
struct fs_dirent st;
int ret;
ret = fs_stat(TEST_FILE, &st);
printk("[fs_stat] %s = %s: name %s, type %s, size %u\n", TEST_FILE, err(ret), st.name,
st.type == FS_DIR_ENTRY_DIR ? "dir" : "file", (uint32_t)st.size);
ret = fs_stat(MOUNT_POINT "/NOFILE.TXT", &st);
printk("[fs_stat] a file that is not there = %s\n", err(ret));
}
static void demo_fs_rename(void)
{
struct fs_file_t f;
struct fs_dirent st;
int ret;
ret = fs_rename(TEST_FILE, TEST_FILE2);
printk("[fs_rename] %s to %s = %s\n", TEST_FILE, TEST_FILE2, err(ret));
ret = fs_stat(TEST_FILE, &st);
printk("[fs_rename] old name: fs_stat = %s\n", err(ret));
ret = fs_stat(TEST_FILE2, &st);
printk("[fs_rename] new name: fs_stat = %s, %u bytes\n", err(ret), (uint32_t)st.size);
/* a new 4 byte file renamed onto a name that exists: the old file there is replaced */
fs_file_t_init(&f);
if (fs_open(&f, TEST_FILE, FS_O_CREATE | FS_O_WRITE) == 0) {
fs_write(&f, "abcd", 4);
fs_close(&f);
}
ret = fs_rename(TEST_FILE, TEST_FILE2);
printk("[fs_rename] onto a name that exists = %s\n", err(ret));
ret = fs_stat(TEST_FILE2, &st);
printk("[fs_rename] the new name now has %u bytes (fs_stat = %s)\n", (uint32_t)st.size,
err(ret));
ret = fs_rename(TEST_FILE, MOUNT_POINT "/F101OLD.TXT");
printk("[fs_rename] a file that is not there = %s\n", err(ret));
}
static void demo_fs_unlink(void)
{
struct fs_dirent st;
int ret;
ret = fs_unlink(TEST_FILE2);
printk("[fs_unlink] %s = %s\n", TEST_FILE2, err(ret));
ret = fs_stat(TEST_FILE2, &st);
printk("[fs_unlink] fs_stat afterwards = %s\n", err(ret));
ret = fs_unlink(TEST_DIR);
printk("[fs_unlink] the empty directory %s = %s\n", TEST_DIR, err(ret));
ret = fs_unlink(TEST_FILE2);
printk("[fs_unlink] a file that is not there = %s\n", err(ret));
}
/* a 1 MiB file: write, read back and compare, through the FAT driver */
static uint8_t pattern[64 * 1024] __aligned(64);
static void demo_fs_speed(void)
{
struct fs_file_t f;
int64_t t0;
int ret, bad = 0;
for (size_t i = 0; i < sizeof(pattern); i++) {
pattern[i] = (uint8_t)(i * 7 + 3);
}
fs_file_t_init(&f);
ret = fs_open(&f, TEST_FILE, FS_O_CREATE | FS_O_RDWR | FS_O_TRUNC);
if (ret != 0) {
printk("[speed] fs_open = %s\n", err(ret));
return;
}
t0 = k_uptime_get();
for (int i = 0; i < 16; i++) {
ret = fs_write(&f, pattern, sizeof(pattern));
if (ret != sizeof(pattern)) {
break;
}
}
fs_sync(&f);
printk("[speed] wrote 1 MiB in 64 KiB pieces = %d, %lld ms\n", ret, k_uptime_get() - t0);
fs_seek(&f, 0, FS_SEEK_SET);
t0 = k_uptime_get();
for (int i = 0; i < 16; i++) {
memset(big, 0, sizeof(pattern));
ret = fs_read(&f, big, sizeof(pattern));
if (ret != sizeof(pattern) || memcmp(big, pattern, sizeof(pattern)) != 0) {
bad++;
}
}
printk("[speed] read it back, %d piece(s) differ, %lld ms\n", bad, k_uptime_get() - t0);
fs_close(&f);
fs_unlink(TEST_FILE);
}
static void demo_fs_unmount(void)
{
int ret = fs_unmount(&mp);
printk("[fs_unmount] %s = %s\n", MOUNT_POINT, err(ret));
ret = fs_unmount(&mp);
printk("[fs_unmount] the same mount point again = %s\n", err(ret));
}
int main(void)
{
if (!device_is_ready(sdhc)) {
printk("sdhc not ready\n");
return 0;
}
demo_sd_is_card_present();
if (!sd_is_card_present(sdhc)) {
printk("no card in the slot, nothing more to do\n");
return 0;
}
demo_sd_init();
demo_sdmmc_ioctl();
demo_sdmmc_read_blocks();
demo_sdmmc_write_blocks();
demo_sdmmc_deinit();
demo_disk_access_init();
demo_disk_access_status();
demo_disk_access_ioctl();
demo_disk_access_read();
demo_disk_access_write();
demo_disk_access_deinit();
demo_fs_mount();
demo_fs_readmount();
demo_fs_statvfs();
demo_fs_dir_t_init();
demo_fs_file_t_init();
demo_fs_opendir();
demo_fs_readdir();
demo_fs_closedir();
demo_fs_mkdir();
demo_fs_open();
demo_fs_write();
demo_fs_sync();
demo_fs_seek();
demo_fs_tell();
demo_fs_read();
demo_fs_truncate();
demo_fs_close();
demo_fs_stat();
demo_fs_rename();
demo_fs_unlink();
demo_fs_speed();
demo_fs_unmount();
printk("sd_api done\n");
return 0;
}
Terminal window
. ~/f101-zephyr/env.sh
cd ~/f101-zephyr
west build -b yuzukineko -d build/sd-api examples/sd_api

For how to download and run, see Build and run. Insert the TF card into the card slot before running. When there is no card in the slot, after sd_is_card_present the program prints no card in the slot, nothing more to do and exits; this path was not run on the board, there was always a card on the board.

The complete output on the console UART1, starting from the Zephyr boot banner:

*** Booting Zephyr OS build b6f3dfe1a1f4 ***
[sd_is_card_present] controller = 1 (1 = a card is in the slot)
[sd_is_card_present] NULL controller = 0
[00:00:00.493,000] <inf> display_sunxi: 1024x768 @ 60 Hz, fb 0x40150b40, video plane 0
[sd_init] sd_init = 0, 1016 ms
[sd_init] status 2 (2 = initialized), type 0 (0 = SD memory card)
[sd_init] OCR c0ff8000, RCA 0001, SD spec version flag 4
[sd_init] 15728640 blocks of 512 bytes, bus width 4 bit, clock 50000000 Hz, timing mode 1
[sd_init] sd_init with a NULL controller = -ENODEV
[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = 0: 15728640
[sdmmc_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = 0: 512
[sdmmc_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = 0: 512
[sdmmc_ioctl] DISK_IOCTL_CTRL_SYNC = 0
[sdmmc_ioctl] DISK_IOCTL_RESERVED (not handled) = -ENOTSUP
[sdmmc_read_blocks] block 0 = 0, boot signature 55 aa
[sdmmc_read_blocks] 2048 blocks (1 MiB) = 0, 44 ms
[sdmmc_read_blocks] buffer + 4 bytes = 0
[sdmmc_read_blocks] the last block (15728639) = 0
[sdmmc_read_blocks] one block beyond the end = -EINVAL
[sdmmc_write_blocks] read the last block (15728639) = 0
[sdmmc_write_blocks] write it back = 0
[sdmmc_write_blocks] sync = 0
[sdmmc_write_blocks] read again = 0, same data
[sdmmc_write_blocks] one block beyond the end = -EINVAL
[sdmmc_ioctl] DISK_IOCTL_CTRL_DEINIT = 0
[disk_access_init] "SD" = 0, 1015 ms
[disk_access_init] "NODISK" = -EINVAL
[disk_access_status] "SD" = 0 (0 = DISK_STATUS_OK)
[disk_access_status] "NODISK" = -22
[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_COUNT = 0: 15728640
[disk_access_ioctl] DISK_IOCTL_GET_SECTOR_SIZE = 0: 512
[disk_access_ioctl] DISK_IOCTL_GET_ERASE_BLOCK_SZ = 0: 512 sectors
[disk_access_ioctl] capacity 7680 MiB
[disk_access_ioctl] DISK_IOCTL_CTRL_SYNC = 0
[disk_access_read] sector 0 = 0, boot signature 55 aa
[disk_access_read] 2048 sectors (1 MiB), aligned buffer = 0, 44 ms
[disk_access_read] 2048 sectors (1 MiB), buffer + 4 bytes = 0, 199 ms
[disk_access_read] the last sector (15728639) = 0
[disk_access_read] one sector beyond the end = -EINVAL
[disk_access_write] read the last sector (15728639) = 0
[disk_access_write] write it back = 0
[disk_access_write] sync = 0
[disk_access_write] read again = 0, same data
[disk_access_write] one sector beyond the end = -EINVAL
[disk_access_ioctl] DISK_IOCTL_CTRL_DEINIT = 0
[disk_access_status] after the deinit = 1 (1 = DISK_STATUS_UNINIT)
[disk_access_ioctl] DISK_IOCTL_CTRL_INIT = 0
[disk_access_read] sector 0 after the init = 0, 55 aa
[fs_mount] /SD: = 0
[fs_mount] the same mount point again = -EBUSY
[fs_readmount] mount point 0: /SD:
[fs_statvfs] /SD: = 0: block size 512, fragment size 32768, 245631 blocks, 188896 free
[fs_statvfs] 7675 MiB total, 5903 MiB free
[fs_dir_t_init] mp = 0, dirp = 0
[fs_file_t_init] mp = 0, filep = 0, flags = 0
[fs_opendir] /SD: = 0
[fs_opendir] a directory that is not there = -ENOENT
[fs_readdir] dir 0 System Volume Information
[fs_readdir] file 8689914 badapple.h264
[fs_readdir] dir 0 arduino
[fs_readdir] file 0 swap.bin
[fs_readdir] file 44907671 video720p.h264
[fs_readdir] file 32768 Pokemon-Red.sav
[fs_closedir] = 0
[fs_mkdir] /SD:/F101DIR = 0
[fs_mkdir] fs_stat = 0: F101DIR, type dir
[fs_mkdir] the same directory again = -EEXIST
[fs_open] /SD:/F101API.TXT, FS_O_CREATE | FS_O_RDWR = 0
[fs_open] a file that is not there, FS_O_READ = -ENOENT
[fs_open] a path with no mount point = -ENOENT
[fs_write] 36 bytes = 36
[fs_write] the same text again = 36
[fs_write] on a file opened with FS_O_READ = -EACCES
[fs_sync] = 0
[fs_sync] fs_stat while the file is open: 108 bytes (3 * 36)
[fs_seek] 10 from the start = 0, now at 10
[fs_seek] 5 from here = 0, now at 15
[fs_seek] -4 from the end = 0, now at 104
[fs_tell] after fs_open = 0
[fs_tell] after reading 8 bytes = 8
[fs_read] 36 bytes: Written by sd_api on the YuzukiNeko
[fs_read] at the end of the file = 0
[fs_truncate] to 10 bytes = 0, now at 10
[fs_truncate] size 10 bytes
[fs_truncate] extended to 100 bytes = 0, size 100 bytes
[fs_close] = 0
[fs_close] the same file again = 0
[fs_stat] /SD:/F101API.TXT = 0: name F101API.TXT, type file, size 100
[fs_stat] a file that is not there = -ENOENT
[fs_rename] /SD:/F101API.TXT to /SD:/F101NEW.TXT = 0
[fs_rename] old name: fs_stat = -ENOENT
[fs_rename] new name: fs_stat = 0, 100 bytes
[fs_rename] onto a name that exists = 0
[fs_rename] the new name now has 4 bytes (fs_stat = 0)
[fs_rename] a file that is not there = -ENOENT
[fs_unlink] /SD:/F101NEW.TXT = 0
[fs_unlink] fs_stat afterwards = -ENOENT
[fs_unlink] the empty directory /SD:/F101DIR = 0
[fs_unlink] a file that is not there = -ENOENT
[speed] wrote 1 MiB in 64 KiB pieces = 65536, 90 ms
[speed] read it back, 0 piece(s) differ, 59 ms
[fs_unmount] /SD: = 0
[fs_unmount] the same mount point again = -EINVAL
sd_api done
[00:00:04.050,000] <err> fs: file system already mounted!!
[00:00:04.080,000] <err> fs: directory open error (-2)
[00:00:04.248,000] <err> fs: failed to create directory (-17)
[00:00:04.263,000] <err> fs: file open error (-2)
[00:00:04.268,000] <err> fs: mount point not found!!
[00:00:04.288,000] <err> fs: file write error (-13)
[00:00:04.435,000] <err> fs: failed to rename file or dir (-2)
[00:00:04.470,000] <err> fs: failed to unlink path (-2)
[00:00:04.645,000] <err> fs: fs not mounted (mp == 0x403211a0)

The few <err> fs: ... lines at the end are the logs the filesystem layer prints for the several deliberately failing calls above (mounting twice, opening a directory that does not exist, creating a directory twice, opening a file that does not exist, a path with no mount point, writing a read-only file, renaming and deleting files that do not exist, unmounting after unmounting); they are output late and come after the program output. The return values in the program are what the application should look at. The display_sunxi on the second line is a log the display driver prints while it finishes initializing in the background, and is unrelated to this component.