Linux 内核 RapidIO 子系统完全指南:驱动接口、枚举发现与设备模型
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
RapidIO 是一种面向嵌入式市场的高速交换式互连(switched fabric interconnect)技术,同时支持内存映射 I/O 与基于消息的事务,并拥有与 PCI 总线类似的标准化设备发现机制。本文以 Linux 内核 Documentation/admin-guide/rapidio.rst 为主干,结合内核源码中的include/linux/rio_drv.h、include/linux/rio.h以及drivers/rapidio/目录下的核心实现,系统讲解 RapidIO 子系统的驱动开发接口、内部数据结构、枚举与发现流程以及设备模型集成方式,帮助读者在 Linux 内核中为新的 RapidIO 架构提供支持、编写新的 RapidIO 驱动,或深入理解该子系统的实现原理。
RapidIO 子系统概览
RapidIO 是面向嵌入式领域设计的高速交换式互连标准,其核心特性包括:
- 内存映射 I/O 支持:允许本地处理器像访问本地内存一样访问交换网络中的远程设备。
- 基于消息的事务:提供 mailbox(邮箱)与 doorbell(门铃)两种轻量级消息机制,适用于控制面通信。
- 标准化发现机制:与 PCI 总线标准类似,通过配置空间扫描即可简单检测网络中存在的设备,无需人工配置。
Linux 内核中的 RapidIO 子系统正是围绕这些特性构建的,它向上为设备驱动提供统一 API,向下通过"主端口(master port)"抽象屏蔽不同硬件平台的差异。该文档面向三类开发者:希望在新架构上支持 RapidIO 的开发者、编写新 RapidIO 设备驱动的开发者,以及希望理解子系统内部机制的开发者。
从源码布局看,整个子系统核心位于drivers/rapidio/目录,主要包含:
| 文件 | 职责 |
|---|---|
| drivers/rapidio/rio.c | 子系统核心:mbox/doorbell/port-write 管理、内存映射、路由表操作、设备生命周期 |
| drivers/rapidio/rio-access.c | 配置空间读写封装(本地与远端) |
| drivers/rapidio/rio-driver.c | Linux 设备模型(LDM)集成:总线注册、驱动注册、probe/remove |
| drivers/rapidio/rio-scan.c | 网络枚举(enumeration)与发现(discovery)算法 |
| drivers/rapidio/rio-sysfs.c | sysfs 属性节点与用户态接口 |
| drivers/rapidio/rio_cm.c | 通道化消息(channelized messaging)驱动,见 Documentation/driver-api/rapidio/rio_cm.rst |
已知缺陷与限制
原文档明确列出了当前子系统的两个已知限制,理解这些限制对驱动开发者尤为重要:
1. 不支持 RapidIO 内存区域的访问/管理
RapidIO 内存区域的访问与管理功能尚未实现。这意味着子系统虽然提供了内存映射 API(如rio_map_inb_region()、rio_map_outb_region()),但通过通用框架对 RapidIO 地址空间的完整访问与资源管理能力仍有限,驱动开发者需要依赖具体的硬件平台实现。
2. 不支持多主机枚举
系统仅支持单一主机对交换网络执行枚举。这与 drivers/rapidio/rio-scan.c 中的实现一致:主机在枚举前通过RIO_HOST_DID_LOCK_CSR寄存器尝试获取"主机设备 ID 锁"(rio_enum_host()中的rio_local_write_config_32(port, RIO_HOST_DID_LOCK_CSR, port->host_deviceid)),同一网络中只允许一个主机完成枚举。此外,drivers/rapidio/rio.c提供的rio_lock_device()/rio_unlock_device()也仅用于单主机场景下对目标设备的独占锁定(通过向RIO_HOST_DID_LOCK_CSR写入本机 host_deviceid 并轮询验证)。
RapidIO 驱动接口
子系统为驱动开发者提供了一整套调用接口,用于获取设备信息、请求/映射内存区域资源,以及管理邮箱与门铃。这些接口主要声明在 include/linux/rio_drv.h,并在 drivers/rapidio/rio-driver.c 与 drivers/rapidio/rio.c 中实现。
配置空间访问接口
配置空间访问分为**本地(local)与远端(网络)**两类,每类均支持 8/16/32 位读写:
- 本地访问:
__rio_local_read_config_8/16/32()、__rio_local_write_config_8/16/32(),以及对应的内联封装rio_local_read_config_*()、rio_local_write_config_*()。 - 远端访问:
rio_mport_read_config_8/16/32()、rio_mport_write_config_8/16/32()(带destid与hopcount参数),以及面向具体设备的封装rio_read_config_*()、rio_write_config_*()。
这些函数的实现位于 drivers/rapidio/rio-access.c,通过宏RIO_LOP_READ/WRITE(本地)与RIO_OP_READ/WRITE(远端)批量生成。注意其中包含对齐检查逻辑:
#define RIO_8_BAD 0 #define RIO_16_BAD (offset & 1) #define RIO_32_BAD (offset & 3)即 16 位访问要求 offset 对齐到 2 字节、32 位访问要求对齐到 4 字节,未对齐时返回RIO_BAD_SIZE(0x81,见 include/linux/rio.h 中的错误码定义)。所有访问最终都经由struct rio_mport的ops(struct rio_ops)回调(lcread/lcwrite/cread/cwrite)下发到底层平台实现,驱动开发者无需关心具体硬件细节。
邮箱(Mailbox)管理
邮箱用于承载基于消息的事务,最多支持RIO_MAX_MBOX(4)个邮箱,单个消息最大长度为RIO_MAX_MSG_SIZE(0x1000 字节)。核心接口如下:
| 接口 | 作用 |
|---|---|
rio_request_outb_mbox(mport, dev_id, mbox, entries, moutb) | 申请出向邮箱,绑定发送回调 |
rio_release_outb_mbox(mport, mbox) | 释放出向邮箱 |
rio_add_outb_message(mport, rdev, mbox, buffer, len) | 将消息加入出向邮箱队列发送 |
rio_request_inb_mbox(mport, dev_id, mbox, entries, minb) | 申请入向邮箱,绑定接收回调 |
rio_release_inb_mbox(mport, mbox) | 释放入向邮箱 |
rio_add_inb_buffer(mport, mbox, buffer) | 为入向邮箱添加接收缓冲区 |
rio_get_inb_message(mport, mbox) | 从入向队列取出一条消息 |
从 drivers/rapidio/rio.c 的实现看,rio_request_inb_mbox()首先通过request_resource()在mport->riores[RIO_INB_MBOX_RESOURCE]上声明资源占用,防止邮箱被重复申请,然后挂接回调并调用mport->ops->open_inb_mbox()打开硬件邮箱;任何一步失败都会回滚释放资源。出向邮箱的申请逻辑与之对称(资源挂载在RIO_OUTB_MBOX_RESOURCE)。
资源编号约定(定义于 include/linux/rio.h)值得特别注意:
- 对于RIO 设备:region 0 为出向门铃,region 1-15 为内存区域;
- 对于RIO 主端口(mport):region 0 为入向门铃,region 1 为入向邮箱,region 2 为出向邮箱。
门铃(Doorbell)管理
门铃是一种携带 16 位 info 字段的轻量级消息,适合快速通知场景。接口包括:
| 接口 | 作用 |
|---|---|
rio_request_inb_dbell(mport, dev_id, start, end, dinb) | 申请入向门铃消息区间(info 值范围),绑定回调 |
rio_release_inb_dbell(mport, start, end) | 释放入向门铃区间 |
rio_request_outb_dbell(rdev, start, end) | 从设备侧申请出向门铃区间,返回struct resource * |
rio_release_outb_dbell(rdev, res) | 释放出向门铃资源 |
rio_mport_send_doorbell(mport, destid, data)/rio_send_doorbell(rdev, data) | 发送门铃消息,data为 16 位 info |
rio_send_doorbell()是面向设备的内联封装,直接转发到rio_mport_send_doorbell(rdev->net->hport, rdev->destid, data)(include/linux/rio_drv.h),底层调用mport->ops->dsend()。辅助宏rio_init_dbell_res()可将struct resource初始化为门铃资源(RIO_RESOURCE_DOORBELL),rio_init_mbox_res()则初始化为邮箱资源(RIO_RESOURCE_MAILBOX)。
内存区域与内存映射接口
尽管原文档指出"内存区域的访问/管理暂不支持",子系统仍提供了完整的映射 API 供平台与驱动使用:
int rio_map_inb_region(struct rio_mport *mport, dma_addr_t local, u64 rbase, u32 size, u32 rflags); void rio_unmap_inb_region(struct rio_mport *mport, dma_addr_t lstart); int rio_map_outb_region(struct rio_mport *mport, u16 destid, u64 rbase, u32 size, u32 rflags, dma_addr_t *local); void rio_unmap_outb_region(struct rio_mport *mport, u16 destid, u64 rstart);入向映射将 RapidIO 地址空间窗口映射到本地物理内存(rbase为分配给该窗口的 RIO 基地址),出向映射则为指定destid建立从 RIO 地址到本地地址的转换。实现上所有映射操作在rio_mmap_lock自旋锁保护下调用mport->ops->map_inb/map_outb等回调(drivers/rapidio/rio.c)。
Port-Write 错误管理接口
Port-Write(端口写)是 RapidIO 的错误管理通知机制,设备通过 64 字节(RIO_PW_MSG_SIZE)的 port-write 消息上报错误。相关接口:
| 接口 | 作用 |
|---|---|
rio_request_inb_pwrite(rdev, pwcback) | 为具体设备注册 port-write 回调 |
rio_release_inb_pwrite(rdev) | 注销设备级回调 |
rio_add_mport_pw_handler(mport, dev_id, pwcback) | 注册 mport 级 port-write 处理器 |
rio_del_mport_pw_handler(mport, dev_id, pwcback) | 移除 mport 级处理器 |
rio_inb_pwrite_handler(mport, pw_msg) | 入向 port-write 消息的统一分发入口 |
rio_pw_enable(mport, enable) | 使能/禁止 mport 的 port-write 处理 |
消息内容由联合体union rio_pw_msg描述(include/linux/rio.h):em子结构包含 Component Tag、Port Error Detect、Port ID、LTL Error Detect 等字段,也可通过raw[]按原始 32 位数组访问。rio_inb_pwrite_handler()的处理流程是:先根据comptag通过rio_get_comptag()查找对应设备,调用设备级回调;若无设备或回调未处理完,再遍历 mport 级处理器列表;对于交换机设备,还会进一步调用rswitch->ops->em_handle(),读取/清除RIO_DEV_PORT_N_ERR_STS_CSR等错误寄存器,并执行"设备插入/拔出服务"(恢复或锁定对应端口)。
LDM 设备模型接口
RapidIO 子系统完整集成了 Linux 设备模型(Linux Device Model),为驱动提供标准的注册/注销与引用计数接口:
int rio_register_driver(struct rio_driver *rdrv); void rio_unregister_driver(struct rio_driver *rdrv); struct rio_dev *rio_dev_get(struct rio_dev *rdev); void rio_dev_put(struct rio_dev *rdev);驱动编写者通过RIO_DEVICE(did, vid)宏构造匹配表项(assembly vendor/device 字段自动置为RIO_ANY_ID),并在struct rio_driver中提供probe、remove、shutdown、suspend/resume等回调。此外还有rio_name()(返回设备唯一标识字符串)、rio_get_drvdata()/rio_set_drvdata()(驱动私有数据存取)等辅助函数。
子系统内部机制
核心数据结构
子系统的主要数据结构定义在 include/linux/rio.h:
struct rio_dev:描述一个 RapidIO 设备,包含设备/厂商 ID(did/vid)、组装件 ID、扩展特性指针(efptr)、处理单元特性(pef)、源/目的操作能力(src_ops/dst_ops)、组件标签(comp_tag)、目的 ID(destid)与跳数(hopcount)、DMA 掩码,以及内嵌的struct rio_switch rswitch[](仅交换机使用)。struct rio_mport:描述一个主端口,即访问网络的入口。关键字段包括:host_deviceid(主机设备 ID)、sys_size(传输系统大小,0 表示小系统 256 个设备,1 表示大系统 65536 个设备)、ops(低层架构相关回调struct rio_ops)、riores[](端口拥有的资源)、inb_msg[]/outb_msg[](消息事件描述符)、nscan(枚举/发现操作)。struct rio_net:描述一个 RapidIO 网络,维护设备列表、交换机列表与关联 mport 列表,hport指向访问该网络的默认端口。struct rio_switch/struct rio_switch_ops:交换机信息(路由表副本route_table、端口状态位图port_ok、每端口下一设备指针数组nextdev[])与交换机专属操作(路由增删查、清表、域设置、错误管理em_init/em_handle)。struct rio_ops:底层硬件回调集合,包括lcread/lcwrite(本地配置读写)、cread/cwrite(网络配置读写)、dsend(门铃发送)、pwenable(port-write 开关)、open/close_inb/outb_mbox、add_outb_message、add_inb_buffer、get_inb_message、map_inb/map_outb、unmap_inb/unmap_outb、query_mport。struct rio_driver:RIO 驱动描述,含id_table、probe、remove等标准回调,通过内嵌struct device_driver接入 LDM。struct rio_scan/struct rio_scan_node:注册枚举/发现方法的结构,enumerate与discover两个回调分别执行枚举与发现。
同时 include/linux/rio.h 定义了若干关键常量:RIO_MAX_MPORTS(8)、RIO_MAX_MPORT_RESOURCES/RIO_MAX_DEV_RESOURCES(16)、RIO_GLOBAL_TABLE(0xff,表示访问交换机全局路由表)、RIO_INVALID_ROUTE(0xff,表示路由表项无效)、RIO_MAX_MBOX(4)。还定义了enum rio_link_speed(从 1.25G 到 6.25G Baud)与enum rio_link_width(1x 到 16x)等链路属性枚举,以及struct rio_mport_attr(通过rio_query_mport()查询端口能力,如 DMA 支持标志RIO_MPORT_DMA、RIO_MPORT_DMA_SG、RIO_MPORT_IBSG)。
枚举与发现(Enumeration and Discovery)
枚举与发现算法实现在 drivers/rapidio/rio-scan.c,是整个子系统最重要的机制之一,其行为与 RapidIO 规范 Annex 1 描述的基本机制一致(对应 Kconfig 中的RAPIDIO_ENUM_BASIC选项)。
- 枚举(enumeration):由
rio_enum_mport()驱动。主机首先通过rio_enum_host()在RIO_HOST_DID_LOCK_CSR写入本机host_deviceid以锁定主机身份,随后从端口 0 开始逐层遍历交换机与端点设备(rio_enum_peer()),为每个设备分配 destid、写入组件标签(component tag)、填充交换机路由表,最终通过rio_enum_complete()检查RIO_PORT_GEN_CTL_CSR中的RIO_PORT_GEN_DISCOVERED位确认枚举完成。 - 发现(discovery):由
rio_disc_mport()驱动,适用于非枚举主机。发现节点等待枚举主机完成网络配置,等待时长受CONFIG_RAPIDIO_DISC_TIMEOUT(默认 30 秒)约束;若设置了RIO_SCAN_ENUM_NO_WAIT标志则不等待直接返回-EAGAIN。枚举完成后,发现节点读取网络拓扑与路由信息,构建本地设备视图。
标准路由表操作(rio_std_route_add_entry、rio_std_route_get_entry、rio_std_route_clr_table)通过RIO_STD_RTE_CONF_DESTID_SEL_CSR/RIO_STD_RTE_CONF_PORT_SEL_CSR标准寄存器读写路由表项,并在大系统(sys_size非零)下读取RIO_SWITCH_RT_LIMIT获取最大 destid 范围。此外,rio_mport_get_feature()/rio_mport_get_physefb()用于遍历扩展特性块(Extended Features Block),判断设备是否支持指定能力并获取寄存器映射类型。
驱动功能实现
drivers/rapidio/rio.c除了前文介绍的接口实现外,还提供设备生命周期管理(rio_add_device()、rio_del_device())、网络管理(rio_alloc_net()、rio_add_net()、rio_free_net())、路由表管理(rio_route_add_entry()等)、设备锁定(rio_lock_device()/rio_unlock_device())、设备可达性检查(rio_mport_chk_dev_access(),最多重试RIO_MAX_CHK_RETRY次、每次延时 1ms)以及扩展特性查询等核心功能。
值得注意的模块参数:rio.c定义了hdid数组参数,用于为本地 RapidIO 控制器分配目的 ID:
static int hdid[RIO_MAX_MPORTS]; module_param_array(hdid, int, &ids_num, 0); MODULE_PARM_DESC(hdid, "Destination ID assignment to local RapidIO controllers");加载模块时可通过hdid=参数为每个 mport 指定 host_deviceid,例如modprobe rio hdid=0,1。
设备模型支持与 sysfs
drivers/rapidio/rio-driver.c 完成了设备模型集成:定义了名为rapidio的总线类型rio_bus_type(提供 match/probe/remove/shutdown/uevent 回调)与名为rapidio_port的设备类rio_mport_class,并在postcore_initcall(rio_bus_init)中注册。总线匹配逻辑rio_match_bus()调用rio_match_device()依次比对vid/did/asm_vid/asm_did四项(支持RIO_ANY_ID通配)。rio_uevent()生成形如MODALIAS=rapidio:vXXXXdXXXXavXXXXadXXXX的模块别名,支持 udev 自动加载驱动。
sysfs 属性实现在 drivers/rapidio/rio-sysfs.c,可归纳为三类:
| 类别 | 属性 | 说明 |
|---|---|---|
| 设备属性 | did、vid、device_rev、asm_did、asm_vid、asm_rev | 设备/组装件 ID 与版本 |
| 设备属性 | destid、hopcount、modalias、lprev | 网络地址与拓扑上游 |
| 交换机专属 | routes(路由表)、lnext(各端口下游设备)、hopcount | 仅对RIO_PEF_SWITCH设备可见 |
| 二进制属性 | config | 直接读写设备配置空间(需CAP_SYS_ADMIN时扩展到完整维护空间RIO_MAINT_SPACE_SZ) |
| 总线属性 | scan | 向该文件写入 mport 号触发重新扫描,写入-1(RIO_MPORT_ANY)触发全部 mport 扫描 |
| mport 属性 | port_destid、sys_size | 主端口的主机设备 ID 与系统大小 |
PPC32 支持
文档专门列出了 PPC32 平台支持章节,对应实现位于 arch/powerpc/sysdev/fsl_rio.c。该文件为 Freescale 处理器的 RapidIO 控制器提供了struct rio_ops低层回调(本地/网络配置空间访问、门铃、邮箱、内存映射等),是理解"新架构如何接入 RapidIO 子系统"的典型参考:即实现struct rio_ops后调用rio_mport_initialize()与rio_register_mport()(声明于 include/linux/rio.h)将 mport 注册进子系统。
内核配置选项
RapidIO 子系统的编译配置集中在 drivers/rapidio/Kconfig,主要选项如下:
| 配置项 | 类型 | 说明 |
|---|---|---|
RAPIDIO | tristate | 总开关,依赖HAVE_RAPIDIO或PCI |
RAPIDIO_DISC_TIMEOUT | int | 发现节点等待枚举完成的超时秒数,默认 30 |
RAPIDIO_ENABLE_RX_TX_PORTS | bool | 使能所有端口的输入接收/输出发送方向(规范建议输入/输出端口默认关闭,仅允许维护事务) |
RAPIDIO_DMA_ENGINE | bool | 通过 DMA Engine 框架执行 NREAD/NWRITE(NWRITE_R/SWRITE) 数据传输 |
RAPIDIO_DEBUG | bool | 子系统调试信息输出 |
RAPIDIO_ENUM_BASIC | tristate | 基本枚举/发现机制(对应规范 Annex 1) |
RAPIDIO_CHMAN | tristate | 通道化消息驱动,提供类 socket 接口供多个用户态程序共享单个邮箱 |
RAPIDIO_MPORT_CDEV | tristate | 通用/devmport 字符设备驱动,供用户态执行 RapidIO 专用操作 |
其中RAPIDIO_DEBUG打开后,rio.c、rio-scan.c中的pr_debug()输出(如"RIO: enumerate master port %d, %s"、路由跟踪、错误状态寄存器等)会进入系统日志,是排查枚举失败与链路问题的有力工具。交换机驱动子菜单见 drivers/rapidio/switches/Kconfig,各具体交换机驱动(如 IDT 系列)位于 drivers/rapidio/switches。
编写 RapidIO 驱动的快速指引
综合上述接口,编写一个新 RapidIO 设备驱动的最小骨架如下:
- 使用
RIO_DEVICE(did, vid)构造id_table匹配表; - 定义
struct rio_driver并实现probe()/remove(); - 在
probe()中通过rio_read_config_*()读取设备能力,通过rio_request_inb_dbell()/rio_request_inb_mbox()申请消息资源并绑定回调,通过rio_set_drvdata()保存私有数据; - 在
remove()中对称地释放资源、注销回调; - 调用
rio_register_driver()注册驱动,模块卸载时调用rio_unregister_driver(); - 若需要接收错误通知,通过
rio_request_inb_pwrite()注册 port-write 回调。
而在新架构上提供 RapidIO 支持,则需要:实现struct rio_ops的全部必需回调 → 调用rio_mport_initialize()初始化 mport → 调用rio_register_mport()注册 → 使能RAPIDIO与RAPIDIO_ENUM_BASIC后由内核完成枚举发现。
结语
Linux 内核的 RapidIO 子系统以 mport 抽象统一了硬件差异,以标准化的枚举发现机制实现了即插即用,并通过完整的 LDM 集成让驱动开发与普通总线驱动别无二致。本文以官方文档为骨架、以内核源码为佐证,梳理了从驱动接口到内部机制的完整脉络。开发者如需进一步深入,可直接研读 include/linux/rio.h、include/linux/rio_drv.h 中的结构体与函数注释(这些注释正是文档 kernel-doc 指令的生成源),以及 drivers/rapidio/rio.c 中各接口的完整实现。
致谢
RapidIO 子系统由以下开发者直接或间接贡献:Matt Porter、Randy Vinson、Dan Malek;本文档的撰写者为 Matt Porter。其工作成果在 drivers/rapidio/ 目录的源码版权声明中均有体现(MontaVista Software, Inc. 与 Integrated Device Technology, Inc.)。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考