ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RT-Thread DM 框架下的 NVMe 设备模型(DM)架构与移植指南

RT-Thread DM 框架下的 NVMe 设备模型(DM)架构与移植指南 操作系统嵌入式物联网嵌入式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 的 Device ModelDM框架为存储类外设提供了一套统一的设备注册与块设备抽象机制。本文基于仓库中 NVMe device model (DM) 文档 展开围绕RT_USING_NVME配置项下的协议核心nvme.c与 PCI 传输层nvme-pci.c深入讲解控制器注册流程、队列与中断的搭建、DMA 与命名空间到块设备的映射并给出 PCI BSP 的移植检查清单与常见陷阱。读完本文你将能够独立完成一个 PCI NVMe 控制器从探测、注册到出现nvme0n1块设备的完整移植并能读懂非 PCI 控制器的接入步骤。一、NVMe DM 子系统的整体架构在RT_USING_DM使能后NVMe 子系统由两个可选构建单元组成协议核心nvme.c实现 NVMe 主机协议栈——Admin 队列、I/O 队列、Identify、命名空间扫描、块设备注册等PCI 传输层nvme-pci.c由RT_NVME_PCI控制负责 PCI 探测、BAR 映射、MSI-X/INTx 中断配置并把控制器交给协议核心。两者的关系是典型的传输层 协议核心分层传输层填充struct rt_nvme_controller并调用rt_nvme_controller_register协议核心随后接管所有队列与命令处理。每个被识别的命名空间Namespace都会注册为一个struct rt_blk_disk进入块设备层最终以nvme0n1之类的名字出现在系统中。相关源码与头文件位置协议核心components/drivers/nvme/nvme.cPCI 传输层components/drivers/nvme/nvme-pci.c头文件控制器、队列、命令结构体定义components/drivers/include/drivers/nvme.hKconfigcomponents/drivers/nvme/Kconfignvme.c中维护两个 IDAnvme_controller_ida、nvme_ida与一个全局控制器链表nvme_nodes并定义了一组nvme_readl/nvme_writel/nvme_readq/nvme_writeq访问 MMIO 寄存器见 nvme.c协议核心的所有寄存器操作都基于nvme-regs基址。二、Kconfig 构建选项OptionDepends onRoleRT_USING_NVMERT_USING_DM、RT_USING_BLK、RT_USING_DMA核心协议栈编译nvme.cRT_USING_NVME_IO_QUEUERT_USING_NVMEI/O 队列数量按优先级配置文件取 2/4/8RT_NVME_PCIRT_USING_NVME、RT_USING_PCIPCI 函数驱动默认y编译nvme-pci.cSOC_DM_NVME_DIRBSP额外非 PCI 控制器的 Kconfig 目录Kconfig 中 I/O 队列数的取值与线程优先级配置联动见 components/drivers/nvme/KconfigRT_THREAD_PRIORITY_8→ 默认2个 I/O 队列RT_THREAD_PRIORITY_32→ 默认4个RT_THREAD_PRIORITY_256→ 默认8个。SConscriptcomponents/drivers/nvme/SConscript中nvme.c在RT_USING_NVME时无条件编译nvme-pci.c额外受RT_NVME_PCI控制因此两者可以独立开关。头文件nvme.h中队列总数的宏nvme.h#define RT_USING_NVME_QUEUE (1 (RT_USING_NVME_IO_QUEUE * RT_CPUS_NR))即1 个 Admin 队列 每 CPU 若干 I/O 队列槽位。该宏同时决定了struct rt_nvme_controller中irqs[]数组和队列数组的静态大小MSI-X 向量数量上限也受其约束。三、PCI 传输层nvme-pci.c详解3.1 设备匹配规则PCI 驱动使用如下 ID 表进行匹配nvme-pci.cstatic const struct rt_pci_device_id pci_nvme_ids[] { { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0010) }, { RT_PCI_DEVICE_CLASS(PCIS_STORAGE_EXPRESS, ~0) }, { /* sentinel */ } };匹配条件有两个显式匹配 RedHat 虚拟机的 NVMe 设备 IDQEMU 模拟场景以及任意 PCI 存储 Express 类PCIS_STORAGE_EXPRESS功能——后者意味着任何符合 NVMe 类代码的 PCI 函数都会被nvme-pci驱动绑定。3.2 Probe 流程pci_nvme_probe的执行路径nvme-pci.c如下pci_nvme_probe(pdev) | | rt_pci_iomap(pdev, 0) → nvme-regs // BAR0 映射失败则 abort | ops quirk-ops ?: pci_nvme_std_ops (PCI) | MSI-X: rt_pci_msix_enable → nvme-irqs[i] | else: 单 pdev-irq, rt_pci_irq_unmask // 回退到 INTx | rt_pci_set_master(pdev) // 使能总线主控 v rt_nvme_controller_register(nvme) | v pdev-parent.user_data pci_nvme具体步骤分配struct pci_nvme_controller内嵌struct rt_nvme_controller parent映射 BAR0rt_pci_iomap(pdev, NVME_REG_BAR)NVME_REG_BAR定义为 0失败返回-RT_EIO选择 ops若pdev-id-data指向的 quirk 提供了ops则使用之否则用pci_nvme_std_ops仅含.name PCI即使用nvme.c中的标准 doorbell 路径MSI-X 尝试先取rt_pci_msix_vector_count失败则回退rt_pci_msi_vector_count向量数取msi_nr与RT_ARRAY_SIZE(msix_entries)即RT_USING_NVME_QUEUE的较小值用rt_pci_msix_entry_index_linear线性编号后调用rt_pci_msix_enable成功则逐个把msix_entries[i].irq填入nvme-irqs[i]INTx 回退MSI-X 不可用时irqs_nr 1、irqs[0] pdev-irq并rt_pci_irq_unmaskrt_pci_set_master(pdev)使能 DMA 主控调用rt_nvme_controller_register(nvme)成功后把pci_nvme存入pdev-parent.user_data供 remove 使用。3.3 Remove / Shutdownpci_nvme_removenvme-pci.c按如下顺序清理调用rt_nvme_controller_unregister由协议核心摘除命名空间、删除 I/O/Admin 队列、执行 shutdown disable若为 MSI-X 则rt_pci_msix_disable否则INTx 掩码注意代码注释INTx 是共享线不全部 mask先rt_hw_interrupt_umask再rt_pci_irq_maskrt_pci_clear_master、rt_iounmap(regs)、rt_free(pci_nvme)。pci_nvme_shutdown直接复用pci_nvme_remove。3.4 Quirks 机制pdev-id-data可以指向struct pci_nvme_quirknvme-pci.cstruct pci_nvme_quirk { const struct rt_nvme_ops *ops; };quirk 提供替代的rt_nvme_ops如厂商私有 MMIO 路径默认情况下走nvme.c的标准 doorbell 路径。四、中断与 NUMA 亲和性4.1 中断模式ModeBehaviorMSI-X最多RT_USING_NVME_QUEUE个向量线性索引映射到队列INTxirqs_nr 1共享中断线——必要时依赖complete_cmd回调区分命令完成在nvme_setup_io_queuesnvme.c中队列数由io_queue_max irqs_nr 1 ? irqs_nr - 1 : 1决定减去 Admin 队列占用并通过RT_NVME_FEAT_NUM_QUEUESSet Features 与设备协商。每个 I/O 队列用nvme-irqs[irq_idx % nvme-irqs_nr]分配中断向量MSI-X 下线性取模然后rt_hw_interrupt_install(irq, nvme_queue_isr, queue, name)rt_hw_interrupt_umask。4.2 二级 CPU 启动时的亲和性修正nvme.c末尾导出了INIT_SECONDARY_CPU_EXPORT(nvme_queue_affinify_fixup)nvme.c在每个二级 CPU 启动时执行对每个满足i ≡ cpuid (mod RT_CPUS_NR)的控制器 I/O 队列索引通过rt_pic_irq_set_affinity将其中断亲和性设置为该 CPU配合 NUMA 文档numaon时让队列中断尽量靠近其内存节点降低跨节点访存开销。源码注释明确指出NVMe 的 I/O 队列应该是Per-CPU的所以把亲和性修正放在二级 CPU 启动阶段能最大概率保证设置成功nvme.c。关键约定nvme-irqs[]必须在rt_nvme_controller_register之前由传输层填好协议核心会在 setup 阶段按队列逐一挂接 ISR。五、DMA 与缓冲管理队列环形缓冲区SQ/CQ通过rt_dma_alloc(nvme-dev, ...)分配见nvme_alloc_queuenvme.cDMA 属性由nvme_queue_dma_flags()提供rt_inline rt_ubase_t nvme_queue_dma_flags(void) { return RT_DMA_F_NOCACHE | RT_DMA_F_LINEAR; }即非缓存 线性映射。nvme-dev是传输层指定的用于 DMA 映射的rt_devicePCI 场景下为pdev-parent。Identify 数据缓冲使用rt_malloc_align(..., nvme-page_size)分配页大小在 Admin 队列配置阶段根据CAP.MPSMIN/MPSMAX协商确定nvme.c主机页移位ARCH_PAGE_SHIFT若小于设备MPSMIN直接报错-RT_EINVAL若大于设备MPSMAX则 clamp 到page_maxpage_size 1U page_shift写入CC.MPS字段。数据传输默认走PRP若 Identify Controller 报告的sgls支持 SGLsgls RT_NVME_ID_SGL_SUPPORT_MASK则切换为SGL 模式nvme-sgl_mode RT_NVME_PSDT_SGL_MPTR_SGL。nvme_blk_rw中针对 PRP 与 SGL 分别构造命令PRP 场景按 ≤4KB、4KB~8KB、8KB 三种情况设置prp1/prp2必要时动态分配 PRP Listnvme.c。六、命名空间到块设备的映射Identify Namespacenvme_identifynvme.c成功后nvme_scan_devicenvme.c对每个 nsid 依次处理ndev-parent.ops nvme_blk_ops; ndev-parent.parallel_io RT_TRUE; ndev-parent.ida nvme_ida; ndev-parent.max_partitions RT_BLK_PARTITION_MAX; rt_dm_dev_set_name(ndev-parent.parent, %sn%u, nvme-name, nsid); rt_hw_blk_disk_register(ndev-parent);要点命名空间从 1 开始NVMe 规范约定nsze 0的命名空间被跳过磁盘命名模式为nvme{N}n{nsid}例如nvme0n1parallel_io RT_TRUE表示读写绕过usr_lock直接进ops允许并发 I/O块层语义见 disk.mdmax_partitions RT_BLK_PARTITION_MAX放开分区解析上限分区行为遵循标准分区文档BSP 使能 GPT/MBR 解析器时生效nvme_blk_ops实现了read/write/getgeome/sync/erase/autorefresh全套回调nvme.c其中getgeome从id.nsze和lba_shift推导sector_count、bytes_per_sector、block_sizeerase在设备支持 Write Zeroesoncs位时用RT_NVME_CMD_WRITE_ZEROES实现否则返回-RT_ENOSYSautorefresh在设备带 Volatile Write Cachevwc位时通过RT_NVME_FEAT_VOLATILE_WCSet Features 切换。协议核心rt_nvme_controller_register的完整序列nvme.c为校验nvme/ops且CSTS ! 0xffffffff→ IDA 分配nvme%u→ 读CAPMQES/STRIDE/MPSMIN/MPSMAX→ 配置 Admin 队列disable → alloc → enable→ 创建 I/O 队列并 attach CQ/SQ → Identify ControllerMDTS、VWC、ONCS、SGL→ 扫描命名空间注册块设备 → 挂入全局nvme_nodes链表。七、非 PCI 控制器的接入步骤对于挂在非 PCI 总线如平台总线、自定义互连上的 NVMe 控制器文档给出五步接入法映射regs并实现struct rt_nvme_ops按需实现setup_queue/submit_cmd/complete_cmd等回调填充irqs[]为 Admin I/O 队列各准备一个中断向量优先 MSI-X把nvme-dev设置为用于 DMA 映射的rt_device调用rt_nvme_controller_register(nvme)在 BSP 的SOC_DM_NVME_DIR下新增 Kconfig并挂接平台/PCIe 替代探测逻辑。SOC_DM_NVME_DIR通过osource $(SOC_DM_NVME_DIR)/Kconfig被 NVMe 的 Kconfig 引用components/drivers/nvme/KconfigBSP 在RT_USING_NVME开启时即可引入额外控制器配置。关于rt_nvme_ops的返回值极性务必注意nvme.md 与 nvme.c当前nvme.c中setup_queue/cleanup_queue使用if (!(err ops-...))判断即非零返回值视为成功、零值视为失败而submit_cmd使用常规RT_EOK语义非零中止提交。移植 quirk 时须与之一致否则队列初始化会静默失败。八、PCI BSP 移植检查清单使能RT_USING_DM、RT_USING_PCI、RT_USING_BLK、RT_USING_DMA、RT_USING_NVME、RT_NVME_PCI确保ECAM / host bridge 探测先于 NVMe 功能执行PCI 枚举依赖确保rt_pci_set_master路径执行pci_nvme_probe内已完成固件/DT 中优先提供 MSI-X多节点服务器DT 中配置numaon CPU/内存numa-node-id参见 NUMA 文档验证命名空间以nvme0n1等形式出现并在需要时挂载分区。九、常见陷阱与对策IssueMitigationBAR 未映射rt_pci_iomap失败 → probe 中止返回-RT_EIOMSI-X 向量不足回退到 INTx——可能需要complete_cmd区分命令完成CSTS -10xffffffff通常是 BAR 映射错误或字节序问题——rt_nvme_controller_register会提前拒绝setup_queue成功返回值颠倒参考 nvme.md ——与nvme.c的极性保持一致非零 成功二级 CPU 中断亲和性确保nvme_queue_affinify_fixup已运行SMP BSP 需要此外从nvme_configure_admin_queue的实现可看出页大小不匹配主机ARCH_PAGE_SHIFT 设备MPSMIN也会直接拒绝注册Admin 命令走admin_queue完成等待超时 60 秒而 I/O 队列命令等待RT_WAITING_FOREVERnvme.c移植时不要在任何会阻塞 Admin 路径的锁内做长时间操作。十、相关文档与源码延伸NVMe 控制器 API协议核心详解struct rt_nvme_controller各字段属主、命令路径、PitfallsPCI 子系统 与 PCI 中断/MSIBAR 映射、MSI-X 使能语义NUMA 文档中断亲和性与内存节点布局DMA 文档 与 DMA poolrt_dma_alloc、rt_dma_sync_out_data语义块设备rt_blk_disk驱动编写指南 与 分区文档parallel_io、分区解析与命名空间注册的块层约定源码nvme.c、nvme-pci.c、nvme.h、Kconfig。赞分享操作系统嵌入式物联网嵌入式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点击查看免费下载相关推荐如何找到 Langfuse 中文文档与 4 份多语言 README如何找到 Langfuse 中文文档与 4 份多语言 README Langfuse 多语言支持的现状很明确文档已有英、中、日、韩 4 个语言版 README操作系统嵌入式物联网嵌入式OSRTOSRT-Thread CAN 总线设备框架完全指南从应用开发到驱动移植RT Thread CAN 总线设备框架完全指南从应用开发到驱动移植 RT Thread 将各类 CAN/CAN FD 控制器统一抽象为 RT_Device_操作系统嵌入式物联网嵌入式OSRTOSRT-Thread STM32F407 星火一号开发板 BSP 使用指南从快速上手到设备模型DM进阶RT Thread STM32F407 星火一号开发板 BSP 使用指南从快速上手到设备模型DM进阶 本文是 RT Thread 官方为 STM32F40操作系统嵌入式物联网嵌入式OSRTOS上一篇黑苹果终极指南OpenCore配置完整手册下一篇3分钟搞定浏览器完美预览Markdown文件的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进