File Index
Files and directories
Sign in
FILE

hisi_uncore_noc_pmu.c

File preview with syntax highlighting.

linux-6.19.14/drivers/perf/hisilicon/hisi_uncore_noc_pmu.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Driver for HiSilicon Uncore NoC (Network on Chip) PMU device
  4 *
  5 * Copyright (c) 2025 HiSilicon Technologies Co., Ltd.
  6 * Author: Yicong Yang <yangyicong@hisilicon.com>
  7 */
  8#include <linux/bitops.h>
  9#include <linux/cpuhotplug.h>
 10#include <linux/device.h>
 11#include <linux/io.h>
 12#include <linux/mod_devicetable.h>
 13#include <linux/module.h>
 14#include <linux/platform_device.h>
 15#include <linux/property.h>
 16#include <linux/sysfs.h>
 17
 18#include "hisi_uncore_pmu.h"
 19
 20#define NOC_PMU_VERSION			0x1e00
 21#define NOC_PMU_GLOBAL_CTRL		0x1e04
 22#define   NOC_PMU_GLOBAL_CTRL_PMU_EN	BIT(0)
 23#define   NOC_PMU_GLOBAL_CTRL_TT_EN	BIT(1)
 24#define NOC_PMU_CNT_INFO		0x1e08
 25#define   NOC_PMU_CNT_INFO_OVERFLOW(n)	BIT(n)
 26#define NOC_PMU_EVENT_CTRL0		0x1e20
 27#define   NOC_PMU_EVENT_CTRL_TYPE	GENMASK(4, 0)
 28/*
 29 * Note channel of 0x0 will reset the counter value, so don't do it before
 30 * we read out the counter.
 31 */
 32#define   NOC_PMU_EVENT_CTRL_CHANNEL	GENMASK(10, 8)
 33#define   NOC_PMU_EVENT_CTRL_EN		BIT(11)
 34#define NOC_PMU_EVENT_COUNTER0		0x1e80
 35
 36#define NOC_PMU_NR_COUNTERS		4
 37#define NOC_PMU_CH_DEFAULT		0x7
 38
 39#define NOC_PMU_EVENT_CTRLn(ctrl0, n)	((ctrl0) + 4 * (n))
 40#define NOC_PMU_EVENT_CNTRn(cntr0, n)	((cntr0) + 8 * (n))
 41
 42HISI_PMU_EVENT_ATTR_EXTRACTOR(ch, config1, 2, 0);
 43HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_en, config1, 3, 3);
 44
 45/* Dynamic CPU hotplug state used by this PMU driver */
 46static enum cpuhp_state hisi_noc_pmu_cpuhp_state;
 47
 48struct hisi_noc_pmu_regs {
 49	u32 version;
 50	u32 pmu_ctrl;
 51	u32 event_ctrl0;
 52	u32 event_cntr0;
 53	u32 overflow_status;
 54};
 55
 56/*
 57 * Tracetag filtering is not per event and all the events should keep
 58 * the consistence. Return true if the new comer doesn't match the
 59 * tracetag filtering configuration of the current scheduled events.
 60 */
 61static bool hisi_noc_pmu_check_global_filter(struct perf_event *curr,
 62					     struct perf_event *new)
 63{
 64	return hisi_get_tt_en(curr) == hisi_get_tt_en(new);
 65}
 66
 67static void hisi_noc_pmu_write_evtype(struct hisi_pmu *noc_pmu, int idx, u32 type)
 68{
 69	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
 70	u32 reg;
 71
 72	reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, idx));
 73	reg &= ~NOC_PMU_EVENT_CTRL_TYPE;
 74	reg |= FIELD_PREP(NOC_PMU_EVENT_CTRL_TYPE, type);
 75	writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, idx));
 76}
 77
 78static int hisi_noc_pmu_get_event_idx(struct perf_event *event)
 79{
 80	struct hisi_pmu *noc_pmu = to_hisi_pmu(event->pmu);
 81	struct hisi_pmu_hwevents *pmu_events = &noc_pmu->pmu_events;
 82	int cur_idx;
 83
 84	cur_idx = find_first_bit(pmu_events->used_mask, noc_pmu->num_counters);
 85	if (cur_idx != noc_pmu->num_counters &&
 86	    !hisi_noc_pmu_check_global_filter(pmu_events->hw_events[cur_idx], event))
 87		return -EAGAIN;
 88
 89	return hisi_uncore_pmu_get_event_idx(event);
 90}
 91
 92static u64 hisi_noc_pmu_read_counter(struct hisi_pmu *noc_pmu,
 93				     struct hw_perf_event *hwc)
 94{
 95	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
 96
 97	return readq(noc_pmu->base + NOC_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx));
 98}
 99
100static void hisi_noc_pmu_write_counter(struct hisi_pmu *noc_pmu,
101				       struct hw_perf_event *hwc, u64 val)
102{
103	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
104
105	writeq(val, noc_pmu->base + NOC_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx));
106}
107
108static void hisi_noc_pmu_enable_counter(struct hisi_pmu *noc_pmu,
109					struct hw_perf_event *hwc)
110{
111	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
112	u32 reg;
113
114	reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
115	reg |= NOC_PMU_EVENT_CTRL_EN;
116	writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
117}
118
119static void hisi_noc_pmu_disable_counter(struct hisi_pmu *noc_pmu,
120					 struct hw_perf_event *hwc)
121{
122	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
123	u32 reg;
124
125	reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
126	reg &= ~NOC_PMU_EVENT_CTRL_EN;
127	writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
128}
129
130static void hisi_noc_pmu_enable_counter_int(struct hisi_pmu *noc_pmu,
131					    struct hw_perf_event *hwc)
132{
133	/* We don't support interrupt, so a stub here. */
134}
135
136static void hisi_noc_pmu_disable_counter_int(struct hisi_pmu *noc_pmu,
137					     struct hw_perf_event *hwc)
138{
139}
140
141static void hisi_noc_pmu_start_counters(struct hisi_pmu *noc_pmu)
142{
143	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
144	u32 reg;
145
146	reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
147	reg |= NOC_PMU_GLOBAL_CTRL_PMU_EN;
148	writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
149}
150
151static void hisi_noc_pmu_stop_counters(struct hisi_pmu *noc_pmu)
152{
153	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
154	u32 reg;
155
156	reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
157	reg &= ~NOC_PMU_GLOBAL_CTRL_PMU_EN;
158	writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
159}
160
161static u32 hisi_noc_pmu_get_int_status(struct hisi_pmu *noc_pmu)
162{
163	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
164
165	return readl(noc_pmu->base + reg_info->overflow_status);
166}
167
168static void hisi_noc_pmu_clear_int_status(struct hisi_pmu *noc_pmu, int idx)
169{
170	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
171	u32 reg;
172
173	reg = readl(noc_pmu->base + reg_info->overflow_status);
174	reg &= ~NOC_PMU_CNT_INFO_OVERFLOW(idx);
175	writel(reg, noc_pmu->base + reg_info->overflow_status);
176}
177
178static void hisi_noc_pmu_enable_filter(struct perf_event *event)
179{
180	struct hisi_pmu *noc_pmu = to_hisi_pmu(event->pmu);
181	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
182	struct hw_perf_event *hwc = &event->hw;
183	u32 tt_en = hisi_get_tt_en(event);
184	u32 ch = hisi_get_ch(event);
185	u32 reg;
186
187	if (!ch)
188		ch = NOC_PMU_CH_DEFAULT;
189
190	reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
191	reg &= ~NOC_PMU_EVENT_CTRL_CHANNEL;
192	reg |= FIELD_PREP(NOC_PMU_EVENT_CTRL_CHANNEL, ch);
193	writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
194
195	/*
196	 * Since tracetag filter applies to all the counters, don't touch it
197	 * if user doesn't specify it explicitly.
198	 */
199	if (tt_en) {
200		reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
201		reg |= NOC_PMU_GLOBAL_CTRL_TT_EN;
202		writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
203	}
204}
205
206static void hisi_noc_pmu_disable_filter(struct perf_event *event)
207{
208	struct hisi_pmu *noc_pmu = to_hisi_pmu(event->pmu);
209	struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
210	u32 tt_en = hisi_get_tt_en(event);
211	u32 reg;
212
213	/*
214	 * If we're not the last counter, don't touch the global tracetag
215	 * configuration.
216	 */
217	if (bitmap_weight(noc_pmu->pmu_events.used_mask, noc_pmu->num_counters) > 1)
218		return;
219
220	if (tt_en) {
221		reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
222		reg &= ~NOC_PMU_GLOBAL_CTRL_TT_EN;
223		writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
224	}
225}
226
227static const struct hisi_uncore_ops hisi_uncore_noc_ops = {
228	.write_evtype		= hisi_noc_pmu_write_evtype,
229	.get_event_idx		= hisi_noc_pmu_get_event_idx,
230	.read_counter		= hisi_noc_pmu_read_counter,
231	.write_counter		= hisi_noc_pmu_write_counter,
232	.enable_counter		= hisi_noc_pmu_enable_counter,
233	.disable_counter	= hisi_noc_pmu_disable_counter,
234	.enable_counter_int	= hisi_noc_pmu_enable_counter_int,
235	.disable_counter_int	= hisi_noc_pmu_disable_counter_int,
236	.start_counters		= hisi_noc_pmu_start_counters,
237	.stop_counters		= hisi_noc_pmu_stop_counters,
238	.get_int_status		= hisi_noc_pmu_get_int_status,
239	.clear_int_status	= hisi_noc_pmu_clear_int_status,
240	.enable_filter		= hisi_noc_pmu_enable_filter,
241	.disable_filter		= hisi_noc_pmu_disable_filter,
242};
243
244static struct attribute *hisi_noc_pmu_format_attrs[] = {
245	HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
246	HISI_PMU_FORMAT_ATTR(ch, "config1:0-2"),
247	HISI_PMU_FORMAT_ATTR(tt_en, "config1:3"),
248	NULL
249};
250
251static const struct attribute_group hisi_noc_pmu_format_group = {
252	.name = "format",
253	.attrs = hisi_noc_pmu_format_attrs,
254};
255
256static struct attribute *hisi_noc_pmu_events_attrs[] = {
257	HISI_PMU_EVENT_ATTR(cycles, 0x0e),
258	/* Flux on/off the ring */
259	HISI_PMU_EVENT_ATTR(ingress_flow_sum, 0x1a),
260	HISI_PMU_EVENT_ATTR(egress_flow_sum, 0x17),
261	/* Buffer full duration on/off the ring */
262	HISI_PMU_EVENT_ATTR(ingress_buf_full, 0x19),
263	HISI_PMU_EVENT_ATTR(egress_buf_full, 0x12),
264	/* Failure packets count on/off the ring */
265	HISI_PMU_EVENT_ATTR(cw_ingress_fail, 0x01),
266	HISI_PMU_EVENT_ATTR(cc_ingress_fail, 0x09),
267	HISI_PMU_EVENT_ATTR(cw_egress_fail, 0x03),
268	HISI_PMU_EVENT_ATTR(cc_egress_fail, 0x0b),
269	/* Flux of the ring */
270	HISI_PMU_EVENT_ATTR(cw_main_flow_sum, 0x05),
271	HISI_PMU_EVENT_ATTR(cc_main_flow_sum, 0x0d),
272	NULL
273};
274
275static const struct attribute_group hisi_noc_pmu_events_group = {
276	.name = "events",
277	.attrs = hisi_noc_pmu_events_attrs,
278};
279
280static const struct attribute_group *hisi_noc_pmu_attr_groups[] = {
281	&hisi_noc_pmu_format_group,
282	&hisi_noc_pmu_events_group,
283	&hisi_pmu_cpumask_attr_group,
284	&hisi_pmu_identifier_group,
285	NULL
286};
287
288static int hisi_noc_pmu_dev_init(struct platform_device *pdev, struct hisi_pmu *noc_pmu)
289{
290	struct hisi_noc_pmu_regs *reg_info;
291
292	hisi_uncore_pmu_init_topology(noc_pmu, &pdev->dev);
293
294	if (noc_pmu->topo.scl_id < 0)
295		return dev_err_probe(&pdev->dev, -EINVAL, "failed to get scl-id\n");
296
297	if (noc_pmu->topo.index_id < 0)
298		return dev_err_probe(&pdev->dev, -EINVAL, "failed to get idx-id\n");
299
300	if (noc_pmu->topo.sub_id < 0)
301		return dev_err_probe(&pdev->dev, -EINVAL, "failed to get sub-id\n");
302
303	noc_pmu->base = devm_platform_ioremap_resource(pdev, 0);
304	if (IS_ERR(noc_pmu->base))
305		return dev_err_probe(&pdev->dev, PTR_ERR(noc_pmu->base),
306				     "fail to remap io memory\n");
307
308	noc_pmu->dev_info = device_get_match_data(&pdev->dev);
309	if (!noc_pmu->dev_info)
310		return -ENODEV;
311
312	noc_pmu->pmu_events.attr_groups = noc_pmu->dev_info->attr_groups;
313	noc_pmu->counter_bits = noc_pmu->dev_info->counter_bits;
314	noc_pmu->check_event = noc_pmu->dev_info->check_event;
315	noc_pmu->num_counters = NOC_PMU_NR_COUNTERS;
316	noc_pmu->ops = &hisi_uncore_noc_ops;
317	noc_pmu->dev = &pdev->dev;
318	noc_pmu->on_cpu = -1;
319
320	reg_info = noc_pmu->dev_info->private;
321	noc_pmu->identifier = readl(noc_pmu->base + reg_info->version);
322
323	return 0;
324}
325
326static void hisi_noc_pmu_remove_cpuhp_instance(void *hotplug_node)
327{
328	cpuhp_state_remove_instance_nocalls(hisi_noc_pmu_cpuhp_state, hotplug_node);
329}
330
331static void hisi_noc_pmu_unregister_pmu(void *pmu)
332{
333	perf_pmu_unregister(pmu);
334}
335
336static int hisi_noc_pmu_probe(struct platform_device *pdev)
337{
338	struct device *dev = &pdev->dev;
339	struct hisi_pmu *noc_pmu;
340	char *name;
341	int ret;
342
343	noc_pmu = devm_kzalloc(dev, sizeof(*noc_pmu), GFP_KERNEL);
344	if (!noc_pmu)
345		return -ENOMEM;
346
347	/*
348	 * HiSilicon Uncore PMU framework needs to get common hisi_pmu device
349	 * from device's drvdata.
350	 */
351	platform_set_drvdata(pdev, noc_pmu);
352
353	ret = hisi_noc_pmu_dev_init(pdev, noc_pmu);
354	if (ret)
355		return ret;
356
357	ret = cpuhp_state_add_instance(hisi_noc_pmu_cpuhp_state, &noc_pmu->node);
358	if (ret)
359		return dev_err_probe(dev, ret, "Fail to register cpuhp instance\n");
360
361	ret = devm_add_action_or_reset(dev, hisi_noc_pmu_remove_cpuhp_instance,
362				       &noc_pmu->node);
363	if (ret)
364		return ret;
365
366	hisi_pmu_init(noc_pmu, THIS_MODULE);
367
368	name = devm_kasprintf(dev, GFP_KERNEL, "hisi_scl%d_noc%d_%d",
369			      noc_pmu->topo.scl_id, noc_pmu->topo.index_id,
370			      noc_pmu->topo.sub_id);
371	if (!name)
372		return -ENOMEM;
373
374	ret = perf_pmu_register(&noc_pmu->pmu, name, -1);
375	if (ret)
376		return dev_err_probe(dev, ret, "Fail to register PMU\n");
377
378	return devm_add_action_or_reset(dev, hisi_noc_pmu_unregister_pmu,
379					&noc_pmu->pmu);
380}
381
382static struct hisi_noc_pmu_regs hisi_noc_v1_pmu_regs = {
383	.version = NOC_PMU_VERSION,
384	.pmu_ctrl = NOC_PMU_GLOBAL_CTRL,
385	.event_ctrl0 = NOC_PMU_EVENT_CTRL0,
386	.event_cntr0 = NOC_PMU_EVENT_COUNTER0,
387	.overflow_status = NOC_PMU_CNT_INFO,
388};
389
390static const struct hisi_pmu_dev_info hisi_noc_v1 = {
391	.attr_groups = hisi_noc_pmu_attr_groups,
392	.counter_bits = 64,
393	.check_event = NOC_PMU_EVENT_CTRL_TYPE,
394	.private = &hisi_noc_v1_pmu_regs,
395};
396
397static const struct acpi_device_id hisi_noc_pmu_ids[] = {
398	{ "HISI04E0", (kernel_ulong_t) &hisi_noc_v1 },
399	{ }
400};
401MODULE_DEVICE_TABLE(acpi, hisi_noc_pmu_ids);
402
403static struct platform_driver hisi_noc_pmu_driver = {
404	.driver = {
405		.name = "hisi_noc_pmu",
406		.acpi_match_table = hisi_noc_pmu_ids,
407		.suppress_bind_attrs = true,
408	},
409	.probe = hisi_noc_pmu_probe,
410};
411
412static int __init hisi_noc_pmu_module_init(void)
413{
414	int ret;
415
416	ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/hisi/noc:online",
417				      hisi_uncore_pmu_online_cpu,
418				      hisi_uncore_pmu_offline_cpu);
419	if (ret < 0) {
420		pr_err("hisi_noc_pmu: Fail to setup cpuhp callbacks, ret = %d\n", ret);
421		return ret;
422	}
423	hisi_noc_pmu_cpuhp_state = ret;
424
425	ret = platform_driver_register(&hisi_noc_pmu_driver);
426	if (ret)
427		cpuhp_remove_multi_state(hisi_noc_pmu_cpuhp_state);
428
429	return ret;
430}
431module_init(hisi_noc_pmu_module_init);
432
433static void __exit hisi_noc_pmu_module_exit(void)
434{
435	platform_driver_unregister(&hisi_noc_pmu_driver);
436	cpuhp_remove_multi_state(hisi_noc_pmu_cpuhp_state);
437}
438module_exit(hisi_noc_pmu_module_exit);
439
440MODULE_IMPORT_NS("HISI_PMU");
441MODULE_DESCRIPTION("HiSilicon SoC Uncore NoC PMU driver");
442MODULE_LICENSE("GPL");
443MODULE_AUTHOR("Yicong Yang <yangyicong@hisilicon.com>");
~/ Read-only file preview.