| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * StarFive's StarLink PMU driver
- *
- * Copyright (C) 2023 StarFive Technology Co., Ltd.
- *
- * Author: Ji Sheng Teoh <jisheng.teoh@starfivetech.com>
- *
- */
- #define STARLINK_PMU_PDEV_NAME "starfive_starlink_pmu"
- #define pr_fmt(fmt) STARLINK_PMU_PDEV_NAME ": " fmt
- #include <linux/bitmap.h>
- #include <linux/cpu_pm.h>
- #include <linux/io.h>
- #include <linux/irq.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/mod_devicetable.h>
- #include <linux/perf_event.h>
- #include <linux/platform_device.h>
- #include <linux/sysfs.h>
- #define STARLINK_PMU_MAX_COUNTERS 64
- #define STARLINK_PMU_NUM_COUNTERS 16
- #define STARLINK_PMU_IDX_CYCLE_COUNTER 63
- #define STARLINK_PMU_EVENT_SELECT 0x060
- #define STARLINK_PMU_EVENT_COUNTER 0x160
- #define STARLINK_PMU_COUNTER_MASK GENMASK_ULL(63, 0)
- #define STARLINK_PMU_CYCLE_COUNTER 0x058
- #define STARLINK_PMU_CONTROL 0x040
- #define STARLINK_PMU_GLOBAL_ENABLE BIT_ULL(0)
- #define STARLINK_PMU_INTERRUPT_ENABLE 0x050
- #define STARLINK_PMU_COUNTER_OVERFLOW_STATUS 0x048
- #define STARLINK_PMU_CYCLE_OVERFLOW_MASK BIT_ULL(63)
- #define STARLINK_CYCLES 0x058
- #define CACHE_READ_REQUEST 0x04000701
- #define CACHE_WRITE_REQUEST 0x03000001
- #define CACHE_RELEASE_REQUEST 0x0003e001
- #define CACHE_READ_HIT 0x00901202
- #define CACHE_READ_MISS 0x04008002
- #define CACHE_WRITE_HIT 0x006c0002
- #define CACHE_WRITE_MISS 0x03000002
- #define CACHE_WRITEBACK 0x00000403
- #define to_starlink_pmu(p) (container_of(p, struct starlink_pmu, pmu))
- #define STARLINK_FORMAT_ATTR(_name, _config) \
- (&((struct dev_ext_attribute[]) { \
- { .attr = __ATTR(_name, 0444, starlink_pmu_sysfs_format_show, NULL), \
- .var = (void *)_config, } \
- })[0].attr.attr)
- #define STARLINK_EVENT_ATTR(_name, _id) \
- PMU_EVENT_ATTR_ID(_name, starlink_pmu_sysfs_event_show, _id)
- static int starlink_pmu_cpuhp_state;
- struct starlink_hw_events {
- struct perf_event *events[STARLINK_PMU_MAX_COUNTERS];
- DECLARE_BITMAP(used_mask, STARLINK_PMU_MAX_COUNTERS);
- };
- struct starlink_pmu {
- struct pmu pmu;
- struct starlink_hw_events __percpu *hw_events;
- struct hlist_node node;
- struct notifier_block starlink_pmu_pm_nb;
- void __iomem *pmu_base;
- cpumask_t cpumask;
- int irq;
- };
- static ssize_t
- starlink_pmu_sysfs_format_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
- {
- struct dev_ext_attribute *eattr = container_of(attr,
- struct dev_ext_attribute, attr);
- return sysfs_emit(buf, "%s\n", (char *)eattr->var);
- }
- static struct attribute *starlink_pmu_format_attrs[] = {
- STARLINK_FORMAT_ATTR(event, "config:0-31"),
- NULL
- };
- static const struct attribute_group starlink_pmu_format_attr_group = {
- .name = "format",
- .attrs = starlink_pmu_format_attrs,
- };
- static ssize_t
- starlink_pmu_sysfs_event_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
- {
- struct perf_pmu_events_attr *eattr = container_of(attr,
- struct perf_pmu_events_attr, attr);
- return sysfs_emit(buf, "event=0x%02llx\n", eattr->id);
- }
- static struct attribute *starlink_pmu_event_attrs[] = {
- STARLINK_EVENT_ATTR(cycles, STARLINK_CYCLES),
- STARLINK_EVENT_ATTR(read_request, CACHE_READ_REQUEST),
- STARLINK_EVENT_ATTR(write_request, CACHE_WRITE_REQUEST),
- STARLINK_EVENT_ATTR(release_request, CACHE_RELEASE_REQUEST),
- STARLINK_EVENT_ATTR(read_hit, CACHE_READ_HIT),
- STARLINK_EVENT_ATTR(read_miss, CACHE_READ_MISS),
- STARLINK_EVENT_ATTR(write_hit, CACHE_WRITE_HIT),
- STARLINK_EVENT_ATTR(write_miss, CACHE_WRITE_MISS),
- STARLINK_EVENT_ATTR(writeback, CACHE_WRITEBACK),
- NULL
- };
- static const struct attribute_group starlink_pmu_events_attr_group = {
- .name = "events",
- .attrs = starlink_pmu_event_attrs,
- };
- static ssize_t
- cpumask_show(struct device *dev, struct device_attribute *attr, char *buf)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(dev_get_drvdata(dev));
- return cpumap_print_to_pagebuf(true, buf, &starlink_pmu->cpumask);
- }
- static DEVICE_ATTR_RO(cpumask);
- static struct attribute *starlink_pmu_cpumask_attrs[] = {
- &dev_attr_cpumask.attr,
- NULL
- };
- static const struct attribute_group starlink_pmu_cpumask_attr_group = {
- .attrs = starlink_pmu_cpumask_attrs,
- };
- static const struct attribute_group *starlink_pmu_attr_groups[] = {
- &starlink_pmu_format_attr_group,
- &starlink_pmu_events_attr_group,
- &starlink_pmu_cpumask_attr_group,
- NULL
- };
- static void starlink_pmu_set_event_period(struct perf_event *event)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct hw_perf_event *hwc = &event->hw;
- int idx = event->hw.idx;
- /*
- * Program counter to half of it's max count to handle
- * cases of extreme interrupt latency.
- */
- u64 val = STARLINK_PMU_COUNTER_MASK >> 1;
- local64_set(&hwc->prev_count, val);
- if (hwc->config == STARLINK_CYCLES)
- writeq(val, starlink_pmu->pmu_base + STARLINK_PMU_CYCLE_COUNTER);
- else
- writeq(val, starlink_pmu->pmu_base + STARLINK_PMU_EVENT_COUNTER +
- idx * sizeof(u64));
- }
- static void starlink_pmu_counter_start(struct perf_event *event,
- struct starlink_pmu *starlink_pmu)
- {
- struct hw_perf_event *hwc = &event->hw;
- int idx = event->hw.idx;
- u64 val;
- /*
- * Enable counter overflow interrupt[63:0],
- * which is mapped as follow:
- *
- * event counter 0 - Bit [0]
- * event counter 1 - Bit [1]
- * ...
- * cycle counter - Bit [63]
- */
- val = readq(starlink_pmu->pmu_base + STARLINK_PMU_INTERRUPT_ENABLE);
- if (hwc->config == STARLINK_CYCLES) {
- /*
- * Cycle count has its dedicated register, and it starts
- * counting as soon as STARLINK_PMU_GLOBAL_ENABLE is set.
- */
- val |= STARLINK_PMU_CYCLE_OVERFLOW_MASK;
- } else {
- writeq(event->hw.config, starlink_pmu->pmu_base +
- STARLINK_PMU_EVENT_SELECT + idx * sizeof(u64));
- val |= BIT_ULL(idx);
- }
- writeq(val, starlink_pmu->pmu_base + STARLINK_PMU_INTERRUPT_ENABLE);
- writeq(STARLINK_PMU_GLOBAL_ENABLE, starlink_pmu->pmu_base +
- STARLINK_PMU_CONTROL);
- }
- static void starlink_pmu_counter_stop(struct perf_event *event,
- struct starlink_pmu *starlink_pmu)
- {
- struct hw_perf_event *hwc = &event->hw;
- int idx = event->hw.idx;
- u64 val;
- val = readq(starlink_pmu->pmu_base + STARLINK_PMU_CONTROL);
- val &= ~STARLINK_PMU_GLOBAL_ENABLE;
- writeq(val, starlink_pmu->pmu_base + STARLINK_PMU_CONTROL);
- val = readq(starlink_pmu->pmu_base + STARLINK_PMU_INTERRUPT_ENABLE);
- if (hwc->config == STARLINK_CYCLES)
- val &= ~STARLINK_PMU_CYCLE_OVERFLOW_MASK;
- else
- val &= ~BIT_ULL(idx);
- writeq(val, starlink_pmu->pmu_base + STARLINK_PMU_INTERRUPT_ENABLE);
- }
- static void starlink_pmu_update(struct perf_event *event)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct hw_perf_event *hwc = &event->hw;
- int idx = hwc->idx;
- u64 prev_raw_count, new_raw_count;
- u64 oldval;
- u64 delta;
- do {
- prev_raw_count = local64_read(&hwc->prev_count);
- if (hwc->config == STARLINK_CYCLES)
- new_raw_count = readq(starlink_pmu->pmu_base +
- STARLINK_PMU_CYCLE_COUNTER);
- else
- new_raw_count = readq(starlink_pmu->pmu_base +
- STARLINK_PMU_EVENT_COUNTER +
- idx * sizeof(u64));
- oldval = local64_cmpxchg(&hwc->prev_count, prev_raw_count,
- new_raw_count);
- } while (oldval != prev_raw_count);
- delta = (new_raw_count - prev_raw_count) & STARLINK_PMU_COUNTER_MASK;
- local64_add(delta, &event->count);
- }
- static void starlink_pmu_start(struct perf_event *event, int flags)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct hw_perf_event *hwc = &event->hw;
- if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED)))
- return;
- if (flags & PERF_EF_RELOAD)
- WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE));
- hwc->state = 0;
- starlink_pmu_set_event_period(event);
- starlink_pmu_counter_start(event, starlink_pmu);
- perf_event_update_userpage(event);
- }
- static void starlink_pmu_stop(struct perf_event *event, int flags)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct hw_perf_event *hwc = &event->hw;
- if (hwc->state & PERF_HES_STOPPED)
- return;
- starlink_pmu_counter_stop(event, starlink_pmu);
- starlink_pmu_update(event);
- hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
- }
- static int starlink_pmu_add(struct perf_event *event, int flags)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct starlink_hw_events *hw_events =
- this_cpu_ptr(starlink_pmu->hw_events);
- struct hw_perf_event *hwc = &event->hw;
- unsigned long *used_mask = hw_events->used_mask;
- u32 n_events = STARLINK_PMU_NUM_COUNTERS;
- int idx;
- /*
- * Cycle counter has dedicated register to hold counter value.
- * Event other than cycle count has to be enabled through
- * event select register, and assigned with independent counter
- * as they appear.
- */
- if (hwc->config == STARLINK_CYCLES) {
- idx = STARLINK_PMU_IDX_CYCLE_COUNTER;
- } else {
- idx = find_first_zero_bit(used_mask, n_events);
- /* All counter are in use */
- if (idx < 0)
- return idx;
- set_bit(idx, used_mask);
- }
- hwc->idx = idx;
- hw_events->events[idx] = event;
- hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
- if (flags & PERF_EF_START)
- starlink_pmu_start(event, PERF_EF_RELOAD);
- perf_event_update_userpage(event);
- return 0;
- }
- static void starlink_pmu_del(struct perf_event *event, int flags)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct starlink_hw_events *hw_events =
- this_cpu_ptr(starlink_pmu->hw_events);
- struct hw_perf_event *hwc = &event->hw;
- starlink_pmu_stop(event, PERF_EF_UPDATE);
- hw_events->events[hwc->idx] = NULL;
- clear_bit(hwc->idx, hw_events->used_mask);
- perf_event_update_userpage(event);
- }
- static bool starlink_pmu_validate_event_group(struct perf_event *event)
- {
- struct perf_event *leader = event->group_leader;
- struct perf_event *sibling;
- int counter = 1;
- /*
- * Ensure hardware events in the group are on the same PMU,
- * software events are acceptable.
- */
- if (event->group_leader->pmu != event->pmu &&
- !is_software_event(event->group_leader))
- return false;
- for_each_sibling_event(sibling, leader) {
- if (sibling->pmu != event->pmu && !is_software_event(sibling))
- return false;
- counter++;
- }
- return counter <= STARLINK_PMU_NUM_COUNTERS;
- }
- static int starlink_pmu_event_init(struct perf_event *event)
- {
- struct starlink_pmu *starlink_pmu = to_starlink_pmu(event->pmu);
- struct hw_perf_event *hwc = &event->hw;
- /*
- * Sampling is not supported, as counters are shared
- * by all CPU.
- */
- if (hwc->sample_period)
- return -EOPNOTSUPP;
- /*
- * Per-task and attach to a task are not supported,
- * as uncore events are not specific to any CPU.
- */
- if (event->cpu < 0 || event->attach_state & PERF_ATTACH_TASK)
- return -EOPNOTSUPP;
- if (!starlink_pmu_validate_event_group(event))
- return -EINVAL;
- hwc->idx = -1;
- hwc->config = event->attr.config;
- event->cpu = cpumask_first(&starlink_pmu->cpumask);
- return 0;
- }
- static irqreturn_t starlink_pmu_handle_irq(int irq_num, void *data)
- {
- struct starlink_pmu *starlink_pmu = data;
- struct starlink_hw_events *hw_events =
- this_cpu_ptr(starlink_pmu->hw_events);
- bool handled = false;
- int idx;
- u64 overflow_status;
- for (idx = 0; idx < STARLINK_PMU_MAX_COUNTERS; idx++) {
- struct perf_event *event = hw_events->events[idx];
- if (!event)
- continue;
- overflow_status = readq(starlink_pmu->pmu_base +
- STARLINK_PMU_COUNTER_OVERFLOW_STATUS);
- if (!(overflow_status & BIT_ULL(idx)))
- continue;
- writeq(BIT_ULL(idx), starlink_pmu->pmu_base +
- STARLINK_PMU_COUNTER_OVERFLOW_STATUS);
- starlink_pmu_update(event);
- starlink_pmu_set_event_period(event);
- handled = true;
- }
- return IRQ_RETVAL(handled);
- }
- static int starlink_setup_irqs(struct starlink_pmu *starlink_pmu,
- struct platform_device *pdev)
- {
- int ret, irq;
- irq = platform_get_irq(pdev, 0);
- if (irq < 0)
- return -EINVAL;
- ret = devm_request_irq(&pdev->dev, irq, starlink_pmu_handle_irq,
- 0, STARLINK_PMU_PDEV_NAME, starlink_pmu);
- if (ret)
- return dev_err_probe(&pdev->dev, ret, "Failed to request IRQ\n");
- starlink_pmu->irq = irq;
- return 0;
- }
- static int starlink_pmu_pm_notify(struct notifier_block *b,
- unsigned long cmd, void *v)
- {
- struct starlink_pmu *starlink_pmu = container_of(b, struct starlink_pmu,
- starlink_pmu_pm_nb);
- struct starlink_hw_events *hw_events =
- this_cpu_ptr(starlink_pmu->hw_events);
- bool enabled = !bitmap_empty(hw_events->used_mask, STARLINK_PMU_MAX_COUNTERS);
- struct perf_event *event;
- int idx;
- if (!enabled)
- return NOTIFY_OK;
- for (idx = 0; idx < STARLINK_PMU_MAX_COUNTERS; idx++) {
- event = hw_events->events[idx];
- if (!event)
- continue;
- switch (cmd) {
- case CPU_PM_ENTER:
- /* Stop and update the counter */
- starlink_pmu_stop(event, PERF_EF_UPDATE);
- break;
- case CPU_PM_EXIT:
- case CPU_PM_ENTER_FAILED:
- /* Restore and enable the counter */
- starlink_pmu_start(event, PERF_EF_RELOAD);
- break;
- default:
- break;
- }
- }
- return NOTIFY_OK;
- }
- static int starlink_pmu_pm_register(struct starlink_pmu *starlink_pmu)
- {
- if (!IS_ENABLED(CONFIG_CPU_PM))
- return 0;
- starlink_pmu->starlink_pmu_pm_nb.notifier_call = starlink_pmu_pm_notify;
- return cpu_pm_register_notifier(&starlink_pmu->starlink_pmu_pm_nb);
- }
- static void starlink_pmu_pm_unregister(struct starlink_pmu *starlink_pmu)
- {
- if (!IS_ENABLED(CONFIG_CPU_PM))
- return;
- cpu_pm_unregister_notifier(&starlink_pmu->starlink_pmu_pm_nb);
- }
- static void starlink_pmu_destroy(struct starlink_pmu *starlink_pmu)
- {
- starlink_pmu_pm_unregister(starlink_pmu);
- cpuhp_state_remove_instance(starlink_pmu_cpuhp_state,
- &starlink_pmu->node);
- }
- static int starlink_pmu_probe(struct platform_device *pdev)
- {
- struct starlink_pmu *starlink_pmu;
- struct starlink_hw_events *hw_events;
- struct resource *res;
- int cpuid, i, ret;
- starlink_pmu = devm_kzalloc(&pdev->dev, sizeof(*starlink_pmu), GFP_KERNEL);
- if (!starlink_pmu)
- return -ENOMEM;
- starlink_pmu->pmu_base =
- devm_platform_get_and_ioremap_resource(pdev, 0, &res);
- if (IS_ERR(starlink_pmu->pmu_base))
- return PTR_ERR(starlink_pmu->pmu_base);
- starlink_pmu->hw_events = alloc_percpu_gfp(struct starlink_hw_events,
- GFP_KERNEL);
- if (!starlink_pmu->hw_events) {
- dev_err(&pdev->dev, "Failed to allocate per-cpu PMU data\n");
- return -ENOMEM;
- }
- for_each_possible_cpu(cpuid) {
- hw_events = per_cpu_ptr(starlink_pmu->hw_events, cpuid);
- for (i = 0; i < STARLINK_PMU_MAX_COUNTERS; i++)
- hw_events->events[i] = NULL;
- }
- ret = starlink_setup_irqs(starlink_pmu, pdev);
- if (ret)
- return ret;
- ret = cpuhp_state_add_instance(starlink_pmu_cpuhp_state,
- &starlink_pmu->node);
- if (ret) {
- dev_err(&pdev->dev, "Failed to register hotplug\n");
- return ret;
- }
- ret = starlink_pmu_pm_register(starlink_pmu);
- if (ret) {
- cpuhp_state_remove_instance(starlink_pmu_cpuhp_state,
- &starlink_pmu->node);
- return ret;
- }
- starlink_pmu->pmu = (struct pmu) {
- .task_ctx_nr = perf_invalid_context,
- .event_init = starlink_pmu_event_init,
- .add = starlink_pmu_add,
- .del = starlink_pmu_del,
- .start = starlink_pmu_start,
- .stop = starlink_pmu_stop,
- .read = starlink_pmu_update,
- .attr_groups = starlink_pmu_attr_groups,
- };
- ret = perf_pmu_register(&starlink_pmu->pmu, STARLINK_PMU_PDEV_NAME, -1);
- if (ret)
- starlink_pmu_destroy(starlink_pmu);
- return ret;
- }
- static const struct of_device_id starlink_pmu_of_match[] = {
- { .compatible = "starfive,jh8100-starlink-pmu" },
- {}
- };
- MODULE_DEVICE_TABLE(of, starlink_pmu_of_match);
- static struct platform_driver starlink_pmu_driver = {
- .driver = {
- .name = STARLINK_PMU_PDEV_NAME,
- .of_match_table = starlink_pmu_of_match,
- .suppress_bind_attrs = true,
- },
- .probe = starlink_pmu_probe,
- };
- static int
- starlink_pmu_online_cpu(unsigned int cpu, struct hlist_node *node)
- {
- struct starlink_pmu *starlink_pmu = hlist_entry_safe(node,
- struct starlink_pmu,
- node);
- if (cpumask_empty(&starlink_pmu->cpumask))
- cpumask_set_cpu(cpu, &starlink_pmu->cpumask);
- WARN_ON(irq_set_affinity(starlink_pmu->irq, cpumask_of(cpu)));
- return 0;
- }
- static int
- starlink_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
- {
- struct starlink_pmu *starlink_pmu = hlist_entry_safe(node,
- struct starlink_pmu,
- node);
- unsigned int target;
- if (!cpumask_test_and_clear_cpu(cpu, &starlink_pmu->cpumask))
- return 0;
- target = cpumask_any_but(cpu_online_mask, cpu);
- if (target >= nr_cpu_ids)
- return 0;
- perf_pmu_migrate_context(&starlink_pmu->pmu, cpu, target);
- cpumask_set_cpu(target, &starlink_pmu->cpumask);
- WARN_ON(irq_set_affinity(starlink_pmu->irq, cpumask_of(target)));
- return 0;
- }
- static int __init starlink_pmu_init(void)
- {
- int ret;
- ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
- "soc/starfive/starlink_pmu:online",
- starlink_pmu_online_cpu,
- starlink_pmu_offline_cpu);
- if (ret < 0)
- return ret;
- starlink_pmu_cpuhp_state = ret;
- return platform_driver_register(&starlink_pmu_driver);
- }
- device_initcall(starlink_pmu_init);
|