Marvell Odyssey DDR PMU:Linux perf 框架下的 DDR 子系统性能监控驱动解析
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文以 Linux 内核文档 mrvl-odyssey-ddr-pmu.rst 为主体,讲解 Marvell Odyssey SoC 中 DRAM Subsystem(DSS)性能监控单元(PMU UNCORE)的事件体系、sysfs 暴露方式与 perf 使用方法,并结合驱动源码 marvell_cn10k_ddr_pmu.c 深入剖析 8 个可编程计数器与 2 个固定计数器的编程模型、事件位图映射、manual/auto 运行模式以及溢出处理机制,帮助读者在 Odyssey 平台上完成 DDR 带宽、调度争用、刷新与低功耗状态等指标的量化测量。
1. DSS PMU 硬件模型:三个观测面、十路计数器
根据文档描述,Odyssey 的 DRAM 子系统(DSS)提供8 个可编程性能计数器,软件可以将这些计数器编程为监控任意已定义的性能事件。这些事件的统计位置覆盖 DDR 数据通路上的三个关键观测面:
- DFI 接口(DDR 控制器与 PHY 之间):反映真正流向内存颗粒的读/写数据访问、DFI 总线周期、错误与重试等;
- HIF 接口(DDR 控制器与 CHI 互连之间):反映来自 SoC 互连的请求访问、读改写(RMW)、高低优先级调度等;
- DDR 控制器内部(CAM 等模块):反映 activate/precharge/read/write 等行级操作、读写转换、RAW/WAR/WAW 冒险、写合并等调度细节。
此外,DSS 还提供2 个固定性能事件计数器:一个统计 DDR 读(ddr reads),一个统计 DDR 写(ddr writes),无需编程即可自由运行(free-run)。这一硬件结构在驱动中体现为明确的常量定义(marvell_cn10k_ddr_pmu.c):
/* 8 Generic event counter + 2 fixed event counters */ #define DDRC_PERF_NUM_GEN_COUNTERS 8 #define DDRC_PERF_NUM_FIX_COUNTERS 2 #define DDRC_PERF_READ_COUNTER_IDX DDRC_PERF_NUM_GEN_COUNTERS #define DDRC_PERF_WRITE_COUNTER_IDX (DDRC_PERF_NUM_GEN_COUNTERS + 1) #define DDRC_PERF_NUM_COUNTERS (DDRC_PERF_NUM_GEN_COUNTERS + \ DDRC_PERF_NUM_FIX_COUNTERS)文档还指出,计数器可工作在manual(手动)或 auto(自动)模式:manual 模式下由驱动显式 start/stop 控制计数窗口,auto 模式则允许硬件在到达溢出值后自行回卷。从源码看,Odyssey 与 CN20K 变体走的是"手动启用 + 溢出后 stop/start 复位"的路径(见第 5 节的溢出处理),而 CN10K 变体在 probe 阶段整体切到 manual 模式。
2. 驱动注册与平台数据:为什么 sysfs 设备名是 mrvl_ddr_pmu_<地址>
该驱动是一个平台驱动,统一服务于 CN10K、Odyssey 与 CN20K 三个 SoC 变体,通过设备匹配数据区分(marvell_cn10k_ddr_pmu.c):
/* SoC variant flags for struct ddr_pmu_platform_data (mutually exclusive in pdata) */ #define IS_CN10K BIT(0) #define IS_ODY BIT(1) #define IS_CN20K BIT(2)匹配表定义了两种发现路径(marvell_cn10k_ddr_pmu.c):
| 变体 | Device Tree compatible | ACPI HID |
|---|---|---|
| CN10K | marvell,cn10k-ddr-pmu | MRVL000A |
| CN20K | marvell,cn20k-ddr-pmu | MRVL000B |
| Odyssey | —(仅 ACPI) | MRVL000C |
值得注意的是,Odyssey 变体的平台数据只在CONFIG_ACPI下定义(odyssey_ddr_pmu_pdata,marvell_cn10k_ddr_pmu.c),说明当前代码中 Odyssey 平台以 ACPI 固件描述 DDR 控制器寄存器区域。probe 阶段完成以下关键动作(cn10k_ddr_perf_probe):
devm_platform_get_and_ioremap_resource(pdev, 0, &res)映射 DDR 控制器的 MMIO 基址;- 依据
silicon_flags选择对应变体的struct ddr_pmu_ops回调与 sysfs attr_groups(Odyssey 使用odyssey_attr_groups); - 以寄存器区域起始地址命名 PMU:
name = "mrvl_ddr_pmu_%llx"(res->start),并通过perf_pmu_register(&ddr_pmu->pmu, name, -1)注册。
这解释了文档中 sysfs 路径里mrvl_ddr_pmu_<>占位符的由来——<>就是 DDR 控制器 MMIO 基址的十六进制值,例如基址为0x6820_0000的系统上会出现/sys/bus/event_source/devices/mrvl_ddr_pmu_68200000/。
每个变体的寄存器布局由struct ddr_pmu_platform_data描述(marvell_cn10k_ddr_pmu.c),它保存计数器值基址、事件配置基址、op-mode/start/end 控制寄存器、freerun 控制与固定计数器值寄存器等偏移。Odyssey 专属的寄存器偏移汇总如下(取自 marvell_cn10k_ddr_pmu.c、[L153-L178] 中的ODY_*定义):
| 寄存器 | 偏移 | 用途 |
|---|---|---|
ODY_DDRC_PERF_CNT_OP_MODE_CTRL | 0x20020 | 计数器运行模式控制(manual=0x1) |
ODY_DDRC_PERF_CNT_START_OP_CTRL | 0x200A0 | 计数器启动操作控制(写 0x1 启动) |
ODY_DDRC_PERF_CNT_END_OP_CTRL | 0x200E0 | 计数器结束操作控制(写 0x1 停止) |
ODY_DDRC_PERF_CNT_END_STATUS | 0x20120 | 结束状态寄存器 |
ODY_DDRC_PERF_CFG_BASE | 0x20160 | 事件配置寄存器基址(每计数器 8 字节) |
ODY_DDRC_PERF_CNT_VALUE_BASE | 0x201C0 | 计数器值寄存器基址(每计数器 8 字节) |
ODY_DDRC_PERF_CNT_FREERUN_CTRL | 0x20240 | 固定计数器使能/清除控制 |
ODY_DDRC_PERF_CNT_FREERUN_CLR | 0x20248 | 固定计数器清除寄存器(Odyssey 独有) |
ODY_DDRC_PERF_CNT_VALUE_WR_OP | 0x20250 | DDR 写固定计数器值 |
ODY_DDRC_PERF_CNT_VALUE_RD_OP | 0x20258 | DDR 读固定计数器值 |
3. 事件暴露:sysfs 的 events/ 与 format/ 目录
PMU 驱动通过 attribute_group 在 sysfs 下暴露可用事件与格式选项(文档原文路径):
/sys/bus/event_source/devices/mrvl_ddr_pmu_<>/events/ /sys/bus/event_source/devices/mrvl_ddr_pmu_<>/format/events/目录下每个文件对应一个具名事件,内容形如event=0x10(由 cn10k_ddr_pmu_event_show 输出),数值即事件 ID;format/目录下只有一个event文件,定义为config:0-8(marvell_cn10k_ddr_pmu.c),即事件 ID 编码在perf_event_attr.config的低 8 位。
PMU_FORMAT_ATTR(event, "config:0-8"); static struct attribute *cn10k_ddr_perf_format_attrs[] = { &format_attr_event.attr, NULL, };Odyssey 变体的具名事件表为odyssey_ddr_perf_events_attrs(marvell_cn10k_ddr_pmu.c),通过宏CN10K_DDR_PMU_EVENT_ATTR(_name, _id)将 sysfs 事件名与硬件事件 ID 绑定。文档给出的perf list | grep ddr完整输出如下(mrvl_ddr_pmu_<>/为 PMU 前缀):
$ perf list | grep ddr mrvl_ddr_pmu_<>/ddr_act_bypass_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_bsm_alloc/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_bsm_starvation/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_active_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_mwr/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_rd_active_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_rd_or_wr_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_read/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_wr_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_cam_write/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_capar_error/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_crit_ref/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_ddr_reads/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_ddr_writes/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dfi_cmd_is_retry/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dfi_cycles/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dfi_parity_poison/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dfi_rd_data_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dfi_wr_data_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dqsosc_mpc/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_dqsosc_mrr/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_enter_mpsm/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_enter_powerdown/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_enter_selfref/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_hif_pri_rdaccess/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_hif_rd_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_hif_rd_or_wr_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_hif_rmw_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_hif_wr_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_hpri_sched_rd_crit_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_load_mode/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_lpri_sched_rd_crit_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_precharge/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_precharge_for_other/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_precharge_for_rdwr/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_raw_hazard/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_rd_bypass_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_rd_crc_error/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_rd_uc_ecc_error/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_rdwr_transitions/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_refresh/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_retry_fifo_full/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_spec_ref/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_tcr_mrr/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_war_hazard/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_waw_hazard/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_win_limit_reached_rd/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_win_limit_reached_wr/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_wr_crc_error/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_wr_trxn_crit_access/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_write_combine/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_zqcl/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_zqlatch/ [Kernel PMU event] mrvl_ddr_pmu_<>/ddr_zqstart/ [Kernel PMU event]按观测面归类,这些事件可分为几组实用指标:
- 带宽/数据量:
ddr_ddr_reads、ddr_ddr_writes(固定计数器),ddr_dfi_rd_data_access/ddr_dfi_wr_data_access(DFI 侧真实数据访问),ddr_hif_rd_or_wr_access(HIF 侧请求量); - 调度与争用:
ddr_raw_hazard、ddr_war_hazard、ddr_waw_hazard(RAW/WAR/WAW 冒险)、ddr_rdwr_transitions(读写切换次数)、ddr_write_combine(写合并)、ddr_bsm_alloc/ddr_bsm_starvation(BSM 分配与饥饿); - 刷新与训练:
ddr_refresh、ddr_crit_ref、ddr_spec_ref、ddr_load_mode、ddr_zqcl/ddr_zqstart/ddr_zqlatch/ddr_tcr_mrr/ddr_dqsosc_mrr/ddr_dqsosc_mpc(ZQ 校准与 DQOSC 训练类操作); - 低功耗状态:
ddr_enter_selfref、ddr_enter_powerdown、ddr_enter_mpsm(进入自刷新/掉电/MPSM 的次数); - 质量与错误:
ddr_rd_crc_error、ddr_wr_crc_error、ddr_capar_error、ddr_rd_uc_ecc_error、ddr_dfi_parity_poison、ddr_dfi_cmd_is_retry、ddr_retry_fifo_full、ddr_dfi_cycles; - 控制器行级操作:
ddr_cam_active_access、ddr_cam_rd_active_access、ddr_cam_rd_or_wr_access、ddr_cam_read、ddr_cam_write、ddr_cam_mwr、ddr_cam_wr_access、ddr_precharge*等。
4. 事件 ID 到硬件位图的映射
事件 ID 是写入硬件事件位图的选择码。驱动源码中保留了全部可编程事件的 ID 定义(marvell_cn10k_ddr_pmu.c),并特别注释"这些 event-id 编号不得改动,它们用于编程硬件事件位图"。Odyssey 相关的常用 ID 摘录:
| 事件名(sysfs) | 事件 ID 宏 | 值 |
|---|---|---|
ddr_hif_rd_or_wr_access | EVENT_HIF_RD_OR_WR | 1 |
ddr_hif_wr_access | EVENT_HIF_WR | 2 |
ddr_hif_rd_access | EVENT_HIF_RD | 3 |
ddr_hif_rmw_access | EVENT_HIF_RMW | 4 |
ddr_hif_pri_rdaccess | EVENT_HIF_HI_PRI_RD | 5 |
ddr_rd_bypass_access | EVENT_READ_BYPASS | 6 |
ddr_act_bypass_access | EVENT_ACT_BYPASS | 7 |
ddr_dfi_wr_data_access | EVENT_DFI_WR_DATA_CYCLES | 8 |
ddr_dfi_rd_data_access | EVENT_DFI_RD_DATA_CYCLES | 9 |
ddr_hpri_sched_rd_crit_access | EVENT_HPR_XACT_WHEN_CRITICAL | 10 |
ddr_lpri_sched_rd_crit_access | EVENT_LPR_XACT_WHEN_CRITICAL | 11 |
ddr_wr_trxn_crit_access | EVENT_WR_XACT_WHEN_CRITICAL | 12 |
ddr_cam_active_access | EVENT_OP_IS_ACTIVATE | 13 |
ddr_cam_rd_or_wr_access | EVENT_OP_IS_RD_OR_WR | 14 |
ddr_cam_rd_active_access | EVENT_OP_IS_RD_ACTIVATE | 15 |
ddr_cam_read | EVENT_OP_IS_RD | 16 |
ddr_cam_write | EVENT_OP_IS_WR | 17 |
ddr_cam_mwr | EVENT_OP_IS_MWR | 18 |
ddr_precharge | EVENT_OP_IS_PRECHARGE | 19 |
ddr_enter_selfref | EVENT_OP_IS_ENTER_SELFREF | 27 |
ddr_enter_powerdown | EVENT_OP_IS_ENTER_POWERDOWN | 31 |
ddr_enter_mpsm | EVENT_OP_IS_ENTER_MPSM | 35 |
ddr_refresh | EVENT_OP_IS_REFRESH | 39 |
ddr_crit_ref | EVENT_OP_IS_CRIT_REF | 40 |
ddr_spec_ref | EVENT_OP_IS_SPEC_REF | 41 |
ddr_load_mode | EVENT_OP_IS_LOAD_MODE | 42 |
ddr_zqcl | EVENT_OP_IS_ZQCL | 43 |
ddr_dqsosc_mpc | EVENT_OP_IS_DQSOSC_MPC | 51 |
ddr_dqsosc_mrr | EVENT_OP_IS_DQSOSC_MRR | 52 |
ddr_tcr_mrr | EVENT_OP_IS_TCR_MRR | 53 |
ddr_zqstart | EVENT_OP_IS_ZQSTART | 54 |
ddr_zqlatch | EVENT_OP_IS_ZQLATCH | 55 |
ddr_dfi_parity_poison | EVENT_DFI_PARITY_POISON | 56 |
ddr_wr_crc_error | EVENT_WR_CRC_ERROR | 57 |
ddr_capar_error | EVENT_CAPAR_ERROR | 58 |
ddr_rd_crc_error | EVENT_RD_CRC_ERROR | 59 |
ddr_rd_uc_ecc_error | EVENT_RD_UC_ECC_ERROR | 60 |
ddr_dfi_cmd_is_retry | EVENT_DFI_CMD_IS_RETRY | 61 |
ddr_dfi_cycles | EVENT_DFI_CYCLES | 45 |
ddr_retry_fifo_full | EVENT_RETRY_FIFO_FULL | 46 |
ddr_bsm_alloc | EVENT_BSM_ALLOC | 47 |
ddr_bsm_starvation | EVENT_BSM_STARVATION | 48 |
ddr_win_limit_reached_rd | EVENT_VISIBLE_WIN_LIMIT_REACHED_RD | 49 |
ddr_win_limit_reached_wr | EVENT_VISIBLE_WIN_LIMIT_REACHED_WR | 50 |
ddr_ddr_reads(固定计数器) | EVENT_DDR_READS | 101 |
ddr_ddr_writes(固定计数器) | EVENT_DDR_WRITES | 100 |
映射函数 ddr_perf_get_event_bitmap 将事件 ID 转换为硬件位图,其中有两个特殊分支值得注意:
- 常规事件:位图为单 bit,
*event_bitmap = (1ULL << (eventid - 1)),即事件 ID 直接对应硬件事件选择寄存器的第eventid-1位; - 低功耗进入事件:
EVENT_OP_IS_ENTER_SELFREF/ENTER_POWERDOWN/ENTER_MPSM对应位图(0xFULL << (eventid - 1)),即这些事件在硬件中占用 4 个连续位(对应每通道/每 rank 的进入指示),驱动会把 4 个 bit 一起置起再写入选件配置寄存器; - ID 冲突消解:事件 ID 58~61 在 CN20K 上被"perf width"事件(
ddr_read16/32、ddr_write16/32)占用,与 Odyssey 的 DFI 错误事件同名段。ddr_perf_get_event_bitmap依据silicon_flags(IS_CN20K/IS_ODY)区分两条路径,两者最终都落到1ULL << (eventid - 1)的同一位图规则,从而避免同一数值 ID 跨变体误编程。
固定计数器不使用事件位图:cn10k_ddr_perf_alloc_counter(marvell_cn10k_ddr_pmu.c)中,config == EVENT_DDR_READS(101)直接绑定到索引 8 的读计数器,EVENT_DDR_WRITES(100)绑定到索引 9 的写计数器;其余事件按先到先占分配 8 个通用计数器槽位,槽满则返回-ENOENT(对 perf 表现为分配失败-EAGAIN)。
5. 计数生命周期:从 perf stat 到寄存器操作
5.1 事件初始化约束
cn10k_ddr_perf_event_init 对事件施加了三点约束,使用 perf 时应知晓:
- 不支持采样:
is_sampling_event(event)为真时直接返回-EOPNOTSUPP并打印 "Sampling not supported!"。该 PMU 只做计数(counting),不能做采样(sampling),因此只能用perf stat,不能用perf record -e mrvl_ddr_pmu_xxx/...; - 不支持 per-task:要求事件绑定 CPU(
event->cpu < 0时拒绝),并把事件所有权固定到 PMU 所在的 CPU(event->cpu = pmu->cpu),"同一事件不能同时在多个 CPU 上被观测"; - 禁止跨 PMU 混组:group leader 与其他 PMU 混合会返回
-EINVAL。
5.2 事件 add 流程(编程计数器)
cn10k_ddr_perf_event_add 完成了文档所说"软件可以编程这些计数器"的全部寄存器操作:
- 分配计数器槽位(固定计数器直配索引 8/9,通用计数器空闲槽优先);
- 首个事件添加时启动高精度轮询定时器(
hrtimer_start,周期由poll_period_sec决定); - 通用计数器:调用
ddr_perf_get_event_bitmap得到事件位图后写入DDRC_PERF_CFG(cfg_base, counter)(基址 + 8×counter),即把事件选择与计数器编号绑定; - 固定计数器:调用变体 ops 的 clear 回调清零(Odyssey 写
ODY_DDRC_PERF_CNT_FREERUN_CLR的读/写清除位,见 ddr_pmu_ody_read_clear_freerun)。
5.3 start/stop 与 manual 模式
cn10k_ddr_perf_counter_enable 实现了文档提到的 manual/auto 两种运行模式下的使能逻辑:
- 通用计数器先读改写配置寄存器的高位
EVENT_ENABLE(BIT_ULL(63)); - 对
IS_ODY或IS_CN20K:enable 时向ODY_DDRC_PERF_CNT_OP_MODE_CTRL偏移的 op-mode 寄存器写入OP_MODE_CTRL_VAL_MANUAL(0x1),随后向 start-op 寄存器写START_OP_CTRL_VAL_START(0x1)启动计数;disable 时写 end-op 寄存器END_OP_CTRL_VAL_END(0x1)停止。也就是说Odyssey 的计数窗口完全由驱动手动开关; - 固定计数器走变体 ops:Odyssey 的读/写 free-run 使能位位于
ODY_DDRC_PERF_CNT_FREERUN_CTRL(bit0=写、bit1=读,见 ddr_pmu_ody_enable_read_freerun),与 CN10K 使用独立FREERUN_EN寄存器的做法不同。
5.4 周期性读取与溢出处理
计数器读取并非只在事件 stop 时发生。驱动用一个 hrtimer 周期性(默认 100 秒)调用 cn10k_ddr_pmu_overflow_handler,源码注释解释了默认值的选择依据:"48 位计数器在最高 5.6 GT/s 下以最大速率增长,溢出可能需要数小时",100 秒的轮询足以避免读数丢失:
static unsigned long cn10k_ddr_pmu_poll_period_sec = 100; module_param_named(poll_period_sec, cn10k_ddr_pmu_poll_period_sec, ulong, 0644);该轮询周期是模块参数,可在加载模块时调整:modprobe marvell_cn10k_ddr_pmu poll_period_sec=10。溢出处理逻辑分两类:
- 固定计数器(读/写 free-run):新读数小于旧读数视为回卷,更新累加值;
- 通用计数器:读数达到
counter_max_val时触发变体溢出回调。Odyssey 的实现 ddr_pmu_ody_overflow_hander 注释说明:计数器到达最大值后会冻结在该值,驱动先更新该事件的累计值,再对该计数器执行 stop + start,使其从 0 重新开始计数; - 每次读数通过 cn10k_ddr_perf_event_update 以 CAS 方式(
local64_xchg循环)无锁地把硬件增量按counter_max_val掩码累加进event->count。
Odyssey 的计数器位宽体现在平台数据counter_max_val = GENMASK_ULL(63, 0)(64 位全值域,marvell_cn10k_ddr_pmu.c),而 CN10K 为 49 位(GENMASK_ULL(48, 0),溢出值BIT_ULL(48))。
5.5 CPU 迁移
PMU 是全局(uncore)设备,其"宿主 CPU"在 probe 时取raw_smp_processor_id()。当该 CPU 下线时,cn10k_ddr_pmu_offline_cpu 通过 CPU hotplug 回调perf_pmu_migrate_context把事件上下文迁到任一仍在线的 CPU 上,保证持续测量不因核下线而中断。
6. 实战:用 perf 测量 DDR 事件
文档给出的标准用法即perf list过滤 +perf stat计数。在 Odyssey 系统上,先确认 PMU 已注册:
$ ls /sys/bus/event_source/devices/ | grep mrvl_ddr_pmu mrvl_ddr_pmu_<hex基址> $ cat /sys/bus/event_source/devices/mrvl_ddr_pmu_<hex基址>/cpumaskcpumask属性(cn10k_ddr_perf_cpumask_show)显示当前 PMU 事件被绑定到的 CPU。
文档示例:统计 CAM 侧的读/写/激活/转换类事件
$ perf stat -e ddr_cam_read,ddr_cam_write,ddr_cam_active_access,ddr_cam_rd_or_wr_access,ddr_cam_rd_active_access,ddr_cam_mwr <workload>典型组合测量思路(均为文档中已暴露事件,可自由组合,但通用事件同批最多 8 个):
# 1. 总 DDR 读/写量(两个固定计数器,可同时启用,不占用通用计数器) $ perf stat -e mrvl_ddr_pmu_x/ / : ddr_ddr_reads,ddr_ddr_writes <workload> # 2. HIF 侧请求量 + 读改写 $ perf stat -e ddr_hif_rd_or_wr_access,ddr_hif_rmw_access <workload> # 3. DFI 侧真实数据访问,评估控制器效率(HIF 请求 / DFI 数据 比值) $ perf stat -e ddr_dfi_rd_data_access,ddr_dfi_wr_data_access <workload> # 4. 调度健康度:读写切换、冒险、可见窗口限制 $ perf stat -e ddr_rdwr_transitions,ddr_raw_hazard,ddr_war_hazard,ddr_waw_hazard \ ddr_win_limit_reached_rd,ddr_win_limit_reached_wr <workload> # 5. 刷新/训练开销与低功耗进入频次 $ perf stat -e ddr_refresh,ddr_spec_ref,ddr_crit_ref,ddr_enter_selfref, ddr_enter_powerdown,ddr_enter_mpsm <workload> # 6. 链路质量:CRC/ECC/奇偶错误与重试 $ perf stat -e ddr_rd_crc_error,ddr_wr_crc_error,ddr_capar_error, ddr_rd_uc_ecc_error,ddr_dfi_parity_poison,ddr_dfi_cmd_is_retry, ddr_retry_fifo_full,ddr_dfi_cycles <workload>注意约束:由于不支持采样与 per-task,上述测量只能得到整机(per-CPU 绑定 PMU 宿主 CPU)维度的计数;事件名在perf stat -e中可用短名(如ddr_cam_read),也可写成完整的mrvl_ddr_pmu_<>/ddr_cam_read/形式。若同批通用事件超过 8 个,驱动将返回分配失败,需要分批测量。
7. 构建与部署
内核配置项定义在 drivers/perf/Kconfig:
config MARVELL_CN10K_DDR_PMU tristate "Enable MARVELL CN10K DRAM Subsystem(DSS) PMU Support" depends on ARCH_THUNDER || (COMPILE_TEST && 64BIT) help Enable perf support for Marvell DDR Performance monitoring event on CN10K platform.即在 Thunder(ARM64 64 位)架构下启用CONFIG_MARVELL_CN10K_DDR_PMU(y/m),drivers/perf/Makefile 中对应obj-$(CONFIG_MARVELL_CN10K_DDR_PMU) += marvell_cn10k_ddr_pmu.o。Odyssey 平台通过 ACPI 表(HIDMRVL000C)描述 DDR 控制器 MMIO 区域,驱动 probe 后在 dmesg 中打印DDR PMU Driver for ddrc@<基址>,此时即可按第 6 节的方法使用 perf 工具链。
8. 小结
- Odyssey DSS PMU 提供 8 路可编程 + 2 路固定(读/写)计数器,事件覆盖 DFI、HIF 与控制器内部三个观测面,全部经 sysfs
events/、format/目录对 perf 框架暴露; - 事件 ID 编码于
perf_event_attr.config:0-8;驱动把 ID 翻译成硬件事件位图(低功耗进入事件占 4 个连续位;58~61 段 ID 按silicon_flags区分 Odyssey/CN20K 语义); - Odyssey 计数器运行在 manual 模式,由驱动 start/stop 控制,配合默认 100 秒(
poll_period_sec可调)的高精度定时器轮询,处理固定计数器回卷与通用计数器"冻结后 stop/start 复位"的溢出; - 该 PMU 仅支持计数(
perf stat),不支持采样与 per-task,通用事件同批上限 8 个,这是使用该方法论做 DDR 性能剖析时的硬边界。
相关源码与文档路径:Documentation/admin-guide/perf/mrvl-odyssey-ddr-pmu.rst、drivers/perf/marvell_cn10k_ddr_pmu.c、drivers/perf/Kconfig、drivers/perf/Makefile。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考