设备管理框架 (dev_mg)
设备管理框架(dev_mg)是 AW30N BLE SDK 中面向嵌入式系统的统一设备抽象层:它通过"设备节点 + 操作函数表"的注册式模型,把 SFC Flash、SD 卡、OTG/U 盘等存储外设抽象为可按名称打开、读写、控制的标准设备,供文件系统、VM 存储、USB 协议栈等上层模块统一访问。
目的与范围
本页面向 dev_mg 框架本身,涵盖:
- 设备模型的核心数据结构(
dev_node、device、device_operations、dev_node_mg); - 链接器段(
.device)驱动的设备注册机制(REGISTER_DEVICE/REGISTER_DEVICES); - 运行时设备管理 API(
devices_init_api、dev_online、dev_open、dev_ioctl、dev_close、字节/扇区读写接口); - IOCTL 命令体系(设置类、查询类、擦除类、VM 信息类);
- 配套的原子操作实现(
dev_mg/atomic.h)。
框架的具体实现以预编译库形式提供(dev_mg_lib.a),本页依据公开头文件描述其对外契约与使用方式。以下主题属于兄弟页面,不在本页展开:文件系统与分区布局、USB Host 协议栈(usb_host.h / usb_storage.h 只是 dev_mg 的使用方)、VM 键值存储的实现细节、以及各具体外设驱动(SFC/SD/OTG)内部的时序。
概述
设计动机
在典型的 MCU 嵌入式固件中,上层模块(文件系统、录音、OTA、VM 存储)需要访问多种存储介质:片内 SPI Flash(sfc)、SD 卡(sd0)、加密 SD(sd_enc)、扩展 Flash(ext_flsh)、USB 大容量存储(udisk0)等。如果每个上层模块都直接面对驱动差异,代码将高度耦合且难以移植。
dev_mg 借鉴类 Linux 的"设备模型"思路,把差异收敛到两层契约:
- 注册期:每个设备驱动在编译期通过
REGISTER_DEVICE宏把自己的dev_node放进链接器.device段,形成一张静态设备表; - 运行期:上层通过设备名字符串查找节点(
dev_online/dev_open),拿到struct device句柄后,只调用device_operations中的统一函数指针(read/write/ioctl/seek…),无需关心底层介质。
这种设计带来的直接收益是:新增一种存储介质只需"注册节点 + 实现 ops",上层代码零改动;同时设备句柄内置 atomic_t ref 引用计数,配合关中断实现的原子操作,保证多任务环境下打开/关闭/读写的并发安全。
位置与形态
dev_mg 的头文件位于 sdk/apps/include_lib/dev_mg/,实现以静态库形式预编译:
- device.h:设备模型与全部 API 声明;
- ioctl_cmds.h:IOCTL 命令号定义;
- atomic.h:原子整数操作;
sdk/apps/include_lib/liba/bd49/mbox_flash/dev_mg_lib.a:BD49 平台 mbox_flash 工程的预编译实现。
它被 flash_wp.h、vm.h、usb_host.h、usb_storage.h 等模块广泛引用,是存储栈的公共底座。
关键概念
| 概念 | 说明 |
|---|---|
设备节点 dev_node | 静态注册的"目录项":名字 + ops + 私有数据,编译期放入 .device 段 |
设备句柄 device | 打开后返回的运行时实例:带引用计数、平台数据、驱动数据 |
操作表 device_operations | 设备的能力契约:online/init/open/read/write/bulk_read/bulk_write/seek/ioctl/close |
| IOCTL 命令 | 对设备进行参数配置、状态查询、擦除等控制的面板 |
原子操作 atomic_t | 基于关中断(CPU_CRITICAL_ENTER/EXIT)实现的多任务安全计数器 |
架构
flowchart TD
subgraph sg_Upper["上层模块 (dev_mg 使用者)"]
FS["文件系统 / FAT"]
VM["VM 键值存储"]
USB["USB Host 协议栈"]
WP["flash_wp 写保护"]
end
subgraph sg_DevMg["dev_mg 设备管理框架"]
API["devices_init_api / dev_open / dev_ioctl<br/>dev_byte_read / dev_bulk_read ..."]
TABLE["设备注册表 (.device 段)<br/>device_node_begin[] ~ device_node_end[]"]
OPS["struct device_operations<br/>read / write / ioctl / seek ..."]
ATOMIC["atomic.h 原子操作<br/>ref 引用计数"]
end
subgraph sg_Driver["设备驱动层 (注册节点)"]
SFC["sfc (SPI Flash)"]
SD0["sd0 / sd_enc / sd_cdrom"]
EXT["ext_flsh (扩展 Flash)"]
OTG["otg / udisk0 (USB 存储)"]
end
FS --> API
VM --> API
USB --> API
WP --> API
API --> TABLE
API --> ATOMIC
TABLE --> OPS
OPS --> SFC
OPS --> SD0
OPS --> EXT
OPS --> OTG
架构说明:上层模块只与 dev_mg 的 API 交互;dev_open 依据名字在 .device 段注册表(device_node_begin ~ device_node_end)中线性查找节点,取出 device_operations 函数表;后续所有读写控制都经由该函数表分发到具体驱动(sfc/sd/ext_flsh/udisk0)。引用计数(atomic_t ref)贯穿句柄生命周期,保证并发打开/关闭安全。整个框架的实现被封装进 dev_mg_lib.a,对外只暴露头文件契约。
核心数据结构
设备操作表 struct device_operations
device.h 第 20-31 行 定义了设备驱动必须实现的能力集:
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);
};
Source: device.h
每个函数指针的含义与设计意图:
online(node):查询设备是否在线(如 SD 卡是否插入、U 盘是否枚举完成),供上层做热插拔检测;init(node, arg):驱动级初始化,在devices_init阶段被逐个调用;open(name, device, arg):打开设备并填充struct device实例(注意驱动层 open 输出的是句柄指针);read/write:带偏移的字节级读写(u32参数为偏移量);bulk_read/bulk_write:扇区级批量读写(u32参数为起始扇区/扇区数),对应文件系统按块访问的模式;seek:移动读写位置,orig语义类似标准 C 的SEEK_SET/CUR/END;ioctl(device, cmd, arg):命令面板入口,cmd 取ioctl_cmds.h中的命令号;close(device):释放设备。
设备节点 struct dev_node 与句柄 struct 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;
};
Source: device.h
dev_node是静态注册表项,name是全局查找键,priv_data携带驱动私有配置(如寄存器基址、引脚配置);device是运行时句柄,ref(atomic_t)记录引用计数,platform_data与driver_data供驱动保存上下文。dev_mg 的打开路径会把dev_node.ops复制/关联到句柄的ops,使上层拿到句柄后即可直接分发调用。
注册表管理 struct dev_node_mg
struct dev_node_mg {
struct dev_node *device_node_begin;
struct dev_node *device_node_end;
};
extern const struct dev_node device_node_begin[];
extern const struct dev_node device_node_end[];
Source: device.h
链接器为 .device 段生成 device_node_begin / device_node_end 两个边界符号,框架据此把"散落"在各驱动文件中的注册项收敛成一段连续内存数组。dev_node_mg 则允许运行时通过 set_device_node() 重新设定注册表范围(例如多分区/多镜像场景下切换设备表)。
内置设备名常量
device.h 第 8-15 行 预定义了框架内置设备的规范名称:
#define __SFC_NANE "sfc"
#define __SD0_NANE "sd0"
#define __SD_CDROM "sd_cdrom"
#define __SD_ENC "sd_enc"
#define __EXT_FLASH_NANE "ext_flsh"
#define __OTG "otg"
// #define __UDISK0 "udisk0"
#define __UDISK "udisk0"
Source: device.h
这些宏是上层调用 dev_open("sfc", ...) 等接口时的推荐写法——使用宏而非裸字符串可以避免拼写错误,并让设备名变更时只改一处。
设备注册机制
编译期注册宏
#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
设计要点:
sec_used(.device)是链接器段属性,把变量放进.device段,并抑制"定义了未使用"的告警(used属性);REGISTER_DEVICE注册单个节点,REGISTER_DEVICES注册数组(例如 sd0/sd_cdrom/sd_enc 多个 SD 变体可一次性注册);- 注册是"零运行时代价"的:不消耗 RAM、不需要构造函数,只是把
const dev_node布局进 Flash; - 多个驱动文件各自调用宏,链接器保证它们在
.device段内按序排列,配合device_node_begin/end边界符号形成完整注册表。
初始化流程
int devices_init(void);//该接口由devices_init_api()调用,不可单独使用
int devices_init_api(void);
Source: device.h
devices_init()是内部实现函数,头文件注释明确要求不可单独调用,必须经由devices_init_api()进入;devices_init_api()是应用层入口:遍历.device段注册表,对每个节点调用ops->init(node, priv_data)完成驱动初始化,并建立后续查找所需的管理结构;- 这种"内部函数 + 公共 API"双层设计是为了防止调用次序错误——直接调用
devices_init会跳过框架自身的准备工作。
运行时设备管理 API
device.h 第 63-72 行 声明了全部公共接口:
int devices_init_api(void);
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);
struct dev_node_mg *set_device_node(struct dev_node *node_start, struct dev_node *node_end);
int device_status_emit(const char *device_name, const u8 status);
Source: device.h
各接口职责:
| 接口 | 职责 |
|---|---|
dev_online(name) | 按名查询设备是否在线(包装 ops->online),返回 bool |
dev_open(name, arg) | 按名打开设备:查找节点 → 分配/初始化句柄(递增 ref)→ 返回 struct device *(以 void * 传递) |
dev_ioctl(device, cmd, arg) | 命令面板:透传到 ops->ioctl,cmd 取 ioctl_cmds.h 命令号 |
dev_close(device) | 关闭设备:递减引用计数,计数归零时执行 ops->close |
dev_byte_read/write | 字节级带偏移读写(offset/len),面向流式或小数据访问 |
dev_bulk_read/write | 扇区级批量读写(sector/sector_num),面向文件系统按块访问 |
set_device_node(start, end) | 运行时重设设备注册表范围,返回旧的 dev_node_mg *,用于多镜像/多设备表切换 |
device_status_emit(name, status) | 上报设备状态事件(如拔插/就绪),供框架或上层订阅处理 |
设计意图:void * 句柄传递是嵌入式 C 中常见的"不透明指针"手法——上层无需包含驱动私有头文件,只依赖 dev_mg 的公共契约;而 dev_byte_* 与 dev_bulk_* 的分离,则让流式访问(OTA、录音)与块式访问(文件系统)各取所需,不必为小数据量付出扇区对齐的代价。
IOCTL 命令体系
ioctl_cmds.h 把设备控制命令按功能分组编号,命令号即 dev_ioctl(device, cmd, arg) 的 cmd 参数。
设置类命令(1-48)
#define IOCTL_SET_IRQ_NUM 1
#define IOCTL_SET_PRIORITY 2
#define IOCTL_SET_DATA_WIDTH 3
#define IOCTL_SET_SPEED 4
#define IOCTL_SET_DETECT_MODE 5
#define IOCTL_SET_DETECT_FUNC 6
#define IOCTL_SET_DETECT_TIME_INTERVAL 7
#define IOCTL_SET_PORT 8
#define IOCTL_SET_PORT_FUNC 9
#define IOCTL_SET_CS_PORT_FUNC 10
#define IOCTL_SET_READ_MODE 11
#define IOCTL_SET_WRITE_MODE 12
#define IOCTL_SET_WRITE_PROTECT 13
#define IOCTL_SET_START_BIT 14
#define IOCTL_SET_STOP_BIT 15
#define IOCTL_FLUSH 16
#define IOCTL_REGISTER_IRQ_HANDLER 17
#define IOCTL_UNREGISTER_IRQ_HANDLER 18
#define IOCTL_GET_SYS_TIME 19
#define IOCTL_SET_SYS_TIME 20
#define IOCTL_GET_ALARM 21
#define IOCTL_SET_ALARM 22
#define IOCTL_SET_CAP_LOWSPEED_CARD 23
#define IOCTL_SET_VDD50_EN 30
#define IOCTL_GET_WEEKDAY 32
#define IOCTL_CLR_READ_MODE 33
#define IOCTL_SET_READ_CRC 34
#define IOCTL_GET_READ_CRC 35
#define IOCTL_GET_VOLUME 36
#define IOCTL_SET_VOLUME 37
#define IOCTL_SET_ALARM_ENABLE 38
#define IOCTL_CMD_RESUME 39
#define IOCTL_CMD_SUSPEND 40
#define IOCTL_SET_BASE_ADDR 41
#define IOCTL_SET_ASYNC_MODE 42
#define IOCTL_SET_READ_USE_CACHE 43
#define IOCTL_SET_CACHE_SYNC_ISR_EN 44
#define IOCTL_SET_POWER_DOWN 45
#define IOCTL_RELEASE_POWER_DOWN 46
#define IOCTL_SET_PROTECT_INFO 47
#define IOCTL_SET_SFC_READ 48
Source: ioctl_cmds.h
查询类命令(100-116)
#define IOCTL_GET_ID 100
#define IOCTL_GET_SECTOR_SIZE 101
#define IOCTL_GET_BLOCK_SIZE 102
#define IOCTL_GET_CAPACITY 103
#define IOCTL_GET_WIDTH 104
#define IOCTL_GET_HEIGHT 105
#define IOCTL_GET_BLOCK_NUMBER 106
#define IOCTL_CHECK_WRITE_PROTECT 107
#define IOCTL_GET_STATUS 108
#define IOCTL_GET_TYPE 109
#define IOCTL_GET_MAX_LUN 110
#define IOCTL_GET_CUR_LUN 111
#define IOCTL_SET_CUR_LUN 112
#define IOCTL_SET_FORCE_RESET 113
#define IOCTL_SET_CAPACITY 114
#define IOCTL_GET_NORFS_INFO 115
#define IOCTL_GET_PART_INFO 116
Source: ioctl_cmds.h
擦除与存储控制命令
#define IOCTL_ERASE_SECTOR 200
#define IOCTL_ERASE_BLOCK 201
#define IOCTL_ERASE_CHIP 202
#define IOCTL_SET_ENC_END 203
#define IOCTL_ERASE_PAGE 204
#define IOCTL_SET_VM_INFO 210
#define IOCTL_GET_VM_INFO 211
#define IOCTL_SET_DATA_CALLBACK 301
Source: ioctl_cmds.h
命令分组的设计意图:
- 1-48 段(配置/控制):涵盖 SPI 时序参数(speed/width/port/CS)、检测机制(detect_mode/func/interval)、中断管理(IRQ handler 注册)、电源管理(power_down/vdd50/resume/suspend)、实时时钟(sys_time/alarm/weekday)等——这些命令多数由驱动初始化或系统策略层在启动早期调用;
- 100-116 段(查询):容量、扇区/块大小、写保护、LUN 数、分区信息等只读几何信息——文件系统挂载前会先通过这些命令探测介质;
- 200-204 段(擦除):Flash 类介质专属的擦除操作(sector/block/chip/page),
IOCTL_SET_ENC_END用于加密存储的结尾标记; - 210-211 段(VM 信息):与 VM 键值存储协作,读写 VM 分区元数据;
- 301 段(数据回调):注册数据回调(如异步读写完成通知)。
注意命令号并非连续(如 23 之后直接跳到 30,24-29 保留),这是为后续扩展预留了编号空间,新增命令时不应占用已定义编号。
原子操作支持
dev_mg/atomic.h 提供轻量原子整数,是 struct device.ref 引用计数的基础。
typedef struct {
int counter;
} atomic_t;
static inline int atomic_add_return(int i, atomic_t *v)
{
int val;
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
val = v->counter;
v->counter = val += i;
CPU_CRITICAL_EXIT();
return val;
}
Source: atomic.h
实现要点:
- 通过
CPU_CRITICAL_ENTER()/CPU_CRITICAL_EXIT()关中断保护"读-改-写"序列,避免任务切换或中断打断造成计数丢失; - 配套宏在 atomic.h 第 56-73 行:
DEFINE_ATOMIC、atomic_read、atomic_inc/dec、atomic_inc_and_test、atomic_dec_and_test、atomic_add_negative等,语义对齐 Linux 内核原子操作 API; - 该头文件同时被 includes.h 全局引用,因此整个 SDK 的共享计数场景都可复用同一套原子语义。
核心流程
sequenceDiagram
participant App as 应用 / 上层模块
participant Mg as dev_mg 框架 (dev_mg_lib.a)
participant Tbl as .device 段注册表
participant Drv as 设备驱动 (sfc/sd0/udisk0)
App->>Mg: devices_init_api()
Mg->>Tbl: 遍历 device_node_begin ~ device_node_end
Mg->>Drv: ops->init(node, priv_data)
Drv-->>Mg: 初始化完成
Mg-->>App: 0 (成功)
App->>Mg: dev_online("sfc")
Mg->>Drv: ops->online(node)
Drv-->>Mg: true
Mg-->>App: true
App->>Mg: dev_open("sfc", arg)
Mg->>Tbl: 按 name 查找 dev_node
Mg->>Drv: ops->open(name, &device, arg)
Mg->>Mg: ref++ (atomic_inc)
Mg-->>App: struct device * (void *)
App->>Mg: dev_ioctl(device, IOCTL_GET_CAPACITY, &cap)
Mg->>Drv: ops->ioctl(device, cmd, arg)
Drv-->>Mg: 容量数据
Mg-->>App: 0
App->>Mg: dev_bulk_read(device, buf, sector, num)
Mg->>Drv: ops->bulk_read(device, buf, len, sector)
Drv-->>Mg: 数据
Mg-->>App: 读取字节数
App->>Mg: dev_close(device)
Mg->>Mg: ref-- (atomic_dec_and_test)
Mg->>Drv: ops->close(device) (ref 归零时)
Mg-->>App: 0
流程要点:
- 初始化阶段:
devices_init_api()扫描链接器段注册表,逐个执行ops->init;此步骤必须早于任何dev_open,否则注册表尚未就绪; - 探测阶段:上层先用
dev_online(name)做非阻塞在线检查(例如 SD 卡未插入时直接跳过挂载流程); - 打开阶段:
dev_open在注册表中按名字查找到dev_node后,通过ops->open得到句柄并递增引用计数——同一个设备可被多个模块同时打开; - 使用阶段:IOCTL 查询几何参数 → 字节/扇区读写;所有调用都经由统一 ops 函数表分发,上层不感知介质差异;
- 关闭阶段:
dev_close递减引用计数,仅当计数归零(最后一个使用者退出)时才真正执行ops->close释放资源,避免提前关闭导致其他使用者悬空。
使用示例
以下示例均从 dev_mg 公开头文件的实际内容中提取,展示上层如何与框架协作。
示例 1:注册一个设备节点
驱动侧在编译期把节点放入 .device 段。单个设备与设备数组两种写法(框架内置 sfc_dev_ops 即通过该机制暴露):
#define REGISTER_DEVICE(node) \
const struct dev_node node sec_used(.device)
#define REGISTER_DEVICES(node) \
const struct dev_node node[] sec_used(.device)
extern const struct device_operations sfc_dev_ops;
Source: device.h
驱动实现侧通常写作(示意,基于宏契约):
static const struct dev_node sfc_node = {
.name = __SFC_NANE, // "sfc"
.ops = &sfc_dev_ops, // 指向框架导出的操作表
};
REGISTER_DEVICE(sfc_node);
示例 2:按名称打开并查询设备
上层打开设备并查询介质几何参数——这是文件系统挂载前的典型动作:
void *dev = dev_open(__SFC_NANE, NULL); // 打开 SPI Flash
if (!dev) {
/* 设备不存在或打开失败 */
return;
}
u32 capacity = 0;
if (dev_ioctl(dev, IOCTL_GET_CAPACITY, (u32)&capacity) != 0) {
/* 查询失败 */
}
/* ... 使用 dev_byte_read / dev_bulk_read / dev_bulk_write ... */
dev_close(dev); // 释放引用
Source: device.h 与 ioctl_cmds.h
示例 3:扇区级批量读写
面向文件系统的块访问模式(sector 为起始扇区号,sector_num 为扇区数):
int ret = dev_bulk_read(dev, buf, sector_start, sector_count);
if (ret < 0) {
/* 读失败,依据返回值做重试或报错 */
}
ret = dev_bulk_write(dev, buf, sector_start, sector_count);
if (ret < 0) {
/* 写失败,注意 Flash 写前通常需先擦除 */
}
Source: device.h
示例 4:Flash 擦除与电源控制
通过 IOCTL 执行介质专属操作——擦除和低功耗控制无法用 read/write 表达,必须走命令面板:
dev_ioctl(dev, IOCTL_ERASE_SECTOR, sector_addr); // 擦除单个扇区
dev_ioctl(dev, IOCTL_ERASE_CHIP, 0); // 整片擦除(谨慎使用)
dev_ioctl(dev, IOCTL_SET_POWER_DOWN, 0); // 进入低功耗
dev_ioctl(dev, IOCTL_RELEASE_POWER_DOWN, 0); // 退出低功耗
Source: ioctl_cmds.h
配置选项
设备名称常量(上层必须与驱动保持一致)
| 宏 | 字符串值 | 对应介质 | 说明 |
|---|---|---|---|
__SFC_NANE | "sfc" | 片内 SPI Flash | 系统主存储,sfc_dev_ops 由框架导出 |
__SD0_NANE | "sd0" | SD 卡 0 | 标准 SD/MMC 卡 |
__SD_CDROM | "sd_cdrom" | SD 只读镜像 | 类似 CD-ROM 的只读 SD 场景 |
__SD_ENC | "sd_enc" | 加密 SD 卡 | 带加密逻辑的 SD |
__EXT_FLASH_NANE | "ext_flsh" | 外部扩展 Flash | 外挂 SPI Flash |
__OTG | "otg" | OTG 控制器 | USB OTG 主机侧设备 |
__UDISK | "udisk0" | U 盘 | USB 大容量存储(__UDISK0 旧名已注释保留) |
Source: device.h
IOCTL 命令号分段(配置面板的总览)
| 编号段 | 分组 | 典型用途 |
|---|---|---|
| 1-23, 30-48 | 配置/控制 | SPI 时序、检测、IRQ、电源、RTC、CRC、缓存 |
| 100-116 | 查询/几何 | ID、容量、扇区/块大小、LUN、分区、写保护 |
| 200-204 | 擦除 | sector/block/chip/page 擦除、加密结尾 |
| 210-211 | VM 信息 | VM 分区元数据读写 |
| 301 | 数据回调 | 注册数据回调 |
API 参考
以下签名均取自 device.h 第 61-72 行。
int devices_init_api(void)
设备框架初始化入口:遍历 .device 段注册表并对每个节点执行 ops->init。
返回:0 表示成功;非 0 表示初始化失败(如某个驱动 init 出错)。
注意:devices_init() 为内部函数,头文件明确标注"该接口由 devices_init_api() 调用,不可单独使用"。
bool dev_online(const char *name)
按名称查询设备在线状态。
参数:name — 设备名(建议使用 __SFC_NANE 等宏)。
返回:true 在线;false 离线或设备不存在。
void *dev_open(const char *name, void *arg)
打开设备,返回不透明句柄。
参数:
name:设备名;arg:打开参数(如访问模式、通道号),无参传NULL。
返回:struct device * 句柄(以 void * 传递);失败返回 NULL。句柄内部引用计数已递增,使用完必须 dev_close。
int dev_ioctl(void *device, int cmd, u32 arg)
向设备发送控制命令。
参数:
device:dev_open返回的句柄;cmd:命令号,取ioctl_cmds.h定义;arg:命令参数(常为数据缓冲地址或数值,查询类命令通常传入结果缓冲区指针)。
返回:0 成功;非 0 失败(具体语义由驱动定义,如参数非法、介质忙)。
int dev_close(void *device)
关闭设备,递减引用计数;计数归零时执行驱动 ops->close 释放资源。
返回:0 成功;非 0 失败。
int dev_byte_read(void *_device, void *buf, u32 offset, u32 len) / dev_byte_write
字节级读写,offset 为介质内字节偏移,len 为长度。
返回:读取/写入的字节数;负值表示错误。
int dev_bulk_read(void *_device, void *buf, u32 sector, u32 sector_num) / dev_bulk_write
扇区级批量读写,sector 为起始扇区号,sector_num 为扇区数量。文件系统块访问的标准路径。
返回:成功处理的扇区数或字节数;负值表示错误。
struct dev_node_mg *set_device_node(struct dev_node *node_start, struct dev_node *node_end)
运行时重设设备注册表范围(多镜像/多分区设备表切换)。
返回:旧的 dev_node_mg *,便于恢复现场。
int device_status_emit(const char *device_name, const u8 status)
上报设备状态事件(热插拔、就绪等),供框架或上层监听处理。
返回:0 成功;非 0 失败。
失败模式、边界情况与并发
打开失败与空句柄
dev_open 在两种典型情况下返回 NULL:注册表中不存在该名字的节点(名字拼写错误或驱动未注册),或驱动 ops->open 失败(介质未就绪、资源不足)。上层必须判空后再使用句柄,否则 dev_ioctl / dev_bulk_read 等接口会解引用空指针。建议一律使用 __SFC_NANE 等宏而非裸字符串,从编译期消除名字不匹配风险。
热插拔与在线检测
dev_online 与 device_status_emit 共同支撑热插拔场景:SD 卡/U 盘随时可能被拔出,正在进行的 dev_bulk_read/write 可能因介质掉线而失败。上层应在批量读写前检查 dev_online,并在读写返回错误后重试或重新挂载。IOCTL_SET_DETECT_MODE/FUNC/TIME_INTERVAL(命令 5-7)允许驱动配置轮询检测周期,平衡响应速度与功耗。
引用计数与关闭时序
struct device.ref 是 atomic_t,dev_open 递增、dev_close 递减,仅当计数归零才真正释放驱动资源。这保证了多个模块(文件系统 + 录音 + VM)同时持有同一设备句柄时,任何一个模块提前关闭都不会破坏其他模块。错误用法:同一个句柄被 dev_close 两次会导致计数过减,可能提前触发资源释放——上层必须保证 open/close 配对。
原子性与中断上下文
atomic.h 的"读-改-写"通过关中断(CPU_CRITICAL_ENTER/EXIT)实现,代价是临界区内不允许执行耗时操作。若在中断服务程序中直接调用依赖原子操作的 dev_mg 接口,需注意临界区长度;IOCTL_REGISTER_IRQ_HANDLER/UNREGISTER_IRQ_HANDLER(命令 17/18)的存在说明框架支持驱动注册自己的中断处理,中断回调里应避免调用可能阻塞的读写路径。
Flash 擦写约束
对 sfc/ext_flsh 等 Flash 介质:写操作前通常需要先擦除(IOCTL_ERASE_SECTOR/BLOCK/CHIP),且擦除粒度(sector/block)可能大于写入粒度;IOCTL_ERASE_CHIP 是全片擦除,误用会销毁整个固件,仅应在烧录/量产流程中使用。加密介质(sd_enc)需注意 IOCTL_SET_ENC_END 的顺序约束。
IOCTL 参数错误
IOCTL 采用 (cmd, u32 arg) 扁平参数模型,查询类命令(如 IOCTL_GET_CAPACITY)要求调用方传入足够大的结果缓冲区地址;arg 传 0 或指针非法会造成驱动写坏内存。使用前应核对命令期望的参数类型(数值 vs 指针)。
并发访问
- 多任务共享设备:读写路径本身是否线程安全由具体驱动决定;框架层只保证句柄引用计数原子。同一介质的并发写需要上层加锁(如文件系统层的互斥)。
- 注册表只读:
.device段是只读的静态数据,运行期set_device_node可整体切换注册表范围,但不应在并发访问设备时修改,否则查找结果不一致。
性能与运维注意事项
- 预编译实现:dev_mg 的实现位于
sdk/apps/include_lib/liba/bd49/mbox_flash/dev_mg_lib.a,面向 BD49 平台 mbox_flash 工程。更换平台或功能裁剪时需替换对应库,头文件契约保持不变。 - 查找复杂度:
dev_open按名字在.device段内线性查找。内置设备数量有限(sfc/sd0/sd_enc/ext_flsh/otg/udisk0 等),线性查找的开销可忽略;但若业务自定义大量设备节点,打开热点路径会随节点数线性增长。 - 批量读写是性能主路径:文件系统的吞吐依赖
dev_bulk_read/write的扇区连续访问,字节级接口用于小数据场景;IOCTL_SET_READ_USE_CACHE(43)/IOCTL_SET_CACHE_SYNC_ISR_EN(44)可启用读缓存与 ISR 同步,属于性能调优开关,需结合具体驱动文档使用。 - 电源管理:空闲时可用
IOCTL_SET_POWER_DOWN让 Flash 进入低功耗,配合IOCTL_RELEASE_POWER_DOWN恢复;功耗敏感产品应在挂起/恢复流程中成对调用。 - 故障定位:所有 API 以
int返回值报告错误,dev_online提供在线探测。排障顺序建议:先确认devices_init_api成功 →dev_online为 true → 单步验证dev_ioctl查询 → 再进入读写路径。
扩展点
dev_mg 的设计为新增介质与扩展控制命令留出了清晰的扩展路径:
- 注册新设备:实现
struct device_operations(可复用sfc_dev_ops等现有模式),用REGISTER_DEVICE/REGISTER_DEVICES宏把dev_node放入.device段,无需改动框架与上层代码。框架导出的sfc_dev_ops是现成的参考实现。 - 新增设备名:在
device.h的命名区(第 8-15 行)追加__XXX_NANE宏,保持命名规范统一。 - 扩展 IOCTL:
ioctl_cmds.h已为各分组预留编号空间(如 24-29、117-199、205-209 等未使用区间);新增命令应写入对应分组,避免与现有编号冲突。 - 多设备表:通过
set_device_node在运行期切换注册表范围,可支持多镜像/多分区固件的设备视图切换。 - 状态订阅:通过
device_status_emit上报事件,上层可据此实现热插拔处理与设备状态机。
测试与使用方
dev_mg 被 SDK 中多个关键模块直接引用,其正确性由这些使用方共同验证:
- flash_wp.h:Flash 写保护模块,基于 dev_mg 访问 Flash 并设置保护区域;
- vm.h:VM 键值存储,通过 dev_mg 读写 Flash 分区(对应
IOCTL_SET/GET_VM_INFO); - usb_host.h / usb_storage.h:USB Host 协议栈,将 U 盘/OTG 设备接入 dev_mg 设备表;
- includes.h:全局包含
dev_mg/atomic.h,使原子操作成为 SDK 公共基础件。
这些使用方覆盖了"打开 → 查询 → 读写 → 关闭"的完整生命周期,是验证框架行为(引用计数、在线检测、IOCTL 分发)的主要场景。
相关链接
- 设备管理框架头文件 device.h
- IOCTL 命令定义 ioctl_cmds.h
- 原子操作实现 atomic.h
- dev_mg 预编译库 dev_mg_lib.a
- 使用方:VM 键值存储(vm.h)、Flash 写保护(flash_wp.h)、USB Host 存储(usb_storage.h)——各自的实现细节见对应目录页面