Devfreq is a general DVFS framework for non-CPU devices in the kernel. It is generally used for bus devices. Implementation of uncore dynamic frequency modulation based on this framework.
Jie Zhan (1): PM / devfreq: Add HiSilicon uncore frequency scaling driver
Xiangwei Li (9): PM / devfreq: Adjust default HiSilicon uncore devfreq polling interval and workqueue PM / devfreq: event: Add devfreq_event_get_edev_by_dev function PM / devfreq: event: Supplement the name content of the event in devfreq-event PM / devfreq: Add related-package parameter in HiSilicon uncore devfreq PM / devfreq: Add associated events configuration in HiSilicon uncore frequency PM / devfreq: event: Add L3C event for HiSilicon uncore devfreq PM / devfreq: Add until governor PM / devfreq: Add bandwidth verification mechanism PM / devfreq: event: Add DDRC event for HiSilicon uncore devfreq
drivers/devfreq/Kconfig | 12 + drivers/devfreq/Makefile | 2 + drivers/devfreq/devfreq-event.c | 35 +- drivers/devfreq/event/Kconfig | 7 + drivers/devfreq/event/Makefile | 2 + drivers/devfreq/event/hisi-uncore-ddrc.c | 160 ++++++ drivers/devfreq/event/hisi-uncore-l3c.c | 160 ++++++ drivers/devfreq/event/hisi-uncore.c | 252 +++++++++ drivers/devfreq/event/hisi-uncore.h | 77 +++ drivers/devfreq/governor_util.c | 127 +++++ drivers/devfreq/hisi_uncore_freq.c | 680 +++++++++++++++++++++++ include/linux/devfreq-event.h | 8 + include/linux/devfreq.h | 15 + 13 files changed, 1536 insertions(+), 1 deletion(-) create mode 100644 drivers/devfreq/event/hisi-uncore-ddrc.c 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 create mode 100644 drivers/devfreq/governor_util.c create mode 100644 drivers/devfreq/hisi_uncore_freq.c
From: Jie Zhan zhanjie9@hisilicon.com
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
Add the HiSilicon uncore frequency scaling driver for Kunpeng SoCs based on the devfreq framework. The uncore domain contains L3 caches and system bus. Hence, the uncore frequency contributes to the system-wide performance as well as power consumption.
Signed-off-by: Jie Zhan zhanjie9@hisilicon.com Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/Kconfig | 6 + drivers/devfreq/Makefile | 1 + drivers/devfreq/hisi_uncore_freq.c | 491 +++++++++++++++++++++++++++++ 3 files changed, 498 insertions(+) create mode 100644 drivers/devfreq/hisi_uncore_freq.c
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 3c4862a752b5..158ef3728bf1 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -150,6 +150,12 @@ config ARM_SUN8I_A33_MBUS_DEVFREQ This adds the DEVFREQ driver for the MBUS controller in some Allwinner sun8i (A33 through H3) and sun50i (A64 and H5) SoCs.
+config ARM_HISI_UNCORE_DEVFREQ + tristate "HiSilicon uncore DEVFREQ Driver" + depends on (PCC && ACPI && ACPI_PPTT) || COMPILE_TEST + help + HiSilicon uncore DEVFREQ Driver + source "drivers/devfreq/event/Kconfig"
endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index bf40d04928d0..04113d583515 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_ARM_MEDIATEK_CCI_DEVFREQ) += mtk-cci-devfreq.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_SUN8I_A33_MBUS_DEVFREQ) += sun8i-a33-mbus.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o +obj-$(CONFIG_ARM_HISI_UNCORE_DEVFREQ) += hisi_uncore_freq.o
# DEVFREQ Event Drivers obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/ diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c new file mode 100644 index 000000000000..d0e58e80db46 --- /dev/null +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HiSilicon uncore frequency scaling driver + * + * Copyright (c) 2024 HiSilicon Co., Ltd + * Author : Jie Zhan zhanjie9@hisilicon.com + */ + +#include <linux/acpi.h> +#include <linux/devfreq.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/errno.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/property.h> +#include <linux/topology.h> + +#include <acpi/pcc.h> + +#define HZ_PER_MHZ 1000000 + +/* Don't care OPP votlage, take 1V as default */ +#define DEF_OPP_VOLT_UV 1000000 + +struct hisi_uncore_freq { + struct device *dev; + struct mbox_client cl; + struct pcc_mbox_chan *pchan; + void __iomem *pcc_shmem_addr; + int chan_id; + unsigned long freq_min; + unsigned long freq_max; + unsigned long freq_step; + struct devfreq *devfreq; + struct cpumask related_cpus; +}; + +struct hisi_uncore_pcc_data { + u16 status; + u16 resv; + u32 data; +}; + +struct hisi_uncore_pcc_shmem { + struct acpi_pcct_shared_memory head; + struct hisi_uncore_pcc_data pcc_data; +}; + +enum hisi_uncore_pcc_cmd_type { + HUCF_PCC_CMD_GET_CAP = 0, + HUCF_PCC_CMD_GET_FREQ, + HUCF_PCC_CMD_SET_FREQ, + HUCF_PCC_CMD_GET_MODE, + HUCF_PCC_CMD_SET_MODE, + HUCF_PCC_CMD_GET_PLAT_FREQ_MIN, + HUCF_PCC_CMD_GET_PLAT_FREQ_MAX, + HUCF_PCC_CMD_GET_PLAT_FREQ_STEP, + HUCF_PCC_CMD_MAX = 256, +}; + +enum hisi_uncore_freq_mode { + HUCF_MODE_PLATFORM = 0, + HUCF_MODE_OS, +}; + +/* Timeout = PCC nominal latency * NUM */ +#define HUCF_PCC_POLL_TIMEOUT_NUM 1000 +#define HUCF_PCC_POLL_INTERVAL_US 5 + +static int hisi_uncore_request_pcc_chan(struct hisi_uncore_freq *uncore) +{ + struct pcc_mbox_chan *pcc_chan; + int rc; + + uncore->cl = (struct mbox_client) { + .dev = uncore->dev, + .tx_block = false, + .knows_txdone = true, + }; + + pcc_chan = pcc_mbox_request_channel(&uncore->cl, uncore->chan_id); + if (IS_ERR(pcc_chan)) { + dev_err(uncore->dev, "Failed to request PCC channel %u\n", + uncore->chan_id); + return -ENODEV; + } + + uncore->pchan = pcc_chan; + if (!pcc_chan->shmem_base_addr) { + dev_err(uncore->dev, "Invalid PCC shared memory address\n"); + rc = -EINVAL; + goto err_pcc_chan_free; + } + + if (pcc_chan->shmem_size < sizeof(struct hisi_uncore_pcc_shmem)) { + dev_err(uncore->dev, "Invalid PCC shared memory size (%lluB)\n", + pcc_chan->shmem_size); + rc = -EINVAL; + goto err_pcc_chan_free; + } + + uncore->pcc_shmem_addr = ioremap(pcc_chan->shmem_base_addr, + pcc_chan->shmem_size); + if (!uncore->pcc_shmem_addr) { + rc = -ENOMEM; + goto err_pcc_chan_free; + } + + return 0; + +err_pcc_chan_free: + pcc_mbox_free_channel(uncore->pchan); + return rc; +} + +static void hisi_uncore_free_pcc_chan(struct hisi_uncore_freq *uncore) +{ + if (uncore->pchan) + pcc_mbox_free_channel(uncore->pchan); +} + +static acpi_status hisi_uncore_pcc_reg_scan(struct acpi_resource *res, + void *ctx) +{ + struct acpi_resource_generic_register *reg; + struct hisi_uncore_freq *uncore; + + if (!res || res->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) + return AE_OK; + + reg = &res->data.generic_reg; + if (reg->space_id != ACPI_ADR_SPACE_PLATFORM_COMM) + return AE_OK; + + /* PCC subspace ID stored in Access Size */ + uncore = ctx; + uncore->chan_id = reg->access_size; + return AE_CTRL_TERMINATE; +} + +static int hisi_uncore_init_pcc_chan(struct hisi_uncore_freq *uncore) +{ + acpi_handle handle = ACPI_HANDLE(uncore->dev); + acpi_status status; + + uncore->chan_id = -1; + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + hisi_uncore_pcc_reg_scan, uncore); + if (ACPI_FAILURE(status) || uncore->chan_id < 0) { + dev_err(uncore->dev, "Failed to get a PCC channel\n"); + return -ENODEV; + } + + return hisi_uncore_request_pcc_chan(uncore); +} + +static int hisi_uncore_cmd_send(struct hisi_uncore_freq *uncore, + u8 cmd, u32 *data) +{ + struct hisi_uncore_pcc_shmem __iomem *addr = uncore->pcc_shmem_addr; + struct pcc_mbox_chan *pchan = uncore->pchan; + struct hisi_uncore_pcc_shmem shmem; + u16 status; + int rc; + + /* Copy data */ + shmem.head = (struct acpi_pcct_shared_memory) { + .signature = PCC_SIGNATURE | uncore->chan_id, + .command = cmd, + .status = 0, + }; + shmem.pcc_data.data = *data; + memcpy_toio(addr, &shmem, sizeof(shmem)); + + /* Ring doorbell */ + rc = mbox_send_message(pchan->mchan, &cmd); + if (rc < 0) { + dev_err(uncore->dev, "Failed to send mbox message, %d\n", rc); + return rc; + } + + /* Wait status */ + rc = readw_poll_timeout(&addr->head.status, status, + status & (PCC_STATUS_CMD_COMPLETE || + PCC_STATUS_ERROR), + HUCF_PCC_POLL_INTERVAL_US, + pchan->latency * HUCF_PCC_POLL_TIMEOUT_NUM); + if (rc) { + dev_err(uncore->dev, "PCC channel response timeout\n"); + return -ETIME; + } + + if (status & PCC_STATUS_ERROR) { + dev_err(uncore->dev, "PCC cmd error\n"); + return -EIO; + } + + /* Success, copy data back */ + memcpy_fromio(data, &addr->pcc_data.data, sizeof(*data)); + + mbox_client_txdone(pchan->mchan, 0); + return rc; +} + +static int hisi_uncore_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct hisi_uncore_freq *uncore = dev_get_drvdata(dev); + u32 data = *freq / HZ_PER_MHZ; + + if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) + data = roundup(data, uncore->freq_step); + else + data = rounddown(data, uncore->freq_step); + + data = clamp((unsigned long)data, uncore->freq_min, uncore->freq_max); + + return hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_FREQ, &data); +} + +static int hisi_uncore_get_dev_status(struct device *dev, + struct devfreq_dev_status *stat) +{ + return 0; +} + +static int hisi_uncore_get_cur_freq(struct device *dev, unsigned long *freq) +{ + struct hisi_uncore_freq *uncore = dev_get_drvdata(dev); + u32 data; + int rc; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_FREQ, &data); + *freq = data * HZ_PER_MHZ; + + return rc; +} + +static int hisi_uncore_add_opp(struct hisi_uncore_freq *uncore) +{ + unsigned long freq_mhz; + u32 data; + int rc; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_MIN, &data); + if (rc) + return rc; + uncore->freq_min = data; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_MAX, &data); + if (rc) + return rc; + uncore->freq_max = data; + + rc = hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_PLAT_FREQ_STEP, &data); + if (rc) + return rc; + uncore->freq_step = data; + + for (freq_mhz = uncore->freq_min; freq_mhz <= uncore->freq_max; + freq_mhz += uncore->freq_step) { + rc = dev_pm_opp_add(uncore->dev, freq_mhz * HZ_PER_MHZ, DEF_OPP_VOLT_UV); + if (rc) { + unsigned long freq_curr = freq_mhz; + dev_err(uncore->dev, "Add OPP %lu failed (%d)\n", + freq_mhz, rc); + for (freq_mhz = uncore->freq_min; freq_mhz < freq_curr; + freq_mhz += uncore->freq_step) + dev_pm_opp_remove(uncore->dev, + freq_mhz * HZ_PER_MHZ); + break; + } + } + + return rc; +} + +static void hisi_uncore_remove_opp(struct hisi_uncore_freq *uncore) +{ + unsigned long freq_mhz; + + for (freq_mhz = uncore->freq_min; freq_mhz <= uncore->freq_max; + freq_mhz += uncore->freq_step) + dev_pm_opp_remove(uncore->dev, freq_mhz * HZ_PER_MHZ); +} + +static int hisi_uncore_devfreq_register(struct hisi_uncore_freq *uncore) +{ + struct devfreq_dev_profile *profile; + u32 data; + + data = HUCF_MODE_OS; + hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data); + msleep(200); + hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_GET_FREQ, &data); + dev_info(uncore->dev, "init freq %u\n", data); + + profile = devm_kzalloc(uncore->dev, sizeof(*profile), GFP_KERNEL); + if (!profile) + return -ENOMEM; + + profile->initial_freq = (unsigned long)data * HZ_PER_MHZ; + profile->polling_ms = 100; + profile->target = hisi_uncore_target; + profile->get_dev_status = hisi_uncore_get_dev_status; + profile->get_cur_freq = hisi_uncore_get_cur_freq; + + uncore->devfreq = devm_devfreq_add_device(uncore->dev, profile, + DEVFREQ_GOV_USERSPACE, NULL); + if (IS_ERR(uncore->devfreq)) { + dev_err(uncore->dev, "Failed to add devfreq device\n"); + return PTR_ERR(uncore->devfreq); + } + + return 0; +} + +static int hisi_uncore_mark_related_cpus(struct hisi_uncore_freq *uncore, + char *property, + int (get_topo_id)(int cpu), + struct cpumask *(get_cpumask)(int cpu)) +{ + unsigned int i, cpu; + size_t len; + u32 *num; + int rc; + + rc = device_property_count_u32(uncore->dev, property); + if (rc < 0) + return rc; + + len = rc; + num = kcalloc(len, sizeof(*num), GFP_KERNEL); + if (!num) + return -ENOMEM; + + rc = device_property_read_u32_array(uncore->dev, property, num, len); + if (rc) + goto out; + + for (i = 0; i < len; i++) { + for_each_possible_cpu(cpu) { + if (get_topo_id(cpu) == num[i]) { + cpumask_or(&uncore->related_cpus, + &uncore->related_cpus, + get_cpumask(cpu)); + break; + } + } + } + +out: + kfree(num); + return rc; + +} + +static int get_package_id(int cpu) +{ + return topology_physical_package_id(cpu); +} + +static struct cpumask *get_package_cpumask(int cpu) +{ + return topology_core_cpumask(cpu); +} + +static int get_cluster_id(int cpu) +{ + return topology_cluster_id(cpu); +} + +static struct cpumask *get_cluster_cpumask(int cpu) +{ + return topology_cluster_cpumask(cpu); +} + +static int hisi_uncore_mark_related_cpus_wrap(struct hisi_uncore_freq *uncore) +{ + int rc; + + cpumask_clear(&uncore->related_cpus); + + rc = hisi_uncore_mark_related_cpus(uncore, "related-package", + get_package_id, + get_package_cpumask); + if (rc == 0) + return rc; + + rc = hisi_uncore_mark_related_cpus(uncore, "related-cluster", + get_cluster_id, + get_cluster_cpumask); + return rc; +} + +static ssize_t related_cpus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + return cpumap_print_to_pagebuf(true, buf, &uncore->related_cpus); +} +DEVICE_ATTR_RO(related_cpus); + +static int hisi_uncore_probe(struct platform_device *pdev) +{ + struct hisi_uncore_freq *uncore; + int rc; + + uncore = devm_kzalloc(&pdev->dev, sizeof(*uncore), GFP_KERNEL); + if (!uncore) + return -ENOMEM; + + uncore->dev = &pdev->dev; + platform_set_drvdata(pdev, uncore); + + rc = hisi_uncore_init_pcc_chan(uncore); + if (rc) { + dev_err(&pdev->dev, "PCC channel init failed %d", rc); + return rc; + } + + rc = hisi_uncore_add_opp(uncore); + if (rc) { + dev_err(&pdev->dev, "Register freq failed (%d)\n", rc); + goto err_free_pcc; + } + + rc = hisi_uncore_devfreq_register(uncore); + if (rc) { + dev_err(&pdev->dev, "Failed to register devfreq (%d)\n", rc); + goto err_free_opp; + } + + hisi_uncore_mark_related_cpus_wrap(uncore); + + rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_cpus); + if (rc) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + goto err_free_opp; + } + + return 0; + +err_free_opp: + hisi_uncore_remove_opp(uncore); +err_free_pcc: + hisi_uncore_free_pcc_chan(uncore); + + return rc; +} + +static int hisi_uncore_remove(struct platform_device *pdev) +{ + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + u32 data = HUCF_MODE_PLATFORM; + hisi_uncore_cmd_send(uncore, HUCF_PCC_CMD_SET_MODE, &data); + + hisi_uncore_remove_opp(uncore); + hisi_uncore_free_pcc_chan(uncore); + device_remove_file(&uncore->devfreq->dev, &dev_attr_related_cpus); + + return 0; +} + +static const struct acpi_device_id hisi_uncore_acpi_match[] = { + { "HISI04F1", }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, hisi_uncore_acpi_match); + +static struct platform_driver hisi_uncore_platdrv = { + .probe = hisi_uncore_probe, + .remove = hisi_uncore_remove, + .driver = { + .name = "hisi_uncore_freq", + .acpi_match_table = hisi_uncore_acpi_match, + }, +}; +module_platform_driver(hisi_uncore_platdrv); + +MODULE_DESCRIPTION("HiSilicon uncore frequency scaling driver"); +MODULE_AUTHOR("Jie Zhan zhanjie9@hisilicon.com"); +MODULE_LICENSE("GPL v2");
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
The uncore frequency adjustment configuration takes effect at the millisecond level, and the status of devices within the uncore range fluctuates greatly.
Increase the polling interval to reduce the impact of the frequency adjustment effective time and smooth the device status fluctuation.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/hisi_uncore_freq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index d0e58e80db46..2e65f6c9f6a1 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -306,7 +306,8 @@ static int hisi_uncore_devfreq_register(struct hisi_uncore_freq *uncore) return -ENOMEM;
profile->initial_freq = (unsigned long)data * HZ_PER_MHZ; - profile->polling_ms = 100; + profile->polling_ms = 1000; + profile->timer = DEVFREQ_TIMER_DELAYED; profile->target = hisi_uncore_target; profile->get_dev_status = hisi_uncore_get_dev_status; profile->get_cur_freq = hisi_uncore_get_cur_freq;
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
When there is no devicetree association between devfreq device and devfreq-events device, devfreq_event_dev cannot be obtained through the devfreq_event_get_edev_by_phandle function.
Therefore, a new interface is added to obtain struct devfreq_event_dev through the specified device.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/devfreq-event.c | 32 ++++++++++++++++++++++++++++++++ include/linux/devfreq-event.h | 8 ++++++++ 2 files changed, 40 insertions(+)
diff --git a/drivers/devfreq/devfreq-event.c b/drivers/devfreq/devfreq-event.c index 3ebac2496679..cd5b12ddc448 100644 --- a/drivers/devfreq/devfreq-event.c +++ b/drivers/devfreq/devfreq-event.c @@ -256,6 +256,38 @@ struct devfreq_event_dev *devfreq_event_get_edev_by_phandle(struct device *dev, } EXPORT_SYMBOL_GPL(devfreq_event_get_edev_by_phandle);
+/** + * devfreq_event_get_edev_by_dev() - Get the devfreq-event dev from + * specified device. + * @dev : the pointer to the given device + * + * Note that this function return the pointer of devfreq-event device. + */ +struct devfreq_event_dev *devfreq_event_get_edev_by_dev(struct device *dev) +{ + struct devfreq_event_dev *edev; + + if (!dev) + return ERR_PTR(-EINVAL); + + mutex_lock(&devfreq_event_list_lock); + list_for_each_entry(edev, &devfreq_event_list, node) { + if (edev->dev.parent == dev) + goto out; + } + + edev = NULL; +out: + mutex_unlock(&devfreq_event_list_lock); + + if (!edev) { + return ERR_PTR(-ENODEV); + } + + return edev; +} +EXPORT_SYMBOL_GPL(devfreq_event_get_edev_by_dev); + /** * devfreq_event_get_edev_count() - Get the count of devfreq-event dev * @dev : the pointer to the given device diff --git a/include/linux/devfreq-event.h b/include/linux/devfreq-event.h index 4a50a5c71a5f..1c7f64f09126 100644 --- a/include/linux/devfreq-event.h +++ b/include/linux/devfreq-event.h @@ -109,6 +109,8 @@ extern struct devfreq_event_dev *devfreq_event_get_edev_by_phandle( struct device *dev, const char *phandle_name, int index); +extern struct devfreq_event_dev *devfreq_event_get_edev_by_dev( + struct device *dev); extern int devfreq_event_get_edev_count(struct device *dev, const char *phandle_name); extern struct devfreq_event_dev *devfreq_event_add_edev(struct device *dev, @@ -162,6 +164,12 @@ static inline struct devfreq_event_dev *devfreq_event_get_edev_by_phandle( return ERR_PTR(-EINVAL); }
+static inline struct devfreq_event_dev *devfreq_event_get_edev_by_dev( + struct device *dev) +{ + return ERR_PTR(-EINVAL); +} + static inline int devfreq_event_get_edev_count(struct device *dev, const char *phandle_name) {
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
The name of the event in devfreq-event only has the ID as a distinguishing mark, which is too simple and not intuitive enough to distinguish different events.
Therefore, the name parameter of desc is added to the name of the event.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/devfreq-event.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/devfreq/devfreq-event.c b/drivers/devfreq/devfreq-event.c index cd5b12ddc448..acc3cfa70daf 100644 --- a/drivers/devfreq/devfreq-event.c +++ b/drivers/devfreq/devfreq-event.c @@ -360,7 +360,8 @@ struct devfreq_event_dev *devfreq_event_add_edev(struct device *dev, edev->dev.class = devfreq_event_class; edev->dev.release = devfreq_event_release_edev;
- dev_set_name(&edev->dev, "event%d", atomic_inc_return(&event_no)); + dev_set_name(&edev->dev, "event-%s-%d", + desc->name, atomic_inc_return(&event_no)); ret = device_register(&edev->dev); if (ret < 0) { put_device(&edev->dev);
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
The related-package parameter is added to specify the package to which the uncore belongs.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/hisi_uncore_freq.c | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 2e65f6c9f6a1..2eccc07a9cac 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -38,6 +38,7 @@ struct hisi_uncore_freq { unsigned long freq_max; unsigned long freq_step; struct devfreq *devfreq; + int related_package; struct cpumask related_cpus; };
@@ -410,6 +411,30 @@ static ssize_t related_cpus_show(struct device *dev, } DEVICE_ATTR_RO(related_cpus);
+static int get_related_package(struct hisi_uncore_freq *uncore) +{ + int rc; + + rc = device_property_read_u32(uncore->dev, "related-package", + &uncore->related_package); + if (rc) { + dev_err(uncore->dev, "failed to read related-package property\n"); + return rc; + } + + return 0; +} + +static ssize_t related_package_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + return sprintf(buf, "%u\n", uncore->related_package); +} +DEVICE_ATTR_RO(related_package); + static int hisi_uncore_probe(struct platform_device *pdev) { struct hisi_uncore_freq *uncore; @@ -440,6 +465,10 @@ static int hisi_uncore_probe(struct platform_device *pdev) goto err_free_opp; }
+ rc = get_related_package(uncore); + if (rc) + goto err_free_opp; + hisi_uncore_mark_related_cpus_wrap(uncore);
rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_cpus); @@ -448,6 +477,12 @@ static int hisi_uncore_probe(struct platform_device *pdev) goto err_free_opp; }
+ rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_package); + if (rc) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + goto err_free_opp; + } + return 0;
err_free_opp: @@ -467,6 +502,7 @@ static int hisi_uncore_remove(struct platform_device *pdev) hisi_uncore_remove_opp(uncore); hisi_uncore_free_pcc_chan(uncore); device_remove_file(&uncore->devfreq->dev, &dev_attr_related_cpus); + device_remove_file(&uncore->devfreq->dev, &dev_attr_related_package);
return 0; }
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
The event configuration interface is added. The input events can be selected in HiSilicon uncore frequency.
For example: echo L3C > /sys/class/devfreq/**/related_event
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/hisi_uncore_freq.c | 128 +++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+)
diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 2eccc07a9cac..481923416166 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -20,6 +20,7 @@ #include <linux/pm_opp.h> #include <linux/property.h> #include <linux/topology.h> +#include <linux/devfreq-event.h>
#include <acpi/pcc.h>
@@ -28,6 +29,15 @@ /* Don't care OPP votlage, take 1V as default */ #define DEF_OPP_VOLT_UV 1000000
+#define RELATED_EVENT_MAX_CNT 4 +#define RELATED_EVENT_NAME_LEN 10 + +struct related_event { + char name[RELATED_EVENT_NAME_LEN]; + struct platform_device *pdev; + struct devfreq_event_dev *edev; +}; + struct hisi_uncore_freq { struct device *dev; struct mbox_client cl; @@ -40,6 +50,8 @@ struct hisi_uncore_freq { struct devfreq *devfreq; int related_package; struct cpumask related_cpus; + int related_event_cnt; + struct related_event related_events[RELATED_EVENT_MAX_CNT]; };
struct hisi_uncore_pcc_data { @@ -435,6 +447,115 @@ static ssize_t related_package_show(struct device *dev, } DEVICE_ATTR_RO(related_package);
+static int creat_related_event(struct hisi_uncore_freq *uncore, char *name) +{ + int evt_id; + struct related_event *event; + char dev_name[RELATED_EVENT_NAME_LEN + 10]; + + evt_id = uncore->related_event_cnt; + event = &uncore->related_events[evt_id]; + + sprintf(dev_name, "%s-%s", "EVT-UNCORE", name); + event->pdev = platform_device_register_data( + uncore->dev, + dev_name, + uncore->related_package, + NULL, + 0); + if (IS_ERR(event->pdev)) + return PTR_ERR(event->pdev); + + event->edev = devfreq_event_get_edev_by_dev(&event->pdev->dev); + if (event->edev) { + dev_err(&event->pdev->dev, "devfreq event dev do not added\n"); + platform_device_unregister(event->pdev); + return -ENODEV; + } + + strncpy(event->name, name, strlen(name)); + + return 0; +} + +static void remove_related_event(struct hisi_uncore_freq *uncore) +{ + int i; + struct related_event *event; + + for (i = 0; i < uncore->related_event_cnt; ++i) { + event = &uncore->related_events[i]; + event->edev = NULL; + memset(event->name, 0, RELATED_EVENT_NAME_LEN); + platform_device_unregister(event->pdev); + } + + uncore->related_event_cnt = 0; + + return; +} + +static ssize_t related_events_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + char *item; + u32 head, tail; + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + if (!buf) + return 0; + + remove_related_event(uncore); + + head = 0; + item = kcalloc(count + 1, sizeof(*item), GFP_KERNEL); + if (!item) + return -ENOMEM; + + while (uncore->related_event_cnt < RELATED_EVENT_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 = creat_related_event(uncore, item); + if (err) { + kfree(item); + return err; + } + uncore->related_event_cnt++; + } + + kfree(item); + return count; +} + +static ssize_t related_events_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int evt_id; + struct platform_device *pdev = to_platform_device(dev->parent); + struct hisi_uncore_freq *uncore = platform_get_drvdata(pdev); + + for (evt_id = 0; evt_id < uncore->related_event_cnt; ++evt_id) { + sprintf(buf, "%s %s", buf, uncore->related_events[evt_id].name); + } + return sprintf(buf, "%s\n", buf); +} +DEVICE_ATTR_RW(related_events); + static int hisi_uncore_probe(struct platform_device *pdev) { struct hisi_uncore_freq *uncore; @@ -483,6 +604,12 @@ static int hisi_uncore_probe(struct platform_device *pdev) goto err_free_opp; }
+ rc = device_create_file(&uncore->devfreq->dev, &dev_attr_related_events); + if (rc) { + dev_err(&pdev->dev, "Failed to create custom sysfs files\n"); + goto err_free_opp; + } + return 0;
err_free_opp: @@ -501,6 +628,7 @@ static int hisi_uncore_remove(struct platform_device *pdev)
hisi_uncore_remove_opp(uncore); hisi_uncore_free_pcc_chan(uncore); + remove_related_event(uncore); device_remove_file(&uncore->devfreq->dev, &dev_attr_related_cpus); device_remove_file(&uncore->devfreq->dev, &dev_attr_related_package);
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__ */
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
Added governor util that adjusts frequency based on resource utilization.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/Kconfig | 6 ++ drivers/devfreq/Makefile | 1 + drivers/devfreq/governor_util.c | 127 +++++++++++++++++++++++++++++ drivers/devfreq/hisi_uncore_freq.c | 32 ++++++-- include/linux/devfreq.h | 15 ++++ 5 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 drivers/devfreq/governor_util.c
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 158ef3728bf1..1bcdce9a89d3 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -73,6 +73,12 @@ config DEVFREQ_GOV_PASSIVE through sysfs entries. The passive governor recommends that devfreq device uses the OPP table to get the frequency/voltage.
+config DEVFREQ_GOV_UTIL + tristate "Util" + help + This governor Adjust the frequency based on the load utilization + rate. + comment "DEVFREQ Drivers"
config ARM_EXYNOS_BUS_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 04113d583515..963f6b573858 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o +obj-$(CONFIG_DEVFREQ_GOV_UTIL) += governor_util.o
# DEVFREQ Drivers obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o diff --git a/drivers/devfreq/governor_util.c b/drivers/devfreq/governor_util.c new file mode 100644 index 000000000000..023a40c66302 --- /dev/null +++ b/drivers/devfreq/governor_util.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/devfreq/governor_util.c + * + * Copyright (C) 2024 HISI UNCORE + * Xiangwei Li liwei728@huawei.com + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/devfreq.h> +#include <linux/math64.h> +#include "governor.h" + +/* Default constants for DevFreq-Util (DFUL) */ +#define BW_UTIL_DEFAULT (50) + +static int devfreq_util_func(struct devfreq *df, + unsigned long *freq) +{ + int err; + struct devfreq_dev_status *stat; + unsigned long cur_bw, max_bw; + unsigned long cur_freq, step_freq; + unsigned long min_freq, max_freq; + unsigned int util, dful_val = BW_UTIL_DEFAULT; + struct devfreq_util_data *data = df->data; + + err = devfreq_update_stats(df); + if (err) + return err; + + stat = &df->last_status; + + if (data) { + dful_val = data->dful_val; + } + + if (dful_val > 100) + return -EINVAL; + + /* Assume MAX if it is going to be divided by zero */ + if (stat->total_time == 0) { + *freq = df->scaling_max_freq;; + return 0; + } + + /* Prevent overflow */ + if (stat->busy_time >= (1 << 24) || stat->total_time >= (1 << 24)) { + stat->busy_time >>= 7; + stat->total_time >>= 7; + } + + min_freq = df->scaling_min_freq; + max_freq = df->scaling_max_freq; + cur_freq = df->previous_freq; + cur_bw = stat->busy_time; + max_bw = stat->total_time; + + /* Set the desired frequency based on the load */ + util = div_u64(cur_bw * 100, + max_bw * div_u64(cur_freq * 100, max_freq) / 100); + *freq = cur_freq * div_u64(util * 100, dful_val) / 100; + + step_freq = div_u64(max_freq - min_freq, + df->profile->max_state - 1); + *freq = div_u64(*freq, step_freq) * step_freq; + *freq = clamp(*freq, min_freq, max_freq); + + return 0; +} + +static int devfreq_util_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + switch (event) { + case DEVFREQ_GOV_START: + devfreq_monitor_start(devfreq); + break; + + case DEVFREQ_GOV_STOP: + devfreq_monitor_stop(devfreq); + break; + + case DEVFREQ_GOV_UPDATE_INTERVAL: + devfreq_update_interval(devfreq, (unsigned int *)data); + break; + + case DEVFREQ_GOV_SUSPEND: + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + break; + + default: + break; + } + + return 0; +} + +static struct devfreq_governor devfreq_util = { + .name = DEVFREQ_GOV_UTIL, + .get_target_freq = devfreq_util_func, + .event_handler = devfreq_util_handler, +}; + +static int __init devfreq_util_init(void) +{ + return devfreq_add_governor(&devfreq_util); +} +subsys_initcall(devfreq_util_init); + +static void __exit devfreq_util_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_util); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_util_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 481923416166..656ae196b599 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -240,6 +240,28 @@ static int hisi_uncore_target(struct device *dev, unsigned long *freq, static int hisi_uncore_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { + int rc, i, ratio; + struct related_event *event; + struct devfreq_event_data edata; + struct hisi_uncore_freq *uncore = dev_get_drvdata(dev); + + ratio = 0; + for (i = 0; i < uncore->related_event_cnt; ++i) { + event = &uncore->related_events[i]; + event->edev = devfreq_event_get_edev_by_dev(&event->pdev->dev); + if (!event->edev) + continue; + rc = devfreq_event_get_event(event->edev, &edata); + if (rc) + return rc; + + if (ratio <= edata.load_count * 1000 / edata.total_count) { + stat->busy_time = edata.load_count; + stat->total_time = edata.total_count; + ratio = edata.load_count * 1000 / edata.total_count; + } + } + return 0; }
@@ -466,13 +488,6 @@ static int creat_related_event(struct hisi_uncore_freq *uncore, char *name) if (IS_ERR(event->pdev)) return PTR_ERR(event->pdev);
- event->edev = devfreq_event_get_edev_by_dev(&event->pdev->dev); - if (event->edev) { - dev_err(&event->pdev->dev, "devfreq event dev do not added\n"); - platform_device_unregister(event->pdev); - return -ENODEV; - } - strncpy(event->name, name, strlen(name));
return 0; @@ -483,6 +498,7 @@ static void remove_related_event(struct hisi_uncore_freq *uncore) int i; struct related_event *event;
+ devfreq_suspend_device(uncore->devfreq); for (i = 0; i < uncore->related_event_cnt; ++i) { event = &uncore->related_events[i]; event->edev = NULL; @@ -538,6 +554,8 @@ static ssize_t related_events_store(struct device *dev, uncore->related_event_cnt++; }
+ devfreq_resume_device(uncore->devfreq); + kfree(item); return count; } diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index d312ffbac4dd..b1a5f62a0cee 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -21,6 +21,7 @@ #define DEVFREQ_GOV_POWERSAVE "powersave" #define DEVFREQ_GOV_USERSPACE "userspace" #define DEVFREQ_GOV_PASSIVE "passive" +#define DEVFREQ_GOV_UTIL "util"
/* DEVFREQ notifier interface */ #define DEVFREQ_TRANSITION_NOTIFIER (0) @@ -337,6 +338,20 @@ struct devfreq_passive_data { struct list_head cpu_data_list; };
+#if IS_ENABLED(CONFIG_DEVFREQ_GOV_UTIL) +/** + * struct devfreq_util_data - ``void *data`` fed to struct devfreq + * and devfreq_add_device + * @dful_val: Resource utilization baseline. + * + * If the fed devfreq_util_data pointer is NULL to the governor, + * the governor uses the default values. + */ +struct devfreq_util_data { + unsigned int dful_val; +}; +#endif + #if !defined(CONFIG_PM_DEVFREQ) static inline struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile,
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
Trigger the highest frequency bandwidth regularly, judge the bandwidth change ratio and the corresponding frequency ratio, and abstract the judgment logic through accuracy loss. If the conditions are not met, the conditions are reset.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/event/hisi-uncore-l3c.c | 30 +++++++++++++++++++++++-- drivers/devfreq/hisi_uncore_freq.c | 10 +++++++-- 2 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/drivers/devfreq/event/hisi-uncore-l3c.c b/drivers/devfreq/event/hisi-uncore-l3c.c index fa6252227acf..90468f7928e5 100644 --- a/drivers/devfreq/event/hisi-uncore-l3c.c +++ b/drivers/devfreq/event/hisi-uncore-l3c.c @@ -16,12 +16,18 @@
#include "hisi-uncore.h"
-HISI_UNCORE_EVENT_TYPE_ATTR; -HISI_UNCORE_EVENT_CONFIG_ATTR; +#define CORRECT_PERIOD 11 + +static HISI_UNCORE_EVENT_TYPE_ATTR; +static HISI_UNCORE_EVENT_CONFIG_ATTR;
static int l3c_get_events(struct devfreq_event_dev *edev, struct devfreq_event_data *edata) { u64 load; + int p0, p1, p2; + static u64 last_load; + static int period = 0; + struct hisi_uncore_event_info *info = devfreq_event_get_drvdata(edev);
load = get_pmu_monitor_status(info); @@ -29,9 +35,29 @@ static int l3c_get_events(struct devfreq_event_dev *edev, struct devfreq_event_d if (info->is_reset) { info->is_reset = false; info->max_load = 0; + period = 0; + return 0; + } + + period++; + if (period == CORRECT_PERIOD - 1) { + edata->load_count = info->max_load; + edata->total_count = info->max_load; + last_load = load; return 0; }
+ if (period == CORRECT_PERIOD) { + period = 0; + p0 = last_load * 100 / load; + p1 = last_load * 100 / info->max_load; + p2 = load * 100 / info->max_load; + + if (p2 > p1 && p1 > 0 && p2 * 105 / p1 < 100 * 100 / p0) { + info->max_load = load; + } + } + info->max_load = max(info->max_load, load); edata->load_count = load; edata->total_count = info->max_load; diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 656ae196b599..6f27264878f5 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -255,10 +255,16 @@ static int hisi_uncore_get_dev_status(struct device *dev, if (rc) return rc;
- if (ratio <= edata.load_count * 1000 / edata.total_count) { + if (edata.load_count == edata.total_count) { stat->busy_time = edata.load_count; stat->total_time = edata.total_count; - ratio = edata.load_count * 1000 / edata.total_count; + return 0; + } + + if (ratio <= edata.load_count * 100 / edata.total_count) { + stat->busy_time = edata.load_count; + stat->total_time = edata.total_count; + ratio = edata.load_count * 100 / edata.total_count; } }
hulk inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/IBC4SJ
--------------------------------
There is no way to directly observe the uncore DDRC load data. However, the hisi uncore provides a PMU that can monitor the DDRC status. Therefore, the DDRC PMU is used to implement the uncore DDRC event.
Signed-off-by: Xiangwei Li liwei728@huawei.com --- drivers/devfreq/event/Makefile | 3 +- drivers/devfreq/event/hisi-uncore-ddrc.c | 160 +++++++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 drivers/devfreq/event/hisi-uncore-ddrc.c
diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile index c22ba093498d..8b905a614433 100644 --- a/drivers/devfreq/event/Makefile +++ b/drivers/devfreq/event/Makefile @@ -4,4 +4,5 @@ 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 +obj-$(CONFIG_DEVFREQ_EVENT_HISI_UNCORE) += hisi-uncore.o \ + hisi-uncore-l3c.o hisi-uncore-ddrc.o diff --git a/drivers/devfreq/event/hisi-uncore-ddrc.c b/drivers/devfreq/event/hisi-uncore-ddrc.c new file mode 100644 index 000000000000..23a49c709cf2 --- /dev/null +++ b/drivers/devfreq/event/hisi-uncore-ddrc.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hisi-uncore-ddrc.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" + +#define CORRECT_PERIOD 11 + +static HISI_UNCORE_EVENT_TYPE_ATTR; +static HISI_UNCORE_EVENT_CONFIG_ATTR; + +static int ddrc_get_events(struct devfreq_event_dev *edev, struct devfreq_event_data *edata) +{ + u64 load; + int p0, p1, p2; + static u64 last_load; + static int period = 0; + + 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; + period = 0; + return 0; + } + + period++; + if (period == CORRECT_PERIOD - 1) { + edata->load_count = info->max_load; + edata->total_count = info->max_load; + last_load = load; + return 0; + } + + if (period == CORRECT_PERIOD) { + period = 0; + p0 = last_load * 100 / load; + p1 = last_load * 100 / info->max_load; + p2 = load * 100 / info->max_load; + + if (p2 > p1 && p1 > 0 && p2 * 105 / p1 < 100 * 100 / p0) { + info->max_load = load; + } + } + + info->max_load = max(info->max_load, load); + edata->load_count = load; + edata->total_count = info->max_load; + + return 0; +} + +static int ddrc_set_events(struct devfreq_event_dev *edev) +{ + return 0; +} + +static const struct devfreq_event_ops ddrc_event_ops = { + .set_event = ddrc_set_events, + .get_event = ddrc_get_events, +}; + +static int hisi_ddrc_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, "ddrc", dev->id); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + desc->ops = &ddrc_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_ddrc_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_ddrc_pmu_plat_match[] = { + { .name = "EVT-UNCORE-DDRC", }, + {} +}; +MODULE_DEVICE_TABLE(platform, hisi_ddrc_pmu_plat_match); + +struct platform_driver hisi_ddrc_event_driver = { + .probe = hisi_ddrc_event_probe, + .remove = hisi_ddrc_event_remove, + .driver = { + .name = "EVT-UNCORE-DDRC", + }, + .id_table = hisi_ddrc_pmu_plat_match, +}; + +module_platform_driver(hisi_ddrc_event_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xiangwei Li liwei728@huawei.com"); +MODULE_DESCRIPTION("Hisi uncore ddrc pmu events driver");