|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * RISC-V performance counter support. |
| 4 | + * |
| 5 | + * Copyright (C) 2021 Western Digital Corporation or its affiliates. |
| 6 | + * |
| 7 | + * This implementation is based on old RISC-V perf and ARM perf event code |
| 8 | + * which are in turn based on sparc64 and x86 code. |
| 9 | + */ |
| 10 | + |
| 11 | +#include <linux/mod_devicetable.h> |
| 12 | +#include <linux/perf/riscv_pmu.h> |
| 13 | +#include <linux/platform_device.h> |
| 14 | + |
| 15 | +#define RISCV_PMU_LEGACY_CYCLE 0 |
| 16 | +#define RISCV_PMU_LEGACY_INSTRET 1 |
| 17 | +#define RISCV_PMU_LEGACY_NUM_CTR 2 |
| 18 | + |
| 19 | +static bool pmu_init_done; |
| 20 | + |
| 21 | +static int pmu_legacy_ctr_get_idx(struct perf_event *event) |
| 22 | +{ |
| 23 | + struct perf_event_attr *attr = &event->attr; |
| 24 | + |
| 25 | + if (event->attr.type != PERF_TYPE_HARDWARE) |
| 26 | + return -EOPNOTSUPP; |
| 27 | + if (attr->config == PERF_COUNT_HW_CPU_CYCLES) |
| 28 | + return RISCV_PMU_LEGACY_CYCLE; |
| 29 | + else if (attr->config == PERF_COUNT_HW_INSTRUCTIONS) |
| 30 | + return RISCV_PMU_LEGACY_INSTRET; |
| 31 | + else |
| 32 | + return -EOPNOTSUPP; |
| 33 | +} |
| 34 | + |
| 35 | +/* For legacy config & counter index are same */ |
| 36 | +static int pmu_legacy_event_map(struct perf_event *event, u64 *config) |
| 37 | +{ |
| 38 | + return pmu_legacy_ctr_get_idx(event); |
| 39 | +} |
| 40 | + |
| 41 | +static u64 pmu_legacy_read_ctr(struct perf_event *event) |
| 42 | +{ |
| 43 | + struct hw_perf_event *hwc = &event->hw; |
| 44 | + int idx = hwc->idx; |
| 45 | + u64 val; |
| 46 | + |
| 47 | + if (idx == RISCV_PMU_LEGACY_CYCLE) { |
| 48 | + val = riscv_pmu_ctr_read_csr(CSR_CYCLE); |
| 49 | + if (IS_ENABLED(CONFIG_32BIT)) |
| 50 | + val = (u64)riscv_pmu_ctr_read_csr(CSR_CYCLEH) << 32 | val; |
| 51 | + } else if (idx == RISCV_PMU_LEGACY_INSTRET) { |
| 52 | + val = riscv_pmu_ctr_read_csr(CSR_INSTRET); |
| 53 | + if (IS_ENABLED(CONFIG_32BIT)) |
| 54 | + val = ((u64)riscv_pmu_ctr_read_csr(CSR_INSTRETH)) << 32 | val; |
| 55 | + } else |
| 56 | + return 0; |
| 57 | + |
| 58 | + return val; |
| 59 | +} |
| 60 | + |
| 61 | +static void pmu_legacy_ctr_start(struct perf_event *event, u64 ival) |
| 62 | +{ |
| 63 | + struct hw_perf_event *hwc = &event->hw; |
| 64 | + u64 initial_val = pmu_legacy_read_ctr(event); |
| 65 | + |
| 66 | + /** |
| 67 | + * The legacy method doesn't really have a start/stop method. |
| 68 | + * It also can not update the counter with a initial value. |
| 69 | + * But we still need to set the prev_count so that read() can compute |
| 70 | + * the delta. Just use the current counter value to set the prev_count. |
| 71 | + */ |
| 72 | + local64_set(&hwc->prev_count, initial_val); |
| 73 | +} |
| 74 | + |
| 75 | +/** |
| 76 | + * This is just a simple implementation to allow legacy implementations |
| 77 | + * compatible with new RISC-V PMU driver framework. |
| 78 | + * This driver only allows reading two counters i.e CYCLE & INSTRET. |
| 79 | + * However, it can not start or stop the counter. Thus, it is not very useful |
| 80 | + * will be removed in future. |
| 81 | + */ |
| 82 | +static void pmu_legacy_init(struct riscv_pmu *pmu) |
| 83 | +{ |
| 84 | + pr_info("Legacy PMU implementation is available\n"); |
| 85 | + |
| 86 | + pmu->num_counters = RISCV_PMU_LEGACY_NUM_CTR; |
| 87 | + pmu->ctr_start = pmu_legacy_ctr_start; |
| 88 | + pmu->ctr_stop = NULL; |
| 89 | + pmu->event_map = pmu_legacy_event_map; |
| 90 | + pmu->ctr_get_idx = pmu_legacy_ctr_get_idx; |
| 91 | + pmu->ctr_get_width = NULL; |
| 92 | + pmu->ctr_clear_idx = NULL; |
| 93 | + pmu->ctr_read = pmu_legacy_read_ctr; |
| 94 | + |
| 95 | + perf_pmu_register(&pmu->pmu, "cpu", PERF_TYPE_RAW); |
| 96 | +} |
| 97 | + |
| 98 | +static int pmu_legacy_device_probe(struct platform_device *pdev) |
| 99 | +{ |
| 100 | + struct riscv_pmu *pmu = NULL; |
| 101 | + |
| 102 | + pmu = riscv_pmu_alloc(); |
| 103 | + if (!pmu) |
| 104 | + return -ENOMEM; |
| 105 | + pmu_legacy_init(pmu); |
| 106 | + |
| 107 | + return 0; |
| 108 | +} |
| 109 | + |
| 110 | +static struct platform_driver pmu_legacy_driver = { |
| 111 | + .probe = pmu_legacy_device_probe, |
| 112 | + .driver = { |
| 113 | + .name = RISCV_PMU_LEGACY_PDEV_NAME, |
| 114 | + }, |
| 115 | +}; |
| 116 | + |
| 117 | +static int __init riscv_pmu_legacy_devinit(void) |
| 118 | +{ |
| 119 | + int ret; |
| 120 | + struct platform_device *pdev; |
| 121 | + |
| 122 | + if (likely(pmu_init_done)) |
| 123 | + return 0; |
| 124 | + |
| 125 | + ret = platform_driver_register(&pmu_legacy_driver); |
| 126 | + if (ret) |
| 127 | + return ret; |
| 128 | + |
| 129 | + pdev = platform_device_register_simple(RISCV_PMU_LEGACY_PDEV_NAME, -1, NULL, 0); |
| 130 | + if (IS_ERR(pdev)) { |
| 131 | + platform_driver_unregister(&pmu_legacy_driver); |
| 132 | + return PTR_ERR(pdev); |
| 133 | + } |
| 134 | + |
| 135 | + return ret; |
| 136 | +} |
| 137 | +late_initcall(riscv_pmu_legacy_devinit); |
| 138 | + |
| 139 | +void riscv_pmu_legacy_skip_init(void) |
| 140 | +{ |
| 141 | + pmu_init_done = true; |
| 142 | +} |
0 commit comments