Linux 内核实战:Amlogic Meson DDR 带宽 PMU(meson_ddr_bw)perf 事件与过滤机制详解
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文基于 Linux 内核文档 meson-ddr-pmu.rst 展开,讲解 Amlogic Meson G12 系列 SoC 内置的 DDR 带宽性能监控单元(PMU):如何用它通过perf命令测量系统总带宽与各通道带宽、如何用 AXI 端口过滤关键字(arm、gpu、pcie等)定位“性能瓶颈是否在 DDR 带宽上”,并结合 核心驱动 与 G12 硬件实现 源码剖析 100ms 定时器中断累加、16 字节计数粒度、设备树配置与 CPU 热插拔迁移等底层机制。读完本文,你可以在 Amlogic 平台上独立完成 DDR 带宽的采集、分通道拆解与按 IP 模块归因分析。
1. 硬件背景:DRAM 控制器内部的带宽监控器
Amlogic Meson G12 SoC 的 DRAM 控制器(DMC)内部集成了一台带宽监控器(bandwidth monitor)。其基本能力包括:
- 4 个计数通道:每个通道都可以对访问 DRAM 的请求进行计数;
- AXI 端口过滤:每个通道可以映射并统计指定的 AXI 端口(来自 SoC 内各个 IP 模块的 DDR 访问请求),从而区分“是谁在消耗带宽”;
- 典型用途:判断系统性能瓶颈是否出在 DDR 带宽上——例如 GPU 解码、以太网转发、eMMC 读写等场景下,单独观察对应 IP 的带宽占用。
这一点直接决定了驱动的软件形态:它不是一个按 CPU 采样的 PMU,而是一个系统级(system-wide)带宽计数器 PMU,通过定时器周期性读硬件计数器并累加,最终换算成 MB 单位的带宽数据。
驱动由两层构成:
| 文件 | 职责 |
|---|---|
| meson_ddr_pmu_core.c | 通用 PMU 框架层:perf 事件生命周期、sysfs 事件/单位/缩放属性、中断累加、CPU 热插拔迁移 |
| meson_g12_ddr_pmu.c | G12 硬件层:寄存器定义、PLL 频率推算、定时器/过滤/计数寄存器操作,以及 g12a/g12b/sm1 三套平台配置 |
编译入口在 Kconfig 中的CONFIG_MESON_DDR_PMU(tristate,"Amlogic DDR Bandwidth Performance Monitor",依赖ARCH_MESON || COMPILE_TEST),Makefile 将两个目标文件合并为meson_ddr_pmu_g12.o。
2. perf 事件模型:total 与 4 个通道事件
文档明确指出当前驱动支持5 个 perf 事件:
meson_ddr_bw/total_rw_bytes/ meson_ddr_bw/chan_1_rw_bytes/ meson_ddr_bw/chan_2_rw_bytes/ meson_ddr_bw/chan_3_rw_bytes/ meson_ddr_bw/chan_4_rw_bytes/其中chan_{1,2,3,4}_rw_bytes是通道特定事件,每个通道都支持过滤,可以让某通道只监控 SoC 中的某个特定 IP 模块。
从源码结构看,通用层的能力上限其实更大:meson_ddr_pmu_core.c 的meson_ddr_perf_event_update()处理了ALL_CHAN_COUNTER_ID以及CHAN1_COUNTER_ID~CHAN8_COUNTER_ID共 9 种事件 ID(定义于 include/soc/amlogic/meson_ddr_pmu.h),事件属性表也预置了 8 个通道(L270-L280)。而 fill_event_attr() 在 probe 时按平台chann_nr动态填充 sysfs 属性——G12A/G12B/SM1 三套dmc_hw_info均为chann_nr = 4(meson_g12_ddr_pmu.c L314-L351),因此 sysfs 下实际暴露的就是文档所述的 5 个事件。
PMU 注册名为meson_ddr_bw(DDR_PERF_DEV_NAME宏,L32),注册时 cpu 传-1(L534),表示这是非 CPU 事件源,必须用-a(系统级)方式使用。
2.1 单位与缩放:为什么原始计数要乘 1.52587890625e-05
perf 框架会通过/sys/bus/event_source/devices/meson_ddr_bw/events/<event>.unit和.scale两个属性把原始计数换算成带单位的显示值。驱动中两者分别是:
- unit = MB:见 event_show_unit();
- scale = 1.52587890625e-05:见 event_show_scale(),注释写明one count = 16 byte,即硬件每记 1 次代表传了 16 字节,
16 / 1e6 = 1.52587890625e-05 MB。
所以perf输出的带宽数值 = 原始计数 × 16 字节,再换算为 MB。理解这一点后就能正确解读perf stat的period/计数值。
2.2 使用限制:只支持系统级统计,不支持采样
meson_ddr_perf_event_init() 中有三条硬性检查,使用时要注意:
if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) return -EOPNOTSUPP; if (event->cpu < 0) return -EOPNOTSUPP;- 不支持 sampling(不能写
period采样); - 不支持绑定到 task(不能
perf record -p <pid>单进程模式),必须-a全系统采集; - 过滤参数个数受限:通道事件的
config1/config2置位总数超过MAX_AXI_PORTS_OF_CHANNEL(定义为 4,L33)时返回-EOPNOTSUPP。注意文档正文描述为“每个通道可同时统计至多 3 个 AXI 端口”,而源码的校验上限是 4——以实际内核版本源码中的校验逻辑为准。
3. 通道过滤关键字:把带宽按 IP 模块拆开
chan_*_rw_bytes事件的强大之处在于过滤:在事件参数中用关键字=1的形式声明要统计的 AXI 端口,perf 会把它们编码进config1/config2,驱动再据此写硬件的端口-通道映射寄存器。文档列出的 DDR 访问请求过滤关键字共 25 个,结合 meson_g12_ddr_pmu.c 中PMU_FORMAT_ATTR的定义,对应的config1位号如下:
| 关键字 | 含义(来源 IP) | config1 位 |
|---|---|---|
arm | CPU | 0 |
gpu | 3D GPU | 1 |
pcie | PCIe 控制器 | 2 |
hdcp | HDCP 控制器 | 3 |
hevc_front | HEVC 解码前端 | 4 |
usb3_0 | USB3.0 控制器 | 6 |
hevc_back | HEVC 解码后端 | 8 |
h265enc | HEVC 编码器 | 9 |
vpu_read1 | OSD + VPP 读 | 16 |
vpu_read2 | DI 读 | 17 |
vpu_write1 | VDIN 写 | 19 |
vpu_write2 | DI 写 | 20 |
vdec | 传统编解码器视频解码器 | 21 |
hcodec | H264 编码器 | 22 |
ge2d | ge2d 2D 图形引擎 | 23 |
spicc1 | SPI 控制器 1 | 32 |
usb0 | USB2.0 控制器 0 | 33 |
dma | 系统 DMA 控制器 1 | 34 |
arb0 | arb0 仲裁器 | 35 |
sd_emmc_b | SD/eMMC b 控制器 | 36 |
usb1 | USB2.0 控制器 1 | 37 |
audio | Audio 模块 | 38 |
sd_emmc_c | SD/eMMC c 控制器 | 43 |
spicc2 | SPI 控制器 2 | 44 |
ethernet | 以太网控制器 | 45 |
几个值得注意的实现细节:
config1与config2的拼接:sysfs format 属性声明的是config1:0~45;在 meson_ddr_perf_event_add() 中,config1的每个置位 biti映射为 AXI idi,config2的每个置位 biti则映射为 AXI idi + 64。也就是说 format 组中只暴露config1,perf 工具按标准 PMU 格式约定自动拆分高位参数。- 不同芯片可见的关键字不同:meson_ddr_perf_format_attr_visible() 会用平台
capability位掩码过滤 format 属性的可见性。三套平台的掩码分别为 g12a0x7EFF00FF03DF、g12b0x7FFF00FF3FDF、sm10x7EFF00FF07DF(L314-L351)。例如nna(config1:10)在注释中标注“for sm1 and g12b”,gdc/mipi_isp/arm1/sd_emmc_a标注“for g12b only”。因此某台机器上/sys/bus/event_source/devices/meson_ddr_bw/format/里能列出哪些过滤项,取决于具体芯片。 - 源码中还存在文档未列出的过滤项(如
device、vpu_read3、aififo、parser、sana等,见 L44-L84),是否可用同样受上述 capability 掩码控制。 - “device” 端口的子端口机制:
config1:7是device这一聚合端口;在 dmc_g12_config_fiter() 中,AXI id ≥ 32(PORT_MAJOR)会被视为 device 端口的子端口,先置位BIT(23)选择 device,再在rs寄存器中按子端口位置位。
4. 实操:用 perf 采集 DDR 带宽
以下命令直接来自原文档示例,要求内核开启CONFIG_MESON_DDR_PMU且设备树中存在对应节点,用户态安装perf工具。
4.1 每秒输出系统总 DDR 带宽
perf stat -a -e meson_ddr_bw/total_rw_bytes/ -I 1000 sleep 10-a:全系统采集(该 PMU 不支持 task 级绑定,见第 2.2 节);-I 1000:每 1000ms 输出一行,得到“每秒带宽”的曲线;- 事件值单位为 MB(.unit 属性),计数粒度 16 字节(.scale 属性)。
4.2 分别统计 CPU 与 GPU 的 DDR 带宽并求和
perf stat -a -e meson_ddr_bw/chan_1_rw_bytes,arm=1/ -I 1000 sleep 10 perf stat -a -e meson_ddr_bw/chan_2_rw_bytes,gpu=1/ -I 1000 sleep 10 perf stat -a -e meson_ddr_bw/chan_3_rw_bytes,arm=1,gpu=1/ -I 1000 sleep 10三条命令的分工:
chan_1过滤arm=1:只统计 CPU(ARM 核)发起的 DDR 访问;chan_2过滤gpu=1:只统计 3D GPU 的 DDR 访问;chan_3同时过滤arm=1,gpu=1:统计两者之和,可与前两条交叉验证。
每条命令占用一个硬件通道,这正是“4 通道可同时监控”的用法——例如可以同时挂起 4 个chan_*事件,把 CPU、GPU、PCIe、以太网各自拆到独立通道上并行观测。注意每个通道上叠加的过滤项不得超过第 2.2 节所述的端口上限。
5. 底层实现:100ms 硬件定时器 + 中断累加
理解带宽数字是怎么产生的,关键在 G12 硬件层的 enable/IRQ/get_counters 三条路径。
5.1 寄存器布局
带宽监控寄存器(偏移均按 4 字节字编址):
| 偏移宏 | 地址 | 用途 |
|---|---|---|
DMC_MON_G12_CTRL0 | 0x00 | 全局控制(使能、用定时器、4 通道使能位、bit30 为 QoS/计时中断标志) |
DMC_MON_G12_CTRL1~CTRL8 | 0x04~0x20 | 4 组通道的端口映射寄存器对(CTRL1/2、CTRL3/4、CTRL5/6、CTRL7/8) |
DMC_MON_G12_ALL_REQ_CNT | 0x24 | 总请求计数 |
DMC_MON_G12_ALL_GRANT_CNT | 0x28 | 总带宽(grant)计数 |
DMC_MON_G12_ONE_GRANT_CNT~FOR_GRANT_CNT | 0x2C~0x3C | 通道 1~4 各自带宽计数 |
DMC_MON_G12_TIMER | 0x40 | 定时器重载值 |
定义见 meson_g12_ddr_pmu.c L24-L40。
5.2 定时器周期如何确定:从 PLL 寄存器反推 DDR 频率
定时器以 DDR 时钟为时基,因此要先知道 DDR 频率。dmc_g12_get_freq_quick() 直接读取设备树第二个 reg 指向的 PLL 寄存器,按xtal(24MHz) × m/n ÷ od1 ÷ od_div推算(DEFAULT_XTAL_FREQ固定 24MHz,od_div由寄存器位域映射为 2/3/4/6/8)。
然后 dmc_g12_counter_enable() 执行:
unsigned long clock_count = dmc_g12_get_freq_quick(info) / 10; /* 100ms */ writel(clock_count, info->ddr_reg[0] + DMC_MON_G12_TIMER); /* CTRL0: bit31 使能 | bit20 用定时器 | 低 4 位使能 4 通道 */即定时器周期固定为100ms。
5.3 中断处理与软件侧累加
计数器在定时器到期中断中被读取。dmc_irq_handler()(通用层)调用硬件层 dmc_g12_irq_handler():后者检查CTRL0的DMC_QOS_IRQ(BIT(30)),置位则读出全部计数器并写回CTRL0清中断。
通用层随后把本次读到的计数累加进pmu->counters(all_cnt/all_req/每通道channel_cnt[i],结构见 meson_ddr_pmu.h L26-L36),并在pmu_enabled时重新调用hw_info->enable()——源码注释解释了原因:the timer interrupt only supports one shot mode, we have to re-enable it in ISR to support continue mode,即硬件定时器是单次触发模式,软件在中断里滚动重装以形成连续计时。
事件 stop 时(PERF_EF_UPDATE路径),meson_ddr_perf_event_update() 再读一次寄存器余量,用 ddr_cnt_addition() 把“软件累计值 + 寄存器当前值”合成最终计数,按事件 ID 选择all_cnt(total 事件)或对应通道值写入event->count。start 时则先清零软件累计值并使能硬件(L143-L149)。这样无论perf stat的采样窗口与 100ms 中断是否对齐,结果都是完整的区间累计。
5.4 CPU 热插拔:PMU 上下文的自动迁移
该 PMU 的计数上下文绑定在一个具体 CPU 上(perf 的 per-CPU 上下文机制)。当绑定 CPU 下线时,ddr_perf_offline_cpu() 会把上下文迁移到另一个在线 CPU(perf_pmu_migrate_context),并用irq_set_affinity把中断也改投过去。probe 阶段通过cpuhp_setup_state_multi+cpuhp_state_add_instance_nocalls注册(L520-L530),同时 sysfs 暴露只读cpumask属性(L188-L198)可以查看当前绑定的 CPU。
6. 设备树配置
硬件使能的前提是设备树中声明了 DDR PMU 节点。设备树绑定文档 amlogic,g12-ddr-pmu.yaml 定义了三个compatible(amlogic,g12a-ddr-pmu、amlogic,g12b-ddr-pmu、amlogic,sm1-ddr-pmu),与驱动 of_device_id 表 一一对应。必填项:
#include <dt-bindings/interrupt-controller/arm-gic.h> pmu@ff638000 { compatible = "amlogic,g12a-ddr-pmu"; reg = <0x0 0xff638000 0x0 0x100>, /* DMC 带宽监控寄存器空间 */ <0x0 0xff638c00 0x0 0x100>; /* DMC PLL 寄存器空间 */ interrupts = <GIC_SPI 52 IRQ_TYPE_EDGE_RISING>; /* 内部定时器超时中断 */ };reg共两段:前dmc_nr(此处为 1)段是 DMC 监控寄存器基址,最后一位是 PLL 寄存器基址(用于 5.2 节的频率推算)——驱动在 ddr_pmu_parse_dt() 中按此顺序ioremap;interrupts为内部定时器超时中断,驱动以IRQF_NOBALANCING申请,保证热插拔时能自由控制亲和性。
7. 小结与适用前提
- 适用平台:Amlogic G12A/G12B/SM1(Meson 家族),对应
compatible见第 6 节;内核配置项为CONFIG_MESON_DDR_PMU。 - 该 PMU 提供的是系统级 DDR 带宽统计:5 个事件(total + 4 通道)、MB 单位、16 字节计数粒度、100ms 硬件定时器中断采样累加;
- 通道过滤允许把带宽归因到 CPU/GPU/PCIe/以太网/编解码/存储等具体 IP,是定位“带宽型瓶颈”的核心手段;
- 限制:不支持采样与 task 级绑定(必须
-a);单通道过滤端口数受硬件上限约束;不同芯片可见的过滤关键字由 capability 掩码决定,以/sys/bus/event_source/devices/meson_ddr_bw/format/实际输出为准。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考