From: Shaokun Zhang <zhangshaokun@xxxxxxxxxxxxx> 1. Add support to count MN hardware events. 2. Mn events are listed in sysfs at /sys/devices/hisi_mn2/events/ The events can be selected as shown in perf list e.g.: For MN_READ_REQUEST event for Super CPU cluster 2 the event format is -e "hisi_mn2/read_req/" Signed-off-by: Shaokun Zhang <zhangshaokun@xxxxxxxxxxxxx> Signed-off-by: Anurup M <anurup.m@xxxxxxxxxx> --- drivers/perf/hisilicon/Makefile | 2 +- drivers/perf/hisilicon/hisi_uncore_mn.c | 571 ++++++++++++++++++++++++++++++++ drivers/perf/hisilicon/hisi_uncore_mn.h | 68 ++++ 3 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 drivers/perf/hisilicon/hisi_uncore_mn.c create mode 100644 drivers/perf/hisilicon/hisi_uncore_mn.h diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile index e1766cf..8975104 100644 --- a/drivers/perf/hisilicon/Makefile +++ b/drivers/perf/hisilicon/Makefile @@ -1 +1 @@ -obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c.o +obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c.o hisi_uncore_mn.o diff --git a/drivers/perf/hisilicon/hisi_uncore_mn.c b/drivers/perf/hisilicon/hisi_uncore_mn.c new file mode 100644 index 0000000..920e346 --- /dev/null +++ b/drivers/perf/hisilicon/hisi_uncore_mn.c @@ -0,0 +1,571 @@ +/* + * HiSilicon SoC MN Hardware event counters support + * + * Copyright (C) 2016 Huawei Technologies Limited + * Author: Shaokun Zhang <zhangshaokun@xxxxxxxxxxxxx> + * + * This code is based on the uncore PMU's like arm-cci and + * arm-ccn. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/bitmap.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/perf_event.h> +#include "hisi_uncore_mn.h" + +static inline int hisi_mn_counter_valid(int idx) +{ + return (idx >= HISI_IDX_MN_COUNTER0 && + idx <= HISI_IDX_MN_COUNTER_MAX); +} + +static u32 hisi_read_mn_counter(struct hisi_mn_data *mn_hwmod_data, + int idx) +{ + u32 module_id = mn_hwmod_data->mn_hwcfg.module_id; + struct hisi_djtag_client *client = mn_hwmod_data->client; + u32 cfg_en, reg_offset, value; + + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + reg_offset = mn_hwmod_data->mn_hwcfg.counter_reg0_off + (idx * 4); + + hisi_djtag_readreg(module_id, + cfg_en, + reg_offset, + client, &value); + + return value; +} + +static u64 hisi_mn_event_update(struct perf_event *event, + struct hw_perf_event *hwc, int idx) +{ + struct hisi_pmu *pmn_pmu = to_hisi_pmu(event->pmu); + struct hisi_mn_data *mn_hwmod_data; + u64 delta, prev_raw_count, new_raw_count = 0; + u32 cfg_en; + + if (!hisi_mn_counter_valid(idx)) { + dev_err(pmn_pmu->dev, + "Unsupported event index:%d!\n", idx); + return 0; + } + + mn_hwmod_data = pmn_pmu->hwmod_data; + + /* Check if the MN data is initialized for this SCCL */ + if (!mn_hwmod_data->client) { + dev_err(pmn_pmu->dev, + "SCL=%d not initialized!\n", pmn_pmu->scl_id); + return 0; + } + + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + + do { + prev_raw_count = local64_read(&hwc->prev_count); + new_raw_count = + hisi_read_mn_counter(mn_hwmod_data, idx); + delta = (new_raw_count - prev_raw_count) & + HISI_MAX_PERIOD; + + local64_add(delta, &event->count); + } while (local64_cmpxchg( + &hwc->prev_count, prev_raw_count, new_raw_count) != + prev_raw_count); + + return new_raw_count; +} + +static void hisi_set_mn_evtype(struct hisi_pmu *pmn_pmu, int idx, u32 val) +{ + struct hisi_djtag_client *client; + struct hisi_mn_data *mn_hwmod_data = pmn_pmu->hwmod_data; + u32 reg_offset = mn_hwmod_data->mn_hwcfg.evtype_reg0_off; + u32 module_id = mn_hwmod_data->mn_hwcfg.module_id; + u32 cfg_en, event_value, value = 0; + + event_value = (val - + HISI_HWEVENT_MN_EO_BARR_REQ); + + /* Value to write to event type register */ + val = event_value << (8 * idx); + + /* Find the djtag Identifier of the Unit */ + client = mn_hwmod_data->client; + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + + /* + * Set the event in MN_EVENT_TYPE Register + */ + hisi_djtag_readreg(module_id, + cfg_en, + reg_offset, + client, &value); + + value &= ~(0xff << (8 * idx)); + value |= val; + + hisi_djtag_writereg(module_id, + cfg_en, + reg_offset, + value, + client); +} + +static u32 hisi_write_mn_counter(struct hisi_pmu *pmn_pmu, + struct hw_perf_event *hwc, u32 value) +{ + struct hisi_djtag_client *client; + struct hisi_mn_data *mn_hwmod_data = pmn_pmu->hwmod_data; + u32 module_id = mn_hwmod_data->mn_hwcfg.module_id; + u32 cfg_en, reg_offset; + int ret; + int idx = GET_CNTR_IDX(hwc); + + if (!hisi_mn_counter_valid(idx)) { + dev_err(pmn_pmu->dev, + "Unsupported event index:%d!\n", idx); + return -EINVAL; + } + + reg_offset = mn_hwmod_data->mn_hwcfg.counter_reg0_off + + (idx * 4); + /* Find the djtag Identifier of the Unit */ + client = mn_hwmod_data->client; + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + + ret = hisi_djtag_writereg(module_id, + cfg_en, + reg_offset, + value, + client); + + return ret; +} + +static void hisi_enable_mn_counter(struct hisi_pmu *pmn_pmu, int idx) +{ + struct hisi_djtag_client *client; + struct hisi_mn_data *mn_hwmod_data = pmn_pmu->hwmod_data; + u32 reg_offset = mn_hwmod_data->mn_hwcfg.event_ctrl_reg_off; + u32 module_id = mn_hwmod_data->mn_hwcfg.module_id; + u32 event_en = mn_hwmod_data->mn_hwcfg.event_enable; + u32 cfg_en, value; + + if (!hisi_mn_counter_valid(idx)) { + dev_err(pmn_pmu->dev, + "Unsupported event index:%d!\n", idx); + return; + } + + /* Find the djtag Identifier of the Unit */ + client = mn_hwmod_data->client; + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + + /* + * Set the event_bus_en bit in MN_EVENT_CTRL to enable counting + */ + hisi_djtag_readreg(module_id, + cfg_en, + reg_offset, + client, + &value); + + value |= event_en; + hisi_djtag_writereg(module_id, + cfg_en, + reg_offset, + value, + client); +} + +static void hisi_disable_mn_counter(struct hisi_pmu *pmn_pmu, int idx) +{ + struct hisi_djtag_client *client; + struct hisi_mn_data *mn_hwmod_data = pmn_pmu->hwmod_data; + u32 reg_offset = mn_hwmod_data->mn_hwcfg.event_ctrl_reg_off; + u32 module_id = mn_hwmod_data->mn_hwcfg.module_id; + u32 event_en = mn_hwmod_data->mn_hwcfg.event_enable; + u32 cfg_en, value; + + if (!hisi_mn_counter_valid(idx)) { + dev_err(pmn_pmu->dev, + "Unsupported event index:%d!\n", idx); + return; + } + + /* Find the djtag Identifier of the Unit */ + client = mn_hwmod_data->client; + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + + /* + * Clear the event_bus_en bit in MN event control + */ + hisi_djtag_readreg(module_id, + cfg_en, + reg_offset, + client, &value); + + value &= ~(event_en); + hisi_djtag_writereg(module_id, + cfg_en, + reg_offset, + value, + client); +} + +static void hisi_clear_mn_event_idx(struct hisi_pmu *pmn_pmu, int idx) +{ + struct hisi_djtag_client *client; + struct hisi_mn_data *mn_hwmod_data = pmn_pmu->hwmod_data; + u32 reg_offset = mn_hwmod_data->mn_hwcfg.evtype_reg0_off; + u32 module_id = mn_hwmod_data->mn_hwcfg.module_id; + void *bitmap_addr; + u32 cfg_en, value; + + if (!hisi_mn_counter_valid(idx)) { + dev_err(pmn_pmu->dev, + "Unsupported event index:%d!\n", idx); + return; + } + + bitmap_addr = mn_hwmod_data->hisi_mn_event_used_mask; + + __clear_bit(idx, bitmap_addr); + + /* Find the djtag Identifier of the Unit */ + client = mn_hwmod_data->client; + cfg_en = mn_hwmod_data->mn_hwcfg.bank_cfgen; + + /* + * Clear the event in MN_EVENT_TYPE Register + */ + hisi_djtag_readreg(module_id, + cfg_en, + reg_offset, + client, &value); + + value &= ~(0xff << (8 * idx)); + value |= (0xff << (8 * idx)); + hisi_djtag_writereg(module_id, + cfg_en, + reg_offset, + value, + client); +} + +static int hisi_mn_get_event_idx(struct hisi_pmu *pmn_pmu) +{ + struct hisi_mn_data *mn_hwmod_data = pmn_pmu->hwmod_data; + int event_idx; + + event_idx = + find_first_zero_bit( + mn_hwmod_data->hisi_mn_event_used_mask, + HISI_MAX_CFG_MN_CNTR); + + if (event_idx == HISI_MAX_CFG_MN_CNTR) + return -EAGAIN; + + __set_bit(event_idx, + mn_hwmod_data->hisi_mn_event_used_mask); + + return event_idx; +} + +static void hisi_free_mn_data(struct hisi_pmu *pmn_pmu) +{ + kfree(pmn_pmu->hwmod_data); + pmn_pmu->hwmod_data = NULL; +} + +static int init_hisi_mn_hwcfg(struct device *dev, + struct hisi_mn_data *pmn_data) +{ + struct hisi_mn_hwcfg *pmn_hwcfg = &pmn_data->mn_hwcfg; + struct device_node *node = dev->of_node; + + if (of_property_read_u32(node, "counter-reg", + &pmn_hwcfg->counter_reg0_off)) { + dev_err(dev, "DT:Couldnot read counter-reg!\n"); + return -EINVAL; + } + + if (of_property_read_u32(node, "evctrl-reg", + &pmn_hwcfg->event_ctrl_reg_off)) { + dev_err(dev, "DT:Couldnot read evctrl-reg!\n"); + return -EINVAL; + } + + if (of_property_read_u32(node, "event-en", + &pmn_hwcfg->event_enable)) { + dev_err(dev, "DT:Couldnot read event-en property!\n"); + return -EINVAL; + } + + if (of_property_read_u32(node, "evtype-reg", + &pmn_hwcfg->evtype_reg0_off)) { + dev_err(dev, "DT:Couldnot read evtype-reg!\n"); + return -EINVAL; + } + + if (of_property_read_u32(node, "module-id", + &pmn_hwcfg->module_id)) { + dev_err(dev, "DT:Couldnot read module-id property!\n"); + return -EINVAL; + } + + if (of_property_read_u32(node, "cfgen-map", + &pmn_hwcfg->bank_cfgen)) { + dev_err(dev, "DT:Couldnot read cfgen-map property!\n"); + return -EINVAL; + } + + return 0; +} + +static int init_hisi_mn_data(struct device *dev, + struct hisi_pmu *pmn_pmu, + struct hisi_djtag_client *client) +{ + struct hisi_mn_data *mn_hwmod_data; + int ret; + + mn_hwmod_data = kzalloc(sizeof(struct hisi_mn_data), + GFP_KERNEL); + if (!mn_hwmod_data) + return -ENOMEM; + + /* Set the djtag Identifier */ + mn_hwmod_data->client = client; + + pmn_pmu->hw_events.events = devm_kcalloc(dev, + pmn_pmu->num_counters, + sizeof(*pmn_pmu->hw_events.events), + GFP_KERNEL); + if (!pmn_pmu->hw_events.events) { + ret = -ENOMEM; + goto fail; + } + + raw_spin_lock_init(&pmn_pmu->hw_events.pmu_lock); + + pmn_pmu->hwmod_data = mn_hwmod_data; + + ret = init_hisi_mn_hwcfg(dev, mn_hwmod_data); + if (ret) + goto fail; + + return 0; + +fail: + hisi_free_mn_data(pmn_pmu); + return ret; +} + +static struct attribute *hisi_mn_format_attr[] = { + HISI_PMU_FORMAT_ATTR(event, "config:0-11"), + NULL, +}; + +static struct attribute_group hisi_mn_format_group = { + .name = "format", + .attrs = hisi_mn_format_attr, +}; + +static struct attribute *hisi_mn_events_attr[] = { + HISI_PMU_EVENT_ATTR_STR(eo_barrier_req, + "event=0x0"), + HISI_PMU_EVENT_ATTR_STR(ec_barrier_req, + "event=0x01"), + HISI_PMU_EVENT_ATTR_STR(dvm_op_req, + "event=0x02"), + HISI_PMU_EVENT_ATTR_STR(dvm_sync_req, + "event=0x03"), + HISI_PMU_EVENT_ATTR_STR(read_req, + "event=0x04"), + HISI_PMU_EVENT_ATTR_STR(write_req, + "event=0x05"), + NULL, +}; + +static struct attribute_group hisi_mn_events_group = { + .name = "events", + .attrs = hisi_mn_events_attr, +}; + +static struct attribute *hisi_mn_attrs[] = { + NULL, +}; + +struct attribute_group hisi_mn_attr_group = { + .attrs = hisi_mn_attrs, +}; + +static DEVICE_ATTR(cpumask, 0444, hisi_cpumask_sysfs_show, NULL); + +static struct attribute *hisi_mn_cpumask_attrs[] = { + &dev_attr_cpumask.attr, + NULL, +}; + +static const struct attribute_group hisi_mn_cpumask_attr_group = { + .attrs = hisi_mn_cpumask_attrs, +}; + +static const struct attribute_group *hisi_mn_pmu_attr_groups[] = { + &hisi_mn_attr_group, + &hisi_mn_format_group, + &hisi_mn_events_group, + &hisi_mn_cpumask_attr_group, + NULL, +}; + +static struct hisi_uncore_ops hisi_uncore_mn_ops = { + .set_evtype = hisi_set_mn_evtype, + .set_event_period = hisi_pmu_set_event_period, + .get_event_idx = hisi_mn_get_event_idx, + .clear_event_idx = hisi_clear_mn_event_idx, + .event_update = hisi_mn_event_update, + .enable_counter = hisi_enable_mn_counter, + .disable_counter = hisi_disable_mn_counter, + .write_counter = hisi_write_mn_counter, +}; + +static int hisi_mn_pmu_init(struct device *dev, + struct hisi_pmu *pmn_pmu) +{ + int ret; + /* Read common PMU properties */ + ret = hisi_uncore_common_fwprop_read(dev, pmn_pmu); + if (ret) + return ret; + + pmn_pmu->name = kasprintf(GFP_KERNEL, "hisi_mn%d", + pmn_pmu->scl_id); + pmn_pmu->ops = &hisi_uncore_mn_ops; + + pmn_pmu->dev = dev; + /* Pick one core to use for cpumask attributes */ + cpumask_set_cpu(smp_processor_id(), &pmn_pmu->cpu); + + return 0; +} + +static int hisi_pmu_mn_dev_probe(struct hisi_djtag_client *client) +{ + struct hisi_pmu *pmn_pmu = NULL; + struct device *dev = &client->dev; + int ret; + + pmn_pmu = hisi_pmu_alloc(dev); + if (IS_ERR(pmn_pmu)) + return PTR_ERR(pmn_pmu); + + ret = hisi_mn_pmu_init(dev, pmn_pmu); + if (ret) + return ret; + + ret = init_hisi_mn_data(dev, pmn_pmu, client); + if (ret) + goto fail_init; + + /* Register with perf PMU */ + pmn_pmu->pmu = (struct pmu) { + .name = pmn_pmu->name, + .task_ctx_nr = perf_invalid_context, + .event_init = hisi_uncore_pmu_event_init, + .add = hisi_uncore_pmu_add, + .del = hisi_uncore_pmu_del, + .start = hisi_uncore_pmu_start, + .stop = hisi_uncore_pmu_stop, + .read = hisi_uncore_pmu_read, + .attr_groups = hisi_mn_pmu_attr_groups, + }; + + ret = hisi_uncore_pmu_setup(pmn_pmu, pmn_pmu->name); + if (ret) { + dev_err(pmn_pmu->dev, "hisi_uncore_pmu_init FAILED!!\n"); + goto fail; + } + + /* Set the drv data to mn_pmu */ + dev_set_drvdata(dev, pmn_pmu); + + return 0; + +fail: + hisi_free_mn_data(pmn_pmu); + +fail_init: + if (pmn_pmu) + devm_kfree(dev, pmn_pmu); + dev_err(pmn_pmu->dev, "%s failed\n", __func__); + + return ret; +} + +static int hisi_pmu_mn_dev_remove(struct hisi_djtag_client *client) +{ + struct hisi_pmu *pmn_pmu = NULL; + struct device *dev = &client->dev; + + pmn_pmu = dev_get_drvdata(dev); + + perf_pmu_unregister(&pmn_pmu->pmu); + hisi_free_mn_data(pmn_pmu); + + return 0; +} + +static const struct of_device_id mn_of_match[] = { + { .compatible = "hisilicon,hisi-pmu-mn-v1", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mn_of_match); + +static struct hisi_djtag_driver hisi_pmu_mn_driver = { + .driver = { + .name = "hisi-pmu-mn", + .of_match_table = mn_of_match, + }, + .probe = hisi_pmu_mn_dev_probe, + .remove = hisi_pmu_mn_dev_remove, +}; + +static int __init hisi_pmu_mn_init(void) +{ + int rc; + + rc = hisi_djtag_register_driver(THIS_MODULE, &hisi_pmu_mn_driver); + if (rc < 0) { + pr_err("hisi pmu mn init failed, rc=%d\n", rc); + return rc; + } + + return 0; +} +module_init(hisi_pmu_mn_init); + +static void __exit hisi_pmu_mn_exit(void) +{ + hisi_djtag_unregister_driver(&hisi_pmu_mn_driver); +} +module_exit(hisi_pmu_mn_exit); + +MODULE_DESCRIPTION("HiSilicon SoC HIP0x MN PMU driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Shaokun Zhang"); diff --git a/drivers/perf/hisilicon/hisi_uncore_mn.h b/drivers/perf/hisilicon/hisi_uncore_mn.h new file mode 100644 index 0000000..4cac2f3 --- /dev/null +++ b/drivers/perf/hisilicon/hisi_uncore_mn.h @@ -0,0 +1,68 @@ +/* + * HiSilicon SoC MN Hardware event counters support + * + * Copyright (C) 2016 Huawei Technologies Limited + * Author: Shaokun Zhang <zhangshaokun@xxxxxxxxxxxxx> + * + * This code is based on the uncore PMU's like arm-cci and + * arm-ccn. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef __HISI_UNCORE_MN_H__ +#define __HISI_UNCORE_MN_H__ + +#include "hisi_uncore_pmu.h" + +/* + * ARMv8 HiSilicon MN RAW event types. + */ +enum armv8_hisi_mn_event_types { + HISI_HWEVENT_MN_EO_BARR_REQ = 0x0, + HISI_HWEVENT_MN_EC_BARR_REQ = 0x01, + HISI_HWEVENT_MN_DVM_OP_REQ = 0x02, + HISI_HWEVENT_MN_DVM_SYNC_REQ = 0x03, + HISI_HWEVENT_MN_READ_REQ = 0x04, + HISI_HWEVENT_MN_WRITE_REQ = 0x05, + HISI_HWEVENT_MN_COPYBK_REQ = 0x06, + HISI_HWEVENT_MN_OTHER_REQ = 0x07, + HISI_HWEVENT_MN_EVENT_MAX = 0x08, +}; + +/* + * ARMv8 HiSilicon Hardware counter Index. + */ +enum armv8_hisi_mn_counters { + HISI_IDX_MN_COUNTER0 = 0x0, + HISI_IDX_MN_COUNTER_MAX = 0x4, +}; + +#define HISI_MAX_CFG_MN_CNTR 0x04 + +struct hisi_mn_hwcfg { + u32 evtype_reg0_off; + u32 counter_reg0_off; + u32 event_ctrl_reg_off; + u32 event_enable; + u32 module_id; + u32 bank_cfgen; +}; + +struct hisi_mn_data { + struct hisi_djtag_client *client; + DECLARE_BITMAP(hisi_mn_event_used_mask, + HISI_MAX_CFG_MN_CNTR); + struct hisi_mn_hwcfg mn_hwcfg; +}; + +#endif /* __HISI_UNCORE_MN_H__ */ -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html