ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

RT-Thread NVMEM 子系统解析:用 Provider/Cell 模型统一管理 eFuse、OTP 与 EEPROM 校准数据

2026/10/7 7:24:40 拓冰建站 浏览量
RT-Thread NVMEM 子系统解析:用 Provider/Cell 模型统一管理 eFuse、OTP 与 EEPROM 校准数据 操作系统嵌入式物联网嵌入式OSRTOS【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址https://gitcode.com/gh_mirrors/rt/rt-thread点击查看免费下载导读NVMEMNon-volatile memory子系统是 RT-Thread 设备驱动框架DM中用于抽象各类一次性可编程存储与掉电非易失存储的统一模型将OTP、eFuse、EEPROM 分片、PMIC 修调trim、MAC 地址与校准数据块calibration blobs统一建模为provider 设备 cell具名或带索引的字节/位区间消费者通过设备树 phandle 直接解析并读写所需的 cell。本文以 nvmem.md 与 dm.md 两份文档为主体结合 nvmem.h 与 nvmem.c 的源码实现展开读完你将掌握何时该用rt_nvmem_*API、Provider 如何注册、Consumer 如何按名/按索引获取 cell 并读写、位打包 cell 的底层位运算以及如何编写并接入一个 SoC 级 OTP/eFuse provider 驱动。注components/drivers/nvmem/目录下没有内置 OTP 驱动SoC 级 provider 统一放在 BSP 中的SOC_DM_NVMEM_DIR目录如 bsp/rockchip/dm/nvmem 下的 rockchip-otp、bsp/raspberry-pi/dm/nvmem 下的 raspberrypi-otp。什么时候该用 NVMEM在决定架构之前先用下面这张对照表判断是否适合引入 NVMEM 框架源自原文档应当使用rt_nvmem_*的场景可以直接使用专用驱动解决的场景多个消费者需要共享同一个 OTP/eFuse 控制器的不同分片只有一个驱动在启动时读取一次固定偏移设备树中使用nvmem-cells/nvmem-cell-namesLinux 风格绑定MAC/校准数据硬编码在 BSP 的 C 源码中需要带引用计数的rt_nvmem_cell句柄生命周期可管理单平台驱动内直接 MMIO 访问就足够核心判断依据是否有多个消费者、是否走设备树、是否需要引用计数句柄。若三者皆无直接写专用驱动反而更轻量反之NVMEM 框架能避免重复实现偏移解析、只读保护与并发访问序列化。核心数据结构struct rt_nvmem_device与struct rt_nvmem_cell头文件 components/drivers/include/drivers/nvmem.h 定义了 NVMEM 的两类核心对象struct rt_nvmem_device { struct rt_device parent; /* 继承标准设备 */ int cells_nr; /* 已挂载 cell 数量 */ rt_list_t cell_nodes; /* cell 链表头 */ rt_ssize_t (*reg_read)(struct rt_nvmem_device *, int offset, void *val, rt_size_t bytes); rt_ssize_t (*reg_write)(struct rt_nvmem_device *, int offset, void *val, rt_size_t bytes); rt_ssize_t size; /* 存储总字节数 */ int word_size; /* 访问粒度 */ int stride; /* 行/步进 */ rt_bool_t read_only; /* 只读 */ rt_bool_t ignore_wp; /* 忽略写保护引脚 */ rt_base_t wp_pin; /* 写保护 GPIO */ rt_uint8_t wp_pin_active; /* 写保护有效电平 */ struct rt_ref ref; /* 引用计数 */ struct rt_spinlock spinlock; /* 自旋锁保护 cell 链表 */ void *priv; /* provider 私有数据 */ }; struct rt_nvmem_cell { rt_list_t list; int index; /* 在 nvmem-cells 中的索引 */ const char *id; /* 名称对应 nvmem-cell-names */ const rt_bool_t free_able; /* 是否为动态可释放 cell */ rt_uint32_t offset; /* 相对 provider 起始的字节偏移 */ rt_uint32_t bytes; /* 字节长度 */ rt_uint32_t bit_offset; /* 位偏移可选 */ rt_uint32_t nbits; /* 位宽可选 */ struct rt_ref ref; /* cell 引用计数 */ struct rt_ofw_node *np; /* 关联的设备树节点 */ struct rt_nvmem_device *nvmem; /* 所属 provider */ };各字段角色原文档要点字段作用reg_read/reg_write在offset处按字节访问的回调reg_write为NULL表示不可编程read_only由设备树read-only属性、缺失reg_write回调或预设标志共同决定见下文注册逻辑wp_pin可选EEPROM 等器件的写保护 GPIO写操作期间会被短暂解除Provider API注册、注销与追加静态 cellProvider 侧对外暴露三个接口nvmem.crt_err_t rt_nvmem_device_register(struct rt_nvmem_device *ndev); rt_err_t rt_nvmem_device_unregister(struct rt_nvmem_device *ndev); rt_err_t rt_nvmem_device_append_cell(struct rt_nvmem_device *ndev, struct rt_nvmem_cell *cell);register做了什么源码依据 nvmem.c若ignore_wp未置位通过rt_pin_get_named_pin(ndev-parent, wp, 0, mode, ndev-wp_pin_active)解析名为wp的写保护引脚wp_pin 0且不等于PIN_NONE时返回-RT_EINVAL。初始化 cell 链表与struct rt_ref引用计数初始值为 1代表 provider 自身。只读判定三条件取或设备树存在read-only属性或ndev-read_only已预设或reg_write为NULL——即不提供写回调 天然只读。若npparent.ofw_node存在执行rt_ofw_data(np) ndev把 provider 句柄绑定到设备树节点上供消费者通过rt_ofw_data()反查。unregister的约束nvmem.c只有引用计数rt_ref_read(ndev-ref) 1即没有任何消费者持有 cell时才允许注销否则返回-RT_EBUSY。这正是忘记put_cell会阻塞注销这一陷阱的源码根源。append_cellnvmem.c在自旋锁保护下把 cell 插入cell_nodes链表、递增cells_nr并rt_ref_get(ndev-ref)增加 provider 的引用——静态 cellfree_able为 false通常在 probe 阶段就已知适合用此接口预注册从而让消费者在启动早期就能按 index/id 命中。Consumer API按索引/名称获取 cell 并读写消费者侧接口均在 nvmem.h 声明struct rt_nvmem_cell *rt_nvmem_get_cell_by_index(struct rt_device *dev, int index); struct rt_nvmem_cell *rt_nvmem_get_cell_by_name(struct rt_device *dev, const char *id); void rt_nvmem_put_cell(struct rt_nvmem_cell *cell); rt_ssize_t rt_nvmem_cell_read(struct rt_nvmem_cell *cell, void *buffer, rt_size_t len); rt_ssize_t rt_nvmem_cell_write(struct rt_nvmem_cell *cell, void *buffer, rt_size_t len);使用规则与实现细节源码依据 nvmem.c长度约束len cell-bytes且len nvmem-size否则返回-RT_EINVALreg_read/reg_write缺失时返回-RT_ENOSYS。位字段当 cell 设置了bit_offset/nbits时——读操作先按字节读出再做移位、掩码清掉多余高/低字节与残留位写操作是典型的read-modify-write先分配cell-bytes大小的缓冲读回周边字节把新数据按位拼入再整体写回。写保护若wp_pin 0写之前rt_pin_write(ndev-wp_pin, !ndev-wp_pin_active)解除写保护写完恢复为有效电平。类型化辅助函数rt_nvmem_cell_read_u8/u16/u32/u64直接封装rt_nvmem_cell_read免去手动指定长度nvmem.c。引用管理rt_nvmem_put_cell内部执行rt_ref_put(cell-ref, cell_release)当最后一个引用释放时nvmem.c动态 cellfree_able为 true会从链表移除、递减cells_nr、rt_ofw_data(cell-np) RT_NULL并释放内存同时归还 provider 的引用计数。完整消费示例读取 MAC 地址原文档给出的最小可运行片段cell rt_nvmem_get_cell_by_name(pdev-parent, mac-address); if (!cell || rt_is_err_ptr(cell)) return -RT_ERROR; rt_nvmem_cell_read(cell, mac, 6); rt_nvmem_put_cell(cell);注意两点实战细节错误判断rt_nvmem_get_cell_by_*失败时可能返回RT_NULL也可能返回rt_err_ptr(-RT_ENOSYS)之类的错误指针见 nvmem.c 的返回值因此必须同时判断!cell与rt_is_err_ptr(cell)。必须配对put_cell获取后及时释放否则 provider 引用计数无法归零rt_nvmem_device_unregister会持续返回-RT_EBUSY。设备树模型RT_USING_DM下Kconfig 依赖与整体架构NVMEM 子系统的设备树化支持以RT_USING_DMRT_USING_OFW为前提相关 Kconfig 配置components/drivers/nvmem/Kconfig配置项作用RT_USING_DM必需设备模型总开关RT_USING_OFW必需设备树解析ofw支持RT_USING_PIN可选用于可选的wp写保护 GPIORT_USING_NVMEM核心开关构建 nvmem.cselect RT_USING_ADT、select RT_USING_ADT_REFSOC_DM_NVMEM_DIRBSP 侧 OTP/eFuse/EEPROM provider 驱动的 Kconfig 引入目录如 bsp/rockchip/dm/Kconfig 中的SOC_DM_NVMEM_DIR $(SOC_DM_DIR)/nvmem一个值得注意的架构特点原文档明确说明NVMEM 不注册到rt_bus总线provider 之间不通过总线匹配而是纯靠 provider 的reg_read/reg_write回调 OFW 节点数据rt_ofw_data查找整个模型极其轻量。整体调用链路原文档架构图BSP provider (efuse/otp/eeprom) | rt_nvmem_device_register, rt_ofw_data ndev v Consumer: nvmem-cells provider N; v rt_nvmem_get_cell_by_name → rt_nvmem_cell_read/write v rt_nvmem_put_cellProvider 设备树绑定一个 eFuse/OTP provider 的典型设备树片段原文档示例efuse: efuse110000 { compatible vendor,soc-efuse; reg 0x110000 0x1000; #address-cells 1; #size-cells 1; #nvmem-cell-cells 1; read-only; mac0 { reg 0x00 0x06; }; };属性作用#nvmem-cell-cells通常为1——phandle 参数即 cell 索引efuse N中的Nread-only注册时强制只读与reg_write缺失等效wp写保护 GPIO除非 provider 设置ignore_wp子节点mac0的reg给出该 cell 的offset与bytes可选bits offset nbits表示位字段Consumer 设备树绑定与ofw_nvmem_get_cell三步查找消费者侧原文档示例gmac: ethernet0 { nvmem-cells efuse 0; nvmem-cell-names mac-address; };rt_nvmem_get_cell_by_name会遍历nvmem-cell-names找到匹配名称并把对应索引交给内部函数ofw_nvmem_get_cellnvmem.c。该函数按以下三步顺序解析 phandle缓存命中rt_ofw_data(cell_np)已缓存 cell 句柄且引用计数 0时直接rt_ref_get复用静态列表匹配遍历 provider 通过append_cell预注册的 cell 链表按index若指定了名称还须id一致命中动态创建都不命中时从子节点reg属性分配动态 cell解析offset、bytes若有bits属性则解析bit_offset、nbits并校验bit_offset 8、nbits非零且不越界然后rt_nvmem_device_append_cell挂入链表并绑定rt_ofw_data(cell_np)。动态 cell 的free_able为 true在最后一次put_cell时被移除并释放——因此这类 cell 不能长时间缓存使用必须配对获取/释放。位打包 cellbit_offset/nbits当存储布局以位而非整字节划分时可在 cell 子节点用bits属性声明位区间源码校验见 nvmem.c。核心语义原文档 源码 nvmem.c 与 nvmem.c读按字节读出后bit_offset向右移位对齐跨字节时把下一字节高位补入上一字节低位最后清零多余字节与掩掉nbits之外的残留位。写read-modify-write——分配cell-bytes缓冲将待写数据左移bit_offset读回周边字节把空出的低位/高位拼接回去再整块写回。最佳实践原文档建议优先使用字节对齐的reg offset length必须位打包时要么在 nvmem.c 中走经过校验的解析路径要么用append_cell在驱动内直接构造好bit_offset/nbits。编写 BSP Provider六步清单原文档给出 Provider 的落地清单结合 bsp/rockchip/dm/nvmem/nvmem-rockchip-otp.cRockchip OTP 控制器含 OTPC 寄存器定义、SBPI/用户模式访问与超时控制OTPC_TIMEOUT可对号入座实现reg_read只有器件真正可编程如可烧写的 OTP/eFuse 或可写的 EEPROM才实现reg_write填充size、parent.ofw_node与回调指针rockchip-otp 中还按型号区分RK3568_NBYTES、RK3576_NO_SECURE_OFFSET等参数调用rt_nvmem_device_register完成注册对固定 index/id 布局的 cell可选调用append_cell预注册在remove阶段先确保所有消费者都已put_cell当rt_ref_read(ndev-ref) 1时再rt_nvmem_device_unregister将驱动源码放入SOC_DM_NVMEM_DIR指定目录如bsp/rockchip/dm/nvmem/、bsp/raspberry-pi/dm/nvmem/使其 Kconfig 被RT_USING_NVMEM自动osource引入components/drivers/nvmem/Kconfig。常见陷阱与规避综合两份文档的 Pitfalls 表问题规避方法忘记调用put_cell导致 provider 引用计数无法归零unregister一直返回-RT_EBUSY对 OTP 执行写操作不提供reg_write回调系统即自动只读BSP 没有 provider 驱动补写驱动并放入SOC_DM_NVMEM_DIRProvider 尚未 probe消费者先调用rt_platform_ofw_request请求对应平台设备#nvmem-cell-cells与 provider 绑定不一致严格匹配 provider 的绑定声明通常为 1并发访问 OTP在 provider 的reg_read/reg_write回调内部自行串行化配合spinlock动态 cell 未及时释放导致 unregister EBUSY释放全部 cell 引用后再注销参考资源本文主体文档documentation/6.components/device-driver/nvmem/nvmem.md、documentation/6.components/device-driver/nvmem/dm.md头文件与实现components/drivers/include/drivers/nvmem.h、components/drivers/nvmem/nvmem.cKconfigcomponents/drivers/nvmem/KconfigBSP 参考实现bsp/rockchip/dm/nvmem/nvmem-rockchip-otp.c、bsp/raspberry-pi/dm/nvmem/raspberrypi-otp.c并在 bsp/rockchip/dm/Kconfig 中声明SOC_DM_NVMEM_DIR相邻子系统documentation/6.components/device-driver/iio、documentation/6.components/device-driver/ofw赞分享操作系统嵌入式物联网嵌入式OSRTOS【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址https://gitcode.com/gh_mirrors/rt/rt-thread点击查看免费下载相关推荐RT-Thread NVMEM 设备模型DM详解OTP/eFuse/EEPROM 的 Provider-Consumer 架构与 nvmem-cells 实战RT Thread NVMEM 设备模型DM详解OTP/eFuse/EEPROM 的 Provider Consumer 架构与 nvmem cells操作系统嵌入式物联网嵌入式OSRTOSRT-Thread clock_time 子系统深度解析统一时间基准与高精度事件调度RT Thread clock_time 子系统深度解析统一时间基准与高精度事件调度 clock_time 是 RT Thread 中负责统一时间基准tim操作系统嵌入式物联网嵌入式OSRTOSRT-Thread Clock Time 子系统深入解析统一高精度时间基准与事件调度架构RT Thread Clock Time 子系统深入解析统一高精度时间基准与事件调度架构 RT Thread 的 Clock Time 子系统将单调计数时钟操作系统嵌入式物联网嵌入式OSRTOS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考