The L3 cache PMU use N-N SPI interrupt which has no support in kernel mainline. So use hrtimer to poll and update event counter to avoid overflow condition for L3 cache PMU. A interval of 10 seconds is used for the hrtimer. The time interval can be configured in the sysfs.
Signed-off-by: Dikshit N <dikshi...@huawei.com> Signed-off-by: Anurup M <anuru...@huawei.com> --- drivers/perf/hisilicon/hisi_uncore_l3c.c | 44 +++++++++++++++ drivers/perf/hisilicon/hisi_uncore_pmu.c | 95 ++++++++++++++++++++++++++++++++ drivers/perf/hisilicon/hisi_uncore_pmu.h | 17 ++++++ 3 files changed, 156 insertions(+) diff --git a/drivers/perf/hisilicon/hisi_uncore_l3c.c b/drivers/perf/hisilicon/hisi_uncore_l3c.c index 5c6bea0..d211020 100644 --- a/drivers/perf/hisilicon/hisi_uncore_l3c.c +++ b/drivers/perf/hisilicon/hisi_uncore_l3c.c @@ -20,6 +20,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/bitmap.h> +#include <linux/hrtimer.h> +#include <linux/ktime.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -53,6 +55,22 @@ enum armv8_hisi_l3c_counters { #define L3C_CNT0_REG_OFF 0x170 #define L3C_EVENT_EN 0x1000000 +/* + * Default timer frequency to poll and avoid counter overflow. + * CPU speed = 2.4Ghz, Therefore Access time = 0.4ns + * L1 cache - 2 way set associative + * L2 - 16 way set associative + * L3 - 16 way set associative. L3 cache has 4 banks. + * + * Overflow time = 2^31 * (acces time L1 + access time L2 + access time L3) + * = 2^31 * ((2 * 0.4ns) + (16 * 0.4ns) + (4 * 16 * 0.4ns)) = 70 seconds + * + * L3 cache is also used by devices like PCIe, SAS etc. at + * the same time. So the overflow time could be even smaller. + * So on a safe side we use a timer interval of 10sec + */ +#define L3C_HRTIMER_INTERVAL (10LL * MSEC_PER_SEC) + #define GET_MODULE_ID(hwmod_data) hwmod_data->l3c_hwcfg.module_id #define GET_BANK_SEL(hwmod_data) hwmod_data->l3c_hwcfg.bank_select @@ -467,6 +485,18 @@ static const struct attribute_group hisi_l3c_attr_group = { static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL); +static DEVICE_ATTR(hrtimer_interval, 0644, hisi_hrtimer_interval_sysfs_show, + hisi_hrtimer_interval_sysfs_store); + +static struct attribute *hisi_l3c_hrtimer_interval_attrs[] = { + &dev_attr_hrtimer_interval.attr, + NULL, +}; + +static const struct attribute_group hisi_l3c_hrtimer_interval_attr_group = { + .attrs = hisi_l3c_hrtimer_interval_attrs, +}; + static struct attribute *hisi_l3c_cpumask_attrs[] = { &dev_attr_cpumask.attr, NULL, @@ -481,6 +511,7 @@ static const struct attribute_group *hisi_l3c_pmu_attr_groups[] = { &hisi_l3c_format_group, &hisi_l3c_events_group, &hisi_l3c_cpumask_attr_group, + &hisi_l3c_hrtimer_interval_attr_group, NULL, }; @@ -496,6 +527,15 @@ static struct hisi_uncore_ops hisi_uncore_l3c_ops = { .write_counter = hisi_l3c_write_counter, }; +/* Initialize hrtimer to poll for avoiding counter overflow */ +static void hisi_l3c_hrtimer_init(struct hisi_pmu *l3c_pmu) +{ + INIT_LIST_HEAD(&l3c_pmu->active_list); + l3c_pmu->ops->start_hrtimer = hisi_hrtimer_start; + l3c_pmu->ops->stop_hrtimer = hisi_hrtimer_stop; + hisi_hrtimer_init(l3c_pmu, L3C_HRTIMER_INTERVAL); +} + static int hisi_l3c_pmu_init(struct hisi_pmu *l3c_pmu, struct hisi_djtag_client *client) { @@ -505,6 +545,7 @@ static int hisi_l3c_pmu_init(struct hisi_pmu *l3c_pmu, l3c_pmu->num_events = HISI_HWEVENT_L3C_EVENT_MAX; l3c_pmu->num_counters = HISI_IDX_L3C_COUNTER_MAX; + l3c_pmu->num_active = 0; l3c_pmu->scl_id = hisi_djtag_get_sclid(client); l3c_pmu->name = kasprintf(GFP_KERNEL, "hisi_l3c%u_%u", @@ -515,6 +556,9 @@ static int hisi_l3c_pmu_init(struct hisi_pmu *l3c_pmu, /* Pick one core to use for cpumask attributes */ cpumask_set_cpu(smp_processor_id(), &l3c_pmu->cpu); + /* Use hrtimer to poll for avoiding counter overflow */ + hisi_l3c_hrtimer_init(l3c_pmu); + return 0; } diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c index 200e673e..377e1bc 100644 --- a/drivers/perf/hisilicon/hisi_uncore_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c @@ -66,6 +66,83 @@ ssize_t hisi_cpumask_sysfs_show(struct device *dev, return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->cpu); } +/* + * sysfs hrtimer_interval attributes + */ +ssize_t hisi_hrtimer_interval_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); + + if (hisi_pmu->hrt_duration) + return sprintf(buf, "%llu\n", + hisi_pmu->hrt_duration); + return 0; +} + +ssize_t hisi_hrtimer_interval_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); + + if (kstrtoull(buf, 0, &hisi_pmu->hrt_duration) < 0) + return -EINVAL; + return count; +} + +/* The counter overflow IRQ is not supported for some PMUs + * use hrtimer to periodically poll and avoid overflow + */ +static enum hrtimer_restart hisi_hrtimer_callback(struct hrtimer *hrtimer) +{ + struct hisi_pmu *hisi_pmu = container_of(hrtimer, + struct hisi_pmu, hrtimer); + struct perf_event *event; + struct hw_perf_event *hwc; + unsigned long flags; + + /* Return if no active events */ + if (!hisi_pmu->num_active) + return HRTIMER_NORESTART; + + local_irq_save(flags); + + /* Update event count for each active event */ + list_for_each_entry(event, &hisi_pmu->active_list, active_entry) { + hwc = &event->hw; + /* Read hardware counter and update the Perf event counter */ + hisi_pmu->ops->event_update(event, hwc, GET_CNTR_IDX(hwc)); + } + + local_irq_restore(flags); + hrtimer_forward_now(hrtimer, ms_to_ktime(hisi_pmu->hrt_duration)); + return HRTIMER_RESTART; +} + +void hisi_hrtimer_init(struct hisi_pmu *hisi_pmu, u64 timer_interval) +{ + /* hr timer clock initalization */ + hrtimer_init(&hisi_pmu->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hisi_pmu->hrtimer.function = &hisi_hrtimer_callback; + hisi_pmu->hrt_duration = timer_interval; +} + +void hisi_hrtimer_start(struct hisi_pmu *hisi_pmu) +{ + hrtimer_start(&hisi_pmu->hrtimer, + ms_to_ktime(hisi_pmu->hrt_duration), + HRTIMER_MODE_REL_PINNED); +} + +void hisi_hrtimer_stop(struct hisi_pmu *hisi_pmu) +{ + hrtimer_cancel(&hisi_pmu->hrtimer); +} + /* djtag read interface - Call djtag driver to access SoC registers */ int hisi_djtag_readreg(int module_id, int bank, u32 offset, struct hisi_djtag_client *client, u32 *value) @@ -268,6 +345,15 @@ void hisi_uncore_pmu_start(struct perf_event *event, int flags) (u32)prev_raw_count); } + /* Start hrtimer when the first event is started in this PMU */ + if (hisi_pmu->ops->start_hrtimer) { + hisi_pmu->num_active++; + list_add_tail(&event->active_entry, &hisi_pmu->active_list); + + if (hisi_pmu->num_active == 1) + hisi_pmu->ops->start_hrtimer(hisi_pmu); + } + hisi_uncore_pmu_enable_event(event); perf_event_update_userpage(event); } @@ -281,6 +367,15 @@ void hisi_uncore_pmu_stop(struct perf_event *event, int flags) WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); hwc->state |= PERF_HES_STOPPED; + /* Stop hrtimer when the last event is stopped in this PMU */ + if (hisi_pmu->ops->stop_hrtimer) { + hisi_pmu->num_active--; + list_del(&event->active_entry); + + if (hisi_pmu->num_active == 0) + hisi_pmu->ops->stop_hrtimer(hisi_pmu); + } + if (hwc->state & PERF_HES_UPTODATE) return; diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.h b/drivers/perf/hisilicon/hisi_uncore_pmu.h index 785618b..4a92585 100644 --- a/drivers/perf/hisilicon/hisi_uncore_pmu.h +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.h @@ -77,13 +77,20 @@ struct hisi_uncore_ops { void (*disable_counter)(struct hisi_pmu *, int); void (*start_counters)(struct hisi_pmu *); void (*stop_counters)(struct hisi_pmu *); + void (*start_hrtimer)(struct hisi_pmu *); + void (*stop_hrtimer)(struct hisi_pmu *); }; /* Generic pmu struct for different pmu types */ struct hisi_pmu { const char *name; struct perf_event **hw_perf_events; + struct list_head active_list; /* Active events list */ struct hisi_uncore_ops *ops; + struct hrtimer hrtimer; /* hrtimer to handle the + * counter overflow + */ + u64 hrt_duration; /* hrtimer timeout */ struct device *dev; void *hwmod_data; /* Hardware module specific data */ cpumask_t cpu; @@ -92,6 +99,7 @@ struct hisi_pmu { u32 scl_id; int num_counters; int num_events; + int num_active; }; void hisi_uncore_pmu_read(struct perf_event *event); @@ -111,6 +119,15 @@ ssize_t hisi_format_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf); ssize_t hisi_cpumask_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t hisi_hrtimer_interval_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf); +ssize_t hisi_hrtimer_interval_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +void hisi_hrtimer_init(struct hisi_pmu *hisi_pmu, u64 timer_interval); +void hisi_hrtimer_start(struct hisi_pmu *hisi_pmu); +void hisi_hrtimer_stop(struct hisi_pmu *hisi_pmu); int hisi_djtag_readreg(int module_id, int bank, u32 offset, struct hisi_djtag_client *client, u32 *value); -- 2.1.4