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

    • 芯片平台与 SDK 概述
    • 环境搭建与编译工具链
    • 快速开始:选型、编译与烧录
    • 烧录与量产工具
  • 构建系统与板级工程

    • 顶层 Makefile 与编译目标
    • 板级工程与配置
    • 后处理与配置工具
  • HID 人机交互应用

    • HID 应用架构总览
    • 键盘、翻页器与遥控应用
    • 鼠标应用:单模、双模与低延迟
    • 空闲应用与初始化流程
  • BLE 透传与数传应用

    • 透传应用总览
    • 多连接与无连接传输
    • AT 命令模组应用
    • Dongle 适配器应用
  • BSP 公共模块

    • 蓝牙公共处理
    • 按键、LED 与红外
    • 传感器与编码器
    • 存储、VM 与文件系统
    • 电源管理与低功耗
    • 消息调度与通信外设
  • 协议栈与预编译库

    • 蓝牙协议栈库
    • 设备驱动与文件系统库
    • 音频、升级与其他库
  • 开发资料与补丁发布

    • 文档资料中心
    • 版本补丁与兼容性修复

设备驱动与文件系统库

本文档介绍 AW31N BLE SDK 中的设备驱动层与文件系统库(VFS 虚拟文件系统、FAT 文件系统适配、设备驱动注册机制及 USB 设备驱动),说明其架构、核心接口、注册机制、数据流与扩展方式。

Purpose and Scope

本页覆盖 SDK 中与"设备驱动"和"文件系统"相关的完整子系统:

  • VFS 虚拟文件系统层(vfs.h / vfs.c):统一文件操作 API、vfs_operations 操作表注册机制、imount 挂载句柄模型。
  • FAT 文件系统适配(vfs_fat.c / fs_lib.a):VFS 之上最常用的 FAT 实现,以及 HAS_VFS_EN 关闭时的直连 API 宏。
  • 设备驱动层(device_app.c / device_list.c / ioctl.h):设备注册、设备列表与 ioctl 控制接口。
  • USB 设备驱动(usb_device.c 及各 class 驱动:cdc.c、hid_*、msd.c、uac1.c 等):作为设备驱动的典型实例。

以下内容不在本页范围,请参考各自页面:蓝牙协议栈与 BLE 应用、电源/低功耗管理、补丁与兼容性说明(patch_release/ 目录)、引导与启动流程。

概述

AW31N 是杰理科技(Jieli)的 BLE SoC,其 SDK 采用"设备驱动 → 文件系统 → 应用"的三层结构。文件系统库建立在设备驱动之上:驱动层通过统一的设备句柄和 ioctl 接口访问 Flash、USB 等物理介质;文件系统层(FAT 为主)把介质上的块数据组织成文件和目录;而最上层的 VFS 则向应用屏蔽不同文件系统(FAT、NOR Flash 文件系统等)的差异,提供一套统一的 fs_*/vfs_* API。

设计上有三个关键决策值得注意:

  1. 以操作表(operations table)为中心的面向对象风格:struct vfs_operations 相当于 C 语言中的"虚函数表",每种文件系统把自己的 init/mount/openbypath/read/write/... 填入该结构,通过链接器段(SEC(".vfs_operations"))自动注册,VFS 层遍历注册表即可发现所有可用的文件系统——新增一种文件系统不需要修改 VFS 核心代码。
  2. 句柄模型统一:文件系统与文件都用 struct imount 表示(union { void *pfs; void *pfile; }),分配/释放由 VFS 层统一管理(vfs_hdl_malloc / vfs_fhdl_free),降低上层使用复杂度。
  3. 可裁剪的双模式编译:HAS_VFS_EN 开启时走完整 VFS 分发路径(灵活、可多文件系统共存);关闭时 fs_* 宏直接映射到 fat_*_api 函数(省代码、省开销,适合固定使用 FAT 的场景)。

架构

下图展示了从应用层到硬件层的完整依赖关系:

flowchart TD
    subgraph sg_App["应用层 (Application)"]
        APP["应用代码 / 播放器 / 录音 / 资源读取"]
    end

    subgraph sg_VFS["虚拟文件系统层 (VFS)"]
        VFS_API["vfs_init / vfs_mount / vfs_openbypath / vfs_read ..."]
        OPS_REG["vfs_operations 注册表<br/>(链接器段 .vfs_operations)"]
        HDL["imount 句柄池<br/>vfs_hdl_malloc / vfs_fhdl_free"]
    end

    subgraph sg_FS["文件系统实现"]
        FAT["FAT 文件系统<br/>(vfs_fat.c + fs_lib.a)"]
        NORFS["NOR Flash 文件系统 (norfs)"]
    end

    subgraph sg_DEV["设备驱动层"]
        DEV_APP["device_app.c / device_list.c<br/>设备注册与调度"]
        IOCTL["ioctl 控制接口 (ioctl.h)"]
    end

    subgraph sg_USB["USB 设备驱动"]
        USB["usb_device.c"]
        CDC["cdc.c 串口类"]
        HID["hid_keyboard / hid_mouse / hid_media"]
        MSD["msd.c / msd_upgrade.c 存储类"]
        UAC["uac1.c 音频类"]
    end

    subgraph sg_HW["硬件层"]
        FLASH[("Flash / 存储介质")]
    end

    APP -->|"fs_* / vfs_* API"| VFS_API
    VFS_API -->|"遍历注册表"| OPS_REG
    VFS_API --> HDL
    OPS_REG --> FAT
    OPS_REG --> NORFS
    FAT -->|"设备读写"| DEV_APP
    NORFS -->|"设备读写"| DEV_APP
    DEV_APP -->|"ioctl"| IOCTL
    DEV_APP --> FLASH
    USB --> DEV_APP

各层职责:

  • VFS 层(vfs.c / vfs.h):不关心具体介质,只负责操作表的分发(dispatch)与句柄生命周期。它维护一个由链接器段生成的 vfs_operations 数组(vfs_ops_begin[] ~ vfs_ops_end[]),vfs_mount 通过 fs_type 字符串匹配选择具体文件系统。
  • 文件系统实现:FAT 是主用实现,声明在 vfs_fat.h,适配代码在 apps/app/bsp/common/fs/vfs_fat.c,核心算法以静态库 apps/include_lib/liba/bd47/flash/fs_lib.a 提供(预编译,源码不可见)。另一个实现是 NOR Flash 文件系统(norfs),挂载时若 type 为 NULL 会被 VFS 主动跳过(见 vfs.c 中 strcmp(ops->fs_type, "norfs") 的判断),说明 NORFS 需要显式指定类型。
  • 设备驱动层(apps/app/bsp/device/):device_app.c 提供设备应用入口,device_list.c 维护设备列表(设备注册/枚举),ioctl.h 定义控制命令。
  • USB 设备驱动(apps/app/bsp/common/usb/device/):usb_device.c 是 USB 协议栈核心,各 class 文件(CDC、HID、MSC、UAC)是具体设备功能的实现,属于"设备驱动"在 USB 外设方向的具体化。

VFS 核心设计

操作表结构 struct vfs_operations

每种文件系统必须实现一张操作表,这是 VFS 的"契约"。关键字段如下(节选自头文件):

struct vfs_operations {
    const char *fs_type;
    void (*init)(void);
    u32(*mount)(void **ppfs, void *p_device);
    u32(*openbypath)(void *pfs, void **ppfile, const char *path);
    u32(*openbyindex)(void *pfs, void **ppfile, u32 index, void *parm);
    u32(*openbyfile)(void *pcvfile, void **ppfile, void *ext_name);
    u32(*openbyclust)(void *pfs, void **ppfile, u32 clust, void *parm);
    u32(*createfile)(void *pfs, void **ppfile, u32 *pindex);
    u32(*read)(void *pfile, void *buff, u32 len);
    u32(*write)(void *pfile, void *buff, u32 len);
    u32(*seek)(void *pfile, u32 offset, u32 mode);
    u32(*close_fs)(void **ppfs);
    u32(*close_file)(void **ppfile);
    u32(*fdelete)(void *);
    int (*fget_attr)(void *, void *attr);
    int (*flen)(void *, u32 *parm);
    int (*ftell)(void *, u32 *parm);
    u32(*name)(void *, void *name, u32 len);
    int (*ioctl)(void *, int cmd, int arg);
    int (*fscan_interrupt)(void *, void *, const char *path, u8 max_deepth, int (*callback)(void));
    void (*fscan_release)(void *);
    int (*fsel)(void *, void *, int sel_mode, void **, int);
    int (*file_crc)(void *pfile);
};

Source: vfs.h

注意 fscan_interrupt 支持可中断的目录扫描(回调 callback 用于让出 CPU/退出扫描),file_crc 提供文件校验能力,这些都是嵌入式文件系统常用的实用扩展。头文件里保留了旧版接口注释,说明该结构经历过多次演进,当前以 void * 泛型指针代替具体结构体指针,解耦了 VFS 层与具体文件系统类型。

挂载句柄 struct imount

文件系统实例和文件句柄共用一种结构,靠 union 复用空间:

struct imount {
    struct vfs_operations *ops;
    union {
        void *pfs;
        void *pfile;
    };
};

Source: vfs.h

ops 指向该句柄所属文件系统的操作表。挂载成功后 pfs 是文件系统实例;打开文件后 pfile 是文件实例。VFS 层 API 通过 pvfs->ops 分派到具体实现,这就是"一个句柄 + 一张表"的 C 语言多态。

注册机制:链接器段 + 宏

文件系统通过宏把操作表放入专用链接段,VFS 启动时按地址顺序遍历:

#define REGISTER_VFS_OPERATIONS(ops) \
	const struct vfs_operations ops SEC(.vfs_operations)

Source: vfs.h

配合 vfs.c 中的遍历实现:

extern struct vfs_operations vfs_ops_begin[];
extern struct vfs_operations vfs_ops_end[];

#define list_for_each_vfs_operation(ops) \
	for (ops=vfs_ops_begin; ops<vfs_ops_end; ops++)

void vfs_init(void)
{
    struct vfs_operations *ops;
    list_for_each_vfs_operation(ops) {
        if (NULL != ops->init) {
            log_info("%s ops_init!!!\n", ops->fs_type);
            ops->init();
        }
    }
}

Source: vfs.c

设计意图:链接器把分散在各文件中的 SEC(".vfs_operations") 变量按声明顺序聚合成 vfs_ops_begin ~ vfs_ops_end 的连续数组,VFS 无需维护"注册列表",新增文件系统只需 REGISTER_VFS_OPERATIONS 一个宏,天然支持按需裁剪(不链接就不存在)。vfs.c 自身也通过 #pragma code_seg(".vfs.text") 等指令把代码放入独立段,便于在链接脚本中精确控制内存布局(例如放入可运行于 RAM 的段以支持在线升级)。

挂载流程

u32 vfs_mount(void **ppvfs, void *device, void *type)
{
    if ((void *)NULL == *ppvfs) {
        *ppvfs = vfs_hdl_malloc();
        if ((void *)NULL == *ppvfs) {
            return E_NO_VFS;
        }
    }

    struct imount *pvfs = *ppvfs;
    struct vfs_operations *ops;
    list_for_each_vfs_operation(ops) {
        if (NULL != ops->mount) {
            if (NULL != type) {
                if (0 != strcmp(ops->fs_type, type)) {
                    continue;
                }
            } else {
                if (0 == strcmp(ops->fs_type, "norfs")) {
                    continue;
                }
            }
            if (0 == ops->mount(&(pvfs->pfs), device)) {
                pvfs->ops = ops;
                return 0;
            } else {
                if (NULL != ops->close_fs) {
                    ops->close_fs(&pvfs->pfs);
                }
            }
        }
    }
    *ppvfs = vfs_fhdl_free(*ppvfs);
    return E_NO_FS;
}

Source: vfs.c

挂载逻辑要点:

  • 句柄先分配后使用,分配失败返回 E_NO_VFS。
  • 按注册表顺序逐个尝试;type 非空时用 strcmp 精确匹配 fs_type;type 为空时跳过 norfs(因为 NOR 文件系统需要显式挂载,避免被默认挂载流程误选)。
  • 具体 mount 失败时调用其 close_fs 做清理,最后释放句柄返回 E_NO_FS。这种"尝试-回滚"模式保证失败后不泄漏句柄。

文件操作流程:打开、读取、关闭

打开文件的路径在 VFS 层统一处理句柄分配与错误回滚:

u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)
{
    if ((void *)NULL == *ppvfile) {
        *ppvfile = vfs_hdl_malloc();
        if ((void *)NULL == *ppvfile) {
            return E_NO_VFS;
        }
    }

    u32 err;
    struct vfs_operations *ops;
    struct imount *p_vfs = pvfs;
    struct imount *p_vfile = *ppvfile;
    if ((void *)NULL == p_vfs) {
        err =  E_VFS_HDL;
        goto __vfs_openbypath;
    }
    p_vfile->ops = p_vfs->ops;
    ops = p_vfs->ops;

    if (NULL != ops->openbypath) {
        err = ops->openbypath(p_vfs->pfs, &p_vfile->pfile, path);
    } else {
        err = E_VFS_OPS;
    }
__vfs_openbypath:
    if (0 != err) {
        vfs_file_close(ppvfile);
    }
    return err;
}

Source: vfs.c

设计意图:vfs_openbypath 并不自己实现打开逻辑,而是把 pvfs 上的操作表复制给新文件句柄(p_vfile->ops = p_vfs->ops),再委托 ops->openbypath。这保证了"从哪个文件系统挂载的文件,就用哪个文件系统的操作表",且文件句柄自带操作表后,后续 vfs_read/write/seek/close 只需传文件句柄即可正确分派。失败路径统一走 vfs_file_close 释放句柄,避免资源泄漏。vfs_openbyindex(按索引打开)、vfs_openbyfile(按已打开文件扩展打开,如按歌曲名取歌词)、vfs_openbyclust(按簇号打开)遵循完全相同的模式。

vfs.h 中定义的 SEEK_SET/SEEK_CUR/SEEK_END 与文件属性标志:

#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

struct vfs_attr {
    u8 attr;		//属性
    u32 fsize;		//文件大小
    u32 sclust;		//地址
};

Source: vfs.h

vfs_attr 中的 sclust 暴露了 FAT 的起始簇号(起始扇区地址),这是底层 FAT 语义在 VFS 层的少量泄漏——某些应用(如录音连续写、固件升级)需要直接操作簇信息以获得性能,设计上予以保留。

设备驱动层

设备驱动位于 apps/app/bsp/device/,是文件系统访问物理介质的桥梁:

文件职责
device_app.c / device_app.h设备应用入口:驱动初始化、设备管理逻辑
device_list.c设备列表:设备注册、枚举、查找(驱动以"列表"形式组织,支持多个同类型设备)
apps/include_lib/device/ioctl.hioctl 命令定义:应用/文件系统通过 ioctl 控制设备行为

文件系统(如 vfs_fat.c)通过设备驱动完成底层扇区读写;应用也可以通过 ioctl 直接控制设备(例如查询容量、擦除、设置写保护等)。设备驱动层与 USB 驱动层分离:USB 设备驱动(usb/device/)是"设备"的一种,当 USB 枚举为 MSC(msd.c)时,它可把主机端表现为 U 盘,与本地 Flash 文件系统形成"本地文件系统 + USB 虚拟存储"的并存结构。

USB 设备驱动

apps/app/bsp/common/usb/device/ 下实现了完整的 USB 设备侧协议栈:

文件功能
usb_device.cUSB 设备核心:枚举、端点管理、标准请求处理
usb_device_config.c设备配置:描述符参数、类配置
descriptor.c / user_setup.c描述符组织与厂商自定义 setup 请求
cdc.cCDC 串口类(虚拟串口)
hid_keyboard.c / hid_mouse.c / hid_media.cHID 键盘/鼠标/多媒体控制
custom_hid.c自定义 HID 上报
msd.c / msd_upgrade.c大容量存储类(U 盘)与 U 盘升级
uac1.cUSB 音频类(UAC1,音频设备)
task_pc.cPC 端交互任务(枚举完成后与主控通信)
usb_suspend_resume.c挂起/恢复电源管理

这些驱动共享 usb_device.c 提供的枚举与传输框架,各自实现 class 特定的端点收发。msd_upgrade.c 表明设备驱动与系统升级流程耦合:SDK 支持通过 USB 枚举为 U 盘后拖入固件完成升级,这是驱动层向业务能力(OTA/量产)延伸的典型例子。

核心流程

sequenceDiagram
    participant App as 应用层
    participant VFS as VFS 层 (vfs.c)
    participant OPS as vfs_operations 注册表
    participant FAT as FAT 文件系统 (vfs_fat.c / fs_lib.a)
    participant DEV as 设备驱动 (device_app.c)
    participant HW as Flash 硬件

    App->>VFS: vfs_init()
    VFS->>OPS: 遍历 vfs_ops_begin[] ~ vfs_ops_end[]
    OPS->>FAT: ops->init() 初始化文件系统

    App->>VFS: vfs_mount(&pfs, device, "fat")
    VFS->>OPS: strcmp 匹配 fs_type == "fat"
    OPS->>FAT: ops->mount(&pfs, device)
    FAT->>DEV: 打开/绑定底层设备
    DEV->>HW: 读取介质 (扇区级)
    FAT-->>VFS: 挂载成功,保存 ops

    App->>VFS: vfs_openbypath(pfs, &pfile, "/dir/file.mp3")
    VFS->>FAT: ops->openbypath(pfs, &pfile, path)
    FAT->>DEV: 读目录项/文件项
    DEV->>HW: 读扇区
    FAT-->>App: 文件句柄就绪

    App->>VFS: vfs_read(pfile, buf, len)
    VFS->>FAT: ops->read(pfile, buf, len)
    FAT->>DEV: 按簇/扇区读数据
    DEV->>HW: 读扇区
    HW-->>App: 数据返回

    App->>VFS: vfs_file_close(&pfile)
    VFS->>FAT: ops->close_file(&pfile)
    FAT-->>App: 句柄释放

流程要点:

  1. 系统启动早期调用 vfs_init(),遍历注册表逐个执行各文件系统的 init。
  2. 应用挂载时,VFS 用 fs_type 字符串(如 "fat")在注册表中匹配操作表,成功则把 ops 写入挂载句柄。
  3. 所有文件操作均通过句柄携带的 ops 分发到 FAT 实现;FAT 再通过设备驱动完成实际介质访问。
  4. 关闭时逐级释放:文件句柄 → 文件系统实例 → 设备资源。

双模式编译:HAS_VFS_EN

vfs.h 提供了两种编译模式,这是 SDK 裁剪灵活性的核心:

#if HAS_VFS_EN
struct imount *vfs_hdl_malloc(void);
...
#define fs_resource_init()
#define fs_init               vfs_init
#define fs_mount              vfs_mount
#define fs_openbypath         vfs_openbypath
...
#else
extern u32 fat_monut_api(void **ppfs, void *p_device);
...
#define fs_init()
#define fs_type_name(...)             "fat"
#define fs_mount(n,m,x)       fat_monut_api(n,m)
#define fs_openbypath         fat_openR_api
#define fs_read               fat_read_api
#define fs_write              fat_write_api
...
#endif

Source: vfs.h

  • HAS_VFS_EN 开启:应用调用 fs_* 宏时走完整 VFS 分发路径,支持多文件系统共存、可中断扫描(vfs_fscan_new)、目录操作(fs_mk_dir)等高级能力。
  • HAS_VFS_EN 关闭:fs_* 宏直接映射到 fat_*_api 裸函数(fat_read_api、fat_write_api、fat_openR_api 等),消除了一层间接调用,代码更小、更快,适合产品固定只使用 FAT 的场景。

这种"宏即适配层"的做法让应用代码无需改动即可在两种模式间切换,是嵌入式 SDK 中常见的编译期抽象手法。

配置选项

配置/宏类型默认值说明
HAS_VFS_EN编译期宏视工程配置使能 VFS 分发层;关闭时 fs_* 宏直连 fat_*_api
VFS_FILE_NAME_LEN宏常量16短文件名缓冲区长度(g_file_sname)
FS_IOCTL_SET_LFN_BUFioctl 命令—设置长文件名缓冲区(512 字节)
FS_IOCTL_SET_LDN_BUFioctl 命令—设置长目录名缓冲区(512 字节)
FS_IOCTL_SET_NAME_FILTERioctl 命令—设置扫描时的文件名过滤规则
FS_IOCTL_SET_EXT_TYPEioctl 命令—设置后缀类型过滤(如只扫 mp3)
SEEK_SET / SEEK_CUR / SEEK_END常量0 / 1 / 2vfs_seek 的定位模式

FS_IOCTL_* 命令枚举定义于 vfs.h,涵盖了文件计数、目录遍历、过滤、文件夹信息、录音文件夹信息、同步、创建目录等能力。这些 ioctl 由各文件系统的 ops->ioctl 实现,VFS 层只负责透传(vfs_ioctl)。

常用 ioctl 命令一览

命令用途
FS_IOCTL_GET_FILE_NUM获取文件总数
FS_IOCTL_FILE_CHECK文件完整性检查
FS_IOCTL_FREE_CACHE释放文件系统缓存
FS_IOCTL_GET_FOLDER_INFO_3获取文件夹序号与文件夹内文件数
FS_IOCTL_OPEN_DIR / ENTER_DIR / EXIT_DIR目录导航
FS_IOCTL_GET_DIR_INFO获取目录信息
FS_IOCTL_GETFILE_BYNAME_INDIR按歌名获取同目录歌词文件
FS_IOCTL_GET_DISP_INFO获取长文件名显示信息
FS_IOCTL_MK_DIR创建文件夹
FS_IOCTL_GET_ENCFOLDER_INFO获取录音文件夹信息
FS_IOCTL_FILE_SYNC文件同步落盘
FS_IOCTL_RESET_VFSCAN重置目录扫描状态

API 参考

vfs_init(void)

遍历 vfs_operations 注册表(.vfs_operations 段),逐个调用非空 init 完成各文件系统初始化。系统启动早期调用一次。

u32 vfs_mount(void **ppvfs, void *device, void *type)

挂载文件系统到 device 设备上。

参数:

  • ppvfs:挂载句柄指针;*ppvfs == NULL 时内部调用 vfs_hdl_malloc 分配
  • device:底层设备句柄(由设备驱动层提供)
  • type:文件系统类型字符串(如 "fat");NULL 表示自动选择并跳过 norfs

返回: 0 成功;E_NO_VFS(句柄分配失败)、E_NO_FS(无匹配/挂载失败)

u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)

按路径打开文件,文件句柄自动继承挂载句柄的操作表。

参数:

  • pvfs:挂载句柄
  • ppvfile:文件句柄指针(可为 NULL,内部分配)
  • path:文件路径

返回: 0 成功;E_NO_VFS、E_VFS_HDL(挂载句柄无效)、E_VFS_OPS(操作表缺失)、底层 openbypath 错误码。失败时自动关闭并释放文件句柄。

u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index, void *param)

按索引打开文件(配合目录扫描结果使用)。

u32 vfs_openbyfile(void *pcvfile, void **ppvfile, void *ext_name)

基于已打开文件再打开关联文件(如由音频文件打开同名歌词)。

u32 vfs_openbyclust(void *pvfs, void **ppvfile, u32 clust, void *param)

按 FAT 起始簇号打开文件,用于底层快速访问。

u32 vfs_read(void *pvfile, void *buf, u32 len) / u32 vfs_write(void *pvfile, void *buf, u32 len)

按当前文件位置读/写 len 字节,返回实际读/写字节数。

u32 vfs_seek(void *pvfile, u32 offset, u32 mode)

按 SEEK_SET / SEEK_CUR / SEEK_END 模式移动文件读写位置。

u32 vfs_file_close(void **ppvfile) / u32 vfs_fs_close(void **ppvfs)

关闭文件/卸载文件系统并释放句柄(内部调用 vfs_fhdl_free)。

int vfs_ioctl(void *pvfile, int cmd, int arg)

透传到 ops->ioctl,执行上表所列的目录/过滤/同步等扩展操作。

文件系统别名宏(应用层常用)

fs_init、fs_mount、fs_openbypath、fs_read、fs_write、fs_seek、fs_file_close、fs_file_name、fs_get_attrs、fs_get_fsize、fs_ftell、fs_file_delete、fs_fscan_new、fs_fscan、fs_fscan_release、fs_select、fs_ioctl、fs_mk_dir、fs_seek —— 应用代码统一使用这些 fs_* 宏,具体指向 vfs_* 或 fat_*_api 由 HAS_VFS_EN 决定。

使用示例

示例 1:注册一个新的文件系统操作表

任何文件系统实现只需定义操作表并调用注册宏,VFS 启动时自动发现:

const struct vfs_operations myfs_ops = {
    .fs_type   = "myfs",
    .init      = myfs_init,
    .mount     = myfs_mount_api,
    .openbypath = myfs_openbypath_api,
    .read      = myfs_read_api,
    .write     = myfs_write_api,
    .seek      = myfs_seek_api,
    .close_fs  = myfs_fs_close_api,
    .close_file = myfs_file_close_api,
    /* ... */
};
REGISTER_VFS_OPERATIONS(myfs_ops)

注册宏定义见 Source: vfs.h;vfs_init 遍历注册表见 Source: vfs.c

示例 2:应用层挂载与文件读取

void *pfs = NULL;
void *pfile = NULL;
u8 buf[512];

/* 挂载 FAT 文件系统到设备 */
if (0 != vfs_mount(&pfs, device, "fat")) {
    /* 处理挂载失败 (E_NO_VFS / E_NO_FS) */
}

/* 打开文件 */
if (0 == vfs_openbypath(pfs, &pfile, "/music/test.mp3")) {
    u32 rd = vfs_read(pfile, buf, sizeof(buf));
    /* 处理数据... */
    vfs_file_close(&pfile);
}

上述 vfs_mount/vfs_openbypath/vfs_read/vfs_file_close 的声明见 Source: vfs.h;挂载的"尝试-回滚"实现见 Source: vfs.c

示例 3:非 VFS 模式的直连 FAT API

当 HAS_VFS_EN 关闭时,宏直接指向 FAT 裸函数,同样的应用代码零改动:

/* HAS_VFS_EN == 0 时的等价映射 */
#define fs_mount(n,m,x)       fat_monut_api(n,m)
#define fs_openbypath         fat_openR_api
#define fs_read               fat_read_api
#define fs_write              fat_write_api
#define fs_file_close         fat_file_close_api
#define fs_seek               fat_seek_api

Source: vfs.h

失败模式、边界情况与并发

  • 挂载失败:vfs_mount 在注册表匹配失败或具体 mount 失败时返回 E_NO_FS,并在返回前调用 close_fs 清理、释放句柄(vfs_fhdl_free),避免半初始化状态泄漏。
  • 句柄耗尽:vfs_hdl_malloc 返回 NULL 时各 vfs_open* 返回 E_NO_VFS。嵌入式环境下句柄池有限,长时间运行的应用必须保证每次 open 都配对 close。
  • 无效句柄:vfs_openbypath 对 pvfs == NULL 返回 E_VFS_HDL;操作表缺函数指针返回 E_VFS_OPS——VFS 层做了防御性检查,但具体文件系统内部错误仍需各自处理。
  • 打开失败自动回收:vfs_openbypath/vfs_openbyindex/vfs_openbyfile 失败时统一走 vfs_file_close(ppvfile) 释放句柄,调用方无需重复释放。
  • 只读介质/属性:文件属性标志 F_ATTR_RO(只读)、F_ATTR_ARC(归档)、F_ATTR_DIR(目录)由 fget_attr 返回,写操作前应检查只读位,防止对只读文件或介质写入失败。
  • 并发与中断:fscan_interrupt 支持带回调的可中断目录扫描——回调返回非 0 可中止扫描,避免长目录扫描阻塞音频播放等实时任务;这暗示文件系统访问主要在单任务上下文中使用,多任务访问同一文件需应用层自行加锁(VFS 层未内置互斥)。
  • 长文件名/长目录名:默认短文件名缓冲区仅 16 字节(VFS_FILE_NAME_LEN),需要显示/匹配长文件名时必须先通过 FS_IOCTL_SET_LFN_BUF / FS_IOCTL_SET_LDN_BUF 提供 512 字节缓冲区,否则获取的显示信息可能被截断。
  • 缓存一致性:FS_IOCTL_FREE_CACHE 与 FS_IOCTL_FILE_SYNC 用于在掉电/拔卡/升级前强制刷盘;未同步就断电可能导致 FAT 表不一致。

性能与运维注意事项

  • 簇级访问:vfs_attr.sclust 与 vfs_openbyclust 提供了跳过目录解析、按簇直读的能力,适合录音、固件升级等大批量顺序写场景;普通文件读建议走 vfs_read 让文件系统维护簇链缓存。
  • 编译裁剪:量产固件若固定使用 FAT,可关闭 HAS_VFS_EN 消除 VFS 分发层开销;需要多文件系统(FAT + NORFS)或目录扫描等能力时再开启。
  • 链接段布局:VFS 代码被放入 .vfs.text 等独立段(见 vfs.c 顶部 #pragma code_seg),链接脚本可将其放置在快速 RAM 或特定 Flash 区域,升级时也可按段搬移。
  • FAT 核心为预编译库:apps/include_lib/liba/bd47/flash/fs_lib.a 提供 FAT 算法实现,vfs_fat.c 是 VFS 与库之间的适配层;修改 FAT 内部行为需通过其公开接口/ioctl 完成,无法直接修改库源码。

扩展点

  1. 新增文件系统:实现 struct vfs_operations 并 REGISTER_VFS_OPERATIONS,VFS 自动发现;注意 fs_type 字符串是挂载匹配的关键字(如 "fat"、"norfs")。
  2. 新增设备驱动:在 device_app.c/device_list.c 中注册设备并实现 ioctl 命令(命令定义见 apps/include_lib/device/ioctl.h),文件系统通过设备句柄访问。
  3. 新增 USB class:参考 usb/device/ 下现有 class(CDC/HID/MSC/UAC),基于 usb_device.c 的枚举框架实现端点处理,并在 usb_device_config.c 中配置描述符。
  4. 文件过滤与扫描:通过 FS_IOCTL_SET_NAME_FILTER、FS_IOCTL_SET_EXT_TYPE 定制扫描行为;fscan_interrupt 回调实现可中断扫描,可插入播放器/低功耗业务逻辑。
  5. 升级/量产集成:msd_upgrade.c 展示了设备驱动与 U 盘升级流程的结合点,可作为自定义量产工具链的参考。

相关链接

  • VFS 接口定义:vfs.h
  • VFS 核心实现:vfs.c
  • FAT 适配层:vfs_fat.c / vfs_fat.h
  • 资源文件系统:vfs_resource.c
  • 设备驱动入口:device_app.c / device_list.c
  • ioctl 命令定义:ioctl.h
  • USB 设备驱动:usb_device.c
  • FAT 预编译库:fs_lib.a

说明:device_app.c、ioctl.h 等设备驱动文件在本页撰写时的源码细节未能全部展开读取,相关描述基于文件清单与 VFS 接口约定;如需精确的设备注册/ioctl 命令清单,请直接查阅上述链接中的源码。

Prev
蓝牙协议栈库
Next
音频、升级与其他库