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