杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

系统文件系统与设备模型

本页介绍 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_ops 512B 块读写、norfs_dev_ops 1B 字节读写),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_config.c

注意设备名字使用 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

流程要点:

  1. 按名查找:dev_open 以字符串名字为键在 dev_node 表中匹配,驱动无需关心上层是谁在调用——文件系统、录音模块、升级模块都可以打开同一个设备。
  2. 句柄隔离:打开后上层只持有 device*,不接触 dev_node 表与驱动内部,天然形成访问边界。
  3. 命令通道: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;

来源:device_drive.h

类型值按功能域分段(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;

来源:device_drive.h

状态分两类语义:在线/离线(可插拔介质,如 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_LUNLUN 错误
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_CRCCRC 校验失败
DEV_ERR_CONTROL_STALL / DEV_ERR_RXSTALL / DEV_ERR_TXSTALL / DEV_ERR_CONTROLUSB 控制传输异常
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)    // 写保护状态检测/设置

来源:device_drive.h

命令使用 _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_ops512B 为单位块模式访问 Nor Flash,适合 FAT/JLFS 扇区 I/O
norfs_dev_ops1B 为单位字节模式直接寻址,适合读配置、资源、单字节随机访问
msfc_dev_ops512B 为单位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
};

来源:device_config.c

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,
};

来源:device_config.c

两个设备共享 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,
};

来源:device_config.c

其地址/大小以 KB 为单位的宏(TCFG_MSFC_DEV_ADDR/TCFG_MSFC_DEV_SIZE)换算而来,保持了配置文件中"人可读单位"的一致性。

配置选项

以下配置在 device_config.c 中直接使用,其中 TCFG_* 宏由工程配置(app_config.h)提供,编译期生效:

配置项类型默认/示例值说明
TCFG_NORFLASH_DEV_ENABLEbool工程配置是否注册 Nor Flash 设备(ext_flsh_bulk + ext_flsh_byte)
TCFG_MSFC_DEV_ENABLEbool工程配置是否注册 MSFC 设备(msfc)
TCFG_MSFC_DEV_ADDRint工程配置MSFC 起始地址(单位 KB)
TCFG_MSFC_DEV_SIZEint工程配置MSFC 容量(单位 KB)
TCFG_HW_SPI2_PORT_CLK/DO/DIint工程配置SPI2 时钟/输出/输入引脚
TCFG_HW_SPI2_PORT_CSint工程配置SPI2 片选引脚
TCFG_HW_SPI2_ROLEint工程配置SPI2 主/从角色
TCFG_HW_SPI2_MODEint工程配置SPI2 模式
TCFG_HW_SPI2_BAUDint工程配置SPI2 波特率
spi2_p_data.bit_modeenumSPI_FIRST_BIT_MSB位序:MSB 先行
spi2_p_data.cpol / cphaint0 / 0SPI Mode 0(空闲低电平、首沿采样)
norflash_dev_data.start_addr / sizeint0 / 1MBext_flsh_bulk 分区(512B 块模式)
norfs_dev_data.start_addr / sizeint1MB / 1MBext_flsh_byte 分区(1B 字节模式)
msfc_dev_data.start_addr / sizeintTCFG_* 换算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,可放心在注册表保留多设备。

扩展点

设备模型的扩展非常直接,新增一种存储介质只需三步:

  1. 实现操作集:按 device_operations 契约实现 open/read/write/bulk_read/bulk_write/ioctl/close(可参考 norflash_dev_ops 的 512B 模式与 norfs_dev_ops 的 1B 模式)。
  2. 定义平台数据:在 device_config.c 中定义总线平台数据与设备分区数据(start_addr/size 等)。
  3. 注册节点:在 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 设备子系统、引导与分区布局
Prev
GPIO 与 IIC 底层驱动