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
42 HISI_PMU_EVENT_ATTR_EXTRACTOR(ch, config1, 2, 0);
43 HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_en, config1, 3, 3);
44
45 /* Dynamic CPU hotplug state used by this PMU driver */
46 static enum cpuhp_state hisi_noc_pmu_cpuhp_state;
47
48 struct 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 */
hisi_noc_pmu_check_global_filter(struct perf_event * curr,struct perf_event * new)61 static 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
hisi_noc_pmu_write_evtype(struct hisi_pmu * noc_pmu,int idx,u32 type)67 static 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
hisi_noc_pmu_get_event_idx(struct perf_event * event)78 static 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
hisi_noc_pmu_read_counter(struct hisi_pmu * noc_pmu,struct hw_perf_event * hwc)92 static 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
hisi_noc_pmu_write_counter(struct hisi_pmu * noc_pmu,struct hw_perf_event * hwc,u64 val)100 static 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
hisi_noc_pmu_enable_counter(struct hisi_pmu * noc_pmu,struct hw_perf_event * hwc)108 static 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
hisi_noc_pmu_disable_counter(struct hisi_pmu * noc_pmu,struct hw_perf_event * hwc)119 static 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
hisi_noc_pmu_enable_counter_int(struct hisi_pmu * noc_pmu,struct hw_perf_event * hwc)130 static 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
hisi_noc_pmu_disable_counter_int(struct hisi_pmu * noc_pmu,struct hw_perf_event * hwc)136 static void hisi_noc_pmu_disable_counter_int(struct hisi_pmu *noc_pmu,
137 struct hw_perf_event *hwc)
138 {
139 }
140
hisi_noc_pmu_start_counters(struct hisi_pmu * noc_pmu)141 static 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
hisi_noc_pmu_stop_counters(struct hisi_pmu * noc_pmu)151 static 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
hisi_noc_pmu_get_int_status(struct hisi_pmu * noc_pmu)161 static 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
hisi_noc_pmu_clear_int_status(struct hisi_pmu * noc_pmu,int idx)168 static 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
hisi_noc_pmu_enable_filter(struct perf_event * event)178 static 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
hisi_noc_pmu_disable_filter(struct perf_event * event)206 static 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
227 static 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
244 static 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
251 static const struct attribute_group hisi_noc_pmu_format_group = {
252 .name = "format",
253 .attrs = hisi_noc_pmu_format_attrs,
254 };
255
256 static 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
275 static const struct attribute_group hisi_noc_pmu_events_group = {
276 .name = "events",
277 .attrs = hisi_noc_pmu_events_attrs,
278 };
279
280 static 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
hisi_noc_pmu_dev_init(struct platform_device * pdev,struct hisi_pmu * noc_pmu)288 static 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
hisi_noc_pmu_remove_cpuhp_instance(void * hotplug_node)326 static 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
hisi_noc_pmu_unregister_pmu(void * pmu)331 static void hisi_noc_pmu_unregister_pmu(void *pmu)
332 {
333 perf_pmu_unregister(pmu);
334 }
335
hisi_noc_pmu_probe(struct platform_device * pdev)336 static 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
382 static 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
390 static 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
397 static const struct acpi_device_id hisi_noc_pmu_ids[] = {
398 { "HISI04E0", (kernel_ulong_t) &hisi_noc_v1 },
399 { }
400 };
401 MODULE_DEVICE_TABLE(acpi, hisi_noc_pmu_ids);
402
403 static 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
hisi_noc_pmu_module_init(void)412 static 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 }
431 module_init(hisi_noc_pmu_module_init);
432
hisi_noc_pmu_module_exit(void)433 static 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 }
438 module_exit(hisi_noc_pmu_module_exit);
439
440 MODULE_IMPORT_NS("HISI_PMU");
441 MODULE_DESCRIPTION("HiSilicon SoC Uncore NoC PMU driver");
442 MODULE_LICENSE("GPL");
443 MODULE_AUTHOR("Yicong Yang <yangyicong@hisilicon.com>");
444