hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
There is no way to directly observe the uncore L3C load data. However, the hisi uncore provides a PMU that can monitor the L3C status. Therefore, the L3C PMU is used to implement the uncore L3C event.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/event/Kconfig | 7 + drivers/devfreq/event/Makefile | 1 + drivers/devfreq/event/hisi-uncore-l3c.c | 134 +++++++++++++ drivers/devfreq/event/hisi-uncore.c | 252 ++++++++++++++++++++++++ drivers/devfreq/event/hisi-uncore.h | 77 ++++++++ 5 files changed, 471 insertions(+) create mode 100644 drivers/devfreq/event/hisi-uncore-l3c.c create mode 100644 drivers/devfreq/event/hisi-uncore.c create mode 100644 drivers/devfreq/event/hisi-uncore.h
diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig index 878825372f6f..b8fbd02a600f 100644 --- a/drivers/devfreq/event/Kconfig +++ b/drivers/devfreq/event/Kconfig @@ -39,4 +39,11 @@ config DEVFREQ_EVENT_ROCKCHIP_DFI This add the devfreq-event driver for Rockchip SoC. It provides DFI (DDR Monitor Module) driver to count ddr load.
+config DEVFREQ_EVENT_HISI_UNCORE + tristate "HISI UNCORE DEVFREQ event Driver" + depends on HISI_PMU + help + This add the devfreq-event driver for HISI uncore. It provides UMM + (UNCORE Monitor Module) driver to count uncore load. + endif # PM_DEVFREQ_EVENT diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile index 3c847e5d5a35..c22ba093498d 100644 --- a/drivers/devfreq/event/Makefile +++ b/drivers/devfreq/event/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o +obj-$(CONFIG_DEVFREQ_EVENT_HISI_UNCORE) += hisi-uncore.o hisi-uncore-l3c.o diff --git a/drivers/devfreq/event/hisi-uncore-l3c.c b/drivers/devfreq/event/hisi-uncore-l3c.c new file mode 100644 index 000000000000..fa6252227acf --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore-l3c.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hisi-uncore-l3c.c - Hisi uncore PMU (Platform Performance Monitoring Unit) support + * + * Copyright (c) 2024 Hisi Electronics Co., Ltd. + * Author : Xiangwei Li liwei728@huawei.com + * + * This driver is based on drivers/devfreq/hisi_uncore/hisi-uncore-pmu.c + */ + +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/devfreq-event.h> + +#include "hisi-uncore.h" + +HISI_UNCORE_EVENT_TYPE_ATTR; +HISI_UNCORE_EVENT_CONFIG_ATTR; + +static int l3c_get_events(struct devfreq_event_dev *edev, struct devfreq_event_data *edata) +{ + u64 load; + struct hisi_uncore_event_info *info = devfreq_event_get_drvdata(edev); + + load = get_pmu_monitor_status(info); + + if (info->is_reset) { + info->is_reset = false; + info->max_load = 0; + return 0; + } + + info->max_load = max(info->max_load, load); + edata->load_count = load; + edata->total_count = info->max_load; + + return 0; +} + +static int l3c_set_events(struct devfreq_event_dev *edev) +{ + return 0; +} + +static const struct devfreq_event_ops l3c_event_ops = { + .set_event = l3c_set_events, + .get_event = l3c_get_events, +}; + +static int hisi_l3c_event_probe(struct platform_device *pdev) +{ + int ret; + struct hisi_uncore_event_info *data; + struct devfreq_event_dev *edev; + struct devfreq_event_desc *desc; + struct device *dev = &pdev->dev; + + data = devm_kzalloc(dev, sizeof(struct hisi_uncore_event_info), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + HISI_UNCORE_EVENT_NAME(data->name, "l3c", dev->id); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + desc->ops = &l3c_event_ops; + desc->driver_data = data; + desc->name = data->name; + data->desc = desc; + + edev = devm_devfreq_event_add_edev(dev, desc); + if (IS_ERR(edev)) { + dev_err(dev, + "failed to add devfreq-event device\n"); + ret = PTR_ERR(edev); + return ret; + } + + data->edev = edev; + + ret = device_create_file(&edev->dev, &dev_attr_hisi_uncore_event_types); + if (ret) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + return ret; + } + + ret = device_create_file(&edev->dev, &dev_attr_hisi_uncore_event_configs); + if (ret) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + return ret; + } + + platform_set_drvdata(pdev, data); + + mutex_init(&data->lock); + + return 0; +} + +static int hisi_l3c_event_remove(struct platform_device *pdev) +{ + struct hisi_uncore_event_info *data = platform_get_drvdata(pdev); + + release_pmu_monitor(data); + device_remove_file(&data->edev->dev, &dev_attr_hisi_uncore_event_types); + device_remove_file(&data->edev->dev, &dev_attr_hisi_uncore_event_configs); + + return 0; +} + +static const struct platform_device_id hisi_l3c_pmu_plat_match[] = { + { .name = "EVT-UNCORE-L3C", }, + {} +}; +MODULE_DEVICE_TABLE(platform, hisi_l3c_pmu_plat_match); + +struct platform_driver hisi_l3c_event_driver = { + .probe = hisi_l3c_event_probe, + .remove = hisi_l3c_event_remove, + .driver = { + .name = "EVT-UNCORE-L3C", + }, + .id_table = hisi_l3c_pmu_plat_match, +}; + +module_platform_driver(hisi_l3c_event_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xiangwei Li liwei728@huawei.com"); +MODULE_DESCRIPTION("Hisi uncore l3c pmu events driver"); diff --git a/drivers/devfreq/event/hisi-uncore.c b/drivers/devfreq/event/hisi-uncore.c new file mode 100644 index 000000000000..fe53f973d63b --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore.c @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * HiSilicon uncore devfreq event support + * + * Copyright (C) 2024 Hisilicon Limited + * Author: Xiangwei Li liwei728@hisilicon.com + * + * This code is based on the uncore PMUs event. + */ +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/ctype.h> +#include <linux/devfreq-event.h> + +#include "hisi-uncore.h" + +void release_pmu_monitor(struct hisi_uncore_event_info *info) +{ + int type_id, evt_id; + struct pmu_info *pmu_info; + + mutex_lock(&info->lock); + for (type_id = 0; type_id < info->related_pmu_cnt; ++type_id) { + pmu_info = &info->related_pmus[type_id]; + for (evt_id = 0; evt_id < pmu_info->event_cnt; ++evt_id) { + if (!pmu_info->event[evt_id]) + continue; + perf_event_release_kernel(pmu_info->event[evt_id]); + pmu_info->event[evt_id] = NULL; + } + pmu_info->event_cnt = 0; + } + mutex_unlock(&info->lock); + if (devfreq_event_is_enabled(info->edev)) + devfreq_event_disable_edev(info->edev); +} +EXPORT_SYMBOL_GPL(release_pmu_monitor); + +static int reset_pmu_monitor(struct hisi_uncore_event_info *info) +{ + int err; + struct pmu_info *pmu_info; + int type_id, cfg_id; + struct perf_event_attr attr = { + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 0, + }; + + info->is_reset = true; + + if (info->config_cnt == 0 || info->related_pmu_cnt == 0) + return 0; + + mutex_lock(&info->lock); + for (type_id = 0; type_id < info->related_pmu_cnt; ++type_id) { + pmu_info = &info->related_pmus[type_id]; + attr.type = pmu_info->type; + for (cfg_id = 0; cfg_id < info->config_cnt; ++cfg_id) { + attr.config = info->configs[cfg_id]; + pmu_info->event[cfg_id] = perf_event_create_kernel_counter(&attr, + smp_processor_id(), NULL, NULL, NULL); + if (IS_ERR(pmu_info->event[cfg_id])) { + err = PTR_ERR(pmu_info->event[cfg_id]); + pmu_info->event[cfg_id] = NULL; + release_pmu_monitor(info); + info->related_pmu_cnt = 0; + return err; + } + pmu_info->event_cnt++; + } + } + mutex_unlock(&info->lock); + + if (!devfreq_event_is_enabled(info->edev)) + devfreq_event_enable_edev(info->edev); + + return 0; +} + +u64 get_pmu_monitor_status(struct hisi_uncore_event_info *info) +{ + int t_id, c_id; + u64 value, max_load; + u64 enabled, running; + struct pmu_info *pmu_info; + + max_load = 0; + + mutex_lock(&info->lock); + for (t_id = 0; t_id < info->related_pmu_cnt; ++t_id) { + pmu_info = &info->related_pmus[t_id]; + value = 0; + for (c_id = 0; c_id < info->config_cnt; ++c_id) { + if (!pmu_info->event[c_id]) { + value = 0; + break; + } + value += perf_event_read_value(pmu_info->event[c_id], + &enabled, &running); + } + + max_load = max(max_load, value - pmu_info->load); + pmu_info->load = value; + } + + mutex_unlock(&info->lock); + return max_load; +} +EXPORT_SYMBOL_GPL(get_pmu_monitor_status); + +ssize_t hisi_uncore_event_configs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + for (i = 0; i < info->config_cnt; ++i) { + sprintf(buf, "%s %lld\n", buf, info->configs[i]); + } + + return sprintf(buf, "%s\n", buf); +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_configs_show); + +ssize_t hisi_uncore_event_configs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + char *item; + u32 head, tail, cfg_cnt; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + if (!buf) + return 0; + + release_pmu_monitor(info); + + head = 0; + cfg_cnt = 0; + item = kcalloc(count + 1, sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + while (cfg_cnt < EVENT_CONFIG_MAX_CNT) { + while (head < count && isspace(buf[head])) + head++; + + if (!isalnum(buf[head])) + break; + + tail = head + 1; + while (tail < count && isalnum(buf[tail])) + tail++; + + strncpy(item, buf + head, tail - head); + item[tail - head] = '\0'; + head = tail; + + err = kstrtou64(item, 10, &info->configs[cfg_cnt]); + if (err) { + info->config_cnt = 0; + return err; + } + + cfg_cnt++; + } + + info->config_cnt = cfg_cnt; + kfree(item); + + err = reset_pmu_monitor(info); + if (err) { + return err; + } + + return count; +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_configs_store); + +ssize_t hisi_uncore_event_types_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + for (i = 0; i < info->related_pmu_cnt; ++i) { + sprintf(buf, "%s %d\n", buf, info->related_pmus[i].type); + } + + return sprintf(buf, "%s\n", buf); +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_types_show); + +ssize_t hisi_uncore_event_types_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + char *item; + u32 head, tail, type_cnt; + struct hisi_uncore_event_info *info = dev_get_drvdata(dev->parent); + + if (!buf) + return 0; + + release_pmu_monitor(info); + + head = 0; + type_cnt = 0; + item = kcalloc(count + 1, sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + while (type_cnt < EVENT_TYPE_MAX_CNT) { + while (head < count && isspace(buf[head])) + head++; + + if (!isalnum(buf[head])) + break; + + tail = head + 1; + while (tail < count && isalnum(buf[tail])) + tail++; + + strncpy(item, buf + head, tail - head); + item[tail - head] = '\0'; + head = tail; + + err = kstrtou32(item, 10, &info->related_pmus[type_cnt].type); + if (err) { + info->related_pmu_cnt = 0;; + return err; + } + + type_cnt++; + } + + info->related_pmu_cnt = type_cnt; + kfree(item); + + err = reset_pmu_monitor(info); + if (err) + return err; + + return count; +} +EXPORT_SYMBOL_GPL(hisi_uncore_event_types_store); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xiangwei Li liwei728@huawei.com"); diff --git a/drivers/devfreq/event/hisi-uncore.h b/drivers/devfreq/event/hisi-uncore.h new file mode 100644 index 000000000000..6cc4d12252ae --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * HiSilicon uncore devfreq event support + * + * Copyright (C) 2024 Hisilicon Limited + * Author: Xiangwei Li liwei728@hisilicon.com + * + * This code is based on the uncore PMUs event. + */ +#ifndef __HISI_UNCORE_H__ +#define __HISI_UNCORE_H__ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/perf_event.h> +#include <linux/types.h> + +#define HISI_UNCORE_EVENT_NAME(name, type_name, package_id) ({ \ + int len; \ + len = sprintf(name, "uncore-%s-%d", type_name, package_id); \ + len; }) + +#define HISI_UNCORE_EVENT_TYPE_ATTR \ + DEVICE_ATTR_RW(hisi_uncore_event_types); + +#define HISI_UNCORE_EVENT_CONFIG_ATTR \ + DEVICE_ATTR_RW(hisi_uncore_event_configs); + +#define EVENT_TYPE_MAX_CNT (20) +#define EVENT_TYPE_INVALID_VAL (0xffff) +#define EVENT_CONFIG_MAX_CNT (2) +#define EVENT_CONFIG_INVALID_VAL (0xffff) + +/* + * The signle uncore pmu info. + */ +struct pmu_info { + __u32 type; + u64 load; + int event_cnt; + struct perf_event *event[EVENT_CONFIG_MAX_CNT]; +}; + +/* + * The uncore pmu controller can monitor device load by read PMU. + */ +struct hisi_uncore_event_info { + char name[0x10]; + bool is_reset; + int config_cnt; + __u64 configs[EVENT_CONFIG_MAX_CNT]; + u64 max_load; + struct device *dev; + struct devfreq_event_dev *edev; + struct devfreq_event_desc *desc; + struct devfreq_perf_event *event; + int related_pmu_cnt; + struct pmu_info related_pmus[EVENT_TYPE_MAX_CNT]; + struct mutex lock; +}; + +ssize_t hisi_uncore_event_configs_show(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t hisi_uncore_event_configs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +ssize_t hisi_uncore_event_types_show(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t hisi_uncore_event_types_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +void release_pmu_monitor(struct hisi_uncore_event_info *info); +u64 get_pmu_monitor_status(struct hisi_uncore_event_info *info); + +#endif /* __HISI_UNCORE_PMU_H__ */