存储设备与设备管理
AD24N SDK 中的设备管理层(device_mge),以统一的设备号抽象封装内置 Flash、外挂 Flash 与 SD 卡等存储介质,向应用层提供设备打开、关闭、在线检测与设备升级等接口。
Purpose and Scope
本页介绍 SDK 中「存储设备与设备管理」这一能力的完整实现,涵盖:
- 设备号的枚举定义与设备名映射表(
INNER_FLASH_RO/INNER_FLASH_RW/EXT_FLASH_RW) - 设备句柄的引用计数管理与打开/关闭流程
- 设备在线状态检测(
device_status/device_online) - 基于 UFW 文件的设备升级入口(
device_update) - SD 卡热插拔事件上报(
device_status_emit) - 底层设备驱动接口(
dev_open/dev_close/dev_online/dev_ioctl)
本页聚焦于设备管理层的实现与调用关系。文件系统的挂载细节(vfs / norfs)、具体的升级算法(UFW 校验)、录音/播放业务逻辑分别属于各自目录页的内容,本页仅在涉及调用链时引用它们。
Overview
device_mge(device manage)是位于应用层(voice_func)与底层设备驱动(dev_mg 库)之间的中间管理层。它的核心设计意图是:
- 统一抽象:上层业务(播放、录音、升级)无需关心存储介质的具体类型,只需传入一个设备号(
dev_index),即可获得一个可用的文件系统句柄。 - 生命周期管理:通过静态数组
device_mge[MAX_DEVICE]维护每个设备的句柄与引用计数,支持多个业务模块共享同一设备,最后一个使用者关闭时才真正释放底层设备。 - 状态感知:提供在线状态轮询能力,检测存储设备是否拔出/掉线,并可将结果以位图形式汇总(
device_online)供上层决策。
设备号与存储介质的对应关系由 device_name[] 表定义:INNER_FLASH_RO(只读内置 Flash)用于读取资源文件,INNER_FLASH_RW(可读写内置 Flash)用于挂载 norfs 或访问虚拟设备,EXT_FLASH_RW(可读写外挂 Flash)在 EXT_FLASH_EN 宏开启时加入。INNER_FLASH_RO 是一个特殊设备:它始终被视为"在线",且不允许被打开/关闭——因为资源文件系统由系统静态管理。
Architecture
flowchart TD
subgraph sg_App["应用层 voice_func"]
Play["simple_play_file.c<br/>播放控制"]
Rec["enc_in_norfs.c<br/>录音存储"]
Upd["toy_update.c<br/>升级流程"]
Msg["common_msg.c<br/>消息处理"]
end
subgraph sg_DevMge["设备管理层 device_mge"]
Open["device_open"]
Close["device_close"]
Status["device_status / device_online"]
Update["device_update"]
Emit["device_status_emit"]
end
subgraph sg_DevLib["设备驱动层 dev_mg 库"]
DevOpen["dev_open"]
DevClose["dev_close"]
DevOnline["dev_online"]
DevIoctl["dev_ioctl"]
end
subgraph sg_Storage["存储介质"]
SFC["SFC 内置 Flash<br/>(RO / RW)"]
EXT["外挂 Flash<br/>(EXT_FLASH_RW)"]
SD["SD 卡 sd0"]
end
Play --> Open
Rec --> Open
Upd --> Update
Msg --> Update
Open --> DevOpen
Close --> DevClose
Status --> DevOnline
Update --> DevIoctl
Emit --> SD
DevOpen --> SFC
DevOpen --> EXT
架构分层说明:
- 应用层:
simple_play_file.c在播放前调用device_open获取设备句柄;enc_in_norfs.c在录音初始化时获取 norfs 所在设备的句柄;toy_update.c与common_msg.c通过device_update触发设备升级检查。它们只依赖device_mge.h暴露的 5 个 API,不直接触碰驱动层。 - 设备管理层:
device_mge.c维护设备表、引用计数与状态机,是本文档的核心。 - 驱动层:
sdk/include_lib/dev_mg/device.h声明的dev_open/dev_close/dev_online/dev_ioctl以设备名为入参,屏蔽了具体介质的差异。该库以预编译.a形式提供(dev_mg_lib.a)。 - 存储介质:SFC 控制器管理的内置 Flash、外挂 Flash,以及 SD 卡(
sd0)。SD 卡通过device_status_emit回调上报插拔事件。
设备号枚举与 API 声明见 device_mge.h:
enum {
INNER_FLASH_RO = 0, //只读内置flash,用于读取资源文件
INNER_FLASH_RW, //可读写内置flash,用于挂载norfs或访问虚拟设备
#if EXT_FLASH_EN
EXT_FLASH_RW, //可读写外挂flash
#endif
MAX_DEVICE,
};
void *device_open(u8 device_id);
u32 device_close(u8 device_id);
void *device_obj(u32 index);
u32 device_online(void);
u32 device_update(char *t_dev_name, bool check_flag);
Source: device_mge.h
设备号是编译期常量:EXT_FLASH_RW 仅在 EXT_FLASH_EN 宏开启时存在,MAX_DEVICE 随之伸缩,因此整个设备表的大小在编译期即确定,无动态内存开销。
设备编号与设备名映射
设备号到驱动层设备名的映射由静态表 device_name[] 完成,定义于 device_mge.c:
const char *device_name[MAX_DEVICE] = {
NULL, //只读内置flash,用于读取资源文件
__SFC_NANE, //可读写内置flash,用于挂载norfs或访问虚拟设备
#if EXT_FLASH_EN
__EXT_FLASH_NANE, //可读写外挂flash
#endif
};
Source: device_mge.c
设计要点:
INNER_FLASH_RO对应的表项为NULL——该设备不经过dev_open,资源文件系统由系统静态管理,因此device_open/device_close对其直接返回失败。__SFC_NANE与__EXT_FLASH_NANE是驱动层注册的设备名宏(SFC = Serial Flash Controller),驱动层凭此名字查找到对应的设备驱动。- 设备名表与设备号枚举按位一一对应,下标即设备号,这是整个管理层"设备号 → 句柄 → 驱动"三级寻址的基石。
句柄与引用计数的管理结构同样为静态数组,避免动态分配带来的碎片与失败风险:
typedef struct __device_mge_t {
void *p_device;
u8 device_used_cnt;
} device_mge_t;
static device_mge_t device_mge[MAX_DEVICE];
Source: device_mge.c
设备句柄管理(引用计数)
device_open:打开设备
void *device_open(u8 device_id)
{
if (device_id >= MAX_DEVICE) {
return NULL;
}
if (INNER_FLASH_RO == device_id) {
return NULL;
}
device_mge[device_id].p_device = dev_open((void *)device_name[device_id], 0);
if (NULL == device_mge[device_id].p_device) {
return NULL;
}
device_mge[device_id].device_used_cnt++;
return device_mge[device_id].p_device;
}
Source: device_mge.c
流程与设计意图:
- 越界防御:
device_id >= MAX_DEVICE直接返回NULL,防止数组越界写。 - 只读设备拒绝:
INNER_FLASH_RO不可通过本接口打开,避免业务层误用资源文件系统句柄。 - 首次打开即真正创建:每次调用都执行
dev_open,但不检查重复打开——多次device_open同一设备号会多次调用dev_open并递增引用计数。这意味着驱动层dev_open必须是可重入/可多次调用的(幂等或返回同一底层对象),管理层只负责计数与最终释放。 - 返回的
void *即文件系统句柄,可直接用于fopen系列调用,是应用层拿到设备访问能力的关键返回值。
device_close:引用计数式释放
u32 device_close(u8 device_id)
{
if ((device_id >= MAX_DEVICE) || (0 == device_mge[device_id].device_used_cnt)) {
return 0;
}
if (INNER_FLASH_RO == device_id) {
return 0;
}
u32 res = 0;
device_mge[device_id].device_used_cnt--;
if (0 == device_mge[device_id].device_used_cnt) {
u32 retry = 20;
do {
res = dev_close(device_mge[device_id].p_device);
if (0 == res) {
device_mge[device_id].p_device = NULL;
}
retry--;
} while (res && (0 != retry));
}
if (0 != res) {
log_info("close_device FAIL\n");
return 0;
}
return 1;
}
Source: device_mge.c
关键行为:
- 引用计数语义:只有
device_used_cnt归零时(即最后一个使用者释放)才真正调用dev_close。这允许多个模块(如播放与录音)共享同一存储设备而互不干扰。 - 关闭重试:
dev_close失败时最多重试 20 次,成功后才将p_device置空。这是对底层介质忙/写缓存未刷完等瞬态失败的容错——直接丢弃句柄会导致后续访问野指针。 - 返回值约定:
0表示设备已关闭或关闭失败(上层可据此认为句柄不可再用),1表示关闭成功。 - 防御性检查:对未打开的设备(计数为 0)与越界设备号直接返回
0,避免对空句柄调用dev_close。
device_obj:获取已打开句柄
void *device_obj(u32 index)
{
if ((index > MAX_DEVICE) || (INNER_FLASH_RO == index)) {
return 0;
}
return device_mge[index].p_device;
}
Source: device_mge.c
device_obj 是 device_open 的"只读视图":不增加引用计数、不触发驱动调用,仅返回当前缓存的句柄。适合查询场景(如播放器检查设备是否已就绪)。注意其越界条件为 index > MAX_DEVICE(与 device_open 的 >= 不同),这是一个边界上的不一致——index == MAX_DEVICE 时会读取越界内存,属于已知代码瑕疵。
在线状态检测
device_status 是设备在线状态的核心检测函数,实现了一个"先检测、必要时关闭、再复检"的三段式逻辑:
u32 device_status(u32 index, bool mode)
{
if (index > MAX_DEVICE) {
return E_IDEV_ILL;
}
if (INNER_FLASH_RO == index) {
return 0;
}
bool lost = 0;
if (!dev_online((void *)&device_name[index][0])) {
log_info("Ask device:%d is't online ", index);
if (0 != mode) {
device_close(index);
lost = 1;
}
} else {
log_info("device:%d status is ok\n", index);
return 0;
}
bool bres = dev_online(&device_name[index][0]);
if (bres) {
if (lost) {
return E_DEV_LOST;
} else {
return 0;
}
} else {
return E_DEV_OFFLINE;
}
}
Source: device_mge.c
flowchart TD
Start([device_status 调用]) --> Chk{"index > MAX_DEVICE?"}
Chk -->|"是"| Ill["返回 E_IDEV_ILL"]
Chk -->|"否"| RO{"index == INNER_FLASH_RO?"}
RO -->|"是"| Ok0["返回 0 始终在线"]
RO -->|"否"| Online{"dev_online 首次检测"}
Online -->|"在线"| Ok["返回 0"]
Online -->|"离线"| Mode{"mode != 0?"}
Mode -->|"是"| Close["device_close 释放句柄"]
Close --> Lost["标记 lost = 1"]
Mode -->|"否"| Skip["不关闭"]
Skip --> Retry{"dev_online 复检"}
Lost --> Retry
Retry -->|"在线"| R1{"lost 标记?"}
R1 -->|"是"| DevLost["返回 E_DEV_LOST"]
R1 -->|"否"| Ok2["返回 0"]
Retry -->|"仍离线"| Offline["返回 E_DEV_OFFLINE"]
图注:该状态机的三次判定(首次在线检查 → 可选关闭 → 复检)用于区分"设备从未在线/已拔出"(
E_DEV_OFFLINE)与"检测瞬间掉线又恢复"(E_DEV_LOST)两种场景。
设计意图与边界行为:
mode参数是"是否清理"开关:mode != 0时检测到离线会立即device_close释放句柄,防止上层持有失效句柄继续读写;mode == 0时仅探测、不清理,适合只读巡检。- 两次
dev_online的目的:首次离线后立刻复检,是为了消除 SD 卡等热插拔介质的瞬时抖动误判。若复检在线且期间关闭过句柄,返回E_DEV_LOST提示上层"设备曾经丢失";若复检仍离线则返回E_DEV_OFFLINE。 INNER_FLASH_RO恒在线:只读资源区不存在热插拔,直接返回0。- 注意首次调用使用
(void *)&device_name[index][0]强转,复检使用&device_name[index][0],二者等价(均为设备名字符串首地址),但写法不一致。
device_online 将全部设备的状态汇总为位图,供上层一次性判断哪些设备可用:
u32 device_online(void)
{
u32 online = 0;
for (u32 i = 0; i < MAX_DEVICE; i++) {
if (INNER_FLASH_RO == i) {
online |= BIT(i);
continue;
}
if (E_DEV_OFFLINE != device_status(i, 1)) {
online |= BIT(i);
}
}
return online;
}
Source: device_mge.c
- 返回值的第
i位为 1 表示设备i在线。INNER_FLASH_RO无条件置位。 - 对每个可插拔设备以
mode=1调用device_status,即巡检的同时会顺带关闭已离线的设备句柄——这是一个有副作用的查询,调用方需知晓。
设备升级支持(device_update)
device_update 是设备管理层的升级入口,由 TFG_DEV_UPGRADE_SUPPORT 宏控制编译。它封装了"检查 UFW 文件 → 执行升级"的完整流程:
u32 device_update(char *t_dev_name, bool check_flag)
{
static char const *t_cur_dev = NULL;
if (NULL == t_dev_name) {
if (NULL != t_cur_dev) {
t_dev_name = (char *)t_cur_dev;
} else {
return UPDATA_DEV_ERR;
}
}
if (!strcmp(t_dev_name, __UDISK0)) {
/* 暂不支持U盘升级 */
log_error("dev err %s\n", t_dev_name);
return UPDATA_DEV_ERR;
}
log_info("dev name %s\n", t_dev_name);
u16 dev_update_check(char *logo, bool check_flag);
u32 err = dev_update_check(t_dev_name, check_flag);
if ((err == UPDATA_READY) && (1 == check_flag)) {
t_cur_dev = t_dev_name;
} else {
t_cur_dev = NULL;
}
return err;
}
Source: device_mge.c
关键设计:
check_flag双模式:1表示仅检查设备上是否存在可升级的 UFW 文件;0表示执行真正升级。检查成功后设备名被缓存到静态变量t_cur_dev,之后可传NULL复用上次检查成功的设备——这是toy_update.c中device_update(NULL, 0)能直接升级的前提。- U 盘升级被显式禁止:
__UDISK0(U 盘设备名)直接返回UPDATA_DEV_ERR,说明该平台暂不支持从 U 盘升级。 - 升级算法下沉:真正的 UFW 校验与写入由驱动层库函数
dev_update_check完成(声明于文件内,未在头文件导出),管理层只负责设备名解析与状态记录,职责边界清晰。 - 升级错误码直接透传,调用方通过
UPDATA_READY判断成功。
调用链实例如 common_msg.c(消息处理中先做升级检查)与 toy_update.c(直接执行升级)。
SD 卡事件上报(device_status_emit)
当 TFG_SD_EN 开启时,设备管理层向驱动层提供一个状态回调,将 SD 卡插拔事件转换为系统消息:
static u8 sd_buffer[512];
void *sdx_dev_get_cache_buf(void)
{
return sd_buffer;
}
int device_status_emit(const char *device_name, const u8 status)
{
log_info("device_name:%s status:%d \n", device_name, status);
if (!strcmp(device_name, "sd0")) {
if (status) {
post_event(EVENT_SD0_IN);
log_info(">>>>>>>sd online \n");
} else {
post_event(EVENT_SD0_OUT);
log_info(">>>>>>>sd offline \n");
}
}
return 0;
}
Source: device_mge.c
- 该函数由驱动层在检测到 SD 卡插拔时回调(回调注册机制在预编译库内部),设备管理层将其翻译为
EVENT_SD0_IN/EVENT_SD0_OUT事件消息投递给系统消息队列,驱动上层(如播放器自动切换、录音停止策略)响应。 sdx_dev_get_cache_buf为 SD 卡驱动提供一块 512 字节的静态缓存(一个扇区大小),避免驱动自行分配内存。
Core Flow:设备生命周期主流程
sequenceDiagram
participant App as 应用层 (simple_play_file / enc_in_norfs)
participant Mge as device_mge 管理层
participant Dev as dev_mg 驱动库
participant HW as 存储介质 (Flash / SD)
App->>Mge: device_open(dev_index)
Mge->>Mge: 校验 dev_index < MAX_DEVICE
Mge->>Mge: INNER_FLASH_RO 直接返回 NULL
Mge->>Dev: dev_open(device_name[dev_index], 0)
Dev-->>Mge: 文件系统句柄
Mge->>Mge: device_used_cnt++
Mge-->>App: 返回句柄
App->>App: 文件读写 (vfs / norfs)
App->>Mge: device_close(dev_index)
Mge->>Mge: device_used_cnt--
alt 计数归零
Mge->>Dev: dev_close(p_device) (失败重试 20 次)
Dev-->>Mge: 0 = 成功
Mge->>Mge: p_device = NULL
else 计数未归零
Mge-->>App: 仅递减,不关闭底层
end
Mge-->>App: 返回 1
图注:引用计数机制保证了多业务共享设备时,底层驱动只在最后一个使用者释放时才真正关闭,避免句柄竞争。
升级流程时序
sequenceDiagram
participant Msg as common_msg.c / toy_update.c
participant Mge as device_mge
participant Lib as dev_update_check (驱动库)
Msg->>Mge: device_update(dev_name, check_flag=1)
Mge->>Lib: dev_update_check(dev_name, 1)
Lib-->>Mge: UPDATA_READY
Mge->>Mge: 缓存 t_cur_dev = dev_name
Mge-->>Msg: UPDATA_READY
Msg->>Mge: device_update(NULL, check_flag=0)
Mge->>Mge: 取缓存 t_cur_dev
Mge->>Lib: dev_update_check(dev_name, 0) 执行升级
Lib-->>Mge: 升级结果
Mge-->>Msg: 升级错误码
图注:
t_cur_dev静态缓存让"先检查、后升级"两步可以分离调用,且升级时无需再次传入设备名。
Usage Examples
播放模块:打开设备获取句柄
simple_play_file.c 在播放初始化时打开目标设备:
{
ppctl->device = device_open(ppctl->dev_index);
if ((NULL == ppctl->device) && (INNER_FLASH_RO != ppctl->dev_index)) {
Source: simple_play_file.c
要点:播放器通过 device_open 拿到设备句柄后用于后续文件操作;对 INNER_FLASH_RO 的特殊处理说明只读资源区不需要(也不能)通过本接口打开,播放器对其走资源文件系统路径。
录音模块:norfs 存储设备的打开
enc_in_norfs.c 在录音编码初始化与启动阶段两次打开设备:
obj->device = device_open(obj->dev_index);
if (NULL == obj->device) {
Source: enc_in_norfs.c
要点:录音数据写入 norfs(挂载于可读写内置 Flash),dev_index 通常为 INNER_FLASH_RW。此例也验证了 device_open 的可重入性——同一设备多次打开,引用计数递增。
升级模块:先检查后升级
toy_update.c 在收到升级消息后直接执行升级(设备名复用检查阶段缓存的 t_cur_dev):
err = device_update(NULL, 0);
Source: toy_update.c
而 common_msg.c 先在消息流程中做升级检查(check_flag=1),为后续真正的升级做好准备:
u32 err = device_update(t_up_device, 1);
Source: common_msg.c
Configuration Options
设备管理层的编译期配置全部来自宏开关,定义于 app_config.h 或工程配置,作用于 device_mge.h 与 device_mge.c:
| 宏 | 类型 | 默认 | 作用 |
|---|---|---|---|
EXT_FLASH_EN | 布尔宏 | 0 | 是否启用外挂 Flash 设备;开启后在枚举中加入 EXT_FLASH_RW 并注册 __EXT_FLASH_NANE 设备名 |
TFG_SD_EN | 布尔宏 | 0 | 是否启用 SD 卡支持;开启后编译 device_status_emit 回调与 512 字节 SD 缓存 |
TFG_DEV_UPGRADE_SUPPORT | 布尔宏(值为 1 时生效) | 0 | 是否启用设备升级;开启后编译 device_update 及 UFW 检查路径 |
__SFC_NANE | 字符串宏 | 由驱动层定义 | 内置 SFC Flash 在驱动层注册的设备名 |
__EXT_FLASH_NANE | 字符串宏 | 由驱动层定义 | 外挂 Flash 在驱动层注册的设备名 |
__UDISK0 | 字符串宏 | 由驱动层定义 | U 盘设备名;device_update 对其显式拒绝升级 |
注:
__SFC_NANE/__EXT_FLASH_NANE/__UDISK0为驱动层(dev_mg库)提供的宏,此处仅引用。device_used_cnt为u8类型,单一设备的最大并发引用数为 255,超出会回绕。
API Reference
void *device_open(u8 device_id)
打开指定设备并返回文件系统句柄。
参数:
device_id(u8):设备号,取值为INNER_FLASH_RO/INNER_FLASH_RW/EXT_FLASH_RW。
返回: 成功返回设备文件系统句柄(可传给 vfs 使用);失败返回 NULL。失败原因包括:设备号越界、INNER_FLASH_RO(不可打开)、底层 dev_open 失败。
u32 device_close(u8 device_id)
按引用计数释放设备。
参数:
device_id(u8):设备号。
返回: 1 表示关闭成功(或计数未归零仅递减);0 表示设备未打开/越界/INNER_FLASH_RO/底层 dev_close 重试 20 次后仍失败。
void *device_obj(u32 index)
获取已打开设备的句柄,不增加引用计数。
参数:
index(u32):设备号。
返回: 当前缓存的设备句柄;INNER_FLASH_RO 或越界返回 0(注意:越界条件为 index > MAX_DEVICE,index == MAX_DEVICE 时存在越界读取风险)。
u32 device_status(u32 index, bool mode)
检测设备在线状态。
参数:
index(u32):设备号。mode(bool):非 0 时离线会触发device_close清理句柄。
返回: 0 在线;E_IDEV_ILL 参数非法;E_DEV_LOST 曾掉线但复检已恢复;E_DEV_OFFLINE 设备离线。
u32 device_online(void)
汇总所有设备在线状态为位图。
返回: 32 位位图,第 i 位为 1 表示设备 i 在线;INNER_FLASH_RO 恒为 1。内部以 mode=1 巡检,会顺带关闭离线设备句柄。
u32 device_update(char *t_dev_name, bool check_flag)(TFG_DEV_UPGRADE_SUPPORT==1 时编译)
检查或执行设备升级。
参数:
t_dev_name(char *):升级设备名(如__SFC_NANE);传NULL时复用上次检查成功的设备名。check_flag(bool):1仅检查 UFW 文件;0执行升级。
返回: UPDATA_READY 表示就绪/成功,其余为升级错误码(UPDATA_DEV_ERR 等)。
int device_status_emit(const char *device_name, const u8 status)(TFG_SD_EN 时编译)
驱动层 SD 卡插拔回调。
参数:
device_name(const char *):设备名,当前仅处理"sd0"。status(u8):非 0 表示插入,0 表示拔出。
返回: 恒为 0。副作用:投递 EVENT_SD0_IN / EVENT_SD0_OUT 系统事件。
底层驱动接口(引用,定义于 device.h)
| 接口 | 签名 | 说明 |
|---|---|---|
dev_online | bool dev_online(const char *name) | 查询设备是否在线 |
dev_open | void *dev_open(const char *name, void *arg) | 按名打开设备,返回句柄 |
dev_close | int dev_close(void *device) | 关闭设备,0 成功 |
dev_ioctl | int dev_ioctl(void *device, int cmd, u32 arg) | 设备控制命令(升级等) |
Source: device.h
Failure Modes、边界与并发
已知边界缺陷
device_obj越界条件不一致:device_obj使用index > MAX_DEVICE而其他接口使用>=。当index == MAX_DEVICE时device_obj会返回device_mge[MAX_DEVICE](越界读取),属于防御性检查的遗漏,上层调用时需自行保证index < MAX_DEVICE。device_status对未打开设备的处理:离线检测会调用dev_online直接探测设备名,不要求句柄已打开;但当mode != 0时若设备从未打开(p_device == NULL、计数为 0),device_close会安全返回0,不会崩溃——这是引用计数防御设计的收益。
错误码语义
| 错误码 | 来源 | 触发条件 |
|---|---|---|
E_IDEV_ILL | device_status | 设备号非法(index > MAX_DEVICE) |
E_DEV_LOST | device_status | 首次检测离线、复检恢复,且期间关闭过句柄 |
E_DEV_OFFLINE | device_status / device_online | 复检仍离线 |
UPDATA_DEV_ERR | device_update | 无缓存设备名、U 盘升级被拒 |
UPDATA_READY | device_update | 升级就绪/成功 |
并发与重入
- 设备表为进程内静态数据,本实现未加锁。所有接口预期在单线程(或同一任务上下文)中调用;若多个任务并发
device_open/device_close,引用计数的读改写(cnt++/cnt--)存在竞态,需由上层任务模型保证串行化。 dev_close的 20 次重试循环为忙等(无延时),关闭瞬时失败时最多占用约 20 次驱动调用时间;重试期间若设备持续忙,调用方会阻塞,上层应避免在中断/实时性要求高的路径调用device_close。
热插拔与升级边界
- SD 卡拔出时,先由驱动回调
device_status_emit投递EVENT_SD0_OUT,但已打开的句柄并不会自动失效;上层需自行调用device_status(..., mode=1)触发清理,否则后续文件操作会访问失效介质。 device_update对 U 盘(__UDISK0)显式拒绝;t_cur_dev静态缓存只保存最后一个检查成功的设备名,多设备交替升级时旧缓存会被覆盖,传NULL前须确认目标设备未变。
性能与运维注意事项
- 零动态内存:设备表、设备名表、SD 缓存(512 B)全部为编译期静态分配,无堆操作,适合资源受限的 MCU 环境;代价是设备数量在编译期固定,运行时不可扩展。
device_online的副作用成本:每次调用会对所有可插拔设备执行两次dev_online探测(device_status内部),高频巡检会放大驱动层探测开销;建议仅在上层需要(如插入事件后、播放切换前)调用。- 升级路径阻塞性:
dev_update_check执行真正的固件写入,耗时与 Flash 容量相关,期间会阻塞调用任务;产品设计上应避免在升级中触发看门狗超时或掉电。 - 日志开关:管理层日志
LOG_TAG "[dev_app]"挂在NORM级别,device_status每次检测都会输出日志,量产固件可关闭log_info以减小开销。
Extension Points
- 新增存储设备:在枚举中追加设备号(
EXT_FLASH_EN模式已示范),在device_name[]中登记驱动层设备名,并保证驱动层已注册对应dev_open/dev_online实现。MAX_DEVICE自动伸缩,device_online位图随之覆盖新设备。 - 新增插拔事件:仿照
device_status_emit中"sd0"的分支,为其他热插拔介质(如 TF 卡第二通道)注册事件投递;注意post_event的事件号需在系统消息枚举中定义。 - 扩展升级来源:
device_update中__UDISK0的拒绝分支是预留的扩展点——去掉该分支并补全 UFW 读取路径即可支持 U 盘升级。 - 复用
device_obj做状态查询:需要只读句柄、不想增加引用计数的模块可直接使用device_obj(注意越界修复)。