643 lines
17 KiB
C
643 lines
17 KiB
C
|
// 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);
|
||
|
int enabled = bitmap_weight(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);
|