杰理 SDK 文档中心
首页
首页
  • 入门指南

    • SDK 概述与芯片平台
    • 环境搭建与开发工具链
    • 编译、烧录与快速开始
  • 应用层开发

    • 语音玩具应用 voice_toy
    • 扩音器应用 voice_enhanced
    • 语音功能状态机 voice_func
    • 应用公共框架与配置
  • 音频子系统

    • 音频解码器与 MIDI 播放
    • 音频编码与录音
    • 音效算法(ANS、变调、变声、混响)
    • 音频输出、功放与硬件重采样
  • 存储与文件系统

    • 文件系统层(FAT、NOR_FS、SYDF 等)
    • 存储设备与设备管理
    • 参数存储 VM 与保留区
  • 系统机制

    • 消息与事件机制
    • 电源管理与低功耗
    • 固件升级机制
    • 外设驱动(按键、红外、SPI、USB)
    • 实时时钟与定时器
  • 构建系统与工具

    • 构建系统(Makefile 与 Code::Blocks)
    • 编译后处理与语音资源打包
  • 硬件平台与文档

    • 芯片平台与启动流程
    • 硬件文档、规格书与原理图

存储设备与设备管理

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 库)之间的中间管理层。它的核心设计意图是:

  1. 统一抽象:上层业务(播放、录音、升级)无需关心存储介质的具体类型,只需传入一个设备号(dev_index),即可获得一个可用的文件系统句柄。
  2. 生命周期管理:通过静态数组 device_mge[MAX_DEVICE] 维护每个设备的句柄与引用计数,支持多个业务模块共享同一设备,最后一个使用者关闭时才真正释放底层设备。
  3. 状态感知:提供在线状态轮询能力,检测存储设备是否拔出/掉线,并可将结果以位图形式汇总(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

流程与设计意图:

  1. 越界防御:device_id >= MAX_DEVICE 直接返回 NULL,防止数组越界写。
  2. 只读设备拒绝:INNER_FLASH_RO 不可通过本接口打开,避免业务层误用资源文件系统句柄。
  3. 首次打开即真正创建:每次调用都执行 dev_open,但不检查重复打开——多次 device_open 同一设备号会多次调用 dev_open 并递增引用计数。这意味着驱动层 dev_open 必须是可重入/可多次调用的(幂等或返回同一底层对象),管理层只负责计数与最终释放。
  4. 返回的 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_onlinebool dev_online(const char *name)查询设备是否在线
dev_openvoid *dev_open(const char *name, void *arg)按名打开设备,返回句柄
dev_closeint dev_close(void *device)关闭设备,0 成功
dev_ioctlint 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_ILLdevice_status设备号非法(index > MAX_DEVICE)
E_DEV_LOSTdevice_status首次检测离线、复检恢复,且期间关闭过句柄
E_DEV_OFFLINEdevice_status / device_online复检仍离线
UPDATA_DEV_ERRdevice_update无缓存设备名、U 盘升级被拒
UPDATA_READYdevice_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

  1. 新增存储设备:在枚举中追加设备号(EXT_FLASH_EN 模式已示范),在 device_name[] 中登记驱动层设备名,并保证驱动层已注册对应 dev_open / dev_online 实现。MAX_DEVICE 自动伸缩,device_online 位图随之覆盖新设备。
  2. 新增插拔事件:仿照 device_status_emit 中 "sd0" 的分支,为其他热插拔介质(如 TF 卡第二通道)注册事件投递;注意 post_event 的事件号需在系统消息枚举中定义。
  3. 扩展升级来源:device_update 中 __UDISK0 的拒绝分支是预留的扩展点——去掉该分支并补全 UFW 读取路径即可支持 U 盘升级。
  4. 复用 device_obj 做状态查询:需要只读句柄、不想增加引用计数的模块可直接使用 device_obj(注意越界修复)。

Related Links

  • device_mge.h(接口声明)
  • device_mge.c(实现)
  • device.h(驱动层接口)
  • simple_play_file.c(播放调用示例)
  • enc_in_norfs.c(录音调用示例)
  • toy_update.c(升级调用示例)
  • common_msg.c(升级检查调用示例)
Prev
文件系统层(FAT、NOR_FS、SYDF 等)
Next
参数存储 VM 与保留区