162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * RISC-V performance counter support. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2021 Western Digital Corporation or its affiliates. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This implementation is based on old RISC-V perf and ARM perf event code 862306a36Sopenharmony_ci * which are in turn based on sparc64 and x86 code. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1262306a36Sopenharmony_ci#include <linux/perf/riscv_pmu.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define RISCV_PMU_LEGACY_CYCLE 0 1662306a36Sopenharmony_ci#define RISCV_PMU_LEGACY_INSTRET 2 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic bool pmu_init_done; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic int pmu_legacy_ctr_get_idx(struct perf_event *event) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci struct perf_event_attr *attr = &event->attr; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci if (event->attr.type != PERF_TYPE_HARDWARE) 2562306a36Sopenharmony_ci return -EOPNOTSUPP; 2662306a36Sopenharmony_ci if (attr->config == PERF_COUNT_HW_CPU_CYCLES) 2762306a36Sopenharmony_ci return RISCV_PMU_LEGACY_CYCLE; 2862306a36Sopenharmony_ci else if (attr->config == PERF_COUNT_HW_INSTRUCTIONS) 2962306a36Sopenharmony_ci return RISCV_PMU_LEGACY_INSTRET; 3062306a36Sopenharmony_ci else 3162306a36Sopenharmony_ci return -EOPNOTSUPP; 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* For legacy config & counter index are same */ 3562306a36Sopenharmony_cistatic int pmu_legacy_event_map(struct perf_event *event, u64 *config) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci return pmu_legacy_ctr_get_idx(event); 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/* cycle & instret are always 64 bit, one bit less according to SBI spec */ 4162306a36Sopenharmony_cistatic int pmu_legacy_ctr_get_width(int idx) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci return 63; 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic u64 pmu_legacy_read_ctr(struct perf_event *event) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 4962306a36Sopenharmony_ci int idx = hwc->idx; 5062306a36Sopenharmony_ci u64 val; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (idx == RISCV_PMU_LEGACY_CYCLE) { 5362306a36Sopenharmony_ci val = riscv_pmu_ctr_read_csr(CSR_CYCLE); 5462306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_32BIT)) 5562306a36Sopenharmony_ci val = (u64)riscv_pmu_ctr_read_csr(CSR_CYCLEH) << 32 | val; 5662306a36Sopenharmony_ci } else if (idx == RISCV_PMU_LEGACY_INSTRET) { 5762306a36Sopenharmony_ci val = riscv_pmu_ctr_read_csr(CSR_INSTRET); 5862306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_32BIT)) 5962306a36Sopenharmony_ci val = ((u64)riscv_pmu_ctr_read_csr(CSR_INSTRETH)) << 32 | val; 6062306a36Sopenharmony_ci } else 6162306a36Sopenharmony_ci return 0; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci return val; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic void pmu_legacy_ctr_start(struct perf_event *event, u64 ival) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 6962306a36Sopenharmony_ci u64 initial_val = pmu_legacy_read_ctr(event); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /** 7262306a36Sopenharmony_ci * The legacy method doesn't really have a start/stop method. 7362306a36Sopenharmony_ci * It also can not update the counter with a initial value. 7462306a36Sopenharmony_ci * But we still need to set the prev_count so that read() can compute 7562306a36Sopenharmony_ci * the delta. Just use the current counter value to set the prev_count. 7662306a36Sopenharmony_ci */ 7762306a36Sopenharmony_ci local64_set(&hwc->prev_count, initial_val); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic uint8_t pmu_legacy_csr_index(struct perf_event *event) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci return event->hw.idx; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic void pmu_legacy_event_mapped(struct perf_event *event, struct mm_struct *mm) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci if (event->attr.config != PERF_COUNT_HW_CPU_CYCLES && 8862306a36Sopenharmony_ci event->attr.config != PERF_COUNT_HW_INSTRUCTIONS) 8962306a36Sopenharmony_ci return; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci event->hw.flags |= PERF_EVENT_FLAG_USER_READ_CNT; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic void pmu_legacy_event_unmapped(struct perf_event *event, struct mm_struct *mm) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci if (event->attr.config != PERF_COUNT_HW_CPU_CYCLES && 9762306a36Sopenharmony_ci event->attr.config != PERF_COUNT_HW_INSTRUCTIONS) 9862306a36Sopenharmony_ci return; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci event->hw.flags &= ~PERF_EVENT_FLAG_USER_READ_CNT; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/* 10462306a36Sopenharmony_ci * This is just a simple implementation to allow legacy implementations 10562306a36Sopenharmony_ci * compatible with new RISC-V PMU driver framework. 10662306a36Sopenharmony_ci * This driver only allows reading two counters i.e CYCLE & INSTRET. 10762306a36Sopenharmony_ci * However, it can not start or stop the counter. Thus, it is not very useful 10862306a36Sopenharmony_ci * will be removed in future. 10962306a36Sopenharmony_ci */ 11062306a36Sopenharmony_cistatic void pmu_legacy_init(struct riscv_pmu *pmu) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci pr_info("Legacy PMU implementation is available\n"); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci pmu->cmask = BIT(RISCV_PMU_LEGACY_CYCLE) | 11562306a36Sopenharmony_ci BIT(RISCV_PMU_LEGACY_INSTRET); 11662306a36Sopenharmony_ci pmu->ctr_start = pmu_legacy_ctr_start; 11762306a36Sopenharmony_ci pmu->ctr_stop = NULL; 11862306a36Sopenharmony_ci pmu->event_map = pmu_legacy_event_map; 11962306a36Sopenharmony_ci pmu->ctr_get_idx = pmu_legacy_ctr_get_idx; 12062306a36Sopenharmony_ci pmu->ctr_get_width = pmu_legacy_ctr_get_width; 12162306a36Sopenharmony_ci pmu->ctr_clear_idx = NULL; 12262306a36Sopenharmony_ci pmu->ctr_read = pmu_legacy_read_ctr; 12362306a36Sopenharmony_ci pmu->event_mapped = pmu_legacy_event_mapped; 12462306a36Sopenharmony_ci pmu->event_unmapped = pmu_legacy_event_unmapped; 12562306a36Sopenharmony_ci pmu->csr_index = pmu_legacy_csr_index; 12662306a36Sopenharmony_ci pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT; 12762306a36Sopenharmony_ci pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci perf_pmu_register(&pmu->pmu, "cpu", PERF_TYPE_RAW); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int pmu_legacy_device_probe(struct platform_device *pdev) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct riscv_pmu *pmu = NULL; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci pmu = riscv_pmu_alloc(); 13762306a36Sopenharmony_ci if (!pmu) 13862306a36Sopenharmony_ci return -ENOMEM; 13962306a36Sopenharmony_ci pmu_legacy_init(pmu); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic struct platform_driver pmu_legacy_driver = { 14562306a36Sopenharmony_ci .probe = pmu_legacy_device_probe, 14662306a36Sopenharmony_ci .driver = { 14762306a36Sopenharmony_ci .name = RISCV_PMU_LEGACY_PDEV_NAME, 14862306a36Sopenharmony_ci }, 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic int __init riscv_pmu_legacy_devinit(void) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci int ret; 15462306a36Sopenharmony_ci struct platform_device *pdev; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (likely(pmu_init_done)) 15762306a36Sopenharmony_ci return 0; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ret = platform_driver_register(&pmu_legacy_driver); 16062306a36Sopenharmony_ci if (ret) 16162306a36Sopenharmony_ci return ret; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci pdev = platform_device_register_simple(RISCV_PMU_LEGACY_PDEV_NAME, -1, NULL, 0); 16462306a36Sopenharmony_ci if (IS_ERR(pdev)) { 16562306a36Sopenharmony_ci platform_driver_unregister(&pmu_legacy_driver); 16662306a36Sopenharmony_ci return PTR_ERR(pdev); 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci return ret; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_cilate_initcall(riscv_pmu_legacy_devinit); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_civoid riscv_pmu_legacy_skip_init(void) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci pmu_init_done = true; 17662306a36Sopenharmony_ci} 177