Commit 65e12c7d authored by Xiangwei Li's avatar Xiangwei Li Committed by Ma Wupeng
Browse files

PM / devfreq: event: Add L3C event for HiSilicon uncore devfreq

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: default avatarXiangwei Li <liwei728@huawei.com>
parent a4aa03ca
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+134 −0
Original line number Diff line number Diff line
// 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");
+252 −0
Original line number Diff line number Diff line
/* 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>");
+77 −0
Original line number Diff line number Diff line
/* 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__ */