设备与设备管理
本文档介绍 AD23N 固件 SDK 中「设备」抽象与「设备管理」机制:从底层驱动接口(dev_io_t)、中层设备注册与操作(dev_node / device / dev_* API)到应用层设备管理器(device_mge)的完整分层结构、工作流程、配置与故障处理。
Purpose and Scope
本页覆盖 AD23N SDK 中**设备(Device)**相关的完整机制:
- 底层设备驱动接口
dev_io_t(见 device_drive.h) - 设备抽象层:
dev_node、device_operations、device结构与dev_open/dev_close/dev_ioctl/dev_byte_read/dev_bulk_read等标准 API(见 device.h) - 应用层设备管理器
device_mge:设备号枚举、引用计数式开关、在线状态轮询、UFW 升级入口(见 device_mge.h 与 device_mge.c)
不在本页范围:具体文件系统挂载(如 norfs/胖文件系统,属于存储/文件系统目录)、具体外设驱动实现(SD 卡控制器、USB Host 等)、升级协议细节(update.h)。这些属于同级目录下其他页面。
Overview
AD23N SDK 中「设备」指系统可访问的存储或外设单元,典型设备包括:
| 设备名宏 | 实际名字符串 | 说明 |
|---|---|---|
__UDISK0 | "udisk0" | U 盘(USB Host) |
__SD0_NANE | "sd0" | SD/TF 卡 |
__SFC_NANE | "sfc" | 内置 SPI Flash 控制器(可读写) |
__EXT_FLASH_NANE | "ext_flsh" | 外挂 Flash(可读写) |
__OTG | "otg" | USB OTG |
这些宏定义于 device.h。
设备体系分为三层,职责边界清晰:
- 驱动层(
device_drive.h):定义dev_io_t,提供mount/unmount/read/write/ioctrl/power/detect原语,以及设备类型、状态、错误码、通用 ioctl 命令。每个硬件驱动按此接口实现。 - 抽象层(
device.h):定义dev_node(节点,含名字与操作集)与device(实例,含引用计数),通过REGISTER_DEVICE宏把节点放入.device段完成静态注册,向上提供dev_open/dev_close/dev_ioctl/dev_byte_read/dev_bulk_read等统一 API。 - 应用层(
device_mge):面向业务代码的设备管理器。把设备映射为整数设备号(UDISK_INDEX、SD0_INDEX、INNER_FLASH_RO、INNER_FLASH_RW、EXT_FLASH_RW),提供引用计数式device_open/device_close、在线状态查询device_status/device_online,以及设备升级入口device_update。
设计意图:业务代码只需要和设备号打交道,不关心底层是 SD 卡还是 Flash;驱动层与抽象层解耦后,新增一种存储介质只需实现 dev_io_t 并注册 dev_node,上层无需改动。
Architecture
flowchart TD
subgraph sg_App["应用层 (mbox_flash/common)"]
App["业务代码 / 文件系统 / 升级模块"]
Mge["device_mge 设备管理器<br/>device_open / device_close / device_status / device_online / device_update"]
MgeTbl["device_mge[MAX_DEVICE]<br/>引用计数 + 句柄缓存"]
end
subgraph sg_Dev["抽象层 (dev_mg)"]
DevApi["dev_open / dev_close / dev_ioctl<br/>dev_byte_read / dev_byte_write / dev_bulk_read / dev_bulk_write"]
DevNode["dev_node (name + ops)<br/>REGISTER_DEVICE 静态注册到 .device 段"]
DevInst["device 实例<br/>atomic ref + ops + driver_data"]
end
subgraph sg_Drv["驱动层 (device)"]
DrvIo["dev_io_t<br/>mount / read / write / ioctrl / power / detect"]
SdDrv["sd0 驱动"]
SfcDrv["sfc / ext_flsh 驱动"]
UdiskDrv["udisk0 / otg 驱动"]
end
App -->|"设备号 device_id"| Mge
Mge -->|"dev_open(name)"| DevApi
Mge --> MgeTbl
DevApi --> DevNode
DevNode -->|"ops->open / ops->ioctl / ops->read ..."| DevInst
DevInst -->|"调用驱动回调"| DrvIo
DrvIo --> SdDrv
DrvIo --> SfcDrv
DrvIo --> UdiskDrv
Mge -.->|"device_update: ufw 升级检查"| Update["update / dev_update_check"]
架构要点:
device_mge是应用层唯一入口:业务代码通过device_open(INNER_FLASH_RW)这类调用获得设备句柄,句柄与引用计数缓存在静态数组device_mge[MAX_DEVICE]中。dev_*API 是抽象层门面:dev_open按名字在.device段注册表中查找dev_node,调用其ops->open创建device实例。- 驱动只实现
dev_io_t:dev_node的ops回调(如ioctl、bulk_read)内部再分发到dev_io_t的方法,形成两层适配。 - 升级功能独立接入:
device_update通过dev_update_check检查/执行 UFW 升级,与设备开关管理解耦。
设备抽象层:dev_node / device / device_operations
核心数据结构
抽象层定义于 device.h:
struct device_operations {
bool (*online)(const struct dev_node *node);
int (*init)(const struct dev_node *node, void *);
int (*open)(const char *name, struct device **device, void *arg);
int (*read)(struct device *device, void *buf, u32 len, u32);
int (*write)(struct device *device, void *buf, u32 len, u32);
int (*bulk_read)(struct device *device, void *buf, u32 len, u32);
int (*bulk_write)(struct device *device, void *buf, u32 len, u32);
int (*seek)(struct device *device, u32 offset, int orig);
int (*ioctl)(struct device *device, u32 cmd, u32 arg);
int (*close)(struct device *device);
};
struct dev_node {
const char *name;
const struct device_operations *ops;
void *priv_data;
};
struct device {
atomic_t ref;
void *private_data;
const struct device_operations *ops;
void *platform_data;
void *driver_data;
};
设计要点:
dev_node是「设备节点」:静态描述一个设备——名字 + 操作集 + 私有数据。它不持有运行期状态,可被多个实例共享。device是「设备实例」:每次open生成的句柄对象,ref是原子引用计数,driver_data存放驱动私有运行期数据(如驱动上下文、缓冲区指针)。device_operations是全部操作契约:online探测、init初始化、open/close生命周期、read/write字节流、bulk_read/bulk_write块读写(按扇区)、seek定位、ioctl控制命令。这一契约同时服务字符型设备(字节读写)与块设备(扇区读写)。
静态注册机制
节点通过段属性宏注册到 .device 链接段:
#define REGISTER_DEVICE(node) \
const struct dev_node node sec_used(.device)
#define REGISTER_DEVICES(node) \
const struct dev_node node[] sec_used(.device)
Source: device.h
REGISTER_DEVICE 注册单个节点,REGISTER_DEVICES 注册节点数组(如一个驱动暴露多个逻辑设备)。链接器将所有 .device 段中的节点聚合成设备注册表,devices_init() 在启动阶段扫描并初始化它们——这种「零初始化代码」的注册方式,使新增驱动只需要在任意源文件里写一个 REGISTER_DEVICE 宏即可挂载到系统。
对外 API
int devices_init();
bool dev_online(const char *name);
void *dev_open(const char *name, void *arg);
int dev_ioctl(void *device, int cmd, u32 arg);
int dev_close(void *device);
int dev_byte_read(void *_device, void *buf, u32 offset, u32 len);
int dev_byte_write(void *_device, void *buf, u32 offset, u32 len);
int dev_bulk_read(void *_device, void *buf, u32 sector, u32 sector_num);
int dev_bulk_write(void *_device, void *buf, u32 sector, u32 sector_num);
Source: device.h
dev_open(name, arg):按名字查注册表,返回device句柄(void*形式),失败返回NULL。dev_byte_read/byte_write:按字节偏移读写,适合小数据量、地址对齐不敏感的场景(如读取 Flash 配置区)。dev_bulk_read/bulk_write:按扇区号 + 扇区数读写,适合大块数据传输(如文件系统缓存刷盘、音频数据流)。dev_ioctl(device, cmd, arg):控制命令通道,命令码定义见驱动层DEV_GET_STATUS等。dev_close:释放句柄,内部递减引用计数,计数归零才真正销毁驱动实例。
应用层设备管理器:device_mge
设备号枚举
设备管理器把设备映射为整数 ID,定义于 device_mge.h:
enum {
UDISK_INDEX = 0, // U 盘
SD0_INDEX, // SD/TF 卡
INNER_FLASH_RO, // 只读内置 flash,用于读取资源文件
INNER_FLASH_RW, // 可读写内置 flash,用于挂载 norfs 或访问虚拟设备
#if EXT_FLASH_EN
EXT_FLASH_RW, // 可读写外挂 flash
#endif
MAX_DEVICE,
NO_DEVICE = 0xff,
};
设计意图:用枚举而非字符串做业务接口,编译期即可发现非法设备号,且 device_mge[MAX_DEVICE] 数组天然与枚举一一对应。NO_DEVICE = 0xff 作为哨兵值表示"无设备"。INNER_FLASH_RO 在多数场景下不真正打开(见下文),只作为"资源文件只读区"的占位。EXT_FLASH_RW 受编译宏 EXT_FLASH_EN 控制,未使能时直接不参与数组与枚举计数。
设备名映射与句柄表
const char *device_name[MAX_DEVICE] = {
__UDISK0,
__SD0_NANE,
NULL, // 只读内置 flash,用于读取资源文件
__SFC_NANE, // 可读写内置 flash,用于挂载 norfs 或访问虚拟设备
#if EXT_FLASH_EN
__EXT_FLASH_NANE, // 可读写外挂 flash
#endif
};
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
注意 INNER_FLASH_RO 的名字为 NULL——它在设备名表中没有对应名字,因为该槽位不允许被 dev_open 打开(device_open 对 INNER_FLASH_RO 直接返回 NULL),其在线状态由 device_online 强制置位。这样设计是为了让资源文件读取路径(只读区)永远"在线可用",不依赖任何热插拔探测。
引用计数式开关设备
device_open 与 device_close 实现了「共享句柄 + 引用计数」:
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
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_close只减计数、不真正关闭,避免"我关了别人还在用"的悬垂句柄。 - 关闭失败重试 20 次:
dev_close失败(如设备忙)时循环重试,最多 20 次;若最终失败返回0且打印close_device FAIL。注意此时计数已减、句柄未置空,属于"延迟释放"路径,需要上层配合重试或强制复位。 - 返回值语义:
0= 未关闭/关闭失败,1= 关闭成功。
在线状态查询
device_status(index, mode) 是设备管理器最核心的查询接口:
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
mode == 0:纯查询,不产生副作用。mode != 0:发现离线时先调用device_close(index)回收句柄(防止句柄泄漏),标记lost。- 返回值:
0在线;E_DEV_LOST设备曾在线但本次掉线且已被回收;E_DEV_OFFLINE确认离线;E_IDEV_ILL非法设备号。 device_online()汇总所有设备状态为位图(每设备占 1 bit),INNER_FLASH_RO恒为在线——这是上层判断"当前有哪些可用存储"的快捷接口:
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
驱动层接口:dev_io_t
设备驱动操作结构
驱动层契约定义于 device_drive.h:
typedef struct DEV_IO {
const char name[8];
s32(*mount)(void *volatile parm);
s32(*unmount)();
s32(*read)(u8 *volatile buf, u32 addr, u32 len);
s32(*write)(u8 *volatile buf, u32 addr, u32 len);
s32(*ioctrl)(void *volatile parm, u32 cmd);
s32(*power)(u32 mod);
s32(*detect)(); // 设备状态检测
struct dev_mutex *mutex; // 驱动互斥锁
dev_type device_type; // 设备类型
void *private_data; // 设备私有属性
} dev_io_t;
mount/unmount:设备挂载/卸载(如 SD 卡初始化、文件系统底层对接)。read/write:按地址读写。ioctrl:控制命令分发,命令码为DEV_GET_STATUS等通用命令。power:电源状态管理(开/关/待机/唤醒,见DEV_POWER_*)。detect:热插拔检测钩子,配合应用层的device_status_emit上报事件。mutex:驱动内互斥锁指针,串行化并发访问,属于驱动层的并发控制手段。
设备类型 / 状态 / 错误码
typedef enum _dev_type {
DEV_SDCRAD_0 = 0X10, DEV_SDCRAD_1, DEV_SDCRAD_2,
DEV_UDISK_H0, DEV_UDISK_H1, DEV_UDISK_F0,
DEV_NOR_FLASH, DEV_NAND_FLASH,
DEV_STORAGE = 0x100, DEV_LOGIC_DISK = 0x101, DEV_USB_SLAVE, DEV_USB_HOST,
DEV_HID = 0x200, DEV_NET, DEV_AUDIO, DEV_ISP,
} dev_type;
Source: device_drive.h
- 设备类型分段:
0x10段为存储介质(SD、U 盘、Flash),0x100段为逻辑设备(存储抽象、逻辑盘、USB 角色),0x200段为外设类(HID、网络、音频、ISP)。这种分段便于按类型做能力探测与策略分发。
typedef enum dev_sta {
DEV_OFFLINE = 0, // 设备从在线切换到离线
DEV_ONLINE = 1, // 设备从离线切换到在线
DEV_HOLD = 2, // 设备状态未改变
DEV_POWER_ON = 0x10, // 开机
DEV_POWER_OFF, // 关机
DEV_POWER_STANDBY, // 待机
DEV_POWER_WAKEUP, // 唤醒
} DEV_STA;
Source: device_drive.h
DEV_STA 把「在线性」与「电源状态」合并到一个枚举:低 4 位描述在线/离线迁移,0x10 以上描述电源管理事件。上层通过 DEV_GET_STATUS ioctl 获取这些状态,驱动返回 DEV_HOLD 表示无变化,避免无谓的事件风暴。
通用 ioctl 命令与错误码
#define DEV_GENERAL_MAGIC 0xe0
#define DEV_GET_STATUS _IOR(DEV_GENERAL_MAGIC,0xe0,u32) // 获取设备状态
#define DEV_GET_BLOCK_SIZE _IOR(DEV_GENERAL_MAGIC,0xe1,u32) // 块大小
#define DEV_GET_BLOCK_NUM _IOR(DEV_GENERAL_MAGIC,0xe2,u32) // 块总数
#define DEV_GET_DEV_ID _IOR(DEV_GENERAL_MAGIC,0xe3,u32) // 设备 ID(SD/TF 返回 "sdtf"=0x73647466)
#define DEV_SECTOR_ERASE _IOW(DEV_GENERAL_MAGIC,0xe4,u32) // 页擦除
#define DEV_BLOCK_ERASE _IOW(DEV_GENERAL_MAGIC,0xe5,u32) // 块擦除
#define DEV_CHIP_ERASE _IOW(DEV_GENERAL_MAGIC,0xe6,u32) // 整片擦除
#define DEV_GET_TYPE _IOR(DEV_GENERAL_MAGIC,0xe7,u32) // 返回 dev_type
#define DEV_CHECK_WPSTA _IOR(DEV_GENERAL_MAGIC,0xe8,u32) // 写保护状态检测/设置
Source: device_drive.h
- 命令码使用 Linux 风格
_IOR/_IOW宏编码(魔数 + 序号 + 类型),DEV_GET_STATUS是每个设备必须支持的命令;其余命令设备不支持时返回-ENOTTY。 DEV_GET_DEV_ID返回设备标识:SD/TF 卡返回"sdtf"(0x73647466),用于区分介质品牌/类型。DEV_CHECK_WPSTA:参数-1查询写保护状态,0解除写保护,1加写保护。- 错误码
DEV_ERR_*覆盖NOT_MOUNT、TIMEOUT、OFFLINE、CRC、STALL、NO_READ/NO_WRITE/NO_IOCTL、INVALID_HANDLE等场景(见 device_drive.h),为上层提供细粒度诊断信息。
Core Flow
设备打开 → 读写 → 关闭全流程
sequenceDiagram
participant App as 业务代码
participant Mge as device_mge
participant Dev as dev_* 抽象层
participant Drv as dev_io_t 驱动
App->>Mge: device_open(INNER_FLASH_RW / SD0_INDEX)
Mge->>Mge: 校验 device_id & 查 device_name[]
Mge->>Dev: dev_open("sfc"/"sd0", 0)
Dev->>Dev: 在 .device 段注册表查找 dev_node
Dev->>Drv: ops->open → 驱动初始化
Drv-->>Dev: 返回 device 实例
Dev-->>Mge: 句柄
Mge->>Mge: device_used_cnt++ 并缓存句柄
Mge-->>App: 返回句柄
App->>Mge: device_obj(index) 取句柄
App->>Dev: dev_bulk_read/sector / dev_byte_read/offset
Dev->>Drv: ops->bulk_read / ops->read
Drv-->>Dev: 数据
Dev-->>App: 数据
App->>Mge: device_status(index, mode=1)
Mge->>Dev: dev_online(name)
Dev-->>Mge: 在线/离线
alt 离线
Mge->>Mge: device_close(index) 回收句柄
Mge-->>App: E_DEV_LOST / E_DEV_OFFLINE
else 在线
Mge-->>App: 0
end
App->>Mge: device_close(SD0_INDEX)
Mge->>Mge: device_used_cnt--
alt 计数归零
Mge->>Dev: dev_close(handle) 失败重试≤20次
Dev->>Drv: ops->close → 驱动释放
end
Mge-->>App: 1(成功) / 0(失败)
SD 卡热插拔事件流
SD 驱动通过 device_status_emit 把在线/离线转换为系统事件:
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); // SD 插入
} else {
post_event(EVENT_SD0_OUT); // SD 拔出
}
}
return 0;
}
Source: device_mge.c
EVENT_SD0_IN/EVENT_SD0_OUT 进入系统消息队列后,由事件处理任务通知文件系统卸载/挂载、刷新 UI 显示等。注意该回调以 TFG_SD_EN 编译开关保护,仅在使能 SD 卡功能时编译;配套的 sdx_dev_get_cache_buf() 返回一块 512 字节静态缓冲区供 SD 驱动使用,避免在中断/回调上下文分配内存。
数据模型与持久化行为
设备层本身不持久化业务数据,但它是所有存储读写的必经通道。其"数据模型"可概括为三层句柄链:
erDiagram
DEVICE_MGE ||--|| DEVICE_MGE_T : "设备号槽位(MAX_DEVICE)"
DEVICE_MGE_T {
u8 device_id
void p_device
u8 device_used_cnt
}
DEVICE_MGE_T ||--o| DEVICE : "持有句柄"
DEVICE {
atomic_t ref
void private_data
void platform_data
void driver_data
}
DEVICE ||--|| DEV_NODE : "引用节点"
DEV_NODE {
string name
ops ops
void priv_data
}
DEV_NODE ||--|| DEV_IO : "驱动实现 ops"
DEV_IO {
string name_8
func mount
func read
func write
func ioctrl
func power
func detect
dev_type device_type
}
持久化行为要点:
- 块设备(SD/Flash):上层文件系统通过
dev_bulk_read/dev_bulk_write以扇区为单位访问;DEV_GET_BLOCK_SIZE/DEV_GET_BLOCK_NUM提供几何信息。 - 字符型访问(虚拟设备/资源区):
dev_byte_read/dev_byte_write按字节偏移直接访问,典型用于读取配置区、资源文件头等。 - 擦除命令:
DEV_SECTOR_ERASE/DEV_BLOCK_ERASE/DEV_CHIP_ERASE通过 ioctl 下发,上层(如 norfs)按需调用,Flash 驱动需实现磨损均衡与写保护(DEV_CHECK_WPSTA)。
Configuration Options
| 配置项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
EXT_FLASH_EN | 编译宏 | 0/1 | 是否使能外挂 Flash 设备;为 1 时 EXT_FLASH_RW 加入设备号枚举、设备名表与句柄表 |
TFG_SD_EN | 编译宏 | 0/1 | 是否使能 SD 卡;为 1 时编译 sdx_dev_get_cache_buf、device_status_emit 与 SD 热插拔事件上报 |
TFG_DEV_UPGRADE_SUPPORT | 编译宏 | 0/1 | 是否使能 device_update 设备升级入口 |
INNER_FLASH_RO 槽位 | 固定逻辑 | 恒在线、不可 open | 只读资源区占位,device_name[] 中为 NULL,device_open 返回 NULL,device_online 恒置位 |
__SFC_NANE / __SD0_NANE / __UDISK0 / __EXT_FLASH_NANE | 宏定义 | "sfc"/"sd0"/"udisk0"/"ext_flsh" | 设备名字符串,必须与驱动 dev_node.name 一致 |
device_close 重试次数 | 常量 | 20 | dev_close 失败时的最大重试次数 |
sd_buffer | 静态缓冲 | 512 字节 | SD 驱动缓存缓冲区(TFG_SD_EN 时编译) |
API Reference
设备管理器(应用层,device_mge.h)
void *device_open(u8 device_id)
- 描述:按设备号打开设备并返回句柄,引用计数 +1。
- 参数:
device_id为UDISK_INDEX/SD0_INDEX/INNER_FLASH_RW/EXT_FLASH_RW。 - 返回:成功返回设备句柄;
device_id >= MAX_DEVICE、INNER_FLASH_RO或底层dev_open失败时返回NULL。
u32 device_close(u8 device_id)
- 描述:释放设备引用,计数归零时真正关闭(失败重试 20 次)。
- 返回:
1成功;0非法设备号 / 计数已为 0 / 关闭失败。
void *device_obj(u32 index)
- 描述:获取指定设备号当前缓存的句柄(不增加引用)。
- 返回:句柄;
index >= MAX_DEVICE或INNER_FLASH_RO返回0。
u32 device_status(u32 index, bool mode)
- 描述:查询设备在线状态;
mode != 0时离线自动回收句柄。 - 返回:
0在线;E_DEV_LOST掉线且已回收;E_DEV_OFFLINE离线;E_IDEV_ILL非法设备号。
u32 device_online(void)
- 描述:汇总所有设备在线状态为位图(bit i = 设备 i 在线,
INNER_FLASH_RO恒在线)。
u32 device_update(char *t_dev_name, bool check_flag)
- 描述:设备升级入口。
check_flag == 1仅检查 UFW 文件并记录设备;0执行升级。t_dev_name == NULL时沿用上次检查成功的设备。 - 返回:升级错误码,
UPDATA_READY表示就绪,其余为出错(UPDATA_DEV_ERR等)。 - 注意:
"udisk0"(U 盘)暂不支持升级,直接返回UPDATA_DEV_ERR。
抽象层(device.h)
void *dev_open(const char *name, void *arg)
- 描述:按名字在
.device段注册表中查找并打开设备。 - 返回:
device句柄(void*),失败NULL。
int dev_close(void *device)
- 描述:关闭设备实例;返回
0成功,非 0 失败(上层据此重试)。
int dev_ioctl(void *device, int cmd, u32 arg)
- 描述:下发控制命令(
DEV_GET_STATUS、DEV_GET_BLOCK_SIZE、DEV_CHIP_ERASE等)。
int dev_byte_read/write(void *_device, void *buf, u32 offset, u32 len)
- 描述:按字节偏移读写;返回实际处理结果码。
int dev_bulk_read/write(void *_device, void *buf, u32 sector, u32 sector_num)
- 描述:按扇区批量读写;
sector为起始扇区号,sector_num为扇区数。
bool dev_online(const char *name)
- 描述:探测设备是否在线(调用节点
ops->online)。
驱动层(device_drive.h)
s32 (*ioctrl)(void *parm, u32 cmd):驱动命令分发,cmd 为 DEV_GENERAL_MAGIC 系列命令;不支持的返回 -ENOTTY。
s32 (*detect)():设备状态检测,返回 DEV_STA 中的在线性状态。
s32 (*power)(u32 mod):电源管理,mod 取 DEV_POWER_ON/OFF/STANDBY/WAKEUP。
Failure Modes, Edge Cases & Concurrency
- 设备离线与句柄回收:
device_status(index, 1)在设备掉线时先device_close回收句柄,避免旧句柄悬垂;返回E_DEV_LOST(曾在线)与E_DEV_OFFLINE(本就离线)区分两种语义,上层可据此决定是否提示用户重新插拔。device_online()内部对每个槽位都做一次带回收的状态查询,因此会引入逐设备的dev_online探测开销。 device_close失败重试:dev_close可能因设备忙/驱动未就绪失败,循环重试 20 次。若仍失败,计数已减但句柄保留,后续device_open会复用该句柄(p_device非空且计数递增),属于"软失败"路径,需上层注意状态一致性。INNER_FLASH_RO的特殊性:该槽位名字为NULL、不可 open/close、恒在线。任何代码若误将INNER_FLASH_RO传入device_open/device_close会得到NULL/0的失败结果而非崩溃——这是防御性设计。- 并发访问:引用计数
device_used_cnt为非原子u8,同一设备号若被多任务并发 open/close 存在计数竞争风险;SDK 的典型用法是文件系统任务与升级任务串行化访问设备。驱动层提供dev_mutex保护底层并发,抽象层device.ref为atomic_t。需要跨任务并发共享设备时,上层必须自行加锁。 - U 盘升级限制:
device_update对udisk0直接报错,避免在 U 盘上执行固件升级的不安全路径。 - SD 热插拔竞态:
device_status_emit在驱动上下文中投递EVENT_SD0_IN/OUT事件;若事件处理任务尚未完成挂载而用户再次插拔,可能出现重复挂载/卸载,依赖消息队列的顺序性缓解。
Usage Examples
示例 1:打开内置 Flash 并批量读写
void *dev = device_open(INNER_FLASH_RW); // 打开可读写内置 flash
if (dev) {
u8 buf[512];
// 按扇区读写(挂载 norfs 前通常用 bulk 接口)
dev_bulk_read(dev, buf, 0, 1); // 读第 0 扇区
dev_bulk_write(dev, buf, 0, 1); // 写回第 0 扇区
device_close(INNER_FLASH_RW);
}
说明:
device_open的引用计数语义保证文件系统与业务模块可安全共享同一设备;读写接口直接来自 device.h,开关流程来自 device_mge.c。
示例 2:轮询设备在线位图并处理掉线
u32 online = device_online(); // 返回位图,bit0=udisk0, bit1=sd0, bit3=sfc ...
if (online & BIT(SD0_INDEX)) {
// SD 卡在线,可挂载文件系统
}
// 对单个设备做带回收的状态查询:
u32 st = device_status(SD0_INDEX, 1); // 掉线时自动 device_close
if (st == E_DEV_LOST) {
// 设备曾在线、现已掉线且句柄已回收,提示用户重新插拔
} else if (st == E_DEV_OFFLINE) {
// 设备未插入
}
说明:
device_status的mode=1语义(掉线自动回收)是避免句柄泄漏的关键;位图聚合逻辑见 device_mge.c。
示例 3:驱动注册一个设备节点
static const struct device_operations mydev_ops = {
.online = mydev_online,
.init = mydev_init,
.open = mydev_open,
.read = mydev_read,
.write = mydev_write,
.bulk_read = mydev_bulk_read,
.bulk_write = mydev_bulk_write,
.seek = mydev_seek,
.ioctl = mydev_ioctl,
.close = mydev_close,
};
REGISTER_DEVICE(my_dev_node) = {
.name = "mydev",
.ops = &mydev_ops,
};
说明:
REGISTER_DEVICE把节点放入.device段,devices_init()启动时自动扫描;新增设备无需改动任何业务代码。宏定义见 device.h。
示例 4:设备升级检查与执行
// 仅检查升级设备上是否存在 ufw 文件
u32 err = device_update("sd0", 1);
if (err == UPDATA_READY) {
// 设备已就绪,稍后执行升级
err = device_update(NULL, 0); // t_dev_name 传 NULL 沿用上次设备
}
说明:
device_update封装dev_update_check,check_flag区分检查/执行两个阶段;"udisk0"不支持升级。实现见 device_mge.c。
Performance & Operational Notes
- 热路径:文件系统 IO 经过
dev_bulk_read/write→ops->bulk_read/write→dev_io_t.read/write,两层函数指针调用开销极小;块传输避免逐字节调用,适合音频/资源流。 - 状态轮询成本:
device_online()对每个槽位执行dev_online探测,频繁调用会放大探测开销;建议由事件驱动(EVENT_SD0_IN/OUT)配合低频轮询。 - 内存:SD 驱动使用 512 字节静态缓冲(
sd_buffer),避免热插拔回调中的动态分配;驱动私有数据通过device.driver_data持有,生命周期与device实例一致。 - 关闭重试:
dev_close失败时 20 次重试可能阻塞调用方;上层应避免在中断上下文调用device_close。
Extension Points
- 新增设备介质:实现
dev_io_t(mount/read/write/ioctrl/power/detect +dev_type),再用REGISTER_DEVICE注册dev_node;如需暴露给业务层,在device_mge的枚举与device_name[]表中增加槽位(受EXT_FLASH_EN等宏控制)。 - 新设备类型:扩展
dev_type枚举段(存储类0x10、逻辑类0x100、外设类0x200),并实现对应 ioctl 命令。 - 热插拔事件:在驱动
detect/状态变化处调用device_status_emit上报EVENT_*,事件语义与消息队列由系统消息模块统一处理。 - 升级设备:通过
TFG_DEV_UPGRADE_SUPPORT使能device_update,新设备接入升级链路只需保证可被dev_open打开并承载 ufw 文件。 - 只读资源区:
INNER_FLASH_RO槽位是预留的只读资源挂载点,应用可通过扩展device_name[]中的NULL槽位为其绑定具体资源设备。
Tests
当前仓库未发现针对 device_mge 的独立单元测试文件;其正确性主要依赖运行期验证路径:
- 启动初始化:
devices_init()扫描.device段并初始化所有注册节点。 - 引用计数验证:多模块交替
device_open/device_close场景(文件系统 + 升级)。 - 热插拔验证:
EVENT_SD0_IN/OUT事件流(TFG_SD_EN使能时)。 - 升级链路验证:
device_update检查/执行 UFW(TFG_DEV_UPGRADE_SUPPORT使能时)。
注:源材料中未见测试文件;如需补充测试策略,可基于上述运行期路径设计。