linux/drivers/perf/hisilicon/hisi_uncore_sllc_pmu.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * HiSilicon SLLC uncore Hardware event counters support
   4 *
   5 * Copyright (C) 2020 HiSilicon Limited
   6 * Author: Shaokun Zhang <zhangshaokun@hisilicon.com>
   7 *
   8 * This code is based on the uncore PMUs like arm-cci and arm-ccn.
   9 */
  10#include <linux/acpi.h>
  11#include <linux/cpuhotplug.h>
  12#include <linux/interrupt.h>
  13#include <linux/irq.h>
  14#include <linux/list.h>
  15#include <linux/smp.h>
  16
  17#include "hisi_uncore_pmu.h"
  18
  19/* SLLC register definition */
  20#define SLLC_INT_MASK                   0x0814
  21#define SLLC_INT_STATUS                 0x0818
  22#define SLLC_INT_CLEAR                  0x081c
  23#define SLLC_PERF_CTRL                  0x1c00
  24#define SLLC_SRCID_CTRL                 0x1c04
  25#define SLLC_TGTID_CTRL                 0x1c08
  26#define SLLC_EVENT_CTRL                 0x1c14
  27#define SLLC_EVENT_TYPE0                0x1c18
  28#define SLLC_VERSION                    0x1cf0
  29#define SLLC_EVENT_CNT0_L               0x1d00
  30
  31#define SLLC_EVTYPE_MASK                0xff
  32#define SLLC_PERF_CTRL_EN               BIT(0)
  33#define SLLC_FILT_EN                    BIT(1)
  34#define SLLC_TRACETAG_EN                BIT(2)
  35#define SLLC_SRCID_EN                   BIT(4)
  36#define SLLC_SRCID_NONE                 0x0
  37#define SLLC_TGTID_EN                   BIT(5)
  38#define SLLC_TGTID_NONE                 0x0
  39#define SLLC_TGTID_MIN_SHIFT            1
  40#define SLLC_TGTID_MAX_SHIFT            12
  41#define SLLC_SRCID_CMD_SHIFT            1
  42#define SLLC_SRCID_MSK_SHIFT            12
  43#define SLLC_NR_EVENTS                  0x80
  44
  45HISI_PMU_EVENT_ATTR_EXTRACTOR(tgtid_min, config1, 10, 0);
  46HISI_PMU_EVENT_ATTR_EXTRACTOR(tgtid_max, config1, 21, 11);
  47HISI_PMU_EVENT_ATTR_EXTRACTOR(srcid_cmd, config1, 32, 22);
  48HISI_PMU_EVENT_ATTR_EXTRACTOR(srcid_msk, config1, 43, 33);
  49HISI_PMU_EVENT_ATTR_EXTRACTOR(tracetag_en, config1, 44, 44);
  50
  51static bool tgtid_is_valid(u32 max, u32 min)
  52{
  53        return max > 0 && max >= min;
  54}
  55
  56static void hisi_sllc_pmu_enable_tracetag(struct perf_event *event)
  57{
  58        struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
  59        u32 tt_en = hisi_get_tracetag_en(event);
  60
  61        if (tt_en) {
  62                u32 val;
  63
  64                val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
  65                val |= SLLC_TRACETAG_EN | SLLC_FILT_EN;
  66                writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
  67        }
  68}
  69
  70static void hisi_sllc_pmu_disable_tracetag(struct perf_event *event)
  71{
  72        struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
  73        u32 tt_en = hisi_get_tracetag_en(event);
  74
  75        if (tt_en) {
  76                u32 val;
  77
  78                val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
  79                val &= ~(SLLC_TRACETAG_EN | SLLC_FILT_EN);
  80                writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
  81        }
  82}
  83
  84static void hisi_sllc_pmu_config_tgtid(struct perf_event *event)
  85{
  86        struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
  87        u32 min = hisi_get_tgtid_min(event);
  88        u32 max = hisi_get_tgtid_max(event);
  89
  90        if (tgtid_is_valid(max, min)) {
  91                u32 val = (max << SLLC_TGTID_MAX_SHIFT) | (min << SLLC_TGTID_MIN_SHIFT);
  92
  93                writel(val, sllc_pmu->base + SLLC_TGTID_CTRL);
  94                /* Enable the tgtid */
  95                val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
  96                val |= SLLC_TGTID_EN | SLLC_FILT_EN;
  97                writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
  98        }
  99}
 100
 101static void hisi_sllc_pmu_clear_tgtid(struct perf_event *event)
 102{
 103        struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
 104        u32 min = hisi_get_tgtid_min(event);
 105        u32 max = hisi_get_tgtid_max(event);
 106
 107        if (tgtid_is_valid(max, min)) {
 108                u32 val;
 109
 110                writel(SLLC_TGTID_NONE, sllc_pmu->base + SLLC_TGTID_CTRL);
 111                /* Disable the tgtid */
 112                val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
 113                val &= ~(SLLC_TGTID_EN | SLLC_FILT_EN);
 114                writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
 115        }
 116}
 117
 118static void hisi_sllc_pmu_config_srcid(struct perf_event *event)
 119{
 120        struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
 121        u32 cmd = hisi_get_srcid_cmd(event);
 122
 123        if (cmd) {
 124                u32 val, msk;
 125
 126                msk = hisi_get_srcid_msk(event);
 127                val = (cmd << SLLC_SRCID_CMD_SHIFT) | (msk << SLLC_SRCID_MSK_SHIFT);
 128                writel(val, sllc_pmu->base + SLLC_SRCID_CTRL);
 129                /* Enable the srcid */
 130                val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
 131                val |= SLLC_SRCID_EN | SLLC_FILT_EN;
 132                writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
 133        }
 134}
 135
 136static void hisi_sllc_pmu_clear_srcid(struct perf_event *event)
 137{
 138        struct hisi_pmu *sllc_pmu = to_hisi_pmu(event->pmu);
 139        u32 cmd = hisi_get_srcid_cmd(event);
 140
 141        if (cmd) {
 142                u32 val;
 143
 144                writel(SLLC_SRCID_NONE, sllc_pmu->base + SLLC_SRCID_CTRL);
 145                /* Disable the srcid */
 146                val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
 147                val &= ~(SLLC_SRCID_EN | SLLC_FILT_EN);
 148                writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
 149        }
 150}
 151
 152static void hisi_sllc_pmu_enable_filter(struct perf_event *event)
 153{
 154        if (event->attr.config1 != 0x0) {
 155                hisi_sllc_pmu_enable_tracetag(event);
 156                hisi_sllc_pmu_config_srcid(event);
 157                hisi_sllc_pmu_config_tgtid(event);
 158        }
 159}
 160
 161static void hisi_sllc_pmu_clear_filter(struct perf_event *event)
 162{
 163        if (event->attr.config1 != 0x0) {
 164                hisi_sllc_pmu_disable_tracetag(event);
 165                hisi_sllc_pmu_clear_srcid(event);
 166                hisi_sllc_pmu_clear_tgtid(event);
 167        }
 168}
 169
 170static u32 hisi_sllc_pmu_get_counter_offset(int idx)
 171{
 172        return (SLLC_EVENT_CNT0_L + idx * 8);
 173}
 174
 175static u64 hisi_sllc_pmu_read_counter(struct hisi_pmu *sllc_pmu,
 176                                      struct hw_perf_event *hwc)
 177{
 178        return readq(sllc_pmu->base +
 179                     hisi_sllc_pmu_get_counter_offset(hwc->idx));
 180}
 181
 182static void hisi_sllc_pmu_write_counter(struct hisi_pmu *sllc_pmu,
 183                                        struct hw_perf_event *hwc, u64 val)
 184{
 185        writeq(val, sllc_pmu->base +
 186               hisi_sllc_pmu_get_counter_offset(hwc->idx));
 187}
 188
 189static void hisi_sllc_pmu_write_evtype(struct hisi_pmu *sllc_pmu, int idx,
 190                                       u32 type)
 191{
 192        u32 reg, reg_idx, shift, val;
 193
 194        /*
 195         * Select the appropriate event select register(SLLC_EVENT_TYPE0/1).
 196         * There are 2 event select registers for the 8 hardware counters.
 197         * Event code is 8-bits and for the former 4 hardware counters,
 198         * SLLC_EVENT_TYPE0 is chosen. For the latter 4 hardware counters,
 199         * SLLC_EVENT_TYPE1 is chosen.
 200         */
 201        reg = SLLC_EVENT_TYPE0 + (idx / 4) * 4;
 202        reg_idx = idx % 4;
 203        shift = 8 * reg_idx;
 204
 205        /* Write event code to SLLC_EVENT_TYPEx Register */
 206        val = readl(sllc_pmu->base + reg);
 207        val &= ~(SLLC_EVTYPE_MASK << shift);
 208        val |= (type << shift);
 209        writel(val, sllc_pmu->base + reg);
 210}
 211
 212static void hisi_sllc_pmu_start_counters(struct hisi_pmu *sllc_pmu)
 213{
 214        u32 val;
 215
 216        val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
 217        val |= SLLC_PERF_CTRL_EN;
 218        writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
 219}
 220
 221static void hisi_sllc_pmu_stop_counters(struct hisi_pmu *sllc_pmu)
 222{
 223        u32 val;
 224
 225        val = readl(sllc_pmu->base + SLLC_PERF_CTRL);
 226        val &= ~(SLLC_PERF_CTRL_EN);
 227        writel(val, sllc_pmu->base + SLLC_PERF_CTRL);
 228}
 229
 230static void hisi_sllc_pmu_enable_counter(struct hisi_pmu *sllc_pmu,
 231                                         struct hw_perf_event *hwc)
 232{
 233        u32 val;
 234
 235        val = readl(sllc_pmu->base + SLLC_EVENT_CTRL);
 236        val |= 1 << hwc->idx;
 237        writel(val, sllc_pmu->base + SLLC_EVENT_CTRL);
 238}
 239
 240static void hisi_sllc_pmu_disable_counter(struct hisi_pmu *sllc_pmu,
 241                                          struct hw_perf_event *hwc)
 242{
 243        u32 val;
 244
 245        val = readl(sllc_pmu->base + SLLC_EVENT_CTRL);
 246        val &= ~(1 << hwc->idx);
 247        writel(val, sllc_pmu->base + SLLC_EVENT_CTRL);
 248}
 249
 250static void hisi_sllc_pmu_enable_counter_int(struct hisi_pmu *sllc_pmu,
 251                                             struct hw_perf_event *hwc)
 252{
 253        u32 val;
 254
 255        val = readl(sllc_pmu->base + SLLC_INT_MASK);
 256        /* Write 0 to enable interrupt */
 257        val &= ~(1 << hwc->idx);
 258        writel(val, sllc_pmu->base + SLLC_INT_MASK);
 259}
 260
 261static void hisi_sllc_pmu_disable_counter_int(struct hisi_pmu *sllc_pmu,
 262                                              struct hw_perf_event *hwc)
 263{
 264        u32 val;
 265
 266        val = readl(sllc_pmu->base + SLLC_INT_MASK);
 267        /* Write 1 to mask interrupt */
 268        val |= 1 << hwc->idx;
 269        writel(val, sllc_pmu->base + SLLC_INT_MASK);
 270}
 271
 272static u32 hisi_sllc_pmu_get_int_status(struct hisi_pmu *sllc_pmu)
 273{
 274        return readl(sllc_pmu->base + SLLC_INT_STATUS);
 275}
 276
 277static void hisi_sllc_pmu_clear_int_status(struct hisi_pmu *sllc_pmu, int idx)
 278{
 279        writel(1 << idx, sllc_pmu->base + SLLC_INT_CLEAR);
 280}
 281
 282static const struct acpi_device_id hisi_sllc_pmu_acpi_match[] = {
 283        { "HISI0263", },
 284        {}
 285};
 286MODULE_DEVICE_TABLE(acpi, hisi_sllc_pmu_acpi_match);
 287
 288static int hisi_sllc_pmu_init_data(struct platform_device *pdev,
 289                                   struct hisi_pmu *sllc_pmu)
 290{
 291        /*
 292         * Use the SCCL_ID and the index ID to identify the SLLC PMU,
 293         * while SCCL_ID is from MPIDR_EL1 by CPU.
 294         */
 295        if (device_property_read_u32(&pdev->dev, "hisilicon,scl-id",
 296                                     &sllc_pmu->sccl_id)) {
 297                dev_err(&pdev->dev, "Cannot read sccl-id!\n");
 298                return -EINVAL;
 299        }
 300
 301        if (device_property_read_u32(&pdev->dev, "hisilicon,idx-id",
 302                                     &sllc_pmu->index_id)) {
 303                dev_err(&pdev->dev, "Cannot read idx-id!\n");
 304                return -EINVAL;
 305        }
 306
 307        /* SLLC PMUs only share the same SCCL */
 308        sllc_pmu->ccl_id = -1;
 309
 310        sllc_pmu->base = devm_platform_ioremap_resource(pdev, 0);
 311        if (IS_ERR(sllc_pmu->base)) {
 312                dev_err(&pdev->dev, "ioremap failed for sllc_pmu resource.\n");
 313                return PTR_ERR(sllc_pmu->base);
 314        }
 315
 316        sllc_pmu->identifier = readl(sllc_pmu->base + SLLC_VERSION);
 317
 318        return 0;
 319}
 320
 321static struct attribute *hisi_sllc_pmu_v2_format_attr[] = {
 322        HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
 323        HISI_PMU_FORMAT_ATTR(tgtid_min, "config1:0-10"),
 324        HISI_PMU_FORMAT_ATTR(tgtid_max, "config1:11-21"),
 325        HISI_PMU_FORMAT_ATTR(srcid_cmd, "config1:22-32"),
 326        HISI_PMU_FORMAT_ATTR(srcid_msk, "config1:33-43"),
 327        HISI_PMU_FORMAT_ATTR(tracetag_en, "config1:44"),
 328        NULL
 329};
 330
 331static const struct attribute_group hisi_sllc_pmu_v2_format_group = {
 332        .name = "format",
 333        .attrs = hisi_sllc_pmu_v2_format_attr,
 334};
 335
 336static struct attribute *hisi_sllc_pmu_v2_events_attr[] = {
 337        HISI_PMU_EVENT_ATTR(rx_req,             0x30),
 338        HISI_PMU_EVENT_ATTR(rx_data,            0x31),
 339        HISI_PMU_EVENT_ATTR(tx_req,             0x34),
 340        HISI_PMU_EVENT_ATTR(tx_data,            0x35),
 341        HISI_PMU_EVENT_ATTR(cycles,             0x09),
 342        NULL
 343};
 344
 345static const struct attribute_group hisi_sllc_pmu_v2_events_group = {
 346        .name = "events",
 347        .attrs = hisi_sllc_pmu_v2_events_attr,
 348};
 349
 350static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL);
 351
 352static struct attribute *hisi_sllc_pmu_cpumask_attrs[] = {
 353        &dev_attr_cpumask.attr,
 354        NULL
 355};
 356
 357static const struct attribute_group hisi_sllc_pmu_cpumask_attr_group = {
 358        .attrs = hisi_sllc_pmu_cpumask_attrs,
 359};
 360
 361static struct device_attribute hisi_sllc_pmu_identifier_attr =
 362        __ATTR(identifier, 0444, hisi_uncore_pmu_identifier_attr_show, NULL);
 363
 364static struct attribute *hisi_sllc_pmu_identifier_attrs[] = {
 365        &hisi_sllc_pmu_identifier_attr.attr,
 366        NULL
 367};
 368
 369static const struct attribute_group hisi_sllc_pmu_identifier_group = {
 370        .attrs = hisi_sllc_pmu_identifier_attrs,
 371};
 372
 373static const struct attribute_group *hisi_sllc_pmu_v2_attr_groups[] = {
 374        &hisi_sllc_pmu_v2_format_group,
 375        &hisi_sllc_pmu_v2_events_group,
 376        &hisi_sllc_pmu_cpumask_attr_group,
 377        &hisi_sllc_pmu_identifier_group,
 378        NULL
 379};
 380
 381static const struct hisi_uncore_ops hisi_uncore_sllc_ops = {
 382        .write_evtype           = hisi_sllc_pmu_write_evtype,
 383        .get_event_idx          = hisi_uncore_pmu_get_event_idx,
 384        .start_counters         = hisi_sllc_pmu_start_counters,
 385        .stop_counters          = hisi_sllc_pmu_stop_counters,
 386        .enable_counter         = hisi_sllc_pmu_enable_counter,
 387        .disable_counter        = hisi_sllc_pmu_disable_counter,
 388        .enable_counter_int     = hisi_sllc_pmu_enable_counter_int,
 389        .disable_counter_int    = hisi_sllc_pmu_disable_counter_int,
 390        .write_counter          = hisi_sllc_pmu_write_counter,
 391        .read_counter           = hisi_sllc_pmu_read_counter,
 392        .get_int_status         = hisi_sllc_pmu_get_int_status,
 393        .clear_int_status       = hisi_sllc_pmu_clear_int_status,
 394        .enable_filter          = hisi_sllc_pmu_enable_filter,
 395        .disable_filter         = hisi_sllc_pmu_clear_filter,
 396};
 397
 398static int hisi_sllc_pmu_dev_probe(struct platform_device *pdev,
 399                                   struct hisi_pmu *sllc_pmu)
 400{
 401        int ret;
 402
 403        ret = hisi_sllc_pmu_init_data(pdev, sllc_pmu);
 404        if (ret)
 405                return ret;
 406
 407        ret = hisi_uncore_pmu_init_irq(sllc_pmu, pdev);
 408        if (ret)
 409                return ret;
 410
 411        sllc_pmu->pmu_events.attr_groups = hisi_sllc_pmu_v2_attr_groups;
 412        sllc_pmu->ops = &hisi_uncore_sllc_ops;
 413        sllc_pmu->check_event = SLLC_NR_EVENTS;
 414        sllc_pmu->counter_bits = 64;
 415        sllc_pmu->num_counters = 8;
 416        sllc_pmu->dev = &pdev->dev;
 417        sllc_pmu->on_cpu = -1;
 418
 419        return 0;
 420}
 421
 422static int hisi_sllc_pmu_probe(struct platform_device *pdev)
 423{
 424        struct hisi_pmu *sllc_pmu;
 425        char *name;
 426        int ret;
 427
 428        sllc_pmu = devm_kzalloc(&pdev->dev, sizeof(*sllc_pmu), GFP_KERNEL);
 429        if (!sllc_pmu)
 430                return -ENOMEM;
 431
 432        ret = hisi_sllc_pmu_dev_probe(pdev, sllc_pmu);
 433        if (ret)
 434                return ret;
 435
 436        name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%u_sllc%u",
 437                              sllc_pmu->sccl_id, sllc_pmu->index_id);
 438        if (!name)
 439                return -ENOMEM;
 440
 441        ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
 442                                       &sllc_pmu->node);
 443        if (ret) {
 444                dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
 445                return ret;
 446        }
 447
 448        sllc_pmu->pmu = (struct pmu) {
 449                .module         = THIS_MODULE,
 450                .task_ctx_nr    = perf_invalid_context,
 451                .event_init     = hisi_uncore_pmu_event_init,
 452                .pmu_enable     = hisi_uncore_pmu_enable,
 453                .pmu_disable    = hisi_uncore_pmu_disable,
 454                .add            = hisi_uncore_pmu_add,
 455                .del            = hisi_uncore_pmu_del,
 456                .start          = hisi_uncore_pmu_start,
 457                .stop           = hisi_uncore_pmu_stop,
 458                .read           = hisi_uncore_pmu_read,
 459                .attr_groups    = sllc_pmu->pmu_events.attr_groups,
 460                .capabilities   = PERF_PMU_CAP_NO_EXCLUDE,
 461        };
 462
 463        ret = perf_pmu_register(&sllc_pmu->pmu, name, -1);
 464        if (ret) {
 465                dev_err(sllc_pmu->dev, "PMU register failed, ret = %d\n", ret);
 466                cpuhp_state_remove_instance(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
 467                                            &sllc_pmu->node);
 468                return ret;
 469        }
 470
 471        platform_set_drvdata(pdev, sllc_pmu);
 472
 473        return ret;
 474}
 475
 476static int hisi_sllc_pmu_remove(struct platform_device *pdev)
 477{
 478        struct hisi_pmu *sllc_pmu = platform_get_drvdata(pdev);
 479
 480        perf_pmu_unregister(&sllc_pmu->pmu);
 481        cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
 482                                            &sllc_pmu->node);
 483        return 0;
 484}
 485
 486static struct platform_driver hisi_sllc_pmu_driver = {
 487        .driver = {
 488                .name = "hisi_sllc_pmu",
 489                .acpi_match_table = hisi_sllc_pmu_acpi_match,
 490                .suppress_bind_attrs = true,
 491        },
 492        .probe = hisi_sllc_pmu_probe,
 493        .remove = hisi_sllc_pmu_remove,
 494};
 495
 496static int __init hisi_sllc_pmu_module_init(void)
 497{
 498        int ret;
 499
 500        ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE,
 501                                      "AP_PERF_ARM_HISI_SLLC_ONLINE",
 502                                      hisi_uncore_pmu_online_cpu,
 503                                      hisi_uncore_pmu_offline_cpu);
 504        if (ret) {
 505                pr_err("SLLC PMU: cpuhp state setup failed, ret = %d\n", ret);
 506                return ret;
 507        }
 508
 509        ret = platform_driver_register(&hisi_sllc_pmu_driver);
 510        if (ret)
 511                cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE);
 512
 513        return ret;
 514}
 515module_init(hisi_sllc_pmu_module_init);
 516
 517static void __exit hisi_sllc_pmu_module_exit(void)
 518{
 519        platform_driver_unregister(&hisi_sllc_pmu_driver);
 520        cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE);
 521}
 522module_exit(hisi_sllc_pmu_module_exit);
 523
 524MODULE_DESCRIPTION("HiSilicon SLLC uncore PMU driver");
 525MODULE_LICENSE("GPL v2");
 526MODULE_AUTHOR("Shaokun Zhang <zhangshaokun@hisilicon.com>");
 527MODULE_AUTHOR("Qi Liu <liuqi115@huawei.com>");
 528