Linux 内核 HiSilicon PCIe PMU 驱动全解:用 perf 精确监控 Hip09 的 PCIe 带宽、时延与总线占用
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
HiSilicon(华为海思)Hip09 芯片为每个 PCIe Core 内置了一个 PCIe Performance Monitoring Unit(PMU),可通过 Linux perf 事件子系统采集 Root Port 及其下游所有 Endpoint 的带宽(bandwidth)、时延(latency)、总线利用率(bus utilization)和缓冲区占用(buffer occupancy)数据。本文基于内核文档 hisi-pcie-pmu.rst 和驱动实现 hisi_pcie_pmu.c 展开,完整覆盖 PMU 的 sysfs 接口、16 个内置 perf 事件、4 类过滤选项(port/bdf、trig、thr、len_mode)以及事件分组的底层原理,读完后可在基于 Hip09 的 ARM64 平台上直接上手做 PCIe 性能调优。
1. PMU 硬件模型与驱动注册
1.1 每个 PCIe Core 一个 PMU
硬件上,每个 PCIe Core 配有 1 个 PMU,负责监控该 Core 的多个 Root Port 以及这些 Root Port 下游的所有 Endpoint 设备。PMU 本身以一个 PCI RCiEP(Root Complex Integrated Endpoint)设备挂在系统总线上,驱动通过 PCI 设备表精确匹配:
static const struct pci_device_id hisi_pcie_pmu_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_HUAWEI, 0xa12d) }, { 0, } };见 hisi_pcie_pmu.c,只匹配 vendor 为 HUAWEI、device ID 为0xa12d的设备,因此该驱动实际只在 Hip09 一类的 HiSilicon 平台上生效。probe 流程中驱动使能设备后,映射 BAR2(pcim_iomap_regions(pdev, BIT(2), DRV_NAME),见 hisi_pcie_pmu.c)作为 PMU 寄存器基址,随后分配 PMU 结构、注册 MSI 中断、注册 CPU 热插拔回调,最后调用perf_pmu_register()把 PMU 挂入 perf 框架(见 hisi_pcie_init_pmu)。
内核配置项为CONFIG_HISI_PCIE_PMU(tristate "HiSilicon PCIE PERF PMU",依赖PCI && ARM64),模块编译规则在 Makefile,帮助文本见 Kconfig。
1.2 sysfs 暴露的 PMU 名称与属性
PMU 的注册名由两个寄存器字段拼成:<sicl-id>(PCIe 交换单元编号)与<core-id>(PCIe Core 编号),两者从HISI_PCIE_REG_INFO(偏移0xfe8)的高低 16 位解析而来,最终生成hisi_pcie<sicl>_core<core>形式,例如hisi_pcie0_core0:
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_pcie%u_core%u", sicl_id, core_id);见 hisi_pcie_alloc_pmu。文档中给出的示例名hisi_pcie0_core0即对应 sicl-id=0、core-id=0 的实例。
perf 框架在/sys/bus/event_source/devices/hisi_pcie<sicl>_core<core>/下自动生成该 PMU 的属性组,驱动通过attr_groups声明了 5 组(见 hisi_pcie_pmu_attr_groups):
| 属性 | 作用 | 驱动实现 |
|---|---|---|
format/ | 描述perf_event_attr中config(事件)、config1(过滤项)、config2(port/bdf)各字段位宽 | format 属性表 |
events/ | 描述全部已文档化事件,perf list的枚举来源 | events 属性表 |
identifier | 读取HISI_PCIE_REG_VERSION(0xfe4),标识 PMU 硬件版本 | identifier_show |
bus/bdf_min/bdf_max | 该 PMU 监控的 Root Port 的总线号及 BDF 范围[bdf_min, bdf_max],取自HISI_PCIE_REG_BDF(0xfe0) | bus/bdf_min/bdf_max |
cpumask | PMU 当前绑定的"计数 CPU" | cpumask_show |
其中format组完整定义了 perf 事件描述与硬件寄存器之间的映射关系:
event config:0-16 thr_len config1:0-3 thr_mode config1:4 trig_len config1:5-8 trig_mode config1:9 len_mode config1:10-11 port config2:0-15 bdf config2:16-31即perf stat -e hisi_pcie0_core0/rx_mwr_latency,port=0x1/这类命令行中,事件名经events/目录解析进config低 16 位,port=写进config2低 16 位,trig_len/thr_len等写进config1对应位段。
2. 寄存器组织与计数器原理
PMU 的寄存器以 8 字节步距(HISI_PCIE_REG_STEP)排布 8 个独立计数通道,每个通道拥有一组同名寄存器:
| 寄存器 | 偏移 | 宽度 | 用途 |
|---|---|---|---|
HISI_PCIE_GLOBAL_CTRL | 0x00 | 32-bit | 全局使能(0x01开、0关) |
HISI_PCIE_EVENT_CTRL | 0x010 | 64-bit | 事件码 + 全部过滤条件 + 事件使能/清 0 |
HISI_PCIE_CNT | 0x090 | 64-bit | 硬件计数器(latency/flux 侧) |
HISI_PCIE_EXT_CNT | 0x110 | 64-bit | 扩展计数器(cnt/time 侧) |
HISI_PCIE_INT_STAT | 0x150 | 32-bit | 溢出中断状态 |
HISI_PCIE_INT_MASK | 0x154 | 32-bit | 溢出中断屏蔽 |
HISI_PCIE_REG_BDF | 0xfe0 | 32-bit | 监控 Root Port 的 bdf_min/bdf_max |
HISI_PCIE_REG_VERSION | 0xfe4 | 32-bit | 硬件版本(identifier) |
HISI_PCIE_REG_INFO | 0xfe8 | 32-bit | sicl-id / core-id |
见 寄存器定义。
2.1 双计数器配对:为什么"相关事件"必须同组
带宽、时延、总线利用率、缓冲区占用这类指标都是"两个量相除"的结果。例如 RX 内存写时延:计数器累加总延时周期数(delay cycles),扩展计数器累加 RX 写 TLP 报文数,两者相除才得到平均时延。驱动用事件索引的bit 16区分两类计数器(见 EXT_COUNTER_IS_USED 的注释):
- bit16=0:走
HISI_PCIE_CNT(偏移 0x090) - bit16=1:走
HISI_PCIE_EXT_CNT(偏移 0x110) - 低 16 位是"真实"事件索引,直接写入
HISI_PCIE_EVENT_CTRL的HISI_PCIE_EVENT_M(bit 0-15)
因此rx_mwr_cnt的事件 ID 是0x10010而非0x00010——bit16 置位只是标记它占用扩展计数器,真实事件码仍是0x0010。
正因为这两个计数器必须同时开始、同时结束统计才能算出正确比值,文档明确要求:相关事件应放进同一个 perf 事件组。判断两个事件是否"相关"有两种方式:
- 按事件名:时延事件形如
xxx_latency/xxx_cnt,带宽事件形如xxx_flux/xxx_time; - 按事件类型编码:形如
event=0xXXXX与event=0x1XXXX(即 bit16 相差的那一对)。
2.2 16 个内置事件全表
驱动以PMU_EVENT_ATTR_ID在 sysfsevents/目录发布以下 16 个事件(见 事件属性表),perf list的枚举即来自这里:
| 事件名 | 事件 ID | 方向/类型 | 配对对象 |
|---|---|---|---|
rx_mwr_latency | 0x0010 | RX 内存写时延(延时周期) | rx_mwr_cnt |
rx_mwr_cnt | 0x10010 | RX 内存写报文数 | rx_mwr_latency |
rx_mrd_latency | 0x0210 | RX 内存读时延 | rx_mrd_cnt |
rx_mrd_cnt | 0x10210 | RX 内存读报文数 | rx_mrd_latency |
tx_mrd_latency | 0x0011 | TX 内存读时延 | tx_mrd_cnt |
tx_mrd_cnt | 0x10011 | TX 内存读报文数 | tx_mrd_latency |
rx_mwr_flux | 0x0104 | RX 内存写流量(字节) | rx_mwr_time |
rx_mwr_time | 0x10104 | RX 内存写有效传输时间 | rx_mwr_flux |
rx_mrd_flux | 0x0804 | RX 内存读流量 | rx_mrd_time |
rx_mrd_time | 0x10804 | RX 内存读有效传输时间 | rx_mrd_flux |
rx_cpl_flux | 0x2004 | RX Completion 流量 | rx_cpl_time |
rx_cpl_time | 0x12004 | RX Completion 有效传输时间 | rx_cpl_flux |
tx_mwr_flux | 0x0105 | TX 内存写流量 | tx_mwr_time |
tx_mwr_time | 0x10105 | TX 内存写有效传输时间 | tx_mwr_flux |
tx_mrd_flux | 0x0405 | TX 内存读流量 | tx_mrd_time |
tx_mrd_time | 0x10405 | TX 内存读有效传输时间 | tx_mrd_flux |
tx_cpl_flux | 0x1005 | TX Completion 流量 | tx_cpl_time |
tx_cpl_time | 0x11005 | TX Completion 有效传输时间 | tx_cpl_flux |
从 ID 的 bit 16 可以看出每一对里latency/flux侧落在普通计数器、cnt/time侧落在扩展计数器,与 2.1 节的编码规则一致。带宽 =flux/time,平均时延 =latency/cnt。
2.3 计数、溢出与"计数 CPU"
每个 64-bit 计数器初始值设为BIT_ULL(63)(HISI_PCIE_INIT_VAL),即只保留 63 位计数空间;计满翻转时硬件置位HISI_PCIE_INT_STAT触发 MSI 中断。中断处理函数 hisi_pcie_pmu_irq 遍历 8 个通道,清状态、累加event->count并重新装载初值(hisi_pcie_pmu_set_period)。由于event->count用补差方式累加(delta = (new_cnt - prev_cnt) & GENMASK_64(63,0),见 hisi_pcie_pmu_event_update),63-bit 翻转不会导致计数错误。
PCIe PMU 不是 per-CPU 的,但 perf 框架要求事件挂在某个 CPU 上,因此驱动注册了 CPU 热插拔回调:CPU 上线时用cpumask_local_spread()在本 NUMA 节点选一个"计数 CPU",并把 MSI 中断亲和性钉死在该 CPU(hisi_pcie_pmu_online_cpu);若该 CPU 下线则迁移上下文到同节点其它在线 CPU(hisi_pcie_pmu_offline_cpu)。sysfs 中的cpumask文件可以随时查看当前绑定情况。
3. 快速上手:perf list 与 perf stat
3.1 列出可用事件
$# perf list hisi_pcie0_core0/rx_mwr_latency/ [kernel PMU event] hisi_pcie0_core0/rx_mwr_cnt/ [kernel PMU event]3.2 基本统计(不推荐单独使用)
$# perf stat -e hisi_pcie0_core0/rx_mwr_latency,port=0xffff/ $# perf stat -e hisi_pcie0_core0/rx_mwr_cnt,port=0xffff/注意:这两条分别运行会得到两个独立的时间窗口,latency / cnt的比值没有意义。相关事件必须放进同一事件组同时统计:
$# perf stat -e "{hisi_pcie0_core0/rx_mwr_latency,port=0xffff/,hisi_pcie0_core0/rx_mwr_cnt,port=0xffff/}"花括号{...}是 perf 的事件组语法,组内事件被强制同时启动/停止。驱动在软件层也做了保障:hisi_pcie_pmu_get_event_idx()(见 源码)会让"配置完全相同的相关事件"复用同一个硬件通道索引,而 hisi_pcie_pmu_validate_event_group 则校验组内不同配置的事件数不超过 8 个计数器(HISI_PCIE_MAX_COUNTERS),超过即拒绝调度。
3.3 能力限制
当前驱动不支持 sampling(perf record不可用),也不支持 attach 到任务(-p/-t用法无效)。对应到 hisi_pcie_pmu_event_init 中的硬拒绝:
/* Sampling is not supported. */ if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) return -EOPNOTSUPP;此外 PMU 注册时带有PERF_PMU_CAP_NO_EXCLUDE能力位,task_ctx_nr = perf_invalid_context,即事件只能以系统级(global)方式运行,这与"监控 Root Port 全部下游流量"的语义相符。
4. 过滤选项详解
过滤条件全部编码在perf_event_attr的config1/config2中,由 HISI_PCIE_PMU_FILTER_ATTR 宏 展开的一组位域提取函数读取。PMU 只统计"目标 Root Port 下游流量",因此过滤分为两大类:目标过滤(port/bdf)与统计条件过滤(trig/thr/len_mode)。
4.1 目标过滤:port 与 bdf
port:16 位位图,选中的 bit 对应一个 Root Port(bit 位置按该口在 Core 内的通道划分)。AP 层事件可以多选端口,TL/DL 层事件只能单选。例如目标 Root Port 是0000:00:00.0(x8 口,bit0)时port=0x1;0000:00:04.0(x4 口,bit8)时port=0x100;两口都监控则port=0x101:
$# perf stat -e hisi_pcie0_core0/rx_mwr_latency,port=0x1/ sleep 5bdf:仅可用于带宽(flux)类事件,按 BDF 选中目标 Endpoint,计数器只统计该 EP 请求产生的流量。例如bdf=0x3900表示目标 EP 的 BDF 是0000:39:00.0:
$# perf stat -e hisi_pcie0_core0/rx_mrd_flux,bdf=0x3900/ sleep 5二者的互斥规则(源码见 hisi_pcie_pmu_get_event_ctrl_val):
- 二者必须至少设置一个;同时设置时只有
port生效(port非零时走HISI_PCIE_TARGET_M直接填位图,port为零时才置HISI_PCIE_TARGET_EN并把bdf写入目标字段)。 port未设置或显式置 0(默认)时,bdf过滤器生效——因为bdf=0本身就是合法值(0000:00:00.0)。- 若
port未设置且bdf指定的设备不在本 PMU 监控范围(其上游 Root Port 不在[bdf_min, bdf_max]内),hisi_pcie_pmu_valid_filter()会直接拒绝事件(返回-EINVAL),校验逻辑见 hisi_pcie_pmu_valid_requester_id:通过pci_get_domain_bus_and_slot()找到目标设备,再用pcie_find_root_port()上溯到 Root Port,最后判断其 BDF 是否落在 PMU 的监控区间。
4.2 触发过滤(Trigger filter)
仅用于带宽事件。首次出现 TLP 长度"大于/小于"触发条件时,统计才开始。trig_len指定条件值(含义为 2^N DW),trig_mode指定比较方向:
trig_mode=0:TLP 长度>条件时开始统计;trig_mode=1:TLP 长度<条件时开始统计。
例如trig_len=4即条件为 2^4 DW(64 字节),trig_mode=0时统计"大 TLP 触发后的带宽":
$# perf stat -e hisi_pcie0_core0/rx_mrd_flux,port=0xffff,trig_len=0x4,trig_mode=1/ sleep 5驱动侧的约束:trig_len超过 10 会被hisi_pcie_pmu_valid_filter()拒绝(HISI_PCIE_TRIG_MAX_VAL = 10);trig_len非零时才写入HISI_PCIE_TRIG_M(bit 56-59)、HISI_PCIE_TRIG_MODE_M(bit 53)并置HISI_PCIE_TRIG_EN(bit 52)。
4.3 阈值过滤(Threshold filter)
同样仅用于带宽事件。计数器只在 TLP 长度落入指定范围时累计:
thr_mode=0:TLP 长度≥阈值时计数;thr_mode=1:TLP 长度<阈值时计数。
thr_len=4即阈值为 2^4 DW:
$# perf stat -e hisi_pcie0_core0/rx_mrd_flux,port=0xffff,thr_len=0x4,thr_mode=1/ sleep 5对应硬件位段HISI_PCIE_THR_M(bit 28-31)、HISI_PCIE_THR_MODE_M(bit 27)与HISI_PCIE_THR_EN(bit 26),上限同样是 10(HISI_PCIE_THR_MAX_VAL)。
4.4 TLP 长度口径过滤(len_mode)
统计带宽时,"流量"可以取 TLP 的不同部分。len_mode为 2 位字段,取值语义:
| 取值 | 含义 |
|---|---|
0b00 | 保留(行为未定义,勿用) |
0b01(1) | 只统计 TLPpayload带宽 |
0b10(2) | 只统计 TLPheader带宽 |
0b11(3) | 同时统计 header 与 payload |
未显式指定时默认2'b11(头+负载,即真实线上流量口径)。驱动中的默认值常量HISI_PCIE_LEN_M_DEFAULT = 3ULL印证了这一点(见 hisi_pcie_pmu.c)。示例:len_mode=2只数 TLP 头带宽,len_mode=1只数负载带宽:
$# perf stat -e hisi_pcie0_core0/rx_mrd_flux,port=0xffff,len_mode=0x1/ sleep 54.5 过滤参数汇总表
| 参数 | perf 属性字段 | 位宽 | 适用事件 | 取值说明 |
|---|---|---|---|---|
port | config2:0-15 | 16-bit 位图 | 全部 | bit 对应 Root Port;AP 层事件可多选,TL/DL 层事件单选 |
bdf | config2:16-31 | 16-bit BDF | 仅带宽事件 | 目标 Endpoint 的 BDF,如0x3900= 0000:39:00.0 |
trig_len | config1:5-8 | 4-bit,≤10 | 仅带宽事件 | 触发条件,2^N DW |
trig_mode | config1:9 | 1-bit | 仅带宽事件 | 0: > 条件开始统计;1: < 条件 |
thr_len | config1:0-3 | 4-bit,≤10 | 仅带宽事件 | 阈值,2^N DW |
thr_mode | config1:4 | 1-bit | 仅带宽事件 | 0: ≥ 阈值计数;1: < 阈值 |
len_mode | config1:10-11 | 2-bit | 带宽事件 | 1=payload,2=header,3=两者(默认) |
event | config:0-16 | 17-bit | 全部 | 事件码,bit16 决定 CNT 还是 EXT_CNT |
5. 事件生命周期:从 perf stat 到寄存器
以perf stat -e "{hisi_pcie0_core0/rx_mwr_latency,port=0xffff/,hisi_pcie0_core0/rx_mwr_cnt,port=0xffff/}"为例,perf 框架依次调用驱动实现的回调:
- event_init(hisi_pcie_pmu_event_init):校验 type、拒绝 sampling/attach-task,按
bit16决定hwc->event_base指向HISI_PCIE_CNT(0x090)还是HISI_PCIE_EXT_CNT(0x110),做过滤合法性与事件组校验,并把event->cpu钉到 PMU 的"计数 CPU"。 - add(hisi_pcie_pmu_add):分配通道索引
hwc->idx(0-7);空闲通道先写HISI_PCIE_RESET_CNT(bit22)再写HISI_PCIE_INIT_SET(bit34)复位。 - pmu_enable:只要还有空通道即向
HISI_PCIE_GLOBAL_CTRL写HISI_PCIE_GLOBAL_EN,开启整个 PMU。 - start(hisi_pcie_pmu_start):核心一步是把事件与全部过滤条件打包成 64-bit 的
EVENT_CTRL值写入对应通道:事件码(bit0-15)、目标 port/bdf(bit36-52 + TARGET_EN bit32)、trig(bit52-59)、thr(bit26-31)、len_mode(bit34-35,缺省补 3);然后置HISI_PCIE_EVENT_EN(bit20)使能计数、解中断屏蔽、把计数器装载为BIT(63)。 - stop/del:读回计数器累加差值(
hisi_pcie_pmu_event_update),清事件使能、写HISI_PCIE_INIT_SET清除EVENT_CTRL配置。 - pmu_disable:全部事件移除后,向
HISI_PCIE_GLOBAL_CTRL写 0 关闭 PMU。
这套流程与文档描述的"相关事件需同时起停"完全吻合——组内所有事件在同一个调度上下文被统一 add/start,硬件上共享同一时钟与触发窗口。
6. 适用前提与排查要点
- 平台前提:依赖
CONFIG_HISI_PCIE_PMU(ARM64 + PCI),且仅匹配 HUAWEI0xa12d的 RCiEP 设备;非 HiSilicon Hip09 类平台不会出现hisi_pcie*_core*事件源。 - 先确认监控范围再过滤:用 sysfs 的
bus、bdf_min、bdf_max确认 PMU 管辖的 Root Port 区间,再决定port位图或bdf目标;bdf 越界的事件会被内核直接拒绝(-EINVAL),而不是静默统计错误流量。 - 必须成组统计比值型指标:时延 =
xxx_latency / xxx_cnt,带宽 =xxx_flux / xxx_time,两个事件必须写进同一{}事件组。 - 不要尝试 perf record 或按任务统计:PCIe PMU 只提供全局聚合计数,驱动显式返回
-EOPNOTSUPP。 - 硬件版本核对:读
identifier文件(HISI_PCIE_REG_VERSION),可在多代平台间区分 PMU 硬件版本。
7. 小结
本文梳理了 Linux 内核中 HiSilicon PCIe PMU 的完整用法与实现:驱动 hisi_pcie_pmu.c 把 Hip09 PCIe Core 的 8 通道双计数器硬件抽象成 16 个成对的 perf 事件,通过format//events/sysfs 接口与port、bdf、trig_*、thr_*、len_mode五组过滤参数对接 perf 命令行;带宽与时延类比值指标必须使用事件组保证同时起停,采样与 per-task attach 则明确不支持。对运行在 Hip09 上的存储、HPC 或高性能网络负载,这套机制提供了不侵入业务、精确到单个 Root Port / 单个 Endpoint 的 PCIe 性能观测手段。官方文档见 Documentation/admin-guide/perf/hisi-pcie-pmu.rst,同系列还有 HiSilicon SoC uncore PMU 文档 与 HNS3 PMU 文档 可对照参考。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考