news 2026/10/7 1:52:31

Tock nRF52840-DK 隔离非易失存储测试板:基于外部 Flash 的存储隔离内核设计与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tock nRF52840-DK 隔离非易失存储测试板:基于外部 Flash 的存储隔离内核设计与验证
  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

本文围绕 Tock 仓库中的nrf52840dk-test-invs测试板内核展开:它是一套用于验证“隔离非易失存储”(Isolated Nonvolatile Storage)的最小内核,将存储区域放在 nRF52840-DK 板载外部 SPI Flash 上,并为每个应用划分互不干扰的私有存储地址空间。读完本文,你将掌握该测试板的硬件接线、驱动装配方式、存储权限策略以及0x50004系统调用接口的完整使用细节,并了解如何在真实硬件上构建与烧录这套测试内核。

一、测试板定位:一个为“存储隔离”而生的最小内核

boards/configurations/nrf52840dk/nrf52840dk-test-invs/README.md 对该板给出了极为简洁的定义:

This is a minimal kernel for testing isolated storage. The board uses the external flash for the storage area.

即:这是一套用于测试隔离存储的最小内核,存储区域使用外部 Flash。它隶属于 Tock 仓库boards/configurations/nrf52840dk/下的 nRF52840-DK 测试板族,目录内还包含同为测试用途的nrf52840dk-test-base、nrf52840dk-test-appid-*、nrf52840dk-test-dynamic-app-load、nrf52840dk-test-kernel等配置,便于在固定硬件平台上快速验证内核子系统的行为。

与普通产品级板级支持包(BSP)不同,这类测试板刻意保持“最小化”:它不追求外设全量覆盖,而是聚焦单一验证目标。本板的完整源码位于 src/main.rs,从代码结构看,其核心链路为:

SPIM0 外设 → SpiMux 复用 → MX25R6435F 外部 SPI Flash → NonvolatileToPages(页对齐转换层) → IsolatedNonvolatileStorage 驱动(grant 为每个 App 分配私有存储区) → 0x50004 系统调用接口(受 StoragePermissions 保护)

整个内核复用 nrf52840dk-test-base 提供的基础平台(调度器、UART、定时器等),在其上叠加存储相关组件,最终通过SyscallDriverLookup把0x50004驱动号路由到隔离存储驱动,其余驱动号全部委托给基础平台。

二、硬件接线与外部 Flash 初始化

2.1 SPI 引脚定义

板载外部 Flash 通过 nRF52840 的SPIM0外设驱动,引脚定义集中在 src/main.rs:

const SPI_MOSI: Pin = Pin::P0_20; const SPI_MISO: Pin = Pin::P0_21; const SPI_CLK: Pin = Pin::P0_19; const SPI_MX25R6435F_CHIP_SELECT: Pin = Pin::P0_17; const SPI_MX25R6435F_WRITE_PROTECT_PIN: Pin = Pin::P0_22; const SPI_MX25R6435F_HOLD_PIN: Pin = Pin::P0_23;
信号nRF52840 引脚用途
SPI_CLKP0_19SPI 时钟
SPI_MOSIP0_20主出从入
SPI_MISOP0_21主入从出
SPI_MX25R6435F_CHIP_SELECTP0_17Flash 片选
SPI_MX25R6435F_WRITE_PROTECT_PINP0_22写保护(可选配置)
SPI_MX25R6435F_HOLD_PINP0_23HOLD 引脚(可选配置)

2.2 SPIM0 与 SpiMux 装配

在main()中,代码先取出基础平台返回的base_peripherals,把spim0接入SpiMuxComponent做总线复用(因为外部 Flash 并非 SPI 总线的唯一潜在使用者),并完成引脚复用配置:

let mux_spi = components::spi::SpiMuxComponent::new(&base_peripherals.spim0) .finalize(components::spi_mux_component_static!(nrf52840::spi::SPIM)); base_peripherals.spim0.configure( nrf52840::pinmux::Pinmux::new(SPI_MOSI), nrf52840::pinmux::Pinmux::new(SPI_MISO), nrf52840::pinmux::Pinmux::new(SPI_CLK), );

随后通过Mx25r6435fComponent实例化 MX25R6435F 驱动,把片选、写保护、HOLD 三个 GPIO 引脚、复用后的 SPI 总线以及mux_alarm(用于 Flash 操作超时/延时)注入组件:

let mx25r6435f = components::mx25r6435f::Mx25r6435fComponent::new( Some(&nrf52840_peripherals.gpio_port[SPI_MX25R6435F_WRITE_PROTECT_PIN]), Some(&nrf52840_peripherals.gpio_port[SPI_MX25R6435F_HOLD_PIN]), &nrf52840_peripherals.gpio_port[SPI_MX25R6435F_CHIP_SELECT], mux_alarm, mux_spi, ) .finalize(components::mx25r6435f_component_static!( nrf52840::spi::SPIM, nrf52840::gpio::GPIOPin, nrf52840::rtc::Rtc ));

该组件以SPIM、GPIOPin、Rtc为泛型参数,最终得到一个实现了hil::flash::Flash特征的 Flash 客户端,供上层存储驱动使用。

三、隔离非易失存储驱动装配

3.1 核心概念:隔离存储 vs 普通存储

隔离非易失存储组件定义在 boards/components/src/isolated_nonvolatile_storage.rs,其文档注释明确区分了它与普通非易失存储驱动的差异:

This differs from NonvolatileStorageComponent in that it provides isolation between apps. Each app has its own storage address space (that starts at 0) which doesn't interfere with other apps.

即:每个应用拥有从地址 0 开始的私有存储地址空间,互不干扰。上层应用感知到的偏移始终是“自己区域内的偏移”,而内核负责把该偏移映射到物理 Flash 的真实地址,这是“隔离”语义的关键。

3.2 组件实例化参数

src/main.rs 中实例化隔离存储组件:

let invs = components::isolated_nonvolatile_storage::IsolatedNonvolatileStorageComponent::new( board_kernel, capsules_extra::isolated_nonvolatile_storage_driver::DRIVER_NUM, mx25r6435f, 0x40000, // start address 0x100000, // length create_capability!(capabilities::MemoryAllocationCapability), ) .finalize(components::isolated_nonvolatile_storage_component_static!( Mx25r6435f, APP_STORAGE_REGION_SIZE ));

各参数含义如下:

参数本板取值说明
board_kernel内核实例用于创建 grant(board_kernel.create_grant(driver_num, &mem_cap))
driver_numDRIVER_NUM(即0x50004)系统调用驱动号,对应 doc/syscalls/50004_isolated_nonvolatile_storage.md
flashmx25r6435f底层 Flash 驱动(外部 SPI Flash)
userspace_start0x40000(256 KiB)用户空间可访问存储区域的起始物理地址
userspace_length0x100000(1 MiB)用户空间可访问存储区域长度
APP_STORAGE_REGION_SIZE4096(4 KiB)每个应用私有存储区大小

其中APP_STORAGE_REGION_SIZE在 src/main.rs 定义为:

const APP_STORAGE_REGION_SIZE: usize = 4096;

它作为常量泛型传给驱动,决定每个应用每次新分配存储区时的固定大小(当前实现中区域不可增长,所以该值就是应用最终获得的存储量)。组件源码中还提供了默认值ISOLATED_NONVOLATILE_STORAGE_APP_REGION_SIZE_DEFAULT = 2048(见 isolated_nonvolatile_storage.rs),本板显式覆盖为 4096,即每个应用可获得 4 KiB 私有存储。

3.3 底层装配细节:NonvolatileToPages 与 grant

finalize()阶段(isolated_nonvolatile_storage.rs)完成三层对象的静态初始化:

  1. 页缓冲区:<F as hil::flash::Flash>::Page::default()分配一页 Flash 读写缓冲区;
  2. NonvolatileToPages转换层:capsules_extra::nonvolatile_to_pages::NonvolatileToPages把上层“任意偏移 + 任意长度”的读写请求翻译成底层 Flash 的“页对齐擦写”操作,并通过hil::flash::HasClient::set_client挂接为 Flash 驱动客户端;
  3. IsolatedNonvolatileStorage驱动本体:通过board_kernel.create_grant(driver_num, &mem_cap)创建 grant,把每个应用可访问的存储区状态(分配标记、基址等)保存在内核 grant 区中,实现进程级隔离。

随后hil::nonvolatile_storage::NonvolatileStorage::set_client(nv_to_page, nonvolatile_storage)把转换层与驱动双向连接,构成完整调用链:应用系统调用 → 驱动 → NonvolatileToPages → Flash 驱动 → 外部 SPI Flash。

静态内存由isolated_nonvolatile_storage_component_static!宏(isolated_nonvolatile_storage.rs)一次性分配Page、NonvolatileToPages、IsolatedNonvolatileStorage以及BUF_LEN字节的内部缓冲,符合 Tock 无堆分配(static_init)的内存模型。

四、存储权限策略:invs_permissions.rs

隔离存储接口受StoragePermissions保护(doc/syscalls/50004_isolated_nonvolatile_storage.md 开篇即注明),因此测试板必须实现一套权限分配策略。本板在 src/invs_permissions.rs 中实现了自定义策略InvsStoragePermissions。

其核心逻辑在get_permissions()(invs_permissions.rs):

match process.short_app_id() { ShortId::Fixed(id) => { if process.get_tbf_storage_permissions().is_some() { self.tbf_permissions.get_permissions(process) } else { StoragePermissions::new_self_only(id, &self.cap) } } ShortId::LocallyUnique => StoragePermissions::new_null(), }

策略可概括为三条规则:

进程 ShortId 类型TBF 头是否含存储权限分配的权限
ShortId::Fixed是采用 TBF 头中声明的存储权限(TbfHeaderStoragePermissions)
ShortId::Fixed否默认“仅访问自身状态”权限(new_self_only)
ShortId::LocallyUnique—空权限(new_null),无法访问任何存储

其中TbfHeaderStoragePermissions来自capsules_system::storage_permissions::tbf_header,即进程二进制(TBF)格式头中携带的存储权限声明;new_self_only允许应用读写自己的私有区域;new_null完全禁止存储访问。这种“TBF 优先、默认自访问、局部唯一 ID 一律禁止”的分层设计,正好覆盖了测试隔离存储所需的典型权限场景,相关设计背景可进一步参考 doc/reference/trd-storage-permissions.md。

在 src/main.rs 中,该策略通过自定义 capabilityAppStoreCapability(实现ApplicationStorageCapability)实例化,并注入进程加载器:

let storage_permissions_policy = static_init!( invs_permissions::InvsStoragePermissions< nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>, kernel::process::ProcessStandardDebugFull, AppStoreCapability, >, invs_permissions::InvsStoragePermissions::new(AppStoreCapability) );

五、进程加载与凭证检查链路

测试板还组装了完整的进程加载管线(src/main.rs),保证存储隔离测试在真实的 Tock 进程模型下进行:

  1. 软件 SHA-256 引擎:ShaSoftware256Component提供纯软件哈希,避免依赖硬件加密单元;
  2. 凭证检查策略:AppCheckerSha256Component基于 SHA-256 校验进程凭证(AppID);
  3. AppID 分配器:AppIdAssignerNamesComponent按进程名称分配 AppID;
  4. 进程检查机:ProcessCheckerMachineComponent包装检查策略,形成异步检查状态机;
  5. 顺序进程加载器:ProcessLoaderSequentialComponent接收检查器、分配器、存储权限策略以及app_flash/app_memory区域(由链接脚本符号_sapps/_eapps/_sappmem/_eappmem界定),在启动时逐个加载并验证应用镜像。

由此可见,存储权限策略被集成在进程加载阶段——应用只有在通过凭证检查并获得 AppID 后,其存储访问权限才会按上一节的三条规则被计算出来。

六、系统调用接口:0x50004

应用通过驱动号0x50004(capsules_extra::isolated_nonvolatile_storage_driver::DRIVER_NUM)访问隔离存储。完整协议定义见 doc/syscalls/50004_isolated_nonvolatile_storage.md,支持 32 位与 64 位地址空间。

6.1 Command

Command名称参数 1 / 参数 2说明
0驱动存在性检查未使用成功返回SUCCESS,否则NODEVICE
1Get Size未使用查询应用存储区大小(字节);若尚未分配则触发分配,结果经 upcall 异步返回
2Read读取偏移低 32 位 / 高 32 位异步读取,数据拷贝到 RW Allow 0 缓冲区;读取长度等于缓冲区长度
3Write写入偏移低 32 位 / 高 32 位异步写入,数据来自 RO Allow 0 缓冲区;写入长度等于缓冲区长度

Command 1/2/3 的同步失败返回码:

  • NOSUPPORT:应用无存储访问权限;
  • BUSY:前一个请求仍在进行中。

6.2 Subscribe(upcall)

Subscribe名称Upcall 签名说明
0Get Size 完成fn upcall(s: Statuscode, length: usize)参数 0 为状态码,参数 1 为区域大小(仅SUCCESS时有效)
1Read 完成fn upcall(s: Statuscode)读取命令完成或出错时触发
2Write 完成fn upcall(s: Statuscode)写入命令完成或出错时触发

各 upcall 的Statuscode取值:

状态码Get SizeReadWrite
SUCCESS返回区域大小已读取length字节到缓冲区已写入length字节
RESERVE—RW Allow 0 未提供或长度为 0RO Allow 0 未提供或长度为 0
NOMEM无剩余空间可分配应用没有存储区域应用没有存储区域
NOSUPPORT无存储权限无存储权限无存储权限
INVAL—读取超出应用存储区写入超出应用存储区
FAIL—底层存储访问出错底层存储访问出错

6.3 Allow

  • Read-Only Allow 0:写入非易失存储时使用的数据缓冲区;
  • Read-Write Allow 0:从非易失存储读取数据时使用的缓冲区。

由于读写均为异步操作,应用需要先通过allow共享缓冲区、再下发 Command 2/3,最后在对应 upcall 中确认结果。

七、构建与烧录

7.1 目录与构建配置

该测试板是一个独立的 Cargo 包,Cargo.toml 依赖本仓库内的nrf52840dk-test-base、components、kernel、nrf52840、segger、nrf52_components以及capsules-core/capsules-extra/capsules-system,全部走 workspace 路径引用,不依赖外部 crate 即可编译。

链接脚本 layout.ld 依次包含两块:

INCLUDE ../../../nordic/nrf52840_chip_layout.ld INCLUDE tock_kernel_layout.ld

即先引入 nRF52840 芯片内存布局(Flash/RAM 分区),再引入 Tock 标准内核布局(内核与应用的 ROM/RAM 区域划分)。其中_sapps、_eapps、_sappmem、_eappmem等符号正是由这些链接脚本定义,进程加载器据此确定应用镜像与内存区域。

7.2 编译

与 Tock 其他板卡一致,在该板目录下执行:

make

Makefile仅两行,依次引入 Makefile.common(workspace 构建规则)与 nrf52840dk.mk(nRF 测试板共享规则),产物为target/<target-triple>/release/nrf52840dk-test-invs.bin。

7.3 烧录

nrf52840dk.mk 定义了基于tockloader的烧录流程:

  • make flash:通过J-Link烧录内核到KERNEL_ADDRESS=0x00000,目标板参数--board nrf52dk;
  • make flash-openocd:通过OpenOCD烧录,其余参数相同;
  • make install:等价于flash。

示例(指定串口端口时):

make flash PORT=/dev/ttyACM0

烧录内核后,即可通过tockloader加载测试应用;应用需预先在 TBF 头中声明(或按默认策略获得)合适的存储权限,才能通过0x50004驱动号访问其 4 KiB 私有存储区。

八、小结:这套测试内核验证了什么

nrf52840dk-test-invs以最小化内核的形式,完整覆盖了 Tock 隔离非易失存储子系统的三个关键环节:

  1. 隔离语义:通过IsolatedNonvolatileStorageComponent+ grant 机制,为每个应用分配独立的APP_STORAGE_REGION_SIZE(本板 4096 字节)私有存储区,应用偏移从 0 开始、互不可见;
  2. 权限模型:InvsStoragePermissions演示了“TBF 头权限优先、默认自访问、局部唯一 ID 拒绝”的分层策略,与 doc/reference/trd-storage-permissions.md 描述的存储权限体系对应;
  3. 真实硬件路径:整条链路跑在 nRF52840-DK 的 SPIM0 → MX25R6435F 外部 SPI Flash 上,经NonvolatileToPages完成任意偏移读写到页对齐擦写的转换,并通过0x50004驱动号的 Command/Subscribe/Allow 协议与用户态应用交互。

对于希望在自有平台上接入隔离存储的开发者,该测试板是一份可以直接对照的参考实现:从引脚配置、组件装配、权限策略到进程加载集成的完整代码路径,均可在 boards/configurations/nrf52840dk/nrf52840dk-test-invs/ 目录中逐一查阅。

  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载
上一篇:Ignite项目中的样式管理实践指南
下一篇:TabPFN 快速上手:如何用 3 分钟跑出第一个表格数据分类结果

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 1:48:00

STM32F103入门实战:从开发板认识、环境搭建到烧录调试全流程

1. 准备工作&#xff1a;先把开发板和工具认清楚做嵌入式开发这几年&#xff0c;我最大的感受是&#xff1a;许多新手倒在起跑线上&#xff0c;不是因为代码写不出来&#xff0c;而是因为开发环境没搭好&#xff0c;或者板子都没认清就开始写代码&#xff0c;最后连程序烧不进去…

作者头像 李华
网站建设 2026/10/7 1:47:01

Agent-Reach实战:让智能体从“能聊”到“能用”的完整指南

我去年在一家公司做内部知识库问答的Agent项目&#xff0c;模型本身选得不错&#xff0c;各个模块的prompt也调得挺顺&#xff0c;结果一上生产就卡住了——Agent什么都答得头头是道&#xff0c;但一问“这个月的账单数据是多少”“帮我拉一下昨天的CRM客户名单”&#xff0c;它…

作者头像 李华