FILE
hisi_uncore_noc_pmu.c
File preview with syntax highlighting.
linux-6.19.14/drivers/perf/hisilicon/hisi_uncore_noc_pmu.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.