API GUIDE / MSC / MIDDLEWARE

MSC Middleware 接口

msc mid api

API 头文件:

  • code/middleware/msc/include/im_msc_device.h
  • code/middleware/msc/include/im_msc.h

本文只说明这两个头文件中的公开 API。code/middleware/msc 下的内部函数、协议层函数和 HAL 接口不属于本文范围。


1. 模块概览

MSC 中间件在 MSC 控制器驱动之上提供统一的 SD/eMMC/SDIO 访问能力。BSP 通过设备层注册控制器和板级参数;应用启动设备后,可使用文件系统、块设备、独占访问或 SDIO function 接口。控制器寄存器、SD/MMC 协议状态机、分区表解析和 HAL 实现不属于本文范围。

MSC 中间件主要提供以下能力:

  • 通过 im_msc_device_*() 管理设备注册、启动、停止、注销、卡检测和 SDIO 上下文。
  • 通过 im_msc_blk_*() 和 im_msc_part_*() 访问 SD/eMMC 整盘和分区块设备。
  • 通过 im_storage 解析 MBR/GPT,并把分区发布为 mscblk<ctrl>p<idx>。
  • 通过 DFS MSC adapter 把块设备接入文件系统路径。
  • 通过 IO claim、消费者保护和介质事件接口协调文件系统、裸块访问与介质变化。
  • 通过 SDIO function 接口访问纯 SDIO 设备,不经过块设备路径。

1.1 头文件选择

使用方式 包含
设备生命周期、SDIO、块设备、claim 和事件 <im_msc.h>
只负责注册和启动控制器 <im_msc_device.h>
文件系统路径访问 <dfs_fs.h>、<dfs_posix.h>,并按需包含 <filesystem_init.h>

im_msc.h 已包含 im_msc_device.h。普通应用直接包含 im_msc.h 即可,不要依赖 MSC 内部头文件。

1.2 使用约定

  • 返回 error_code_t 的接口以 IM_OK 表示成功。失败时不要使用本次调用写出的数据,除非接口另有说明。
  • im_msc_device_register() 只复制配置,不访问硬件。注册成功后还需要启动控制器。
  • im_msc_device_get()、im_msc_blk_get()、im_msc_part_get() 和 SDIO 上下文返回借用指针,不能 free(),也不能长期缓存。
  • stop()、介质移除、模式重配或注销后,旧的块对象、分区对象和 SDIO 借用指针都会失效。
  • 块 API 的 start 和 claim API 的 lba 都是逻辑块号(LBA),不是字节偏移。字节数按 LBA × logical_block_size 计算。
  • 擦除还受 geometry.erase_granularity_blocks 约束;粒度为 0 表示介质不支持擦除。
  • 生命周期、块 I/O、claim、SDIO 和文件操作都只能在任务上下文调用,不能在 ISR 中调用。
  • 介质事件回调在 MSC 启动/停止路径的任务上下文中同步执行。回调应短小,不要在回调中等待用户交互或执行长时间文件操作。
  • SD/eMMC 的 MBR/GPT 分区最多发布 16 个,名称为 mscblk<ctrl>p<idx>;没有分区表时,整盘作为 p0 发布。
  • 文件系统挂载期间不要直接写块设备。需要裸块访问时,先关闭文件、卸载文件系统,再按 4.5.2 节获取 claim。

2. 典型流程

MSC 中间件把一次设备使用分成以下层次:

  1. 注册控制器:im_msc_device_register() 保存板级配置和控制器编号,但不操作硬件。
  2. 启动控制器:im_msc_device_start() 配置总线、供电/复位、识别卡并发布块设备。
  3. 使用块设备:通过 im_msc_blk_*() 访问 SD/eMMC 的整盘或分区。
  4. 文件系统访问:块设备通常注册为 mscblk<ctrl>,分区注册为 mscblk<ctrl>p<idx>,DFS/elmfat 可自动挂载为 /<块设备名>。
  5. 独占访问:需要绕过文件系统做裸块读写时,先获取 im_msc_io_claim_t,再使用 claim API。
  6. SDIO 访问:SDIO 配置不走块设备接口,通过 im_msc_device_sdio_init() 和 im_msc_device_sdio_function_get() 取得 SDIO 上下文。

典型 eMMC/SD 流程:

im_msc_device_register()
        |
        v
im_msc_device_start()
        |
        +--> im_msc_event_t: IM_MSC_EVENT_AVAILABLE
        +--> 发布 mscblkX / mscblkXpN
        |
        v
im_msc_blk_get() / im_msc_part_get()
        |
        v
im_msc_blk_read() / im_msc_blk_write() / im_msc_blk_erase()
        |
        v
im_msc_device_stop()
        |
        +--> IM_MSC_EVENT_REMOVING
        +--> IM_MSC_EVENT_REMOVED
        |
        v
im_msc_device_unregister()

设备状态机:

UNREGISTERED
    |
    v
REGISTERED --start--> STARTING --成功--> READY --stop--> STOPPING --成功--> REGISTERED
    ^                    |                  |                 |
    |                    +----失败-----> FAILED <------------+
    |                                       |
    +----------------unregister-------------+

3. 配置和关键类型

3.1 卡类型

typedef enum {
    MSC_CARD_NONE = 0,
    MSC_CARD_EMMC,
    MSC_CARD_SD,
    MSC_CARD_SDIO,
} msc_card_type_t;
值 含义
MSC_CARD_NONE 没有识别到卡
MSC_CARD_EMMC eMMC 介质
MSC_CARD_SD SD 存储卡
MSC_CARD_SDIO SDIO 设备

3.2 im_msc_card_info_t

typedef struct {
    msc_card_type_t type;
    uint32_t block_size;
    uint64_t block_count;
    bool read_only;
    bool erased_byte_valid;
    uint8_t erased_byte;
    uint32_t raw_cid[4];
} im_msc_card_info_t;
字段 说明
type 卡类型
block_size 逻辑块大小,通常为 512 字节
block_count 整个介质的逻辑块总数
read_only 介质是否只读;只读介质写入会失败
erased_byte_valid eMMC 擦除后的字节值是否有效
erased_byte 有效时表示擦除后的数据模式,通常为 0xFF 或 0x00
raw_cid 原始 CID,可用于识别当前介质;换卡后通常变化

3.3 块设备信息

typedef struct {
    im_storage_block_geometry_t geometry;
    bool available;
    bool partition;
} im_msc_blk_info_t;

geometry 包含:

字段 说明
logical_block_size 逻辑块大小,LBA 以该大小为单位
block_count 当前块设备或分区的块数
erase_granularity_blocks 擦除对齐粒度;为 0 表示不支持擦除
flags 块设备标志,由存储层定义

available == true 表示调用时介质可用;partition == true 表示该对象是分区,否则是整盘块设备。

3.4 设备类型、状态和介质状态

typedef enum {
    IM_MSC_DEVICE_SD = 0,
    IM_MSC_DEVICE_EMMC,
    IM_MSC_DEVICE_SDIO,
} im_msc_device_type_t;

typedef enum {
    IM_MSC_DEVICE_UNREGISTERED = 0,
    IM_MSC_DEVICE_REGISTERED,
    IM_MSC_DEVICE_STARTING,
    IM_MSC_DEVICE_READY,
    IM_MSC_DEVICE_STOPPING,
    IM_MSC_DEVICE_FAILED,
} im_msc_device_state_t;

typedef enum {
    IM_MSC_MEDIA_ABSENT = 0,
    IM_MSC_MEDIA_PROBING,
    IM_MSC_MEDIA_AVAILABLE,
    IM_MSC_MEDIA_REMOVING,
    IM_MSC_MEDIA_ERROR,
} im_msc_media_state_t;

应用通常只需要区分:

  • 只有 REGISTERED 或 FAILED 的设备可以再次 start() 或 unregister()。
  • READY 表示控制器已启动;SD 卡槽仍可能暂时没有卡。
  • 只有块设备处于可用状态时,im_msc_blk_get() 和块 I/O 才会成功。

3.5 速度模式和电压

typedef enum {
    IM_MSC_SPEED_LEGACY = 0,
    IM_MSC_SPEED_SD_HS,
    IM_MSC_SPEED_SD_SDR12,
    IM_MSC_SPEED_SD_SDR25,
    IM_MSC_SPEED_SD_SDR50,
    IM_MSC_SPEED_SD_SDR104,
    IM_MSC_SPEED_SD_DDR50,
    IM_MSC_SPEED_EMMC_HS,
    IM_MSC_SPEED_EMMC_DDR52,
    IM_MSC_SPEED_EMMC_HS200,
    IM_MSC_SPEED_MODE_COUNT,
} im_msc_speed_mode_t;

typedef enum {
    IM_MSC_SIGNAL_VOLTAGE_3V3 = 0,
    IM_MSC_SIGNAL_VOLTAGE_1V8,
    IM_MSC_SIGNAL_VOLTAGE_1V2,
} im_msc_signal_voltage_t;

平台约束:

  • G32S10 只支持 1-bit 和 4-bit 总线;配置 8-bit 会返回 IM_ERR_UNSUPPORTED。
  • SD 的 UHS 模式要求 4-bit,并需要板级 1.8V 切换能力。
  • eMMC 的 DDR52/HS200 要求 4-bit;板级需要 1.8V VCCQ。HS200 配置成 3.3V 时注册会返回 IM_ERR_UNSUPPORTED。
  • 速度模式最终仍由卡和板级能力协商,配置的是“目标模式”,不保证一定运行在该模式。

OCR 电压范围宏:

宏 含义
IM_MSC_OCR_165_195 1.65V~1.95V
IM_MSC_OCR_29_30 2.9V~3.0V
IM_MSC_OCR_30_31 3.0V~3.1V
IM_MSC_OCR_32_33 3.2V~3.3V
IM_MSC_OCR_33_34 3.3V~3.4V

3.6 当前 I/O 状态

typedef struct {
    im_msc_speed_mode_t speed_mode;
    uint8_t bus_width;
    uint32_t target_frequency_hz;
    uint32_t actual_frequency_hz;
    im_msc_signal_voltage_t signal_voltage;
} im_msc_io_state_t;

target_frequency_hz 是配置目标,actual_frequency_hz 是控制器实际应用频率。两者可能不同。

3.7 SD 配置

typedef struct {
    uint8_t bus_width;
    im_msc_speed_mode_t speed_mode;
    im_gpio_signal_t detect;
    im_gpio_signal_t voltage_1v8;
    im_gpio_pull_t detect_pull;
    hal_gpio_irq_trigger_t detect_trigger;
    uint32_t detect_debounce_us;
} im_msc_sd_config_t;
字段 说明
bus_width 1 或 4
speed_mode 目标速度模式
detect 卡检测 GPIO;无效信号表示不使用热插拔检测
voltage_1v8 1.8V 信号电压切换 GPIO;UHS 模式需要
detect_pull 卡检测引脚上下拉配置
detect_trigger 检测中断边沿;未填写时默认双边沿
detect_debounce_us 检测去抖时间;为 0 时中间件使用默认值

3.8 eMMC 配置

typedef struct {
    uint8_t bus_width;
    im_msc_speed_mode_t speed_mode;
    im_msc_signal_voltage_t io_voltage;
} im_msc_emmc_config_t;

io_voltage 是板级固定的 VCCQ 电压,不是运行时由 MSC 任意切换的电压。配置必须与实际电路一致。

3.9 SDIO 配置

typedef struct {
    uint8_t bus_width;
    bool high_speed;
    hal_msc_dma_mode_t dma_mode;
    im_gpio_signal_t irq_gpio;
    hal_gpio_irq_trigger_t irq_trigger;
} im_msc_sdio_config_t;
字段 说明
bus_width 1 或 4
high_speed 是否允许 SDIO High Speed
dma_mode HAL_MSC_DMA_PIO_POLL、HAL_MSC_DMA_SDMA_IRQ 或 HAL_MSC_DMA_ADMA2_IRQ
irq_gpio 有效时使用 GPIO 中断;无效时使用控制器原生 SDIO IRQ
irq_trigger GPIO IRQ 触发方式;使用有效 irq_gpio 时不能为 NONE

3.10 设备总配置

typedef struct {
    im_msc_device_type_t type;
    uint32_t ocr_avail;
    uint32_t max_frequency_hz;
    const msc_bus_pins_t *bus_pins;
    im_gpio_signal_t reset;
    im_gpio_signal_t power;
    union {
        im_msc_sd_config_t sd;
        im_msc_emmc_config_t emmc;
        im_msc_sdio_config_t sdio;
    } variant;
} im_msc_device_config_t;
字段 说明
type 选择 variant 中有效的成员
ocr_avail 支持的工作电压范围,至少填写一个合法 OCR 位;SDIO 配置还必须包含 3.3V 档(不能只声明 1.65V~1.95V)
max_frequency_hz 控制器最大目标频率,不能为 0
bus_pins 非 NULL 时由 MSC 申请、配置并在停止时释放 CLK/CMD/DAT;NULL 表示 BSP/pinmux 已经配置好总线
reset 复位 GPIO;无效信号表示不使用
power 电源控制 GPIO;无效信号表示不使用
variant.sd SD 专用配置
variant.emmc eMMC 专用配置
variant.sdio SDIO 专用配置

im_msc_device_register() 会把配置复制到中间件内部。传入的配置对象可以位于栈上,但其中的 bus_pins 数据会在注册时复制;不要把无效的临时指针继续保留给中间件。

3.11 SDIO 上下文

typedef struct {
    im_msc_device_t *device;
    struct msc_host *host;
    struct msc_card *card;
    im_msc_sdio_function_t *func0;
    im_msc_sdio_function_t *func1;
    uint32_t block_size;
    int bus_number;
} im_msc_device_sdio_t;

调用 im_msc_device_sdio_init() 前只需设置 bus_number。其他成员由中间件填充,且全部是借用对象,不能释放。

3.12 常见错误码

错误码 数值 常见原因
IM_OK 0 成功
IM_ERR_INVALID_PARAM 1 空指针、非法配置、LBA/长度越界、擦除未对齐
IM_ERR_NOT_READY 2 控制器未启动、卡尚未就绪、SDIO function 未准备好
IM_ERR_TIMEOUT 3 底层命令或传输超时
IM_ERR_BUSY 4 已启动、正在移除、已有 claim、消费者仍占用设备
IM_ERR_NO_MEMORY 5 分配设备、事件、claim 或 guard 对象失败
IM_ERR_NOT_FOUND 6 控制器未注册、分区/function 不存在、事件监听器不存在
IM_ERR_ALREADY_EXISTS 7 存储层对象已存在
IM_ERR_UNSUPPORTED 8 8-bit 总线、SDIO 调用了块接口、擦除不支持、模式不受支持
IM_ERR_ALREADY_REGISTERED 9 控制器、事件 cb+ctx 或 consumer guard 重复注册
IM_ERR_IO 10 底层读写、校验或设备错误
IM_ERR_NO_DEVICE 12 卡不存在、介质已移除、设备正在注销
IM_ERR_CROSS_DEVICE 14 claim 期间介质被替换或身份变化
IM_ERR_READ_ONLY 19 向只读介质写/擦除
IM_ERR_UNKNOWN 21 回调未返回有效 token 等未知失败
IM_ERR_HARDWARE 22 硬件错误
IM_ERR_INVALID_STATE 23 对象状态不允许当前操作

底层 HAL、SD/MMC 协议和文件系统还可能返回其他错误码,调用方应按非 0 统一处理。


4. API 参考

4.1 API 总览

im_msc_device.h

API 用途
im_msc_device_register() 注册一个 SD/eMMC/SDIO 控制器配置
im_msc_device_unregister() 注销设备并释放内部控制对象
im_msc_device_start() 启动控制器、识别卡并发布块设备
im_msc_device_stop() 停止控制器并撤销块设备
im_msc_device_start_all() 启动所有已注册但未就绪的设备
im_msc_device_deinit_all() 停止所有已就绪设备,但不注销
im_msc_device_get() 按控制器编号取得借用设备指针
im_msc_device_registered_config_get() 读取注册时的配置副本
im_msc_device_runtime_config_get() 读取当前运行配置
im_msc_device_sdio_function_get() 取得某个 SDIO function 的借用指针
im_msc_device_sdio_init() 绑定 SDIO 控制器并枚举 function 1
im_msc_device_sdio_deinit() 清除 SDIO 客户端上下文
im_msc_device_test_reconfigure() 停止、切换模式/频率/位宽/DMA,再启动
im_msc_device_ctrl_id_get() 从设备指针取得控制器编号
im_msc_device_io_state_get() 读取协商后的模式、位宽和频率

im_msc.h

API 用途
im_msc_card_info_get() 读取卡容量、块大小、只读和 CID 信息
im_msc_blk_get() 取得整盘块设备借用指针
im_msc_part_get() 按索引取得分区块设备借用指针
im_msc_blk_info_get() 读取块设备几何信息和可用状态
im_msc_blk_read() 从块设备读取若干逻辑块
im_msc_blk_write() 向块设备写入若干逻辑块
im_msc_blk_erase() 擦除块设备的一段逻辑块
im_msc_device_io_claim_acquire() 获取整个设备的独占裸块访问权
im_msc_io_claim_release() 释放独占访问权
im_msc_io_claim_card_info_get() 在 claim 内重新校验并读取卡信息
im_msc_io_claim_read() 在 claim 内按整盘 LBA 读取
im_msc_io_claim_write() 在 claim 内按整盘 LBA 写入
im_msc_io_claim_erase() 在 claim 内按整盘 LBA 擦除
im_msc_consumer_guard_register() 注册“独占访问前置检查/释放”守卫
im_msc_consumer_guard_unregister() 注销消费者守卫
im_msc_device_consumer_access_check() 检查当前设备是否被 claim 独占
im_msc_event_register() 注册全局介质事件回调
im_msc_event_unregister() 注销全局介质事件回调

4.2 设备生命周期 API

4.2.1 im_msc_device_register()

error_code_t im_msc_device_register(
    uint32_t ctrl_id, const im_msc_device_config_t *config);

作用: 把一个控制器的板级配置登记到 MSC 中间件。函数只保存配置,不启动时钟、不配置引脚、不识别卡。

参数:

参数 说明
ctrl_id MSC 控制器编号,必须已被 SoC 支持
config 配置对象;函数返回后调用方可以释放或复用该对象

返回:

  • IM_OK:注册成功。
  • IM_ERR_INVALID_PARAM:配置指针为空、OCR、类型、位宽、DMA 或 GPIO 配置非法,或总线引脚配置不完整。
  • IM_ERR_NO_DEVICE:当前 SoC/BSP 不存在该控制器编号。
  • IM_ERR_ALREADY_REGISTERED:该控制器已经注册。
  • IM_ERR_UNSUPPORTED:例如要求 8-bit、SDIO 仅声明 1.65V~1.95V 而没有 3.3V 档、HS200 与 3.3V VCCQ 冲突等。
  • IM_ERR_NO_MEMORY:无法分配控制对象。

注意事项:

  • 每个控制器只注册一次。
  • 当前 S107 SK-SLT 参考板把 eMMC 接在 MSC1;不能直接套用成 MSC0。
  • 如果 bus_pins == NULL,BSP 必须在启动前完成 CLK/CMD/DAT pinmux。
  • 传入的 bus_pins 需要覆盖配置位宽所需的全部 DAT 引脚。

4.2.2 im_msc_device_start()

error_code_t im_msc_device_start(uint32_t ctrl_id);

作用: 启动控制器并完成板上初始化,包括总线引脚、复位/电源、卡识别、块设备发布和分区发布。

返回:

  • IM_OK:控制器已进入 READY。
  • IM_ERR_NOT_FOUND:控制器未注册。
  • IM_ERR_BUSY:已经启动、正在启动或当前状态不允许再次启动。
  • HAL/协议层错误:引脚、时钟、供电、卡识别或块设备发布失败。

注意事项:

  • 对带卡检测 GPIO 的 SD 配置,启动成功时卡可能尚未插入;插入后由中间件检测线程继续发布介质。
  • 对 eMMC,启动过程会直接尝试识别介质。
  • 成功后可调用 im_msc_device_get()、im_msc_card_info_get()、im_msc_blk_get() 等接口。
  • 如果启动失败,状态可能进入 FAILED;通常可以修正硬件或配置后再次 start(),或在 FAILED 状态注销。

4.2.3 im_msc_device_stop()

error_code_t im_msc_device_stop(uint32_t ctrl_id);

作用: 停止控制器,撤销块设备/分区,停止卡检测,并关闭电源/复位。

返回:

  • IM_OK:已停止并回到 REGISTERED。
  • IM_ERR_NOT_FOUND:控制器未注册。
  • IM_ERR_NOT_READY:控制器尚未启动,或没有可停止的状态。
  • IM_ERR_BUSY:介质正在探测、移除或处于不安全的过渡状态。

注意事项:

  • 调用前应先释放所有 im_msc_io_claim_t、关闭文件系统和打开的块设备使用者。
  • 停止会导致借用块设备指针、SDIO function 指针以及已取得的 im_msc_blk_t 失效。
  • 块介质停止时会依次产生 IM_MSC_EVENT_REMOVING 和 IM_MSC_EVENT_REMOVED。
  • stop() 不会释放消费者守卫或事件监听器。

4.2.4 im_msc_device_unregister()

error_code_t im_msc_device_unregister(uint32_t ctrl_id);

作用: 注销控制器并释放中间件保存的设备对象。

前置条件: 状态必须是 REGISTERED 或 FAILED;不能直接注销 READY 设备。

返回:

  • IM_OK:注销成功。
  • IM_ERR_NOT_FOUND:控制器存在但未注册。
  • IM_ERR_NO_DEVICE:控制器编号不存在。
  • IM_ERR_BUSY:设备仍在运行、正在过渡,或存在未释放的 claim。

正确顺序:

(void)im_msc_device_stop(1U);
(void)im_msc_device_unregister(1U);

4.2.5 im_msc_device_start_all() 和 im_msc_device_deinit_all()

error_code_t im_msc_device_start_all(void);
error_code_t im_msc_device_deinit_all(void);

start_all() 遍历所有已注册且尚未处于 READY 的设备并逐个启动,返回遇到的第一个错误。

deinit_all() 只停止处于 READY 的设备,不会调用 unregister(),也返回第一个错误。

4.2.6 im_msc_device_get()

im_msc_device_t *im_msc_device_get(uint32_t ctrl_id);

作用: 根据控制器编号取得中间件设备对象。

返回: 已注册时返回借用指针;未注册或编号不存在时返回 NULL。

注意: 不能释放该指针;设备注销后立即失效。

4.2.7 im_msc_device_registered_config_get() 和 im_msc_device_runtime_config_get()

error_code_t im_msc_device_registered_config_get(
    uint32_t ctrl_id, im_msc_device_config_t *config_out);
error_code_t im_msc_device_runtime_config_get(
    uint32_t ctrl_id, im_msc_device_config_t *config_out);

作用:

  • registered_config_get() 返回注册时保存的配置。
  • runtime_config_get() 返回当前正在使用的配置;im_msc_device_test_reconfigure() 修改的也是运行配置。

返回: IM_OK、IM_ERR_NOT_FOUND 或 IM_ERR_INVALID_PARAM。

注意: 输出结构是配置副本;其中 bus_pins 仍指向中间件内部数据,只应读取,不能修改或长期脱离设备生命周期使用。


4.3 SDIO API

4.3.1 im_msc_device_sdio_function_get()

error_code_t im_msc_device_sdio_function_get(
    im_msc_device_t *device, uint8_t function_number,
    im_msc_sdio_function_t **function_out);

作用: 取得已枚举 SDIO function 的借用指针。function_number 从 1 开始,0 号 function 不能通过该接口取得。

返回:

  • IM_OK:成功,*function_out 指向借用对象。
  • IM_ERR_INVALID_PARAM:device/function_out 为空,或 function_number == 0。
  • IM_ERR_UNSUPPORTED:该控制器不是 SDIO 配置。
  • IM_ERR_NOT_READY:控制器尚未启动。
  • IM_ERR_NO_DEVICE:控制器已启动但当前没有 SDIO 卡。
  • IM_ERR_NOT_FOUND:指定 function 不存在。

注意: SDIO 驱动在停止设备前必须释放 IRQ 所有权;stop() 开始后该 function 指针不再有效。

4.3.2 im_msc_device_sdio_init()

error_code_t im_msc_device_sdio_init(im_msc_device_sdio_t *sdio);

作用: 绑定 MSC SDIO 控制器,必要时启动控制器,枚举 function 1,并填充 device/host/card/func0/func1/block_size。

调用前:

  • 把 sdio->bus_number 设置为 MSC 控制器编号。
  • 其他成员不需要初始化。
  • 调用方必须先给 SDIO 外设上电。

返回:

  • IM_OK:成功。
  • IM_ERR_INVALID_PARAM:sdio == NULL 或 bus_number < 0。
  • IM_ERR_NOT_FOUND:控制器未注册。
  • IM_ERR_UNSUPPORTED:控制器不是 SDIO 类型。
  • IM_ERR_NOT_READY:function 1 或 host 不可用。
  • IM_ERR_NO_DEVICE:启动过程中找不到 SDIO 卡。
  • 其他启动/探测错误。

注意:

  • 只能在任务上下文调用;内部可能启动控制器、执行 SDIO I/O 和等待任务完成。
  • 如果控制器已经注册但没有启动,本函数会自动启动它。
  • host/card/func0/func1 都是借用指针,不能释放或修改。
  • 当前接口实际枚举并要求 function 1 可用;应用业务 I/O 应通过对应 SDIO/WiFi 驱动的接口完成。

4.3.3 im_msc_device_sdio_deinit()

error_code_t im_msc_device_sdio_deinit(im_msc_device_sdio_t *sdio);

作用: 清空 SDIO 上下文中的借用指针和 block_size,但保留 bus_number。

返回: IM_OK;参数为空时返回 IM_ERR_INVALID_PARAM。

注意: 该函数不停止 MSC 设备、不释放 SDIO IRQ、不释放任何中间件对象。调用方应先停止使用借用对象并释放 IRQ,然后再调用。

4.3.4 SDIO 初始化示例

以下代码只示意 MSC 侧的注册和 SDIO 上下文绑定;bus_pins、电源和 SDIO IRQ 必须按实际板级接线替换。不要直接照搬当前 eMMC 的 PA23~PA28 引脚。

#include <im_msc.h>
#include "msc_pinmux.h"

static im_msc_device_sdio_t s_sdio = {
    .bus_number = 1,
};

static error_code_t board_sdio_start(void)
{
    const im_msc_device_config_t cfg = {
        .type = IM_MSC_DEVICE_SDIO,
        .ocr_avail = IM_MSC_OCR_32_33 | IM_MSC_OCR_33_34,
        .max_frequency_hz = 50000000U,
        .bus_pins = &g32s10_msc1_pa_pins, /* 按实际 SDIO 板级接线替换 */
        .variant.sdio = {
            .bus_width = 4U,
            .high_speed = true,
            .dma_mode = HAL_MSC_DMA_ADMA2_IRQ,
            /* irq_gpio 留空表示使用控制器原生 SDIO IRQ。 */
            .irq_trigger = HAL_GPIO_IRQ_TRIGGER_NONE,
        },
    };
    error_code_t status;

    /* 1. 注册 SDIO 配置。 */
    status = im_msc_device_register(1U, &cfg);
    if (status == IM_ERR_ALREADY_REGISTERED)
        status = IM_OK;
    if (status != IM_OK)
        return status;

    /* 2. 给 SDIO/WiFi 外设上电。这里是业务板级代码。 */
    /* board_sdio_power_on(); */

    /* 3. 绑定 host/card/function 1;必要时会自动启动控制器。 */
    status = im_msc_device_sdio_init(&s_sdio);
    if (status != IM_OK)
        return status;

    /* s_sdio.func1、s_sdio.card、s_sdio.host 为借用指针。 */
    return IM_OK;
}

static void board_sdio_stop(void)
{
    /* 先停止 SDIO 业务 I/O,并释放 SDIO IRQ 所有权。 */
    (void)im_msc_device_sdio_deinit(&s_sdio);
    (void)im_msc_device_stop(1U);
    (void)im_msc_device_unregister(1U);
}

如果 SDIO 中断由外部 GPIO 提供,则把 irq_gpio 设置为有效信号,并给出非 HAL_GPIO_IRQ_TRIGGER_NONE 的 irq_trigger。实际 CMD52/CMD53 和功能寄存器访问由对应的 SDIO/WiFi 驱动基于这些借用对象完成;MSC 公共头文件不提供额外的 SDIO 块读写封装。


4.4 块设备和块 I/O API

4.4.1 im_msc_card_info_get()

error_code_t im_msc_card_info_get(
    im_msc_device_t *device, im_msc_card_info_t *info_out);

作用: 读取当前 SD/eMMC 的卡信息。

返回:

  • IM_OK:成功。
  • IM_ERR_INVALID_PARAM:参数为空。
  • IM_ERR_NOT_READY:控制器尚未进入 READY。
  • IM_ERR_NO_DEVICE:没有可用块介质;纯 SDIO 设备也会返回该错误。
  • 协议层错误:读取 CID/CSD/EXT_CSD 失败等。

4.4.2 im_msc_blk_get()

error_code_t im_msc_blk_get(im_msc_device_t *device, im_msc_blk_t **blk_out);

作用: 取得整个卡/设备的块设备对象,名称格式为 mscblk<ctrl>。

返回:

  • IM_OK:成功,输出借用指针。
  • IM_ERR_INVALID_PARAM:参数为空。
  • IM_ERR_NO_DEVICE:控制器未就绪或当前没有块介质。

注意: 该对象是整盘视图,不会应用分区偏移。普通文件系统业务优先使用分区对象或挂载路径。

4.4.3 im_msc_part_get()

error_code_t im_msc_part_get(
    im_msc_device_t *device, uint32_t idx, im_msc_blk_t **part_out);

作用: 按索引取得分区块设备,名称为 mscblk<ctrl>p<idx>。

参数:

参数 说明
device 已注册设备
idx 分区索引,有效范围 0~IM_STORAGE_PARTITION_MAP_CAPACITY - 1,当前为 0~15
part_out 返回借用分区对象

返回: IM_OK、IM_ERR_INVALID_PARAM、IM_ERR_NOT_READY 或 IM_ERR_NOT_FOUND。

注意:

  • start 参数是相对于该分区的 LBA,不是整盘 LBA。
  • 分区可能因介质移除或重新识别而消失,不能永久缓存该指针。
  • mscblk1p0 这类名字由存储层发布,可用于 im_storage_block_device_find() 或 DFS 挂载。

4.4.4 im_msc_blk_info_get()

error_code_t im_msc_blk_info_get(
    im_msc_blk_t *blk, im_msc_blk_info_t *info_out);

作用: 返回块设备几何信息、当前可用性和是否为分区的快照。

返回: IM_OK 或 IM_ERR_INVALID_PARAM。

即使介质已经不可用,该函数仍可能成功返回,此时应检查 available。因此它适合在操作前后做状态快照,但不应仅凭一次成功调用就认为可以持续 I/O。

4.4.5 im_msc_blk_read() / im_msc_blk_write() / im_msc_blk_erase()

error_code_t im_msc_blk_read(
    im_msc_blk_t *blk, uint64_t start, uint32_t cnt,
    void *buf, uint32_t *completed_blocks_out);

error_code_t im_msc_blk_write(
    im_msc_blk_t *blk, uint64_t start, uint32_t cnt,
    const void *buf, uint32_t *completed_blocks_out);

error_code_t im_msc_blk_erase(
    im_msc_blk_t *blk, uint64_t start, uint32_t cnt);

参数:

参数 说明
blk im_msc_blk_get() 或 im_msc_part_get() 返回的块对象
start 相对当前块设备的起始逻辑块号
cnt 逻辑块数量,应大于 0
buf 数据缓冲区,大小至少为 cnt × logical_block_size
completed_blocks_out 已完成块数;读写接口必须提供

返回:

  • IM_OK:操作完成。
  • IM_ERR_INVALID_PARAM:参数为空、块数溢出、LBA/长度超出块设备范围、擦除未对齐等。
  • IM_ERR_NO_DEVICE:介质已移除或尚未注册为可用块设备。
  • IM_ERR_BUSY:当前设备被 im_msc_device_io_claim_acquire() 独占。
  • IM_ERR_READ_ONLY:只读介质执行写入/擦除。
  • IM_ERR_UNSUPPORTED:介质不支持擦除。
  • IM_ERR_IO / HAL 错误:底层传输失败。

注意事项:

  • 写入可能缩短介质寿命;测试或裸块写前必须确认目标 LBA 不包含需要保留的数据。
  • 文件系统挂载期间直接裸块写会破坏文件系统一致性。需要裸块访问时,先卸载文件系统,再使用 claim API。
  • completed_blocks_out 在失败时可能小于 cnt;调用方应检查返回值和完成块数。

4.4.6 块设备 API 示例

#include <stdlib.h>
#include <im_msc.h>

static error_code_t read_first_blocks(void)
{
    im_msc_device_t *device = im_msc_device_get(1U);
    im_msc_blk_t *blk = NULL;
    im_msc_blk_info_t info;
    uint8_t *buffer = NULL;
    uint32_t count;
    uint32_t completed = 0U;
    size_t bytes;
    error_code_t status;

    if (device == NULL)
        return IM_ERR_NOT_FOUND;

    status = im_msc_blk_get(device, &blk);
    if (status != IM_OK)
        return status;

    status = im_msc_blk_info_get(blk, &info);
    if (status != IM_OK || !info.available ||
        info.geometry.logical_block_size == 0U)
        return status != IM_OK ? status : IM_ERR_NO_DEVICE;

    count = 2U;
    if ((uint64_t)count > info.geometry.block_count)
        return IM_ERR_INVALID_PARAM;

    bytes = (size_t)count * info.geometry.logical_block_size;
    buffer = malloc(bytes);
    if (buffer == NULL)
        return IM_ERR_NO_MEMORY;

    status = im_msc_blk_read(blk, 0U, count, buffer, &completed);
    if (status == IM_OK && completed != count)
        status = IM_ERR_IO;

    free(buffer);
    return status;
}

上述示例只读,不修改介质。真实业务不应默认 LBA 0 可以随意覆盖;写操作必须由业务明确选择安全区域。


4.5 独占访问(IO Claim)API

4.5.1 为什么需要 claim

im_msc_blk_*() 是普通块访问路径。文件系统可能同时挂载同一设备,多个消费者直接交替访问会导致数据不一致。claim API 提供以下保证:

  1. 同一控制器同时只能存在一个 claim。
  2. claim 持有期间,普通块 I/O 返回 IM_ERR_BUSY,DFS 的挂载准入也会被阻止。
  3. 获取 claim 时,中间件会依次调用已注册的 consumer guard 的 acquire();任一 guard 返回失败,claim 获取失败。
  4. claim 记录介质身份(类型、块大小、块数和 CID)。之后每次 I/O 都重新校验,换卡或介质变化会返回 IM_ERR_CROSS_DEVICE。

claim 不会自动卸载已经挂载的文件系统。如果 DFS 已挂载并占用该设备,DFS 的 consumer guard 通常会阻止 claim,返回 IM_ERR_BUSY。应用应先关闭文件、卸载对应 mscblkXpN 卷,再获取 claim。

4.5.2 im_msc_device_io_claim_acquire()

error_code_t im_msc_device_io_claim_acquire(
    im_msc_device_t *device, im_msc_io_claim_t **claim_out);

作用: 获取一个控制器的整设备独占裸块访问权。

返回:

  • IM_OK:成功,输出 claim 对象。
  • IM_ERR_INVALID_PARAM:参数为空。
  • IM_ERR_NO_DEVICE:设备未就绪、没有块介质,或正在注销。
  • IM_ERR_BUSY:已经存在 claim,或某个 consumer guard 拒绝释放资源。
  • IM_ERR_NO_MEMORY:无法分配 claim 或守卫快照。
  • guard 返回的其他错误:例如消费者仍在使用设备。

注意:

  • claim 只适用于 SD/eMMC 块介质,不适用于纯 SDIO。
  • claim 不是递归锁;同一任务也不能重复获取第二个 claim。
  • 持有 claim 的时间应尽量短,不要跨用户交互或长时间等待。

4.5.3 im_msc_io_claim_release()

error_code_t im_msc_io_claim_release(im_msc_io_claim_t *claim);

作用: 释放 claim,并调用所有 consumer guard 的 release()。

返回:

  • IM_OK:释放成功。
  • IM_ERR_INVALID_PARAM:claim == NULL。
  • IM_ERR_BUSY:claim 已失效、状态不正确,或仍有进行中的 claim I/O。

注意: 每个成功获取的 claim 都必须且只能释放一次。释放后 claim 指针立即失效。

4.5.4 im_msc_io_claim_card_info_get()

error_code_t im_msc_io_claim_card_info_get(
    im_msc_io_claim_t *claim, im_msc_card_info_t *info_out);

在 claim 状态下重新校验介质身份并返回最新卡信息。若介质被替换,返回 IM_ERR_CROSS_DEVICE;介质已移除时返回 IM_ERR_NO_DEVICE 或 IM_ERR_NOT_READY。

4.5.5 im_msc_io_claim_read() / im_msc_io_claim_write()

error_code_t im_msc_io_claim_read(
    im_msc_io_claim_t *claim, uint64_t lba, uint32_t block_count,
    void *buffer, uint32_t *completed_blocks_out);

error_code_t im_msc_io_claim_write(
    im_msc_io_claim_t *claim, uint64_t lba, uint32_t block_count,
    const void *buffer, uint32_t *completed_blocks_out);

参数:

参数 说明
claim 成功的 claim 对象
lba 整个设备的逻辑块号;不是分区内偏移
block_count 块数,必须大于 0,且范围不能超出介质
buffer block_count × 逻辑块大小 的数据区
completed_blocks_out 已完成块数,不能为空

返回: 除 claim 的通用错误外,写接口还可能返回 IM_ERR_READ_ONLY;媒体身份变化返回 IM_ERR_CROSS_DEVICE;范围非法返回 IM_ERR_INVALID_PARAM。

4.5.6 im_msc_io_claim_erase()

error_code_t im_msc_io_claim_erase(
    im_msc_io_claim_t *claim, uint64_t lba, uint32_t block_count);

擦除前必须检查 erase_granularity_blocks。该值为 0 时返回 IM_ERR_UNSUPPORTED;lba 或 block_count 未按粒度对齐时返回 IM_ERR_INVALID_PARAM。

4.5.7 claim 使用示例

#include <stdlib.h>
#include <im_msc.h>

static error_code_t read_claimed_region(uint64_t lba, uint32_t count)
{
    im_msc_device_t *device = im_msc_device_get(1U);
    im_msc_io_claim_t *claim = NULL;
    im_msc_card_info_t card;
    uint8_t *buffer = NULL;
    uint32_t completed = 0U;
    error_code_t status;

    if (device == NULL)
        return IM_ERR_NOT_FOUND;

    /* 调用前应确认 DFS 卷已卸载,且业务已停止访问该设备。 */
    status = im_msc_device_io_claim_acquire(device, &claim);
    if (status != IM_OK)
        return status;

    status = im_msc_io_claim_card_info_get(claim, &card);
    if (status != IM_OK)
        goto out;

    if (count == 0U || card.block_size == 0U ||
        lba >= card.block_count ||
        (uint64_t)count > card.block_count - lba ||
        (uint64_t)count * card.block_size > SIZE_MAX) {
        status = IM_ERR_INVALID_PARAM;
        goto out;
    }

    buffer = malloc((size_t)count * card.block_size);
    if (buffer == NULL) {
        status = IM_ERR_NO_MEMORY;
        goto out;
    }

    status = im_msc_io_claim_read(claim, lba, count, buffer, &completed);
    if (status == IM_OK && completed != count)
        status = IM_ERR_IO;

out:
    free(buffer);
    (void)im_msc_io_claim_release(claim);
    return status;
}

写操作把 im_msc_io_claim_read() 替换为 im_msc_io_claim_write() 即可;擦除则先检查 card/块几何信息中的擦除粒度,再调用 im_msc_io_claim_erase()。


4.6 消费者保护 API

消费者保护用于把“文件系统挂载、DMA 使用、业务缓存持有”等外部状态纳入 claim 的前置检查。

4.6.1 回调结构

typedef struct {
    const char *name;
    error_code_t (*acquire)(void *context, uint32_t ctrl_id,
                            void **hold_out);
    void (*release)(void *context, void *hold);
} im_msc_consumer_guard_ops_t;

acquire() 应完成以下动作:

  1. 检查调用方是否还有会访问该控制器的资源。
  2. 如果安全,分配或返回一个 hold,后续由 release() 释放。
  3. 资源仍在使用时返回 IM_ERR_BUSY 或其他错误。

acquire() 成功时 hold_out 必须是非 NULL 值。release() 只会对成功的 acquire 返回的 hold 调用。

4.6.2 im_msc_consumer_guard_register()

error_code_t im_msc_consumer_guard_register(
    const im_msc_consumer_guard_ops_t *ops, void *context,
    im_msc_consumer_guard_registration_t **registration_out);

返回:

  • IM_OK:注册成功。
  • IM_ERR_INVALID_PARAM:ops、name、acquire、release 或输出指针为空。
  • IM_ERR_ALREADY_REGISTERED:相同 context 和相同回调函数组合已注册。
  • IM_ERR_BUSY:已有 claim 存在,或测试套件禁止注册。
  • IM_ERR_NO_MEMORY:分配注册对象失败。

注意: 应在任何 claim 获取前完成注册;ops 的内容会被复制,但回调中的 context 由调用方保证生命周期。

4.6.3 im_msc_consumer_guard_unregister()

error_code_t im_msc_consumer_guard_unregister(
    im_msc_consumer_guard_registration_t *registration);

返回: IM_OK、IM_ERR_INVALID_PARAM、IM_ERR_NOT_FOUND 或 IM_ERR_BUSY。

如果该 guard 正被 claim 调用或仍持有 hold,返回 IM_ERR_BUSY。应等待 claim 释放后重试。

4.6.4 im_msc_device_consumer_access_check()

error_code_t im_msc_device_consumer_access_check(im_msc_device_t *device);

返回:

返回值 含义
IM_OK 当前允许消费者访问
IM_ERR_BUSY 设备正被 claim 独占
IM_ERR_NO_DEVICE 设备正在注销
IM_ERR_INVALID_PARAM device == NULL

该接口适合文件系统挂载、业务打开设备前做准入检查,不能代替真正的 I/O 互斥。


4.7 介质事件 API

4.7.1 事件和回调

typedef enum {
    IM_MSC_EVENT_AVAILABLE = 0,
    IM_MSC_EVENT_REMOVING,
    IM_MSC_EVENT_REMOVED,
} im_msc_event_t;

typedef void (*im_msc_event_cb_t)(im_msc_device_t *device,
                                  im_msc_event_t event,
                                  im_msc_blk_t *blk,
                                  void *ctx);
事件 含义
IM_MSC_EVENT_AVAILABLE 块设备和分区已发布,可以进行块访问
IM_MSC_EVENT_REMOVING 介质即将撤销,消费者应停止新的 I/O 并卸载文件系统
IM_MSC_EVENT_REMOVED 介质已经撤销,块设备对象不再可用

blk 是整盘块设备的借用指针。事件期间和回调返回后都不能把该指针当作长期有效对象保存。

4.7.2 im_msc_event_register()

error_code_t im_msc_event_register(im_msc_event_cb_t cb, void *ctx);

作用: 注册全局 MSC 事件监听器。回调会对所有 MSC 控制器的事件生效。

返回: IM_OK、IM_ERR_INVALID_PARAM、IM_ERR_ALREADY_REGISTERED 或 IM_ERR_NO_MEMORY。

重要行为: 注册时,如果某个控制器已经有可用介质,回调会立即同步调用一次 IM_MSC_EVENT_AVAILABLE。因此注册前必须准备好回调使用的所有资源。

4.7.3 im_msc_event_unregister()

error_code_t im_msc_event_unregister(im_msc_event_cb_t cb, void *ctx);

作用: 按 cb + ctx 精确匹配注销监听器。

返回: IM_OK、IM_ERR_INVALID_PARAM 或 IM_ERR_NOT_FOUND。

4.7.4 事件监听示例

#include <im_msc.h>

static void msc_event_handler(im_msc_device_t *device,
                              im_msc_event_t event,
                              im_msc_blk_t *blk,
                              void *ctx)
{
    uint32_t ctrl_id = 0U;
    (void)ctx;
    (void)blk;

    (void)im_msc_device_ctrl_id_get(device, &ctrl_id);

    switch (event) {
    case IM_MSC_EVENT_AVAILABLE:
        /* 只做轻量通知;不要在回调里执行长时间文件操作。 */
        break;
    case IM_MSC_EVENT_REMOVING:
        /* 停止新的访问,关闭文件/卸载文件系统。 */
        break;
    case IM_MSC_EVENT_REMOVED:
        /* 丢弃所有已缓存的块对象和分区名。 */
        break;
    default:
        break;
    }
}

static error_code_t register_msc_events(void)
{
    return im_msc_event_register(msc_event_handler, NULL);
}

static void unregister_msc_events(void)
{
    (void)im_msc_event_unregister(msc_event_handler, NULL);
}

回调应短小、避免长时间阻塞。不要假设回调一定运行在某个固定的用户任务中。


4.8 模式重配和状态查询

4.8.1 im_msc_device_test_reconfigure()

error_code_t im_msc_device_test_reconfigure(
    uint32_t ctrl_id, im_msc_speed_mode_t speed_mode, uint32_t speed_hz,
    uint8_t bus_width, hal_msc_dma_mode_t dma_mode,
    bool *hardware_changed_out);

作用: 为性能/兼容性验证切换速度模式、频率、位宽和 DMA 模式。必要时会停止控制器、更新运行配置并重新启动。

参数:

参数 说明
ctrl_id 控制器编号
speed_mode 目标速度模式
speed_hz 目标频率
bus_width 1 或 4;8 返回 IM_ERR_UNSUPPORTED
dma_mode PIO_POLL、SDMA_IRQ 或 ADMA2_IRQ
hardware_changed_out 必须非 NULL;成功时报告是否发生了硬件/运行配置切换

返回: IM_OK、IM_ERR_INVALID_PARAM、IM_ERR_NOT_FOUND、IM_ERR_UNSUPPORTED、IM_ERR_BUSY 或控制器启动错误。

注意:

  • 仅适用于 SD/eMMC;SDIO 返回 IM_ERR_UNSUPPORTED。
  • 可能触发 REMOVING/REMOVED/AVAILABLE 事件,并重建块设备。
  • 调用前应释放 claim、关闭文件系统和所有块设备使用者。
  • 成功后所有旧的 im_msc_blk_t * 和 SDIO 借用指针都应视为失效,需要重新获取。

示例:

bool changed = false;
error_code_t status = im_msc_device_test_reconfigure(
    1U, IM_MSC_SPEED_EMMC_HS, 50000000U, 4U,
    HAL_MSC_DMA_ADMA2_IRQ, &changed);

4.8.2 im_msc_device_ctrl_id_get()

error_code_t im_msc_device_ctrl_id_get(
    const im_msc_device_t *device, uint32_t *ctrl_id_out);

取得设备对应的控制器编号。参数为空返回 IM_ERR_INVALID_PARAM。

4.8.3 im_msc_device_io_state_get()

error_code_t im_msc_device_io_state_get(
    uint32_t ctrl_id, im_msc_io_state_t *state_out);

读取控制器实际应用的速度模式、位宽、目标/实际频率和信号电压。

返回:

  • IM_OK:成功。
  • IM_ERR_INVALID_PARAM:输出指针为空。
  • IM_ERR_NOT_FOUND:控制器未注册。
  • IM_ERR_NO_DEVICE:没有块介质。
  • IM_ERR_NOT_READY:卡或控制器尚未就绪。

对 eMMC,signal_voltage 会反映板级配置的固定 io_voltage。


5. 典型完整示例

5.1 S107 SK-SLT eMMC(MSC1,4-bit,3.3V)注册和启动

#include <im_log.h>
#include <im_msc.h>
#include "msc_pinmux.h"

#define BOARD_EMMC_RST IM_GPIO_SIGNAL(IM_GPIO_PORT_A, 22U, false)

error_code_t board_msc_init(void)
{
    const im_msc_device_config_t emmc = {
        .type = IM_MSC_DEVICE_EMMC,
        .ocr_avail = IM_MSC_OCR_32_33 | IM_MSC_OCR_33_34,
        .max_frequency_hz = 50000000U,
        .bus_pins = &g32s10_msc1_pa_pins,
        .reset = BOARD_EMMC_RST,
        .variant.emmc = {
            .bus_width = 4U,
            .speed_mode = IM_MSC_SPEED_EMMC_HS,
            .io_voltage = IM_MSC_SIGNAL_VOLTAGE_3V3,
        },
    };
    error_code_t status;

    status = im_msc_device_register(1U, &emmc);
    if (status == IM_ERR_ALREADY_REGISTERED)
        status = IM_OK;
    if (status != IM_OK)
        return status;

    status = im_msc_device_start(1U);
    if (status != IM_OK) {
        IM_LOGE("msc: MSC1 eMMC init failed ret=%d", (int)status);
        return status;
    }

    IM_LOGI("msc: MSC1 eMMC ready");
    return IM_OK;
}

void board_msc_deinit(void)
{
    (void)im_msc_device_stop(1U);
    (void)im_msc_device_unregister(1U);
}

说明:

  • 当前 SK-SLT 的 eMMC 总线为 PA23 CLK、PA24 CMD、PA25~PA28 D0~D3,复位脚为 PA22。
  • bus_pins = &g32s10_msc1_pa_pins 表示由 MSC 中间件申请并配置总线引脚。
  • 该配置为 3.3V VCCQ,因此只适合 DS/High Speed 等当前板级可支持的模式;DDR52、HS200 需要 1.8V 板级条件。

5.2 文件系统路径与裸块路径的选择

卡信息和整盘块读取示例见 4.4.6 节。实际产品中建议读取业务指定区域,不要默认写 LBA 0。

普通文件读写: 使用 DFS/VFS,例如访问 /mscblk1p0/...。这是最安全的方式。

需要整盘镜像、生产测试或性能验证: 先卸载对应卷,再获取 claim,使用 im_msc_io_claim_*()。不要只调用 im_msc_blk_write() 就假设文件系统不存在。

SDIO: 只使用 im_msc_device_sdio_*() 和对应 SDIO/WiFi 驱动,不要调用 im_msc_blk_*() 或 claim API。


6. 使用约束和检查清单

6.1 使用规则和常见坑

  1. 注册不等于启动。 im_msc_device_register() 成功后必须调用 im_msc_device_start(),否则不会识别卡,也不会发布 mscblkX。
  2. 不要释放借用指针。 im_msc_device_get()、im_msc_blk_get()、im_msc_part_get()、im_msc_device_sdio_function_get() 和 SDIO 上下文中的指针都由中间件拥有。
  3. SDIO 不是块设备。 纯 SDIO 没有 mscblk,im_msc_card_info_get() 和块 API 会失败;请使用 SDIO function 接口。
  4. LBA 不是字节地址。 先读取 logical_block_size,再用 bytes = blocks × logical_block_size 计算缓冲区。
  5. 普通块 API 和 claim 不能混用。 claim 持有期间普通块 I/O 返回 IM_ERR_BUSY。
  6. claim 前先卸载文件系统。 claim 不会自动卸载 DFS 卷,DFS guard 可能会直接拒绝获取。
  7. 停止前清理所有使用者。 释放 claim、关闭文件、注销消费者、停止 SDIO IRQ,再 stop()。
  8. 不要让 deinit_all() 代替注销。 它只停止设备,不释放注册对象。
  9. 换卡后不要复用旧 claim。 claim 会检测介质身份;SD 热插拔后应重新获取块对象和 claim。
  10. 当前参考板 eMMC 在 MSC1。 如果板级没有 MSC0,使用 ctrl_id = 0 会得到 IM_ERR_NO_DEVICE,而不是自动切换到 MSC1。
  11. 速度模式不是强制保证。 设置 IM_MSC_SPEED_EMMC_HS 等目标后,应以 im_msc_device_io_state_get() 返回的实际值为准。
  12. 写测试数据前确认区域安全。 裸块写和擦除会绕过 FAT 元数据,可能造成不可恢复的数据损坏。

6.2 最小检查清单

6.2.1 首次集成

  • 确认控制器编号与原理图一致(当前 S107 eMMC:MSC1)。
  • 确认 bus_pins 与实际 CLK/CMD/DAT/复位引脚一致。
  • 确认总线位宽(1/4)、VCCQ 电压和目标速度模式匹配。
  • 调用 register() 后检查返回值并调用 start()。
  • 启动后调用 im_msc_card_info_get() 和 im_msc_device_io_state_get() 核对容量与实际模式。

6.2.2 文件系统/裸块测试

  • 文件系统测试使用 /mscblkXpN,不要直接覆盖元数据。
  • 裸块测试先卸载卷、获取 claim,并在安全 LBA 范围内操作。
  • 读写后校验 completed_blocks_out 和数据内容。
  • 测试结束恢复原始数据并释放 claim。

6.2.3 关机/重新初始化

  • 停止业务和文件系统访问。
  • 释放 claim 和 SDIO IRQ。
  • 调用 stop(),确认成功。
  • 确认不再使用旧块指针后再 unregister()。