# Day 6 Supporting PMUs on RISC-V platforms (二)

今天一样是 Supporting PMUs on RISC-V platforms 相关的内容,先来简单回顾昨天的简介:

  • perf 是一个效能检测软件,可以检测一个程序执行时,所使用的 cycle 数、cache miss 次数等等。
  • PMU (Performance Monitoring Unit) 是一个硬体元件,具备基础的计数功能,作为 perf 运作的基础
  • 在 RISC-V 1.10 版本的指令集架构规格中,对於 HPM (Hardware Performance Monitor) 相关支援并不多,相较於其他队 perf 有完整支援的架构,RISC-V 缺少了:
    1. 开关计数器的功能:计数器永远在增加,没有可以让计数器暂停的功能
    2. 写入计数器的功能:OS 作为一个 S mode 软件,并没有写入 m mode 暂存器的能力
    3. 计数器溢位的中断 & 中断指示器::若计数器溢位,OS 是没有能力知道计数器溢位
    4. 层级辨认机制:perf 可以指定要记录哪一个层级(user space, kernel space),但 RISC-V 目前没有支援

导致了 perf stat 堪用,而 perf record 无法使用的状况。
接下来,一样先就文件本身来研究,再来记录 perf stat 和 perf record 的区别。

文件

  • 文件原文:Supporting PMUs on RISC-V platforms
  • 这份文件是用来提供一些准则和列出在移植 perf 到 RISC-V 平台上所需要做的事情;而最近 RISC-V HPM 部分的规格经过重新修订,标准化了变成一个新的扩充项目,而相对应的程序码也正在审核中,所以在不久的将来,这篇文件也许就会 deprecated,这边就不作详细的翻译,而是从程序码来对照着理解。

移植守则(续)

  1. pmu 初始化 (Initialization)

    • riscv_pmu 是 RISC-V 平台上,pmu 的一个实例(instance),预设是指向 riscv_base_pmu 这一个最基础的(baseline) pmu 实作,不同的实作者可以根据自己的需求来扩充这个资料结构。
    /* ${linux}/arch/riscv/kernel/perf_event.c */
    static const struct riscv_pmu *riscv_pmu __read_mostly;
    static const struct riscv_pmu riscv_base_pmu = {  // pmu 内部资料结构
        .pmu = &min_pmu,
        .max_events = ARRAY_SIZE(riscv_hw_event_map),
        .map_hw_event = riscv_map_hw_event,
        .hw_events = riscv_hw_event_map,
        .map_cache_event = riscv_map_cache_event,
        .cache_events = &riscv_cache_event_map,
        .counter_width = 63,
        .num_counters = RISCV_BASE_COUNTERS + 0,
        .handle_irq = &riscv_base_pmu_handle_irq,
        /* This means this PMU has no IRQ. */
        .irq = -1,
    };
    static int __init init_hw_perf_events(void)
    {
        struct device_node *node = of_find_node_by_type(NULL, "pmu");
        const struct of_device_id *of_id;
        riscv_pmu = &riscv_base_pmu;         // 预设为 riscv_base_pmu
    
        if (node) {
            of_id = of_match_node(riscv_pmu_of_ids, node);  // 找寻 dts 里面的 pmu node
    
            if (of_id)
                riscv_pmu = of_id->data;
            of_node_put(node);
        }
    
        perf_pmu_register(riscv_pmu->pmu, "cpu", PERF_TYPE_RAW);
        return 0;
    }
    arch_initcall(init_hw_perf_events); // kernel 在 initcall 初始化 arch 时,会执行这个 function
    
  2. pmu 事件初始化 (Event Initialization)
    + 使用 perf 时,perf 会执行 perf_event_open 这个系统呼叫 (system call),接下来就会执行 event_init 里面的 member。
    + 目前 RISC-V 仅支援 cycle、instruction count 这两项 event

    /* ${linux}/arch/riscv/kernel/perf_event.c */
    static const int riscv_hw_event_map[] = {    // baseline 仅支援计算 cycle、instruction count
        [PERF_COUNT_HW_CPU_CYCLES]		= RISCV_PMU_CYCLE,
        [PERF_COUNT_HW_INSTRUCTIONS]		= RISCV_PMU_INSTRET,
        [PERF_COUNT_HW_CACHE_REFERENCES]	= RISCV_OP_UNSUPP,
        [PERF_COUNT_HW_CACHE_MISSES]		= RISCV_OP_UNSUPP,
        [PERF_COUNT_HW_BRANCH_INSTRUCTIONS]	= RISCV_OP_UNSUPP,
        [PERF_COUNT_HW_BRANCH_MISSES]		= RISCV_OP_UNSUPP,
        [PERF_COUNT_HW_BUS_CYCLES]		= RISCV_OP_UNSUPP,
    };
    static const int riscv_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
    [PERF_COUNT_HW_CACHE_OP_MAX]
    [PERF_COUNT_HW_CACHE_RESULT_MAX] = {
        [C(L1D)] = {                         // L1 Dcache
            [C(OP_READ)] = {                 // READ 操作
                [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
                [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
            },
            [C(OP_WRITE)] = {
                [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
                [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
            },
            [C(OP_PREFETCH)] = {
                [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
                [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
            },
        },
        ...
    };
    static int riscv_event_init(struct perf_event *event)
    {
        ...
        switch (event->attr.type) {
        case PERF_TYPE_HARDWARE:
            code = riscv_pmu->map_hw_event(attr->config);   // init hardware 相关 event
            break;
        case PERF_TYPE_HW_CACHE:
            code = riscv_pmu->map_cache_event(attr->config);
            break;
        case PERF_TYPE_RAW:
            return -EOPNOTSUPP;
        default:
            return -ENOENT;
        }
    
        event->destroy = riscv_event_destroy;
        if (code < 0) {
            event->destroy(event);
            return code;
        }
        ...
    }
    static int riscv_map_hw_event(u64 config)           // 实际 init (mapping) 过程
    {
        ...
        return riscv_pmu->hw_events[config];
    }
    
  3. 中断 (Interrupt)

    • 中断在这边的用意是,IRQ handler 会运行 overflow 相关的处理,且在 event_init 时,会透过 reserve_pmc_hardware,将这个 service routine 变成 globally 可存取的。
    • 不过目前在 RISC-V 中,是没有提供这个功能的,而这部份的程序码也仅仅是一个 stub。
    static int reserve_pmc_hardware(void)
    {
        int err = 0;
    
        mutex_lock(&pmc_reserve_mutex);
        if (riscv_pmu->irq >= 0 && riscv_pmu->handle_irq) {
            err = request_irq(riscv_pmu->irq, riscv_pmu->handle_irq,
                      IRQF_PERCPU, "riscv-base-perf", NULL);
        }
        mutex_unlock(&pmc_reserve_mutex);
    
        return err;
    }
    
  4. 存取计数器 (Reading/Writing Counters)

    • 这里 read write 感觉起来很对称,一个在 read 的时候做、一个在 write 的时候做;然而 perf 中其实并没有主动做 write counter 的操作,而是只有主动 read counter,很直觉的 read counter 就是在事件开始时,读一次;结束时,读一次,然後及可计算出整个执行过程,事件发生的次数。
    • write counter 的操作,主要是在 pmu->start 时,要将 counter 设置成一个适当的数值,并且等待 overflow 发生;另一个则是,在 overflow 的 handler 中,把 counter 设回一开始那个适当的数值。
    static inline u64 read_counter(int idx)
    {
        u64 val = 0;
    
        switch (idx) {
        case RISCV_PMU_CYCLE:
            val = csr_read(CSR_CYCLE);
            break;
        case RISCV_PMU_INSTRET:
            val = csr_read(CSR_INSTRET);
            break;
        default:
            WARN_ON_ONCE(idx < 0 ||	idx > RISCV_MAX_COUNTERS);
            return -EINVAL;
        }
    
        return val;
    }
    
    static inline void write_counter(int idx, u64 value)  // 目前在 S mode 不支援 write counter
    {
        /* currently not supported */
        WARN_ON_ONCE(1);
    }
    
  5. add()/del()/start()/stop()

    • 基本的概念是 add/del:可以增加或是减少事件到 PMU 中; start/stop:则是启动 counter 或是停止 counter
    • 程序码整个放上来会过於冗长,连结

後记

今天简单回顾了昨天提到的部分,然後简单地把文件下半部份记录完成 (缺少了 perf 的部分,明天再来补罗),明天我们来看看新的 extension 长什麽样子,以及 Alan 提出的 Andes 解决方案到社群和大家讨论的过程! 明天见!


<<:  D15 - 如何用 Apps Script 自动化地创造与客制 Google Docs?(二)快速生出大量寄件信封资料

>>:  Day 15 | 同步与非同步- Coroutines

【Day 24】- 用方便的 Postman 储存或测试 API

前情提要 昨天带各位用 Selenium 写了自动发留言的 Discord 机器人,可以在指定的文字...

WUSON CISSP应考策略

参加WUSON CISSP的培训课程,请大家务必上课作好笔记、课後复习及课前预习喔! 笔记是通过考...

# iOS APP 开发 OC 第十八天,MRC 实作

tags: OC 30 day 为什麽放这张图?应为我觉得MRC就像是古老的仪式。既然MRC已经没什...

Day07 - 语音特徵撷取 - MFCC

要让语音讯号能够输入到模型中进行训练,就必须将其转换成电脑看得懂的数值格式,也就是语音特徵。 我们使...

各种 Code Generator 的功能

上一篇我们有提到用 KAPT 参数去呼叫 纯 Kotlin 和 Android 的 code gen...