| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | // SPDX-License-Identifier: GPL-2.0
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * RISC-V performance counter support. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2021 Western Digital Corporation or its affiliates. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This implementation is based on old RISC-V perf and ARM perf event code | 
					
						
							|  |  |  |  * which are in turn based on sparc64 and x86 code. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/mod_devicetable.h>
 | 
					
						
							|  |  |  | #include <linux/perf/riscv_pmu.h>
 | 
					
						
							|  |  |  | #include <linux/platform_device.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define RISCV_PMU_LEGACY_CYCLE		0
 | 
					
						
							| 
									
										
										
										
											2023-08-02 10:03:21 +02:00
										 |  |  | #define RISCV_PMU_LEGACY_INSTRET	2
 | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | static bool pmu_init_done; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pmu_legacy_ctr_get_idx(struct perf_event *event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct perf_event_attr *attr = &event->attr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (event->attr.type != PERF_TYPE_HARDWARE) | 
					
						
							|  |  |  | 		return -EOPNOTSUPP; | 
					
						
							|  |  |  | 	if (attr->config == PERF_COUNT_HW_CPU_CYCLES) | 
					
						
							|  |  |  | 		return RISCV_PMU_LEGACY_CYCLE; | 
					
						
							|  |  |  | 	else if (attr->config == PERF_COUNT_HW_INSTRUCTIONS) | 
					
						
							|  |  |  | 		return RISCV_PMU_LEGACY_INSTRET; | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		return -EOPNOTSUPP; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* For legacy config & counter index are same */ | 
					
						
							|  |  |  | static int pmu_legacy_event_map(struct perf_event *event, u64 *config) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return pmu_legacy_ctr_get_idx(event); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-27 20:00:02 +03:00
										 |  |  | /* cycle & instret are always 64 bit, one bit less according to SBI spec */ | 
					
						
							|  |  |  | static int pmu_legacy_ctr_get_width(int idx) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return 63; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | static u64 pmu_legacy_read_ctr(struct perf_event *event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct hw_perf_event *hwc = &event->hw; | 
					
						
							|  |  |  | 	int idx = hwc->idx; | 
					
						
							|  |  |  | 	u64 val; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (idx == RISCV_PMU_LEGACY_CYCLE) { | 
					
						
							|  |  |  | 		val = riscv_pmu_ctr_read_csr(CSR_CYCLE); | 
					
						
							|  |  |  | 		if (IS_ENABLED(CONFIG_32BIT)) | 
					
						
							|  |  |  | 			val = (u64)riscv_pmu_ctr_read_csr(CSR_CYCLEH) << 32 | val; | 
					
						
							|  |  |  | 	} else if (idx == RISCV_PMU_LEGACY_INSTRET) { | 
					
						
							|  |  |  | 		val = riscv_pmu_ctr_read_csr(CSR_INSTRET); | 
					
						
							|  |  |  | 		if (IS_ENABLED(CONFIG_32BIT)) | 
					
						
							|  |  |  | 			val = ((u64)riscv_pmu_ctr_read_csr(CSR_INSTRETH)) << 32 | val; | 
					
						
							|  |  |  | 	} else | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return val; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void pmu_legacy_ctr_start(struct perf_event *event, u64 ival) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct hw_perf_event *hwc = &event->hw; | 
					
						
							|  |  |  | 	u64 initial_val = pmu_legacy_read_ctr(event); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/**
 | 
					
						
							|  |  |  | 	 * The legacy method doesn't really have a start/stop method. | 
					
						
							|  |  |  | 	 * It also can not update the counter with a initial value. | 
					
						
							|  |  |  | 	 * But we still need to set the prev_count so that read() can compute | 
					
						
							|  |  |  | 	 * the delta. Just use the current counter value to set the prev_count. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	local64_set(&hwc->prev_count, initial_val); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-02 10:03:24 +02:00
										 |  |  | static uint8_t pmu_legacy_csr_index(struct perf_event *event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return event->hw.idx; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void pmu_legacy_event_mapped(struct perf_event *event, struct mm_struct *mm) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (event->attr.config != PERF_COUNT_HW_CPU_CYCLES && | 
					
						
							|  |  |  | 	    event->attr.config != PERF_COUNT_HW_INSTRUCTIONS) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	event->hw.flags |= PERF_EVENT_FLAG_USER_READ_CNT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void pmu_legacy_event_unmapped(struct perf_event *event, struct mm_struct *mm) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (event->attr.config != PERF_COUNT_HW_CPU_CYCLES && | 
					
						
							|  |  |  | 	    event->attr.config != PERF_COUNT_HW_INSTRUCTIONS) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	event->hw.flags &= ~PERF_EVENT_FLAG_USER_READ_CNT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 15:35:32 +01:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  |  * This is just a simple implementation to allow legacy implementations | 
					
						
							|  |  |  |  * compatible with new RISC-V PMU driver framework. | 
					
						
							|  |  |  |  * This driver only allows reading two counters i.e CYCLE & INSTRET. | 
					
						
							|  |  |  |  * However, it can not start or stop the counter. Thus, it is not very useful | 
					
						
							|  |  |  |  * will be removed in future. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void pmu_legacy_init(struct riscv_pmu *pmu) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pr_info("Legacy PMU implementation is available\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 18:53:05 +03:00
										 |  |  | 	pmu->cmask = BIT(RISCV_PMU_LEGACY_CYCLE) | | 
					
						
							|  |  |  | 		BIT(RISCV_PMU_LEGACY_INSTRET); | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | 	pmu->ctr_start = pmu_legacy_ctr_start; | 
					
						
							|  |  |  | 	pmu->ctr_stop = NULL; | 
					
						
							|  |  |  | 	pmu->event_map = pmu_legacy_event_map; | 
					
						
							|  |  |  | 	pmu->ctr_get_idx = pmu_legacy_ctr_get_idx; | 
					
						
							| 
									
										
										
										
											2024-02-27 20:00:02 +03:00
										 |  |  | 	pmu->ctr_get_width = pmu_legacy_ctr_get_width; | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | 	pmu->ctr_clear_idx = NULL; | 
					
						
							|  |  |  | 	pmu->ctr_read = pmu_legacy_read_ctr; | 
					
						
							| 
									
										
										
										
											2023-08-02 10:03:24 +02:00
										 |  |  | 	pmu->event_mapped = pmu_legacy_event_mapped; | 
					
						
							|  |  |  | 	pmu->event_unmapped = pmu_legacy_event_unmapped; | 
					
						
							|  |  |  | 	pmu->csr_index = pmu_legacy_csr_index; | 
					
						
							| 
									
										
										
										
											2024-02-27 20:00:01 +03:00
										 |  |  | 	pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT; | 
					
						
							|  |  |  | 	pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE; | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	perf_pmu_register(&pmu->pmu, "cpu", PERF_TYPE_RAW); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pmu_legacy_device_probe(struct platform_device *pdev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct riscv_pmu *pmu = NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pmu = riscv_pmu_alloc(); | 
					
						
							|  |  |  | 	if (!pmu) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							| 
									
										
										
										
											2024-04-12 17:10:39 +01:00
										 |  |  | 	pmu->pmu.parent = &pdev->dev; | 
					
						
							| 
									
										
										
										
											2022-02-18 16:46:55 -08:00
										 |  |  | 	pmu_legacy_init(pmu); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct platform_driver pmu_legacy_driver = { | 
					
						
							|  |  |  | 	.probe		= pmu_legacy_device_probe, | 
					
						
							|  |  |  | 	.driver		= { | 
					
						
							|  |  |  | 		.name	= RISCV_PMU_LEGACY_PDEV_NAME, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init riscv_pmu_legacy_devinit(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	struct platform_device *pdev; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (likely(pmu_init_done)) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = platform_driver_register(&pmu_legacy_driver); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pdev = platform_device_register_simple(RISCV_PMU_LEGACY_PDEV_NAME, -1, NULL, 0); | 
					
						
							|  |  |  | 	if (IS_ERR(pdev)) { | 
					
						
							|  |  |  | 		platform_driver_unregister(&pmu_legacy_driver); | 
					
						
							|  |  |  | 		return PTR_ERR(pdev); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | late_initcall(riscv_pmu_legacy_devinit); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void riscv_pmu_legacy_skip_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	pmu_init_done = true; | 
					
						
							|  |  |  | } |