系统文件系统与设备模型
本页介绍 AC82N 平台(GP-MCU SDK)的设备模型(Device Model)与文件系统层(File System):从设备节点的注册、初始化、打开/读写/控制(ioctl)的标准操作流程,到基于设备模型之上的文件系统访问接口(fs.h / sdfile.h),以及 Nor Flash、MSFC 等存储设备在工程中的实际配置方式。
Purpose and Scope
本文档面向需要理解或扩展 AC82N 存储子系统的固件工程师,覆盖以下内容:
- 设备模型:
struct device_operations、struct dev_node、struct device、struct dev_node_mg等核心数据结构,以及REGISTER_DEVICE(S)链接节注册机制。 - 设备驱动层:
dev_type、DEV_STA(设备状态)、DEV_ERR(错误码)、通用ioctl命令(DEV_GET_STATUS、DEV_GET_BLOCK_SIZE等)。 - 文件系统层:
fs.h中的定位与文件属性定义,以及sdfile.h提供的文件级 API 入口。 - 工程配置:
sdk/apps/gp_mcu/device_config.c中 Nor Flash(512B 块模式 / 1B 字节模式)与 MSFC 设备的平台数据与注册表。
以下主题属于其他页面,不在本文展开:
- USB 设备协议栈(
sdk/apps/common/device/usb/device/usb_device.c、cdc_defs.h)——属于 USB 子系统。 - UBOOT 中的 JLFS 实现(
sdk/UBOOT工程/include_lib/driver/jlfs/jlfs.h)——属于引导加载程序文件系统,本文仅提及它与运行期 FS 的关系。 - 具体文件系统格式实现(FAT/JLFS 内部结构)——本文聚焦设备抽象层与 FS 访问接口的分层关系。
Overview
在嵌入式系统中,"文件系统"与"设备"之间存在清晰的抽象分层:文件系统层向应用提供 f_open、f_read 之类的文件语义接口;设备模型层向文件系统提供统一的"打开—读写—控制—关闭"设备操作接口;驱动层把设备操作翻译成具体的 SPI/SDIO/USB 硬件时序。
AC82N 的设备模型借鉴了 Linux 设备模型的思路但做了面向 MCU 的简化:
- 每个设备是一个
dev_node,包含名字(name)、操作集指针(ops)和私有数据(priv_data)。 - 所有
dev_node通过sec_used(.device)链接节机制静态注册进一个设备节点表,由dev_node_mg描述表的起止范围。 - 应用通过
dev_open(name, arg)以名字查找并打开设备,得到一个带**原子引用计数(atomic_t ref)**的device句柄,之后所有读写都经由device->ops分发到具体驱动。 - 文件系统通过
dev_byte_read/dev_byte_write(1 字节粒度)或dev_bulk_read/dev_bulk_write(512B 扇区粒度)与存储设备交互——粒度选择由设备 ops 决定:Nor Flash 提供两种 ops(norflash_dev_ops512B 块读写、norfs_dev_ops1B 字节读写),MSFC 提供msfc_dev_ops。
这种"名字查找 + 操作集分发"的设计使上层代码与具体存储介质解耦:更换 Flash 型号、切换 SPI 引脚或增加新存储设备时,只需修改设备注册表与平台数据,文件系统与应用层代码无需改动。
Architecture
flowchart TD
subgraph sg_App["应用层 (App)"]
App["应用代码 / 业务模块"]
end
subgraph sg_FS["文件系统层 (FS)"]
FSAPI["fs.h / sdfile.h<br/>文件级 API"]
JLFS["JLFS / FAT 文件系统"]
end
subgraph sg_Dev["设备模型层 (Device Model)"]
DevAPI["dev_open / dev_byte_read<br/>dev_bulk_read / dev_ioctl"]
DevNode["dev_node 设备节点表<br/>(.device 链接节)"]
Ops["device_operations 操作集"]
end
subgraph sg_Drv["驱动层 (Driver)"]
Norflash["norflash_dev_ops<br/>512B 扇区读写"]
Norfs["norfs_dev_ops<br/>1B 字节读写"]
Msfc["msfc_dev_ops<br/>512B 扇区读写"]
end
subgraph sg_Hw["硬件层 (Hardware)"]
SPI2["SPI2 控制器"]
Flash[("外部 Nor Flash<br/>2 x 1MB 分区")]
end
App --> FSAPI
FSAPI --> JLFS
JLFS --> DevAPI
DevAPI --> DevNode
DevNode --> Ops
Ops --> Norflash
Ops --> Norfs
Ops --> Msfc
Norflash --> SPI2
Norfs --> SPI2
Msfc --> SPI2
SPI2 --> Flash
分层说明:
- 应用层只与文件 API 交互,不感知底层是 Nor Flash、MSFC 还是将来的其他介质。
- 文件系统层(
fs.h→sdfile.h)把文件/目录语义翻译为对设备模型的标准操作;fs.h同时定义了SEEK_SET/SEEK_CUR/SEEK_END与文件属性F_ATTR_*,供文件系统实现与上层共用。 - 设备模型层是核心抽象:
dev_open按名查表返回句柄,随后所有操作通过ops函数指针分发;dev_node表由编译期链接节静态构建,零运行时注册开销。 - 驱动层实现
device_operations:norflash_dev_ops与msfc_dev_ops支持 512B 为单位的块读写(适合 FAT/JLFS 扇区访问),norfs_dev_ops支持 1B 为单位的字节读写(适合直接寻址访问)。 - 硬件层:Nor Flash 通过 SPI2 挂接,工程默认划分为两个 1MB 分区——
ext_flsh_bulk(起始地址 0,块模式)与ext_flsh_byte(起始地址 1MB,字节模式),详见 device_config.c。
设备模型核心类型关系如下:
classDiagram
class dev_node {
+name: const char*
+ops: const struct device_operations*
+priv_data: void*
}
class device_operations {
+online(node) bool
+init(node, void*) int
+open(name, device**, arg) int
+read(device, buf, len, offset) int
+write(device, buf, len, offset) int
+bulk_read(device, buf, len, offset) int
+bulk_write(device, buf, len, offset) int
+seek(device, offset, orig) int
+ioctl(device, cmd, arg) int
+close(device) int
}
class device {
+ref: atomic_t
+private_data: void*
+ops: const struct device_operations*
+platform_data: void*
+driver_data: void*
}
class dev_node_mg {
+device_node_begin: struct dev_node*
+device_node_end: struct dev_node*
}
dev_node --> device_operations : "引用 ops"
device --> device_operations : "引用 ops"
dev_node_mg --> dev_node : "管理节点表范围"
dev_node 是静态注册表项(编译期生成、只读),device 是运行时句柄(打开时创建、带引用计数),二者通过 ops 共享同一操作集——这是典型的"表项/实例分离"设计,既保证了注册零开销,又允许同一驱动服务多个实例(如多分区)。
设备模型核心数据结构
设备模型定义在 device.h 中,由四个结构体构成完整抽象:
device_operations —— 驱动操作集
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);
};
来源:device.h
这是整个设备模型的"契约"。设计要点:
- 读写双粒度:
read/write与bulk_read/bulk_write并存。read/write通常实现为字节寻址访问(供norfs_dev_ops),bulk_read/bulk_write实现为扇区(512B)对齐访问(供norflash_dev_ops、msfc_dev_ops)。文件系统根据自身 I/O 粒度选择调用哪一组。 - 名字驱动打开:
open的签名接收设备名字符串,这意味着驱动内部可以按名字区分同一 ops 下的不同实例(例如多个分区共用同一驱动)。 - 统一控制通道:
ioctl接收(cmd, arg),标准命令在 device_drive.h 中以_IOR/_IOW宏定义,驱动不支持的命令约定返回-ENOTTY。
dev_node —— 设备节点(注册表项)
struct dev_node {
const char *name;
const struct device_operations *ops;
void *priv_data;
};
struct dev_node_mg {
struct dev_node *device_node_begin;
struct dev_node *device_node_end;
};
来源:device.h
dev_node 只包含三要素:名字(对外查找键)、操作集(行为)、私有数据(配置,如平台数据指针)。dev_node_mg 用 begin/end 指针划定整个设备节点表的范围,set_device_node() 负责把链接节区间注册进系统。
device —— 运行时句柄
struct device {
atomic_t ref;
void *private_data;
const struct device_operations *ops;
void *platform_data;
void *driver_data;
};
来源:device.h
device 是 open 之后返回的实例句柄,与静态的 dev_node 分离:
ref是atomic_t原子引用计数,保证多任务并发打开/关闭时实例不被提前释放;private_data通常指向设备实例状态;platform_data/driver_data分别承载平台配置与驱动私有数据,便于同一驱动服务多个设备实例。
设备注册机制:链接节静态注册
设备注册不依赖运行时初始化,而是利用编译器的**自定义段(section)**机制:
#define REGISTER_DEVICE(node) \
const struct dev_node node sec_used(.device)
#define REGISTER_DEVICES(node) \
const struct dev_node node[] sec_used(.device)
来源:device.h
所有 REGISTER_DEVICE(S) 声明的节点被链接器集中放入 .device 段,形成一张连续的表。devices_init() 通过 set_device_node() 把该段的起止地址填入 dev_node_mg,从而完成"扫描式注册"——新增设备只需在任意源文件中添加一条 REGISTER_DEVICE 声明,无需修改任何注册代码。这正是 Linux 内核 __section 设备表思想的 MCU 版实现,优点是扩展设备零侵入、启动开销极小。
工程中的实际注册表如下:
REGISTER_DEVICES(device_table) = {
#if TCFG_NORFLASH_DEV_ENABLE
//如需按512byte读写的,选用下面的ops
{.name = __EXT_FLASH_BULK, .ops = &norflash_dev_ops, .priv_data = (void *) &norflash_dev_data},
//如需要按1byte读写的,选用下面的ops
{.name = __EXT_FLASH_BYTE, .ops = &norfs_dev_ops, .priv_data = (void *) &norfs_dev_data},
#endif
#if TCFG_MSFC_DEV_ENABLE
{.name = __MSFC_NAME, .ops = &msfc_dev_ops, .priv_data = (void *) &msfc_dev_data},
#endif
};
注意设备名字使用 device.h 预定义宏:
| 宏 | 值 | 含义 |
|---|---|---|
__EXT_FLASH_BULK | "ext_flsh_bulk" | 外部 Nor Flash,512B 块读写 |
__EXT_FLASH_BYTE | "ext_flsh_byte" | 外部 Nor Flash,1B 字节读写 |
__MSFC_NAME | "msfc" | MSFC 存储控制器 |
__SFC_NAME / __OTG / __UDISK | "sfc" / "otg" / "udisk0" | 内部 SFC、OTG、U 盘(声明于 device.h) |
来源:device.h
同一片 Nor Flash 被注册为两个逻辑设备(ext_flsh_bulk 与 ext_flsh_byte),共享同一 SPI 平台数据但使用不同 ops、占据不同地址区间——这是"一个物理器件、多个逻辑设备"的典型用法,文件系统可按自身 I/O 粒度挂载到合适的逻辑设备上。
设备初始化流程
flowchart TD
Start([系统启动]) --> Init["devices_init()"]
Init --> SetNode["set_device_node(begin, end)<br/>注册 .device 段节点表"]
SetNode --> Scan{"遍历每个 dev_node"}
Scan --> HasOps{"ops->init 已提供?"}
HasOps -->|"是"| DoInit["ops->init(node, priv_data)<br/>驱动自检 / 硬件初始化"]
HasOps -->|"否"| Skip["跳过(设备仅按需初始化)"]
DoInit --> Next["下一个节点"]
Skip --> Next
Next --> More{"还有节点?"}
More -->|"是"| Scan
More -->|"否"| Ready["设备模型就绪<br/>dev_open 可被调用"]
Ready --> App["应用 / 文件系统挂载"]
设计意图:初始化采用"声明式"而非"命令式"。devices_init() 只负责把链接节表登记为 dev_node_mg,并逐一调用各节点的 ops->init 做硬件自检;真正的资源打开延迟到 dev_open() 时进行。这种延迟初始化(lazy init)使系统启动时只为实际使用的设备付出代价,符合 MCU 资源受限的场景。
设备操作 API 与核心流程
设备模型向文件系统与上层应用暴露一组统一 API(均在 device.h 声明):
| API | 说明 |
|---|---|
devices_init() | 初始化设备模型,登记 .device 段节点表并执行各节点 ops->init |
dev_online(name) | 查询设备是否在线(返回 bool) |
dev_open(name, arg) | 按名字打开设备,返回 device* 句柄(失败返回 NULL) |
dev_ioctl(device, cmd, arg) | 向设备发送控制命令 |
dev_close(device) | 关闭设备,递减引用计数 |
dev_byte_read/write(dev, buf, offset, len) | 字节粒度读/写(offset 为字节地址) |
dev_bulk_read/write(dev, buf, sector, sector_num) | 扇区粒度读/写(sector 为扇区号) |
set_device_node(begin, end) | 注册设备节点表范围,返回 dev_node_mg* |
device_status_emit(name, status) | 向系统广播设备状态变化(在线/离线/电源事件) |
一次典型的数据读取完整时序如下:
sequenceDiagram
participant App as 应用 / 文件系统
participant Dev as 设备模型 (device.h)
participant Ops as device_operations 分发
participant Drv as 驱动 (norfs/norflash/msfc)
participant HW as SPI2 / Nor Flash
App->>Dev: dev_online("ext_flsh_byte")
Dev-->>App: bool(设备是否在线)
App->>Dev: dev_open("ext_flsh_byte", arg)
Dev->>Dev: 按名字查找 dev_node 节点
Dev->>Ops: ops->open(name, &device, arg)
Ops->>Drv: 驱动实例初始化 / 硬件使能
Drv-->>Ops: 返回 device 实例(ref = 1)
Ops-->>Dev: 成功
Dev-->>App: device* 句柄
App->>Dev: dev_byte_read(dev, buf, offset, len)
Dev->>Ops: ops->read(device, buf, len, offset)
Ops->>Drv: 按字节地址发起 SPI 读
Drv->>HW: SPI 读命令序列
HW-->>Drv: 数据
Drv-->>Ops: 实际读取字节数
Ops-->>Dev: 返回值
Dev-->>App: 读取长度 / 错误码
App->>Dev: dev_ioctl(dev, DEV_GET_BLOCK_SIZE, &size)
Dev->>Ops: ops->ioctl(device, cmd, arg)
Ops-->>Dev: 块大小
Dev-->>App: 结果
App->>Dev: dev_close(dev)
Dev->>Ops: ops->close(device)
Ops->>Drv: 释放资源 / 硬件下电
Ops-->>Dev: 完成
Dev-->>App: 0
流程要点:
- 按名查找:
dev_open以字符串名字为键在dev_node表中匹配,驱动无需关心上层是谁在调用——文件系统、录音模块、升级模块都可以打开同一个设备。 - 句柄隔离:打开后上层只持有
device*,不接触dev_node表与驱动内部,天然形成访问边界。 - 命令通道:
dev_ioctl是"带外"控制通道,用于查询块大小、块数、设备 ID、执行擦除等不适合用读/写表达的操。
设备状态、类型与错误模型
dev_type —— 设备分类
device_drive.h 定义了设备类型枚举,用于 DEV_GET_TYPE 查询与上层策略判断:
typedef enum _dev_type {
DEV_SDCRAD_0 = 0X10, // SD/TF 卡
DEV_SDCRAD_1,
DEV_SDCRAD_2,
DEV_UDISK_H0, // U 盘(host)
DEV_UDISK_H1,
DEV_UDISK_F0,
DEV_NOR_FLASH, // Nor Flash
DEV_NAND_FLASH, // 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;
类型值按功能域分段(0x10 存储卡、0x100 存储抽象、0x200 外设),便于上层用区间判断设备类别,而非逐一比较。
DEV_STA —— 设备状态
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;
状态分两类语义:在线/离线(可插拔介质,如 SD 卡、U 盘)与电源状态(整机电源管理)。DEV_HOLD 表示状态未变化,用于事件上报时的"保持"语义;device_status_emit() 即用于向系统广播这些状态。
DEV_ERR —— 统一错误码
设备操作返回统一的负数错误码(device_drive.h),覆盖传输、挂载、容量、句柄、权限等典型失败场景:
| 错误码 | 含义 |
|---|---|
DEV_ERR_NONE | 无错误 |
DEV_ERR_NOT_MOUNT | 设备未挂载 |
DEV_ERR_OVER_CAPACITY | 超出容量 |
DEV_ERR_UNKNOW_CLASS | 未知设备类别 |
DEV_ERR_NOT_READY | 设备在线但未初始化完成 |
DEV_ERR_LUN | LUN 错误 |
DEV_ERR_TIMEOUT / DEV_ERR_CMD_TIMEOUT / DEV_ERR_READ_TIMEOUT / DEV_ERR_WRITE_TIMEOUT | 各类超时 |
DEV_ERR_OFFLINE | 设备已离线 |
DEV_ERR_CRC / DEV_ERR_CMD_CRC / DEV_ERR_READ_CRC / DEV_ERR_WRITE_CRC | CRC 校验失败 |
DEV_ERR_CONTROL_STALL / DEV_ERR_RXSTALL / DEV_ERR_TXSTALL / DEV_ERR_CONTROL | USB 控制传输异常 |
DEV_ERR_NOT_STORAGE / DEV_ERR_INVALID_PATH / DEV_ERR_INVALID_DATA / DEV_ERR_OUTOFMEMORY | 语义/资源错误 |
DEV_ERR_HANDLE_FREE / DEV_ERR_INVALID_HANDLE / DEV_ERR_INVALID_BUF | 句柄/缓冲区错误 |
DEV_ERR_INUSE | 设备被占用 |
DEV_ERR_NO_READ / DEV_ERR_NO_WRITE / DEV_ERR_NO_IOCTL | 设备不支持该操作 |
DEV_ERR_NO_POWER | 无电源 |
DEV_ERR_NOT_EXIST | 设备不存在 |
DEV_ERR_UNKNOW | 未知错误 |
错误码体系的分层设计(传输层超时/CRC → 协议层 STALL → 语义层容量/路径)使上层能够按类别进行恢复策略决策,例如 CRC 错误触发重读、超时触发重置、离线触发重新挂载。
通用 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) // 写保护状态检测/设置
命令使用 _IOR/_IOW 编码(magic=0xe0),沿用 Linux ioctl 编码风格。每个设备必须支持 DEV_GET_STATUS;存储类设备支持块大小/块数/擦除命令;不支持的命令约定返回 -ENOTTY。这种"必选命令 + 可选命令"的约定让文件系统可以在挂载前用统一命令探测设备能力。
文件系统层(fs.h / sdfile.h)
文件系统层位于设备模型之上,向应用提供文件语义。fs.h 是该层的公共头,定义了定位方式与文件属性常量:
#define SEEK_SET 0 /* Seek from beginning of file. */
#define SEEK_CUR 1 /* Seek from current position. */
#define SEEK_END 2 /* Seek from end of file. */
#define F_ATTR_RO 0x01
#define F_ATTR_ARC 0x02
#define F_ATTR_DIR 0x04
#define F_ATTR_VOL 0x08
来源:fs.h
设计意图:
SEEK_SET/CUR/END与标准 C 文件定位语义一致,让上层代码可移植;文件系统实现将其映射为设备模型的ops->seek。F_ATTR_*是文件属性位掩码:只读(RO)、归档(ARC)、目录(DIR)、卷标(VOL)。属性按位组合,例如目录文件为F_ATTR_DIR,只读目录为F_ATTR_RO | F_ATTR_DIR。文件系统遍历目录时据此区分文件与子目录。
fs.h 通过 #include "sdfile.h" 引入完整的文件级 API(打开/读/写/关闭/目录遍历等),因此应用代码只需包含 fs.h 即可获得全部文件系统接口。sdfile.h 中的文件 API 内部通过本文档所述的设备模型 API(dev_open/dev_bulk_read/dev_bulk_write)访问底层存储——文件系统层与设备模型层的边界就在于此:文件系统不直接操作寄存器或 SPI,而是操作 device* 句柄。
说明:
sdfile.h的具体函数签名(如f_open、f_read、f_opendir等)位于该头文件内,本页聚焦设备抽象层;运行期 FS 实现细节请参阅 SDK 中文件系统相关源码。
在 UBOOT 工程中还存在独立的 JLFS(JieLi File System)实现(sdk/UBOOT工程/include_lib/driver/jlfs/jlfs.h),用于引导阶段的固件/资源读取;它与运行期 FS 共享同一设备模型思想,但实现独立,两者不在同一镜像中同时编译。
存储设备驱动与平台配置
驱动 ops 一览
device.h 声明了三个内置存储驱动操作集(device.h):
| 操作集 | 读写粒度 | 适用场景 |
|---|---|---|
norflash_dev_ops | 512B 为单位 | 块模式访问 Nor Flash,适合 FAT/JLFS 扇区 I/O |
norfs_dev_ops | 1B 为单位 | 字节模式直接寻址,适合读配置、资源、单字节随机访问 |
msfc_dev_ops | 512B 为单位 | MSFC 存储控制器块访问 |
选择哪个 ops 取决于上层 I/O 模式:文件系统按扇区读写时选 norflash_dev_ops,需要字节精确寻址(如读 Flash 参数区、校准数据)时选 norfs_dev_ops。两种 ops 在工程中注册为两个逻辑设备名,互不干扰。
平台数据配置(device_config.c)
设备驱动与硬件参数的绑定在 device_config.c 中完成,由 SPI 平台数据与设备平台数据两层构成:
static struct spi_platform_data spi2_p_data = {
.port = {
TCFG_HW_SPI2_PORT_CLK,
TCFG_HW_SPI2_PORT_DO,
TCFG_HW_SPI2_PORT_DI,
0xff,//d2
0xff,//d3
0xff,//cs
},
.role = TCFG_HW_SPI2_ROLE,
.mode = TCFG_HW_SPI2_MODE,
.clk = TCFG_HW_SPI2_BAUD,
.bit_mode = SPI_FIRST_BIT_MSB,
.cpol = 0,//clk level in idle state:0:low, 1:high
.cpha = 0,//sampling edge:0:first, 1:second
};
SPI 平台数据描述总线物理特性:引脚(CLK/DO/DI,D2/D3/CS 不用时填 0xff)、角色、模式、波特率、MSB 先行、CPOL/CPHA 时序。CPOL=0/CPHA=0 即 SPI Mode 0。
设备平台数据把总线映射到存储分区:
static struct norflash_dev_platform_data norflash_dev_data = {
.spi_hw_num = HW_SPI2,
.spi_cs_port = TCFG_HW_SPI2_PORT_CS,
.spi_pdata = &spi2_p_data,
.start_addr = 0,
.size = 1 * 1024 * 1024,
};
static struct norflash_dev_platform_data norfs_dev_data = {
.spi_hw_num = HW_SPI2,
.spi_cs_port = TCFG_HW_SPI2_PORT_CS,
.spi_pdata = &spi2_p_data,
.start_addr = 1 * 1024 * 1024,
.size = 1 * 1024 * 1024,
};
两个设备共享 HW_SPI2 与同一 SPI 平台数据,但地址区间不同:norflash_dev_data 从 0 开始 1MB,norfs_dev_data 从 1MB 开始 1MB。这就是 Nor Flash 的分区方案——块模式文件系统区 + 字节模式数据区。start_addr/size 字段使驱动无需感知分区布局,分区策略完全收敛在设备注册表这一处配置点。
MSFC 设备(若 TCFG_MSFC_DEV_ENABLE 使能)配置类似:
static struct msfc_dev_platform_data msfc_dev_data = {
.start_addr = TCFG_MSFC_DEV_ADDR * 1024,
.size = TCFG_MSFC_DEV_SIZE * 1024,
};
其地址/大小以 KB 为单位的宏(TCFG_MSFC_DEV_ADDR/TCFG_MSFC_DEV_SIZE)换算而来,保持了配置文件中"人可读单位"的一致性。
配置选项
以下配置在 device_config.c 中直接使用,其中 TCFG_* 宏由工程配置(app_config.h)提供,编译期生效:
| 配置项 | 类型 | 默认/示例值 | 说明 |
|---|---|---|---|
TCFG_NORFLASH_DEV_ENABLE | bool | 工程配置 | 是否注册 Nor Flash 设备(ext_flsh_bulk + ext_flsh_byte) |
TCFG_MSFC_DEV_ENABLE | bool | 工程配置 | 是否注册 MSFC 设备(msfc) |
TCFG_MSFC_DEV_ADDR | int | 工程配置 | MSFC 起始地址(单位 KB) |
TCFG_MSFC_DEV_SIZE | int | 工程配置 | MSFC 容量(单位 KB) |
TCFG_HW_SPI2_PORT_CLK/DO/DI | int | 工程配置 | SPI2 时钟/输出/输入引脚 |
TCFG_HW_SPI2_PORT_CS | int | 工程配置 | SPI2 片选引脚 |
TCFG_HW_SPI2_ROLE | int | 工程配置 | SPI2 主/从角色 |
TCFG_HW_SPI2_MODE | int | 工程配置 | SPI2 模式 |
TCFG_HW_SPI2_BAUD | int | 工程配置 | SPI2 波特率 |
spi2_p_data.bit_mode | enum | SPI_FIRST_BIT_MSB | 位序:MSB 先行 |
spi2_p_data.cpol / cpha | int | 0 / 0 | SPI Mode 0(空闲低电平、首沿采样) |
norflash_dev_data.start_addr / size | int | 0 / 1MB | ext_flsh_bulk 分区(512B 块模式) |
norfs_dev_data.start_addr / size | int | 1MB / 1MB | ext_flsh_byte 分区(1B 字节模式) |
msfc_dev_data.start_addr / size | int | TCFG_* 换算 | MSFC 分区 |
配置哲学:硬件差异(引脚、速率、时序)收敛在 spi2_p_data 一层;分区差异收敛在 *_dev_data 一层;使能开关收敛在 TCFG_*_ENABLE 宏。更换 Flash 型号或调整分区时,通常只需改这一个文件,驱动与文件系统代码保持零改动。
API 参考
设备模型 API(device.h)
以下 API 均在 device.h 中声明,由设备模型实现提供:
int devices_init(void) 初始化设备模型。登记 .device 链接节节点表并逐节点执行 ops->init。
- 返回:0 表示成功,非 0 表示初始化失败。
- 调用时机:系统启动早期,任何
dev_open之前。
bool dev_online(const char *name) 查询名为 name 的设备是否在线。
- 参数:
name—— 设备名(如"ext_flsh_byte")。 - 返回:
true在线;false离线或不存在。 - 设计意图:可插拔设备(SD/U 盘)在读写前先查询,避免对离线设备发起 I/O。
void *dev_open(const char *name, void *arg) 按名字打开设备。
- 参数:
name—— 设备名;arg—— 传给驱动的打开参数(可为 NULL)。 - 返回:
device*句柄;失败返回NULL。 - 注意:返回类型为
void *,调用方按struct device *使用;引用计数在打开时递增。
int dev_ioctl(void *device, int cmd, u32 arg) 向设备发送控制命令。
- 参数:
device—— 打开得到的句柄;cmd——DEV_GET_STATUS、DEV_GET_BLOCK_SIZE等命令;arg—— 命令参数/输出缓冲。 - 返回:0 成功;
-ENOTTY表示设备不支持该命令;其他DEV_ERR_*负值。 - 抛错约定:非存储设备对块大小/擦除命令应返回
-ENOTTY。
int dev_close(void *device) 关闭设备句柄,递减引用计数。
- 参数:
device—— 待关闭句柄。 - 返回:0 成功;错误码如
DEV_ERR_INVALID_HANDLE。
int dev_byte_read(void *_device, void *buf, u32 offset, u32 len) 字节粒度读。
- 参数:
_device—— 句柄;buf—— 输出缓冲;offset—— 字节地址;len—— 读取长度。 - 返回:实际读取字节数或负错误码。对应
norfs_dev_ops(1B 粒度)。
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) 扇区粒度读。
- 参数:
_device—— 句柄;buf—— 输出缓冲;sector—— 起始扇区号;sector_num—— 扇区数。 - 返回:实际读取扇区数或负错误码。对应
norflash_dev_ops/msfc_dev_ops(512B 粒度)。
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) 注册设备节点表范围(由 devices_init 内部调用,也可用于动态扩展设备表)。
- 参数:
node_start/node_end—— 节点表起止指针。 - 返回:
dev_node_mg *,即当前设备表管理器。
int device_status_emit(const char *device_name, const u8 status) 广播设备状态事件(DEV_ONLINE/DEV_OFFLINE/DEV_POWER_*)。
- 参数:
device_name—— 设备名;status——DEV_STA值。 - 返回:0 成功;非 0 表示事件分发失败。
驱动操作集(device_operations)
device_operations 各函数指针的语义:online 检测存在性;init 在系统启动时执行硬件自检;open/close 管理实例生命周期;read/write 字节访问;bulk_read/bulk_write 扇区访问;seek 移动读写位置;ioctl 统一控制通道。驱动实现只需按需填充这些指针,未实现的置 NULL 由设备模型层做缺省处理。
失败模式、边界情况与并发
- 设备离线竞争:
DEV_ERR_OFFLINE表明读写过程中设备被拔出(SD/U 盘场景)。文件系统应捕获该错误并触发重新挂载流程;Nor Flash 等固定设备通常不产生此错误。 - 超时与 CRC:
DEV_ERR_READ_TIMEOUT/DEV_ERR_CMD_TIMEOUT/DEV_ERR_*_CRC表明传输层异常,常见于 SPI 干扰或 Flash 损坏;上层可重试有限次数,重试无效后上报错误。 - 未初始化访问:
DEV_ERR_NOT_READY表示设备在线但初始化未完成,此时应等待初始化完成事件(device_status_emit)而非直接重试。 - 容量越界:
DEV_ERR_OVER_CAPACITY用于防止写越过分区末尾;norflash_dev_data.size等分区配置是驱动执行边界检查的依据。 - 不支持的操作:
DEV_ERR_NO_READ/NO_WRITE/NO_IOCTL与-ENOTTY区分"设备不支持"与"操作失败",调用方必须分别处理。 - 并发访问:
device.ref是atomic_t原子引用计数,支持多任务同时dev_open同一设备;dev_close只在计数归零后真正释放实例。但读/写本身是否线程安全取决于驱动实现——块模式设备通常需在驱动内部加锁串行化 SPI 事务。 - 句柄合法性:
DEV_ERR_INVALID_HANDLE/DEV_ERR_HANDLE_FREE用于拦截悬垂句柄,要求上层遵循"打开→使用→关闭"生命周期,杜绝关闭后继续读写。
性能与运维注意事项
- 读写粒度决定吞吐:块模式(
norflash_dev_ops,512B)适合大块文件 I/O,每事务开销摊薄;字节模式(norfs_dev_ops,1B)适合小数据随机访问,但逐字节事务开销高,不应作为文件系统主挂载点。 - SPI 速率配置:
TCFG_HW_SPI2_BAUD直接决定 Flash 吞吐;提高波特率前需确认 Flash 型号支持的最高时钟及 PCB 信号完整性。 - 分区规划:
start_addr/size一旦固化到产线镜像即成为 ABI,调整分区需同步升级引导与运行期 FS 的布局定义,避免越界覆盖。 - 启动开销:设备模型采用延迟初始化,
devices_init只做登记与自检;未使用的设备不会产生 I/O,可放心在注册表保留多设备。
扩展点
设备模型的扩展非常直接,新增一种存储介质只需三步:
- 实现操作集:按
device_operations契约实现open/read/write/bulk_read/bulk_write/ioctl/close(可参考norflash_dev_ops的 512B 模式与norfs_dev_ops的 1B 模式)。 - 定义平台数据:在
device_config.c中定义总线平台数据与设备分区数据(start_addr/size等)。 - 注册节点:在
REGISTER_DEVICES(device_table)中增加{.name = ..., .ops = ..., .priv_data = ...},并用TCFG_*_ENABLE宏控制编译期使能。
由于文件系统层只依赖设备模型 API(dev_open/dev_bulk_*),新介质对文件系统完全透明。同理,扩展新的 dev_type 需在 dev_type 枚举中按功能域分段取值,并相应补充 DEV_GET_TYPE 返回逻辑。
相关链接
- 设备模型定义 device.h
- 设备驱动类型/状态/错误码/ioctl device_drive.h
- 文件系统公共头 fs.h
- GP-MCU 设备注册与平台配置 device_config.c
- UBOOT JLFS 文件系统(引导阶段)
- USB 设备实现(USB 子系统,本页不展开)
- 相关目录页:系统平台(System Platform)、USB 设备子系统、引导与分区布局