UAR(User Access Region)是 CPU 直接访问 HCA 的寄存器空间。发送方将 WQE 放进 Host Memory,再通过 Doorbell Record 发布工作,通过 UAR Doorbell 通知设备;BlueFlame 进一步通过 MMIO 将 WQE 内容直接写入设备。
1. 门铃原理
1.1 工作发布
HCA 与 CPU 异步执行。CPU 准备一个 WQE 后,需要把其所有权交给 HCA;HCA 获取描述符并执行工作,随后按完成生成条件发布 CQE。提交函数返回只表示软件完成了本次发布操作,硬件执行结果由完成路径报告。
工作队列使用 Doorbell Record(DBR)中的累计计数器标记已发布范围。SQ 计数器的单位是 64 B WQEBB,RQ 计数器的单位是固定步长 WQE。HCA 可以在看到 DBR 更新后开始处理工作,发送门铃用于保证新工作被处理。已经执行的 WQE 再次收到门铃通知时,HCA 会忽略该重复通知。
下面按存储位置展示发送所需内容。Host SQ 保存完整 WQE;UAR 中的普通门铃携带目标 QP/SQ 编号和被通知 WQE 的起始 WQEBB 索引,BF Buffer 则用于接收通过 MMIO 直接写入的 WQE 内容。

1.2 描述符与通知
WQE 描述操作类型、数据地址、Key、长度及传输参数。普通 Send Doorbell 使用被通知 WQE 的前 8 B,其中包含 opcode、wqe_index、qpn 和 ds,让 HCA 识别目标 QP/SQ,并定位该 WQE 在 SQ 中的起始 WQEBB 索引。队列的完整配置已经保存在 QPC 等硬件上下文中,包括队列缓冲区的物理地址段列表(PAS)、PD 和关联 CQ;门铃只需携带队列编号与 WQE 定位信息,无需重复提交这些配置。
BlueFlame 通过 MMIO 将有效 WQE 内容直接写入设备,使 HCA 可以减少读取 Host Memory 的次数。非 Inline WQE 仍引用业务内存,HCA 仍需按地址和 Key 读取业务数据。BF 优化的首先是描述符传递路径;业务数据是否随 WQE 一同写入设备,取决于 WQE 是否包含 Inline Data。
1.3 MMIO 与 DBR
| 内容 | 位置 | 写入方 | HCA 的使用方式 |
|---|---|---|---|
| WQE | 队列缓冲区 | 软件 | 按队列映射读取并执行 |
| WQ DBR | 可供 DMA 访问的 Host Memory | 软件 | 获取 Producer Counter,识别新发布范围 |
| Send Doorbell | UAR 的 BF Buffer 起始处 | CPU MMIO | 通知目标 QP/SQ 有工作可处理 |
| BF Buffer | UAR 下半部 | CPU MMIO | 接收 CPU 通过 MMIO 直接写入的 WQE |
| CQ DBR | 可供 DMA 访问的 Host Memory | 软件 | 获取 CQ 消费进度和通知请求状态 |
| CQ/EQ Doorbell | UAR 上半部 | CPU MMIO | 执行通知请求或更新 EQ 消费状态 |
DBR 地址通过相应上下文配置,必须在设备使用期间保持 DMA 映射有效。UAR 地址由 PCI BAR 和页面映射确定,CPU 对该地址的写入形成设备访问。二者分别属于内存访问路径和 MMIO 路径,因此需要分别处理 DMA 可见顺序及设备寄存器访问顺序。
WQ/CQ DBR 的存储位置、位域和计数规则见《RDMA-HCA-Mellanox网卡队列 (三)》;本文只保留它们与 UAR Doorbell 配合所必需的发布和顺序关系。
1.4 各队列通知方式
| 队列 | 发布或消费动作 | Host Memory 更新 | UAR 操作 |
|---|---|---|---|
| SQ | 发布发送 WQE | QP DBR.send_counter |
普通 Send Doorbell 或 BF |
| RQ | 发布接收 WQE | QP DBR.rcv_counter |
普通接收发布无需 Doorbell |
| SRQ | 发布共享接收 WQE | SRQ DBR.rq_wqe_counter |
普通接收发布无需 Doorbell |
| CQ | 消费完成记录 | CQ DBR.update_ci |
仅消费 CQE 时无需开启下一轮通知 |
| CQ | 请求完成通知 | CQ DBR.cmd_sn / cmd / arm_ci |
写 CQ Doorbell,选择是否开启下一轮通知 |
| EQ | 消费事件记录 | 无 | 写 EQ Doorbell,选择是否开启下一轮中断通知 |
RQ/SRQ 提供的接收资源由 HCA 接收的数据驱动;SQ 需要主动启动工作。本文中的 Arm 表示"开启下一轮通知",不是启动队列。CQ Doorbell 控制 HCA 向 EQ 产生对应CQ 的 Completion Event,EQ Doorbell 控制新 EQE 是否引发中断,两者分别作用于不同通知层级。
2. UAR 布局
2.1 编号与地址
每个 PCI Function 拥有独立的 UAR BAR 区域。PCI 枚举给出 BAR 窗口的地址、大小和 PCI 属性;软件通过 QUERY_HCA_CAP 查询 UAR 布局,其中 UAR 区域总大小为 1 MiB × 2^uar_sz,uar_4k、log_uar_page_sz 和 num_of_uars_per_page 分别描述 4 KiB UAR 模式、系统映射页大小以及每个系统页包含的 4 KiB UAR 数量。
直接映射 BAR 的驱动或 VFIO 程序可按 BAR0 映射起点 + uar_index × 4096 + 页内寄存器偏移 访问 4 KiB UAR;普通用户态程序则使用内核返回的 mmap 偏移和长度建立映射,实际访问的是"CPU 虚拟映射起点 + UAR 在映射内的偏移 + 页内寄存器偏移",而不是直接使用 PCI 地址。HCA 根据实际被访问的 UAR 页面和门铃中的队列编号校验目标。
2.2 页面分区
下面展示 4 KiB UAR 单元的完整布局。上半部主要保留给控制寄存器,下半部分为四组 BF Register;图中的空白地址区间保留。

| 页内偏移 | 大小 | 功能 |
|---|---|---|
0x000--0x01F |
32 B | 保留 |
0x020--0x027 |
8 B | CQ 通知启用门铃(CQ Arm Doorbell) |
0x028--0x03F |
24 B | 保留 |
0x040--0x043 |
4 B | 更新 EQ Consumer 并开启下一轮中断通知 |
0x044--0x047 |
4 B | 保留 |
0x048--0x04B |
4 B | 仅更新 EQ Consumer,不开启下一轮中断通知 |
0x04C--0x7FF |
1972 B | 保留 |
0x800--0x8FF/0x900--0x9FF |
各 256 B | BF Register 0:even/odd |
0xA00--0xAFF/0xB00--0xBFF |
各 256 B | BF Register 1:even/odd |
0xC00--0xCFF/0xD00--0xDFF |
各 256 B | BF Register 2:even/odd,fast path |
0xE00--0xEFF/0xF00--0xFFF |
各 256 B | BF Register 3:even/odd,fast path |
每组 BF Register 包含两个 Buffer;表中一组总长 512 B,单个 Buffer 长 256 B。普通 Send Doorbell 复用被分配 BF Buffer 的起始 8 B。软件只写定义的有效字段,保留位按接口要求置零。
2.3 NC 与 WC 映射
NC 表示 Non-Cached 设备映射,WC 表示 Write Combining 映射。BF 表示 CPU 通过 MMIO 将 WQE 内容直接写入设备的提交方式。映射属性和提交方式是两个维度,名称中的"BF UAR"通常指用于 BF 的 WC 映射。
| 项目 | NC 映射上的普通门铃 | WC 映射上的 BF |
|---|---|---|
| CPU 写入内容 | WQE 前 8 B | WQE 内容,补齐到 Cache Line 边界 |
| 主要作用 | 通知设备读取已发布工作 | 通过 MMIO 将 WQE 直接写入设备 |
| Host SQ | 保存完整 WQE | 同样保存完整 WQE |
| CPU 缓冲行为 | 使用设备访问语义 | 允许合并写入并形成较大事务 |
| 关键顺序要求 | 内存发布先于门铃 | 内存发布、WC 写出、Buffer 切换有序 |
PRM 推荐同一 UAR 页建立 WC 和非 WC 两种映射,分别用于 BF 和普通门铃。普通 8 B Doorbell 也可以经 WC 映射写入,软件应按 WC 路径执行顺序控制和 Flush。能否建立相应映射、采用何种屏障,由 CPU 架构和操作系统的设备内存接口决定。
2.4 队列关联
软件获得 UAR 编号后,在创建 QP 或 CQ 时将该编号写入 QPC 或 CQC 的 uar_page 字段,从而建立队列与 UAR 页面的关联。关联信息保存在 QPC 或 CQC 中,UAR 本身不保存 QP/CQ 清单,因此多个队列可以共享同一 UAR。
写门铃时,MMIO 地址确定实际访问的 UAR 页面,Send Doorbell 中的 QP/SQ 编号或 CQ Doorbell 中的 CQN 确定目标队列。HCA 再将实际 UAR 与目标队列上下文中的 uar_page 进行校验。因此,映射地址只选定 UAR,门铃中的队列编号才选定具体 QP/SQ 或 CQ。
uar_page 只指定 UAR 页面,不指定页面内的 BF Register。BF Register 由软件分配和共享;多个 QP 共享同一 BF Register 时需要串行化访问,同一 QP/SQ 的 Send Doorbell 不能交错分散到多个 BF Register。
2.5 分配与共享
ALLOC_UAR 返回可用于上下文关联和 BAR 映射的 UAR 编号。在以 4 KiB UAR 为单元、按较大页面分配的模式中,返回值表示所分配 UAR 组的第一个编号。用户态只能访问内核授予的映射范围;HCA 在队列操作时继续执行 UAR 关联校验。
UAR 释放受队列依赖约束。软件需要先停止使用映射的提交者,完成相关 MMIO 写出,并销毁或解除仍依赖该 UAR 的 QP 或 CQ,再执行 DEALLOC_UAR 和解除映射。进程不再发起调用只说明软件入口停止,实际在途访问和设备侧的队列依赖仍需完成收尾。
3. 门铃格式
3.1 Send Doorbell
Send Doorbell 使用 Control Segment 的前两个 DWORD。

| 偏移 | 位域 | 字段 | 取值/编码 | 含义 |
|---|---|---|---|---|
+0x00 |
[31:24] |
opmod |
依 opcode 对应的 WQE 格式解释 |
操作修饰值,不存在跨 opcode 的统一编码 |
+0x00 |
[23:8] |
wqe_index |
0x0000--0xFFFF,按 16 bit 回绕 |
被通知 WQE 在 SQ 中的起始 WQEBB 索引 |
+0x00 |
[7:0] |
opcode |
见下表 | WQE 操作类型 |
+0x04 |
[31:8] |
qpn |
24 bit QP/SQ 编号 | 目标 QP/SQ |
+0x04 |
[7:6] |
Reserved | 0 |
保留位,软件必须写 0 |
+0x04 |
[5:0] |
ds |
WQE 有效字节数 ÷ 16 |
WQE 有效长度,以 16 B 为单位 |
opcode 复用 WQE Control Segment 中的操作码。本版 PRM 公开定义的编码如下;其他值只能在相应能力或接口明确定义时使用。
| 编码 | 操作 | 编码 | 操作 |
|---|---|---|---|
0x00 |
NOP |
0x01 |
SND_INV(Send with Invalidate) |
0x08 |
RDMA_WRITE |
0x09 |
RDMA_WRITE_WITH_IMMEDIATE |
0x0A |
SEND |
0x0B |
SEND_WITH_IMMEDIATE |
0x0E |
LSO |
0x0F |
WAIT |
0x10 |
RDMA_READ |
0x11 |
ATOMIC_COMPARE_AND_SWAP |
0x12 |
ATOMIC_FETCH_AND_ADD |
0x13 |
READ_AND_INVALIDATE(该版不支持) |
0x14 |
ATOMIC_MASKED_COMPARE_AND_SWAP |
0x15 |
ATOMIC_MASKED_FETCH_AND_ADD |
0x16 |
RECEIVE_EN |
0x17 |
SEND_EN |
0x20 |
SET_PSV |
0x21 |
GET_PSV |
0x22 |
CHECK_PSV |
0x23 |
DUMP |
0x25 |
UMR |
0x26 |
RGET_PSV |
0x27 |
RCHECK_PSV |
0x28 |
TAG_MATCHING |
0x29 |
ENHANCED_MPSW |
0x2C |
FLOW_TABLE_ACCESS |
0x2D |
ACCESS_ASO |
0x2F |
MMO |
0x30 |
LOAD_REMOTE_MICRO_APP |
0x31 |
STORE_REMOTE_MICRO_APP |
wqe_index 标识被通知 WQE 在 SQ 中的起始 WQEBB 索引,DBR 则标识所有已发布工作的末尾之后。批量发布时,门铃中的 wqe_index 指向最后一个 WQE 在 SQ 中的起始 WQEBB 索引,DBR 的计数器覆盖整批 WQE。HCA 根据队列已有状态连续处理尚未执行的工作。
3.2 CQ 通知启用门铃(CQ Arm Doorbell)
CQ 通知启用请求(Arm)表示"开启下一轮完成通知"。它不会启动或停止 CQ;即使未开启通知,HCA 仍可以向 CQ 写入 CQE,软件也仍可以主动轮询。开启通知后,满足条件的 CQE 会使 HCA 向 CQC 关联的 EQ 写入 Completion Event;若 EQ 也已开启下一轮中断通知,该事件可进一步触发中断。

| UAR 偏移 | 位域 | 字段 | 取值/编码 | 含义 |
|---|---|---|---|---|
0x20 |
[29:28] |
cmdsn |
0x0--0x3;首次请求为 0,每处理一个新 Completion Event 后加 1,按 2 bit 回绕 |
通知轮次序号(Arm 命令序号) |
0x20 |
[24] |
cmd |
0:ARM_NEXT;1:ARM_SOLICITED |
ARM_NEXT 请求下一个 Solicited 或 Unsolicited 完成通知;ARM_SOLICITED 仅请求 Solicited 完成通知 |
0x20 |
[23:0] |
cq_ci |
Consumer Counter 的低 24 bit,按 24 bit 回绕 | 请求通知时的消费位置 |
0x24 |
[23:0] |
cqn |
24 bit CQ 编号 | 要开启通知的 CQ |
序号按完成事件推进,首次实际开启通知时使用序号 0,后续在处理新的完成事件后进入下一通知轮次,按 2 bit 回绕。它标识通知轮次,CQE 数量和通知启用请求次数分别由各自状态维护。cq_ci 提供请求时的消费位置,使硬件能够判断已经到达而尚未消费的完成记录。
3.3 EQ Doorbell
EQ 有两个独立地址,软件通过选择地址,决定是否在更新 CI 的同时开启下一轮中断通知。

| UAR 偏移 | 位域 | 字段 | 取值/编码 | 含义 |
|---|---|---|---|---|
0x40 |
[31:24] |
eqn_with_arm |
8 bit EQ 编号 | 目标 EQ |
0x40 |
[23:0] |
eqn_consumer_index_with_arm |
Consumer Counter 的低 24 bit,按 24 bit 回绕 | 更新 Consumer 并开启下一轮中断通知 |
0x48 |
[31:24] |
eqn |
8 bit EQ 编号 | 目标 EQ |
0x48 |
[23:0] |
eqn_consumer_index |
Consumer Counter 的低 24 bit,按 24 bit 回绕 | 仅更新 Consumer |
消费 EQE 的过程中,软件可写入 0x48 更新 Consumer Index,告诉 HCA 哪些 EQE 已处理完、对应的环形队列槽位可以重新使用,但不开启中断通知。处理完当前事件后,软件再写入 0x40 更新最终 Consumer Index,并重新开启下一轮中断通知。首次开启通知时 Consumer 为 0,此后累计消费多少条 EQE,Consumer 就推进多少,而不是按中断次数增加。
3.4 访问宽度与字节序
普通 Send Doorbell 和 CQ Doorbell 是 64 bit 格式,优先使用一次 64 bit 总线写入;EQ Doorbell 使用一次 32 bit 写入。位域表描述设备 DWORD 中的位号,Host Memory 描述符、DBR 和门铃内容按接口要求编码为 Big Endian;CPU 是 Little Endian 时需要在写入前完成转换。
平台若不能保证一次 64 bit 总线写入,Send/CQ Doorbell 可拆为两次连续 32 bit 写入,顺序是低地址 DWORD 在前、高地址 DWORD 在后。在这两个写入之间,同一 UAR 页面不能插入其他门铃,保护范围应覆盖整个双 DWORD 操作。该规则依据地址顺序定义,与 CPU 整数高低半部的本地存储习惯分别处理。
BF 内容可以按 4 B 对齐的 DWORD 或多个 DWORD 写入。软件通常按地址递增写入,以便形成连续 WC 事务,并避免两个 WQE 的分片交错。寄存器访问宽度保证一个操作的结构完整,内存屏障保证不同操作之间的可见顺序。
4. BlueFlame
4.1 WQE 直写
普通发送需要 HCA 从 Host SQ 获取 WQE。BF 将同一 WQE 写入 PCI BAR,设备可直接解析收到的描述符,从而减少读取 Host Memory 的次数。Host SQ 仍保存完整 WQE,并且 DBR 仍按常规方式发布。
下面比较两条路径。BF 分支由 CPU 通过 MMIO 将 WQE 直接写入设备,普通分支由设备从 Host SQ 读取 WQE。

4.2 寄存器布局
BF 能力通过 bf、log_bf_reg_size、log_max_bf_regs_per_page 和 log_max_bf_pages 等字段查询。一个 BF Register 的总大小为 2^log_bf_reg_size B,包含两个等大的 Buffer,单个 Buffer 大小为总大小的一半。
在前述 4 KiB 布局中,Register 0 的两个 Buffer 起点分别是 0x800、0x900。它们用于同一个 BF Register 的交替提交。Register 1、2、3 是其他独立提交入口,其中 Register 2、3 属于 fast path。
4.3 双缓冲交替
同一 BF Register 的 even、odd Buffer 必须交替使用。交替状态属于 BF Register,多个 QP 共享该 Register 时也需要共享同一个切换状态。软件不能为各 QP 分别维护一个互不协调的 even/odd 选择值。
下面展示同一提交者通过 MMIO 连续写入两个 WQE。每次先完成 Host SQ 发布,再写当前 Buffer,完成 WC Flush 后切换 Buffer。图中设备可以与 CPU 的后续准备异步重叠,软件通过完成机制判断 WQE 和业务缓冲区何时可以复用。

Buffer 交替用于组织 MMIO 写入。切换动作以及 WC Flush 均不代表网络操作完成,Host SQ 槽位和业务内存的回收仍受 CQE 与队列完成语义约束。
4.4 大小与对齐
BF 提交的 WQE 必须能装入一个 Buffer,实际 MMIO 写入长度还要满足 Cache Line 对齐要求,且写入不能越过所选 Buffer。WQE 本身按对应 SQ 的 WQEBB 规则对齐;MMIO 写入按 DWORD 对齐并采用整 DWORD 长度。
ds 给出 WQE 的有效长度,单位为 16 B。DS=1 的 WQE 不支持直接使用 BF;这类 NOP 或零长度 SEND 可采用普通门铃,也可按 WQE 格式增加零长度 Inline Data Segment,使 DS 增大到 2 后再使用 BF。增加 Segment 属于 WQE 格式变化,尾部 Cache Line 补齐属于传输填充,两者需要分别处理。
4.5 写入与填充
PRM 推荐通过 WC 产生一个或多个 Cache Line 的突发写。WQE 有效长度没有到达 Cache Line 边界时,写往 BF 的副本补齐到最近的 Cache Line 边界;HCA 通过 ds 截取有效描述符,忽略传输填充区。
Host SQ 中按 WQE 自身规则存储,额外的 BF Cache Line 填充不改变 WQE 长度,也不额外增加 Producer Counter。一个提交者应通过 MMIO 完整写入当前 WQE,完成 WC Flush,再允许其他提交者使用同一个 BF Register。
4.6 使用条件
BF 更适合设备轻载下的低时延发送;PRM 对高带宽场景推荐普通提交。BF 将部分 PCIe 读取替换为 CPU 发起的写入,其收益受 WQE 长度、CPU 写入成本、PCIe 流量和设备负载影响,不能仅依据"支持 BF"推导所有场景都更快。
5. UAR 访问与门铃操作
5.1 Send Doorbell 与 BF 写入
软件完成 WQE 和 DBR 的发布后,通过 UAR 映射地址定位所分配 BF Register 的当前 Buffer。普通 Send Doorbell 向该 Buffer 起始处写入被通知 WQE 的前 8 B;BlueFlame 则从同一位置写入有效 WQE 内容及必要的对齐填充。
写入前应建立所需的 MMIO 顺序;经 WC 映射写入后,应完成 WC Flush,再切换 even/odd Buffer 或释放共享锁。普通门铃的访问宽度与字节序见 3.4 节,BF 写入大小和对齐要求见第 4 节。
5.2 CQ 与 EQ Doorbell
CQ Doorbell 和 EQ Doorbell 分别控制完成通知与中断通知,操作入口如下。
| UAR 页内偏移 | 写入内容 | 操作 |
|---|---|---|
0x20--0x27 |
cmdsn、cmd、cq_ci、cqn |
为指定 CQ 开启下一轮完成通知;cmd 选择 ARM_NEXT 或 ARM_SOLICITED |
0x40--0x43 |
EQN、Consumer Index | 更新指定 EQ 的消费位置,并重新开启下一轮中断通知 |
0x48--0x4B |
EQN、Consumer Index | 仅更新指定 EQ 的消费位置 |
写 CQ Doorbell 前,软件应先更新 CQ DBR,并保证其对设备可见;门铃携带同一轮次、同一通知类型和同一消费位置。CQ 通知为一次性请求,处理新的 Completion Event 后,下一轮请求使用更新后的 cmdsn。
写 EQ Doorbell 时,通过选择 0x40 或 0x48 决定是否重新开启中断通知;写入 0x40 本身不意味着立即产生中断,仍需满足 EQ 的通知条件。
5.3 批量通知
同一 SQ 的多个 WQE 完成发布后,可以只写一次普通 Send Doorbell。门铃内容取自最后一个 WQE 的前 8 B,因此 opcode、opmod、wqe_index 和 ds 均按该 WQE 填写。BF 写入仍受单个 Buffer 容量和 WQE 格式限制。
EQ 可以在批量处理事件期间写 0x48 更新消费位置,处理结束后再写 0x40 提交最终位置并重新开启中断通知。
5.4 共享访问约束
多个 QP 共享同一 BF Register 时,必须串行化完成一次门铃或 BF 写入,并统一维护 even/odd Buffer 的切换状态;同一 QP/SQ 的 Send Doorbell 不能交错分散到多个 BF Register。
不同 UAR 页面、同页不同 BF Register 通常可以独立访问。但当一个 64 bit 门铃拆成两次 32 bit 写入时,两次写入之间不得插入同一 UAR 页面上的其他门铃,串行化范围应覆盖整个双 DWORD 操作。
6. 总结
UAR 是 CPU 通过 MMIO 访问 HCA 的寄存器空间。在本文的 4 KiB 布局中,上半部包含 CQ Doorbell 和 EQ Doorbell,下半部包含四组 BF Register。普通 Send Doorbell 使用 BF Buffer 的起始 8 B;BlueFlame 从同一 Buffer 起始处直接写入 WQE 内容。CQ Doorbell 用于开启下一轮完成通知,EQ Doorbell 用于更新事件消费位置,并通过不同地址选择是否重新开启中断通知。
软件通过 UAR 映射地址和页内偏移选择寄存器,通过门铃中的队列编号指定目标。QP/CQ 与 UAR 的关联由 QPC/CQC 的 uar_page 字段保存,多个队列可以共享同一 UAR。
访问 UAR 时,应遵守寄存器的写入宽度、字节序和 MMIO 顺序要求。NC/WC 是 CPU 映射属性;经 WC 映射写入后需要执行 WC Flush。每组 BF Register 的两个 Buffer 交替使用,共享该 Register 的提交者必须串行化访问,并共同维护 Buffer 切换状态。
7. 参考资料
- Mellanox Adapters Programmer's Reference Manual,Rev 0.53:UAR、BlueFlame、工作队列发布、Doorbell Record、CQ/EQ 通知及相关能力字段。