API GUIDE / MSC / MIDDLEWARE
MSC Middleware 接口
msc mid api
API 头文件:
code/middleware/msc/include/im_msc_device.hcode/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 中间件把一次设备使用分成以下层次:
- 注册控制器:
im_msc_device_register()保存板级配置和控制器编号,但不操作硬件。 - 启动控制器:
im_msc_device_start()配置总线、供电/复位、识别卡并发布块设备。 - 使用块设备:通过
im_msc_blk_*()访问 SD/eMMC 的整盘或分区。 - 文件系统访问:块设备通常注册为
mscblk<ctrl>,分区注册为mscblk<ctrl>p<idx>,DFS/elmfat 可自动挂载为/<块设备名>。 - 独占访问:需要绕过文件系统做裸块读写时,先获取
im_msc_io_claim_t,再使用 claim API。 - 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 提供以下保证:
- 同一控制器同时只能存在一个 claim。
- claim 持有期间,普通块 I/O 返回
IM_ERR_BUSY,DFS 的挂载准入也会被阻止。 - 获取 claim 时,中间件会依次调用已注册的 consumer guard 的
acquire();任一 guard 返回失败,claim 获取失败。 - 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() 应完成以下动作:
- 检查调用方是否还有会访问该控制器的资源。
- 如果安全,分配或返回一个
hold,后续由release()释放。 - 资源仍在使用时返回
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 使用规则和常见坑
- 注册不等于启动。
im_msc_device_register()成功后必须调用im_msc_device_start(),否则不会识别卡,也不会发布mscblkX。 - 不要释放借用指针。
im_msc_device_get()、im_msc_blk_get()、im_msc_part_get()、im_msc_device_sdio_function_get()和 SDIO 上下文中的指针都由中间件拥有。 - SDIO 不是块设备。 纯 SDIO 没有
mscblk,im_msc_card_info_get()和块 API 会失败;请使用 SDIO function 接口。 - LBA 不是字节地址。 先读取
logical_block_size,再用bytes = blocks × logical_block_size计算缓冲区。 - 普通块 API 和 claim 不能混用。 claim 持有期间普通块 I/O 返回
IM_ERR_BUSY。 - claim 前先卸载文件系统。 claim 不会自动卸载 DFS 卷,DFS guard 可能会直接拒绝获取。
- 停止前清理所有使用者。 释放 claim、关闭文件、注销消费者、停止 SDIO IRQ,再
stop()。 - 不要让
deinit_all()代替注销。 它只停止设备,不释放注册对象。 - 换卡后不要复用旧 claim。 claim 会检测介质身份;SD 热插拔后应重新获取块对象和 claim。
- 当前参考板 eMMC 在 MSC1。 如果板级没有 MSC0,使用
ctrl_id = 0会得到IM_ERR_NO_DEVICE,而不是自动切换到 MSC1。 - 速度模式不是强制保证。 设置
IM_MSC_SPEED_EMMC_HS等目标后,应以im_msc_device_io_state_get()返回的实际值为准。 - 写测试数据前确认区域安全。 裸块写和擦除会绕过 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()。