2022-01-21 19:00:55

by Daniel Lezcano

[permalink] [raw]
Subject: [PATCH v6 0/6] powercap/drivers/dtpm: Create the dtpm hierarchy

The DTPM hierarchy is the base to build on top of it a power budget
allocator. It reflects the power consumption of the group of devices
and allows to cap their power.

The core code is there but there is no way to describe the desired
hierarchy yet.

A first proposal introduced the description through configfs [1] but
was rejected [2].

A second proposal based on the device tree with a binding similar to
the power domains [3] was proposed but finally rejected [4].

This sixth version delegates the hierarchy creation to the SoC with a
specific and self-encapsulated code using an array to describe the tree. The
SoC DTPM driver defines an array of nodes pointing to their parents. The
hierarchy description can integrate a DT node and in the future a SCMI node,
that means the description can mix different type of nodes.

As the DTPM tree depends on different devices which could be modules,
the SoC specific description must always be compiled as a module and
describe the module softdeps in order to let the userspace to handle
proper loading ordering.

In addition to the hierarchy creation, the devfreq dtpm support is also
integrated into this series.

This series was tested on a rock960 (revision B - rk3399 based) and a
db845c (Qualcomm sdm845 based).

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/YGYg6ZeZ1181%[email protected]/
[3] https://lore.kernel.org/all/[email protected]/
[4] https://lore.kernel.org/all/[email protected]/

Changelog:
V6:
- Switched the init table to a subsystem arrays
- Checked 'setup' function is set before calling it
- Moved out of the loop the 'of_node_put'
- Explicitely add DTPM_NODE_VIRTUAL in documentation
- Moved powercap_register_control_type() into the hierarchy creation function
- Removed the sdm845 description
- Made rk3399 always as a module and added module softdeps

V5:
- Remove DT bindings
- Added description with an array
- Added simple description for rk3399 and sdm845
- Moved dtpm table to the data section

V4:
- Added missing powerzone-cells
- Changed powerzone name to comply with the pattern property

V3:
- Remove GPU section as no power is available (yet)
- Remove '#powerzone-cells' conforming to the bindings change
- Removed required property 'compatible'
- Removed powerzone-cells from the topmost node
- Removed powerzone-cells from cpus 'consumers' in example
- Set additionnal property to false

V2:
- Added pattern properties and stick to powerzone-*
- Added required property compatible and powerzone-cells
- Added additionnal property
- Added compatible
- Renamed to 'powerzones'
- Added missing powerzone-cells to the topmost node
- Fixed errors reported by 'make DT_CHECKER_FLAGS=-m dt_binding_check'
- Move description in the SoC dtsi specific file
- Fixed missing prototype warning reported by lkp@

V1: Initial post

Daniel Lezcano (5):
powercap/drivers/dtpm: Convert the init table section to a simple
array
powercap/drivers/dtpm: Add hierarchy creation
powercap/drivers/dtpm: Add CPU DT initialization support
powercap/drivers/dtpm: Add dtpm devfreq with energy model support
rockchip/soc/drivers: Add DTPM description for rk3399

drivers/powercap/Kconfig | 8 ++
drivers/powercap/Makefile | 1 +
drivers/powercap/dtpm.c | 170 ++++++++++++++++++++++++-
drivers/powercap/dtpm_cpu.c | 41 +++++-
drivers/powercap/dtpm_devfreq.c | 204 ++++++++++++++++++++++++++++++
drivers/powercap/dtpm_subsys.h | 22 ++++
drivers/soc/rockchip/Kconfig | 8 ++
drivers/soc/rockchip/Makefile | 1 +
drivers/soc/rockchip/dtpm.c | 59 +++++++++
include/asm-generic/vmlinux.lds.h | 11 --
include/linux/dtpm.h | 33 +++--
11 files changed, 519 insertions(+), 39 deletions(-)
create mode 100644 drivers/powercap/dtpm_devfreq.c
create mode 100644 drivers/powercap/dtpm_subsys.h
create mode 100644 drivers/soc/rockchip/dtpm.c

--
2.25.1


2022-01-21 19:01:03

by Daniel Lezcano

[permalink] [raw]
Subject: [PATCH v6 1/5] powercap/drivers/dtpm: Convert the init table section to a simple array

The init table section is freed after the system booted. However the
next changes will make per module the DTPM description, so the table
won't be accessible when the module is loaded.

In order to fix that, we should move the table to the data section
where there are very few entries and that makes strange to add it
there.

The main goal of the table was to keep self-encapsulated code and we
can keep it almost as it by using an array instead.

Suggested-by: Ulf Hansson <[email protected]>
Signed-off-by: Daniel Lezcano <[email protected]>
---
drivers/powercap/dtpm.c | 2 ++
drivers/powercap/dtpm_cpu.c | 5 ++++-
drivers/powercap/dtpm_subsys.h | 18 ++++++++++++++++++
include/asm-generic/vmlinux.lds.h | 11 -----------
include/linux/dtpm.h | 24 +++---------------------
5 files changed, 27 insertions(+), 33 deletions(-)
create mode 100644 drivers/powercap/dtpm_subsys.h

diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c
index 8cb45f2d3d78..0e5c93443c70 100644
--- a/drivers/powercap/dtpm.c
+++ b/drivers/powercap/dtpm.c
@@ -24,6 +24,8 @@
#include <linux/slab.h>
#include <linux/mutex.h>

+#include "dtpm_subsys.h"
+
#define DTPM_POWER_LIMIT_FLAG 0

static const char *constraint_name[] = {
diff --git a/drivers/powercap/dtpm_cpu.c b/drivers/powercap/dtpm_cpu.c
index b740866b228d..5763e0ce2af5 100644
--- a/drivers/powercap/dtpm_cpu.c
+++ b/drivers/powercap/dtpm_cpu.c
@@ -269,4 +269,7 @@ static int __init dtpm_cpu_init(void)
return 0;
}

-DTPM_DECLARE(dtpm_cpu, dtpm_cpu_init);
+struct dtpm_subsys_ops dtpm_cpu_ops = {
+ .name = KBUILD_MODNAME,
+ .init = dtpm_cpu_init,
+};
diff --git a/drivers/powercap/dtpm_subsys.h b/drivers/powercap/dtpm_subsys.h
new file mode 100644
index 000000000000..2a3a2055f60e
--- /dev/null
+++ b/drivers/powercap/dtpm_subsys.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2022 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <[email protected]>
+ */
+#ifndef ___DTPM_SUBSYS_H__
+#define ___DTPM_SUBSYS_H__
+
+extern struct dtpm_subsys_ops dtpm_cpu_ops;
+
+struct dtpm_subsys_ops *dtpm_subsys[] = {
+#ifdef CONFIG_DTPM_CPU
+ &dtpm_cpu_ops,
+#endif
+};
+
+#endif
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 42f3866bca69..2a10db2f0bc5 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -321,16 +321,6 @@
#define THERMAL_TABLE(name)
#endif

-#ifdef CONFIG_DTPM
-#define DTPM_TABLE() \
- . = ALIGN(8); \
- __dtpm_table = .; \
- KEEP(*(__dtpm_table)) \
- __dtpm_table_end = .;
-#else
-#define DTPM_TABLE()
-#endif
-
#define KERNEL_DTB() \
STRUCT_ALIGN(); \
__dtb_start = .; \
@@ -723,7 +713,6 @@
ACPI_PROBE_TABLE(irqchip) \
ACPI_PROBE_TABLE(timer) \
THERMAL_TABLE(governor) \
- DTPM_TABLE() \
EARLYCON_TABLE() \
LSM_TABLE() \
EARLY_LSM_TABLE() \
diff --git a/include/linux/dtpm.h b/include/linux/dtpm.h
index d37e5d06a357..506048158a50 100644
--- a/include/linux/dtpm.h
+++ b/include/linux/dtpm.h
@@ -32,29 +32,11 @@ struct dtpm_ops {
void (*release)(struct dtpm *);
};

-typedef int (*dtpm_init_t)(void);
-
-struct dtpm_descr {
- dtpm_init_t init;
+struct dtpm_subsys_ops {
+ const char *name;
+ int (*init)(void);
};

-/* Init section thermal table */
-extern struct dtpm_descr __dtpm_table[];
-extern struct dtpm_descr __dtpm_table_end[];
-
-#define DTPM_TABLE_ENTRY(name, __init) \
- static struct dtpm_descr __dtpm_table_entry_##name \
- __used __section("__dtpm_table") = { \
- .init = __init, \
- }
-
-#define DTPM_DECLARE(name, init) DTPM_TABLE_ENTRY(name, init)
-
-#define for_each_dtpm_table(__dtpm) \
- for (__dtpm = __dtpm_table; \
- __dtpm < __dtpm_table_end; \
- __dtpm++)
-
static inline struct dtpm *to_dtpm(struct powercap_zone *zone)
{
return container_of(zone, struct dtpm, zone);
--
2.25.1

2022-01-21 19:01:15

by Daniel Lezcano

[permalink] [raw]
Subject: [PATCH v6 2/5] powercap/drivers/dtpm: Add hierarchy creation

The DTPM framework is available but without a way to configure it.

This change provides a way to create a hierarchy of DTPM node where
the power consumption reflects the sum of the children's power
consumption.

It is up to the platform to specify an array of dtpm nodes where each
element has a pointer to its parent, except the top most one. The type
of the node gives the indication of which initialization callback to
call. At this time, we can create a virtual node, where its purpose is
to be a parent in the hierarchy, and a DT node where the name
describes its path.

In order to ensure a nice self-encapsulation, the DTPM subsys array
contains a couple of initialization functions, one to setup the DTPM
backend and one to initialize it up. With this approach, the DTPM
framework has a very few material to export.

Signed-off-by: Daniel Lezcano <[email protected]>
---
drivers/powercap/Kconfig | 1 +
drivers/powercap/dtpm.c | 168 ++++++++++++++++++++++++++++++++++++++-
include/linux/dtpm.h | 15 ++++
3 files changed, 181 insertions(+), 3 deletions(-)

diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
index 8242e8c5ed77..b1ca339957e3 100644
--- a/drivers/powercap/Kconfig
+++ b/drivers/powercap/Kconfig
@@ -46,6 +46,7 @@ config IDLE_INJECT

config DTPM
bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
+ depends on OF
help
This enables support for the power capping for the dynamic
thermal power management userspace engine.
diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c
index 0e5c93443c70..10032f7132c4 100644
--- a/drivers/powercap/dtpm.c
+++ b/drivers/powercap/dtpm.c
@@ -23,6 +23,7 @@
#include <linux/powercap.h>
#include <linux/slab.h>
#include <linux/mutex.h>
+#include <linux/of.h>

#include "dtpm_subsys.h"

@@ -463,14 +464,175 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
return 0;
}

-static int __init init_dtpm(void)
+static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
+ struct dtpm *parent)
{
+ struct dtpm *dtpm;
+ int ret;
+
+ dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
+ if (!dtpm)
+ return ERR_PTR(-ENOMEM);
+ dtpm_init(dtpm, NULL);
+
+ ret = dtpm_register(hierarchy->name, dtpm, parent);
+ if (ret) {
+ pr_err("Failed to register dtpm node '%s': %d\n",
+ hierarchy->name, ret);
+ kfree(dtpm);
+ return ERR_PTR(ret);
+ }
+
+ return dtpm;
+}
+
+static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
+ struct dtpm *parent)
+{
+ struct device_node *np;
+ int i, ret;
+
+ np = of_find_node_by_path(hierarchy->name);
+ if (!np) {
+ pr_err("Failed to find '%s'\n", hierarchy->name);
+ return ERR_PTR(-ENXIO);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
+
+ if (!dtpm_subsys[i]->setup)
+ continue;
+
+ ret = dtpm_subsys[i]->setup(parent, np);
+ if (ret) {
+ pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
+ of_node_put(np);
+ return ERR_PTR(ret);
+ }
+ }
+
+ of_node_put(np);
+
+ /*
+ * By returning a NULL pointer, we let know the caller there
+ * is no child for us as we are a leaf of the tree
+ */
+ return NULL;
+}
+
+typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
+
+dtpm_node_callback_t dtpm_node_callback[] = {
+ [DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
+ [DTPM_NODE_DT] = dtpm_setup_dt,
+};
+
+static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
+ const struct dtpm_node *it, struct dtpm *parent)
+{
+ struct dtpm *dtpm;
+ int i, ret;
+
+ for (i = 0; hierarchy[i].name; i++) {
+
+ if (hierarchy[i].parent != it)
+ continue;
+
+ dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
+ if (!dtpm || IS_ERR(dtpm))
+ continue;
+
+ ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * dtpm_create_hierarchy - Create the dtpm hierarchy
+ * @hierarchy: An array of struct dtpm_node describing the hierarchy
+ *
+ * The function is called by the platform specific code with the
+ * description of the different node in the hierarchy. It creates the
+ * tree in the sysfs filesystem under the powercap dtpm entry.
+ *
+ * The expected tree has the format:
+ *
+ * struct dtpm_node hierarchy[] = {
+ * [0] { .name = "topmost", type = DTPM_NODE_VIRTUAL },
+ * [1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
+ * [2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
+ * [3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
+ * [4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
+ * [5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
+ * [6] { }
+ * };
+ *
+ * The last element is always an empty one and marks the end of the
+ * array.
+ *
+ * Return: zero on success, a negative value in case of error. Errors
+ * are reported back from the underlying functions.
+ */
+int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
+{
+ const struct of_device_id *match;
+ const struct dtpm_node *hierarchy;
+ struct device_node *np;
+ int i, ret;
+
+ if (pct)
+ return -EBUSY;
+
pct = powercap_register_control_type(NULL, "dtpm", NULL);
if (IS_ERR(pct)) {
pr_err("Failed to register control type\n");
- return PTR_ERR(pct);
+ ret = PTR_ERR(pct);
+ goto out_pct;
+ }
+
+ ret = -ENODEV;
+ np = of_find_node_by_path("/");
+ if (!np)
+ goto out_err;
+
+ match = of_match_node(dtpm_match_table, np);
+
+ of_node_put(np);
+
+ if (!match)
+ goto out_err;
+
+ hierarchy = match->data;
+ if (!hierarchy) {
+ ret = -EFAULT;
+ goto out_err;
+ }
+
+ ret = dtpm_for_each_child(hierarchy, NULL, NULL);
+ if (ret)
+ goto out_err;
+
+ for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
+
+ if (!dtpm_subsys[i]->init)
+ continue;
+
+ ret = dtpm_subsys[i]->init();
+ if (ret)
+ pr_info("Failed to initialze '%s': %d",
+ dtpm_subsys[i]->name, ret);
}

return 0;
+
+out_err:
+ powercap_unregister_control_type(pct);
+out_pct:
+ pct = NULL;
+
+ return ret;
}
-late_initcall(init_dtpm);
+EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
diff --git a/include/linux/dtpm.h b/include/linux/dtpm.h
index 506048158a50..f7a25c70dd4c 100644
--- a/include/linux/dtpm.h
+++ b/include/linux/dtpm.h
@@ -32,9 +32,23 @@ struct dtpm_ops {
void (*release)(struct dtpm *);
};

+struct device_node;
+
struct dtpm_subsys_ops {
const char *name;
int (*init)(void);
+ int (*setup)(struct dtpm *, struct device_node *);
+};
+
+enum DTPM_NODE_TYPE {
+ DTPM_NODE_VIRTUAL = 0,
+ DTPM_NODE_DT,
+};
+
+struct dtpm_node {
+ enum DTPM_NODE_TYPE type;
+ const char *name;
+ struct dtpm_node *parent;
};

static inline struct dtpm *to_dtpm(struct powercap_zone *zone)
@@ -52,4 +66,5 @@ void dtpm_unregister(struct dtpm *dtpm);

int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent);

+int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table);
#endif
--
2.25.1

2022-01-21 19:01:27

by Daniel Lezcano

[permalink] [raw]
Subject: [PATCH v6 4/5] powercap/drivers/dtpm: Add dtpm devfreq with energy model support

Currently the dtpm supports the CPUs via cpufreq and the energy
model. This change provides the same for the device which supports
devfreq.

Each device supporting devfreq and having an energy model can be added
to the hierarchy.

The concept is the same as the cpufreq DTPM support: the QoS is used
to aggregate the requests and the energy model gives the value of the
instantaneous power consumption ponderated by the load of the device.

Cc: Chanwoo Choi <[email protected]>
Cc: Lukasz Luba <[email protected]>
Cc: Kyungmin Park <[email protected]>
Cc: MyungJoo Ham <[email protected]>
Signed-off-by: Daniel Lezcano <[email protected]>
---
drivers/powercap/Kconfig | 7 ++
drivers/powercap/Makefile | 1 +
drivers/powercap/dtpm_devfreq.c | 204 ++++++++++++++++++++++++++++++++
drivers/powercap/dtpm_subsys.h | 4 +
4 files changed, 216 insertions(+)
create mode 100644 drivers/powercap/dtpm_devfreq.c

diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
index b1ca339957e3..515e3ceb3393 100644
--- a/drivers/powercap/Kconfig
+++ b/drivers/powercap/Kconfig
@@ -57,4 +57,11 @@ config DTPM_CPU
help
This enables support for CPU power limitation based on
energy model.
+
+config DTPM_DEVFREQ
+ bool "Add device power capping based on the energy model"
+ depends on DTPM && ENERGY_MODEL
+ help
+ This enables support for device power limitation based on
+ energy model.
endif
diff --git a/drivers/powercap/Makefile b/drivers/powercap/Makefile
index fabcf388a8d3..494617cdad88 100644
--- a/drivers/powercap/Makefile
+++ b/drivers/powercap/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DTPM) += dtpm.o
obj-$(CONFIG_DTPM_CPU) += dtpm_cpu.o
+obj-$(CONFIG_DTPM_DEVFREQ) += dtpm_devfreq.o
obj-$(CONFIG_POWERCAP) += powercap_sys.o
obj-$(CONFIG_INTEL_RAPL_CORE) += intel_rapl_common.o
obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o
diff --git a/drivers/powercap/dtpm_devfreq.c b/drivers/powercap/dtpm_devfreq.c
new file mode 100644
index 000000000000..755f55f941bf
--- /dev/null
+++ b/drivers/powercap/dtpm_devfreq.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2021 Linaro Limited
+ *
+ * Author: Daniel Lezcano <[email protected]>
+ *
+ * The devfreq device combined with the energy model and the load can
+ * give an estimation of the power consumption as well as limiting the
+ * power.
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpumask.h>
+#include <linux/devfreq.h>
+#include <linux/dtpm.h>
+#include <linux/energy_model.h>
+#include <linux/of.h>
+#include <linux/pm_qos.h>
+#include <linux/slab.h>
+#include <linux/units.h>
+
+struct dtpm_devfreq {
+ struct dtpm dtpm;
+ struct dev_pm_qos_request qos_req;
+ struct devfreq *devfreq;
+};
+
+static struct dtpm_devfreq *to_dtpm_devfreq(struct dtpm *dtpm)
+{
+ return container_of(dtpm, struct dtpm_devfreq, dtpm);
+}
+
+static int update_pd_power_uw(struct dtpm *dtpm)
+{
+ struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
+ struct devfreq *devfreq = dtpm_devfreq->devfreq;
+ struct device *dev = devfreq->dev.parent;
+ struct em_perf_domain *pd = em_pd_get(dev);
+
+ dtpm->power_min = pd->table[0].power;
+ dtpm->power_min *= MICROWATT_PER_MILLIWATT;
+
+ dtpm->power_max = pd->table[pd->nr_perf_states - 1].power;
+ dtpm->power_max *= MICROWATT_PER_MILLIWATT;
+
+ return 0;
+}
+
+static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
+{
+ struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
+ struct devfreq *devfreq = dtpm_devfreq->devfreq;
+ struct device *dev = devfreq->dev.parent;
+ struct em_perf_domain *pd = em_pd_get(dev);
+ unsigned long freq;
+ u64 power;
+ int i;
+
+ for (i = 0; i < pd->nr_perf_states; i++) {
+
+ power = pd->table[i].power * MICROWATT_PER_MILLIWATT;
+ if (power > power_limit)
+ break;
+ }
+
+ freq = pd->table[i - 1].frequency;
+
+ dev_pm_qos_update_request(&dtpm_devfreq->qos_req, freq);
+
+ power_limit = pd->table[i - 1].power * MICROWATT_PER_MILLIWATT;
+
+ return power_limit;
+}
+
+static void _normalize_load(struct devfreq_dev_status *status)
+{
+ if (status->total_time > 0xfffff) {
+ status->total_time >>= 10;
+ status->busy_time >>= 10;
+ }
+
+ status->busy_time <<= 10;
+ status->busy_time /= status->total_time ? : 1;
+
+ status->busy_time = status->busy_time ? : 1;
+ status->total_time = 1024;
+}
+
+static u64 get_pd_power_uw(struct dtpm *dtpm)
+{
+ struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
+ struct devfreq *devfreq = dtpm_devfreq->devfreq;
+ struct device *dev = devfreq->dev.parent;
+ struct em_perf_domain *pd = em_pd_get(dev);
+ struct devfreq_dev_status status;
+ unsigned long freq;
+ u64 power;
+ int i;
+
+ mutex_lock(&devfreq->lock);
+ status = devfreq->last_status;
+ mutex_unlock(&devfreq->lock);
+
+ freq = DIV_ROUND_UP(status.current_frequency, HZ_PER_KHZ);
+ _normalize_load(&status);
+
+ for (i = 0; i < pd->nr_perf_states; i++) {
+
+ if (pd->table[i].frequency < freq)
+ continue;
+
+ power = pd->table[i].power * MICROWATT_PER_MILLIWATT;
+ power *= status.busy_time;
+ power >>= 10;
+
+ return power;
+ }
+
+ return 0;
+}
+
+static void pd_release(struct dtpm *dtpm)
+{
+ struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
+
+ if (dev_pm_qos_request_active(&dtpm_devfreq->qos_req))
+ dev_pm_qos_remove_request(&dtpm_devfreq->qos_req);
+
+ kfree(dtpm_devfreq);
+}
+
+static struct dtpm_ops dtpm_ops = {
+ .set_power_uw = set_pd_power_limit,
+ .get_power_uw = get_pd_power_uw,
+ .update_power_uw = update_pd_power_uw,
+ .release = pd_release,
+};
+
+static int __dtpm_devfreq_setup(struct devfreq *devfreq, struct dtpm *parent)
+{
+ struct device *dev = devfreq->dev.parent;
+ struct dtpm_devfreq *dtpm_devfreq;
+ struct em_perf_domain *pd;
+ int ret = -ENOMEM;
+
+ pd = em_pd_get(dev);
+ if (!pd) {
+ ret = dev_pm_opp_of_register_em(dev, NULL);
+ if (ret) {
+ pr_err("No energy model available for '%s'\n", dev_name(dev));
+ return -EINVAL;
+ }
+ }
+
+ dtpm_devfreq = kzalloc(sizeof(*dtpm_devfreq), GFP_KERNEL);
+ if (!dtpm_devfreq)
+ return -ENOMEM;
+
+ dtpm_init(&dtpm_devfreq->dtpm, &dtpm_ops);
+
+ dtpm_devfreq->devfreq = devfreq;
+
+ ret = dtpm_register(dev_name(dev), &dtpm_devfreq->dtpm, parent);
+ if (ret) {
+ pr_err("Failed to register '%s': %d\n", dev_name(dev), ret);
+ goto out_dtpm_devfreq;
+ }
+
+ ret = dev_pm_qos_add_request(dev, &dtpm_devfreq->qos_req,
+ DEV_PM_QOS_MAX_FREQUENCY,
+ PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
+ if (ret) {
+ pr_err("Failed to add QoS request: %d\n", ret);
+ goto out_dtpm_unregister;
+ }
+
+ dtpm_update_power(&dtpm_devfreq->dtpm);
+
+ return 0;
+
+out_dtpm_unregister:
+ dtpm_unregister(&dtpm_devfreq->dtpm);
+out_dtpm_devfreq:
+ kfree(dtpm_devfreq);
+
+ return ret;
+}
+
+static int dtpm_devfreq_setup(struct dtpm *dtpm, struct device_node *np)
+{
+ struct devfreq *devfreq;
+
+ devfreq = devfreq_get_devfreq_by_node(np);
+ if (IS_ERR(devfreq))
+ return 0;
+
+ return __dtpm_devfreq_setup(devfreq, dtpm);
+}
+
+struct dtpm_subsys_ops dtpm_devfreq_ops = {
+ .name = KBUILD_MODNAME,
+ .setup = dtpm_devfreq_setup,
+};
diff --git a/drivers/powercap/dtpm_subsys.h b/drivers/powercap/dtpm_subsys.h
index 2a3a2055f60e..db1712938a96 100644
--- a/drivers/powercap/dtpm_subsys.h
+++ b/drivers/powercap/dtpm_subsys.h
@@ -8,11 +8,15 @@
#define ___DTPM_SUBSYS_H__

extern struct dtpm_subsys_ops dtpm_cpu_ops;
+extern struct dtpm_subsys_ops dtpm_devfreq_ops;

struct dtpm_subsys_ops *dtpm_subsys[] = {
#ifdef CONFIG_DTPM_CPU
&dtpm_cpu_ops,
#endif
+#ifdef CONFIG_DTPM_DEVFREQ
+ &dtpm_devfreq_ops,
+#endif
};

#endif
--
2.25.1

2022-01-21 19:01:49

by Daniel Lezcano

[permalink] [raw]
Subject: [PATCH v6 3/5] powercap/drivers/dtpm: Add CPU DT initialization support

Based on the previous DT changes in the core code, use the 'setup'
callback to initialize the CPU DTPM backend.

Code is reorganized to stick to the DTPM table description. No
functional changes.

Signed-off-by: Daniel Lezcano <[email protected]>
Reviewed-by: Ulf Hansson <[email protected]>
---
drivers/powercap/dtpm_cpu.c | 36 ++++++++++++++++++++++++++++++------
1 file changed, 30 insertions(+), 6 deletions(-)

diff --git a/drivers/powercap/dtpm_cpu.c b/drivers/powercap/dtpm_cpu.c
index 5763e0ce2af5..eed5ad688d46 100644
--- a/drivers/powercap/dtpm_cpu.c
+++ b/drivers/powercap/dtpm_cpu.c
@@ -21,6 +21,7 @@
#include <linux/cpuhotplug.h>
#include <linux/dtpm.h>
#include <linux/energy_model.h>
+#include <linux/of.h>
#include <linux/pm_qos.h>
#include <linux/slab.h>
#include <linux/units.h>
@@ -176,6 +177,17 @@ static int cpuhp_dtpm_cpu_offline(unsigned int cpu)
}

static int cpuhp_dtpm_cpu_online(unsigned int cpu)
+{
+ struct dtpm_cpu *dtpm_cpu;
+
+ dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
+ if (dtpm_cpu)
+ return dtpm_update_power(&dtpm_cpu->dtpm);
+
+ return 0;
+}
+
+static int __dtpm_cpu_setup(int cpu, struct dtpm *parent)
{
struct dtpm_cpu *dtpm_cpu;
struct cpufreq_policy *policy;
@@ -183,6 +195,10 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
char name[CPUFREQ_NAME_LEN];
int ret = -ENOMEM;

+ dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
+ if (dtpm_cpu)
+ return 0;
+
policy = cpufreq_cpu_get(cpu);
if (!policy)
return 0;
@@ -191,10 +207,6 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
if (!pd)
return -EINVAL;

- dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
- if (dtpm_cpu)
- return dtpm_update_power(&dtpm_cpu->dtpm);
-
dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL);
if (!dtpm_cpu)
return -ENOMEM;
@@ -207,7 +219,7 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)

snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu);

- ret = dtpm_register(name, &dtpm_cpu->dtpm, NULL);
+ ret = dtpm_register(name, &dtpm_cpu->dtpm, parent);
if (ret)
goto out_kfree_dtpm_cpu;

@@ -231,7 +243,18 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
return ret;
}

-static int __init dtpm_cpu_init(void)
+static int dtpm_cpu_setup(struct dtpm *dtpm, struct device_node *np)
+{
+ int cpu;
+
+ cpu = of_cpu_node_to_id(np);
+ if (cpu < 0)
+ return 0;
+
+ return __dtpm_cpu_setup(cpu, dtpm);
+}
+
+static int dtpm_cpu_init(void)
{
int ret;

@@ -272,4 +295,5 @@ static int __init dtpm_cpu_init(void)
struct dtpm_subsys_ops dtpm_cpu_ops = {
.name = KBUILD_MODNAME,
.init = dtpm_cpu_init,
+ .setup = dtpm_cpu_setup,
};
--
2.25.1

2022-01-21 19:02:52

by Daniel Lezcano

[permalink] [raw]
Subject: [PATCH v6 5/5] rockchip/soc/drivers: Add DTPM description for rk3399

The DTPM framework does support now the hierarchy description.

The platform specific code can call the hierarchy creation function
with an array of struct dtpm_node pointing to their parent.

This patch provides a description of the big / Little CPUs and the
GPU and tie them together under a virtual 'package' name. Only rk3399 is
described now.

The description could be extended in the future with the memory
controller with devfreq.

The description is always a module and it describes the soft
dependencies. The userspace has to load the softdeps module in the
right order.

Signed-off-by: Daniel Lezcano <[email protected]>
---
drivers/soc/rockchip/Kconfig | 8 +++++
drivers/soc/rockchip/Makefile | 1 +
drivers/soc/rockchip/dtpm.c | 59 +++++++++++++++++++++++++++++++++++
3 files changed, 68 insertions(+)
create mode 100644 drivers/soc/rockchip/dtpm.c

diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig
index 25eb2c1e31bb..6dc017f02431 100644
--- a/drivers/soc/rockchip/Kconfig
+++ b/drivers/soc/rockchip/Kconfig
@@ -34,4 +34,12 @@ config ROCKCHIP_PM_DOMAINS

If unsure, say N.

+config ROCKCHIP_DTPM
+ tristate "Rockchip DTPM hierarchy"
+ depends on DTPM && DRM_PANFROST && m
+ help
+ Describe the hierarchy for the Dynamic Thermal Power
+ Management tree on this platform. That will create all the
+ power capping capable devices.
+
endif
diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile
index 875032f7344e..05f31a4e743c 100644
--- a/drivers/soc/rockchip/Makefile
+++ b/drivers/soc/rockchip/Makefile
@@ -5,3 +5,4 @@
obj-$(CONFIG_ROCKCHIP_GRF) += grf.o
obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o
obj-$(CONFIG_ROCKCHIP_PM_DOMAINS) += pm_domains.o
+obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o
diff --git a/drivers/soc/rockchip/dtpm.c b/drivers/soc/rockchip/dtpm.c
new file mode 100644
index 000000000000..0b73a9cba954
--- /dev/null
+++ b/drivers/soc/rockchip/dtpm.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2021 Linaro Limited
+ *
+ * Author: Daniel Lezcano <[email protected]>
+ *
+ * DTPM hierarchy description
+ */
+#include <linux/dtpm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+static struct dtpm_node __initdata rk3399_hierarchy[] = {
+ [0]{ .name = "rk3399",
+ .type = DTPM_NODE_VIRTUAL },
+ [1]{ .name = "package",
+ .type = DTPM_NODE_VIRTUAL,
+ .parent = &rk3399_hierarchy[0] },
+ [2]{ .name = "/cpus/cpu@0",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [3]{ .name = "/cpus/cpu@1",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [4]{ .name = "/cpus/cpu@2",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [5]{ .name = "/cpus/cpu@3",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [6]{ .name = "/cpus/cpu@100",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [7]{ .name = "/cpus/cpu@101",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [8]{ .name = "/gpu@ff9a0000",
+ .type = DTPM_NODE_DT,
+ .parent = &rk3399_hierarchy[1] },
+ [9]{ },
+};
+
+static struct of_device_id __initdata rockchip_dtpm_match_table[] = {
+ { .compatible = "rockchip,rk3399", .data = rk3399_hierarchy },
+ {},
+};
+
+static int __init rockchip_dtpm_init(void)
+{
+ return dtpm_create_hierarchy(rockchip_dtpm_match_table);
+}
+module_init(rockchip_dtpm_init);
+
+MODULE_SOFTDEP("pre: panfrost cpufreq-dt");
+MODULE_DESCRIPTION("Rockchip DTPM driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dtpm");
+MODULE_AUTHOR("Daniel Lezcano <[email protected]");
--
2.25.1

2022-01-21 19:10:47

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v6 1/5] powercap/drivers/dtpm: Convert the init table section to a simple array

On Wed, 19 Jan 2022 at 09:57, Daniel Lezcano <[email protected]> wrote:
>
> The init table section is freed after the system booted. However the
> next changes will make per module the DTPM description, so the table
> won't be accessible when the module is loaded.
>
> In order to fix that, we should move the table to the data section
> where there are very few entries and that makes strange to add it
> there.
>
> The main goal of the table was to keep self-encapsulated code and we
> can keep it almost as it by using an array instead.
>
> Suggested-by: Ulf Hansson <[email protected]>
> Signed-off-by: Daniel Lezcano <[email protected]>

Thanks for updating!

Reviewed-by: Ulf Hansson <[email protected]>

Kind regards
Uffe

> ---
> drivers/powercap/dtpm.c | 2 ++
> drivers/powercap/dtpm_cpu.c | 5 ++++-
> drivers/powercap/dtpm_subsys.h | 18 ++++++++++++++++++
> include/asm-generic/vmlinux.lds.h | 11 -----------
> include/linux/dtpm.h | 24 +++---------------------
> 5 files changed, 27 insertions(+), 33 deletions(-)
> create mode 100644 drivers/powercap/dtpm_subsys.h
>
> diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c
> index 8cb45f2d3d78..0e5c93443c70 100644
> --- a/drivers/powercap/dtpm.c
> +++ b/drivers/powercap/dtpm.c
> @@ -24,6 +24,8 @@
> #include <linux/slab.h>
> #include <linux/mutex.h>
>
> +#include "dtpm_subsys.h"
> +
> #define DTPM_POWER_LIMIT_FLAG 0
>
> static const char *constraint_name[] = {
> diff --git a/drivers/powercap/dtpm_cpu.c b/drivers/powercap/dtpm_cpu.c
> index b740866b228d..5763e0ce2af5 100644
> --- a/drivers/powercap/dtpm_cpu.c
> +++ b/drivers/powercap/dtpm_cpu.c
> @@ -269,4 +269,7 @@ static int __init dtpm_cpu_init(void)
> return 0;
> }
>
> -DTPM_DECLARE(dtpm_cpu, dtpm_cpu_init);
> +struct dtpm_subsys_ops dtpm_cpu_ops = {
> + .name = KBUILD_MODNAME,
> + .init = dtpm_cpu_init,
> +};
> diff --git a/drivers/powercap/dtpm_subsys.h b/drivers/powercap/dtpm_subsys.h
> new file mode 100644
> index 000000000000..2a3a2055f60e
> --- /dev/null
> +++ b/drivers/powercap/dtpm_subsys.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2022 Linaro Ltd
> + *
> + * Author: Daniel Lezcano <[email protected]>
> + */
> +#ifndef ___DTPM_SUBSYS_H__
> +#define ___DTPM_SUBSYS_H__
> +
> +extern struct dtpm_subsys_ops dtpm_cpu_ops;
> +
> +struct dtpm_subsys_ops *dtpm_subsys[] = {
> +#ifdef CONFIG_DTPM_CPU
> + &dtpm_cpu_ops,
> +#endif
> +};
> +
> +#endif
> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> index 42f3866bca69..2a10db2f0bc5 100644
> --- a/include/asm-generic/vmlinux.lds.h
> +++ b/include/asm-generic/vmlinux.lds.h
> @@ -321,16 +321,6 @@
> #define THERMAL_TABLE(name)
> #endif
>
> -#ifdef CONFIG_DTPM
> -#define DTPM_TABLE() \
> - . = ALIGN(8); \
> - __dtpm_table = .; \
> - KEEP(*(__dtpm_table)) \
> - __dtpm_table_end = .;
> -#else
> -#define DTPM_TABLE()
> -#endif
> -
> #define KERNEL_DTB() \
> STRUCT_ALIGN(); \
> __dtb_start = .; \
> @@ -723,7 +713,6 @@
> ACPI_PROBE_TABLE(irqchip) \
> ACPI_PROBE_TABLE(timer) \
> THERMAL_TABLE(governor) \
> - DTPM_TABLE() \
> EARLYCON_TABLE() \
> LSM_TABLE() \
> EARLY_LSM_TABLE() \
> diff --git a/include/linux/dtpm.h b/include/linux/dtpm.h
> index d37e5d06a357..506048158a50 100644
> --- a/include/linux/dtpm.h
> +++ b/include/linux/dtpm.h
> @@ -32,29 +32,11 @@ struct dtpm_ops {
> void (*release)(struct dtpm *);
> };
>
> -typedef int (*dtpm_init_t)(void);
> -
> -struct dtpm_descr {
> - dtpm_init_t init;
> +struct dtpm_subsys_ops {
> + const char *name;
> + int (*init)(void);
> };
>
> -/* Init section thermal table */
> -extern struct dtpm_descr __dtpm_table[];
> -extern struct dtpm_descr __dtpm_table_end[];
> -
> -#define DTPM_TABLE_ENTRY(name, __init) \
> - static struct dtpm_descr __dtpm_table_entry_##name \
> - __used __section("__dtpm_table") = { \
> - .init = __init, \
> - }
> -
> -#define DTPM_DECLARE(name, init) DTPM_TABLE_ENTRY(name, init)
> -
> -#define for_each_dtpm_table(__dtpm) \
> - for (__dtpm = __dtpm_table; \
> - __dtpm < __dtpm_table_end; \
> - __dtpm++)
> -
> static inline struct dtpm *to_dtpm(struct powercap_zone *zone)
> {
> return container_of(zone, struct dtpm, zone);
> --
> 2.25.1
>

2022-01-24 21:48:28

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v6 2/5] powercap/drivers/dtpm: Add hierarchy creation

On Wed, 19 Jan 2022 at 09:58, Daniel Lezcano <[email protected]> wrote:
>
> The DTPM framework is available but without a way to configure it.
>
> This change provides a way to create a hierarchy of DTPM node where
> the power consumption reflects the sum of the children's power
> consumption.
>
> It is up to the platform to specify an array of dtpm nodes where each
> element has a pointer to its parent, except the top most one. The type
> of the node gives the indication of which initialization callback to
> call. At this time, we can create a virtual node, where its purpose is
> to be a parent in the hierarchy, and a DT node where the name
> describes its path.
>
> In order to ensure a nice self-encapsulation, the DTPM subsys array
> contains a couple of initialization functions, one to setup the DTPM
> backend and one to initialize it up. With this approach, the DTPM
> framework has a very few material to export.
>
> Signed-off-by: Daniel Lezcano <[email protected]>
> ---
> drivers/powercap/Kconfig | 1 +
> drivers/powercap/dtpm.c | 168 ++++++++++++++++++++++++++++++++++++++-
> include/linux/dtpm.h | 15 ++++
> 3 files changed, 181 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
> index 8242e8c5ed77..b1ca339957e3 100644
> --- a/drivers/powercap/Kconfig
> +++ b/drivers/powercap/Kconfig
> @@ -46,6 +46,7 @@ config IDLE_INJECT
>
> config DTPM
> bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
> + depends on OF
> help
> This enables support for the power capping for the dynamic
> thermal power management userspace engine.
> diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c
> index 0e5c93443c70..10032f7132c4 100644
> --- a/drivers/powercap/dtpm.c
> +++ b/drivers/powercap/dtpm.c
> @@ -23,6 +23,7 @@
> #include <linux/powercap.h>
> #include <linux/slab.h>
> #include <linux/mutex.h>
> +#include <linux/of.h>
>
> #include "dtpm_subsys.h"
>
> @@ -463,14 +464,175 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
> return 0;
> }
>
> -static int __init init_dtpm(void)
> +static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
> + struct dtpm *parent)
> {
> + struct dtpm *dtpm;
> + int ret;
> +
> + dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
> + if (!dtpm)
> + return ERR_PTR(-ENOMEM);
> + dtpm_init(dtpm, NULL);
> +
> + ret = dtpm_register(hierarchy->name, dtpm, parent);
> + if (ret) {
> + pr_err("Failed to register dtpm node '%s': %d\n",
> + hierarchy->name, ret);
> + kfree(dtpm);
> + return ERR_PTR(ret);
> + }
> +
> + return dtpm;
> +}
> +
> +static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
> + struct dtpm *parent)
> +{
> + struct device_node *np;
> + int i, ret;
> +
> + np = of_find_node_by_path(hierarchy->name);
> + if (!np) {
> + pr_err("Failed to find '%s'\n", hierarchy->name);
> + return ERR_PTR(-ENXIO);
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
> +
> + if (!dtpm_subsys[i]->setup)
> + continue;
> +
> + ret = dtpm_subsys[i]->setup(parent, np);
> + if (ret) {
> + pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
> + of_node_put(np);
> + return ERR_PTR(ret);
> + }
> + }
> +
> + of_node_put(np);
> +
> + /*
> + * By returning a NULL pointer, we let know the caller there
> + * is no child for us as we are a leaf of the tree
> + */
> + return NULL;
> +}
> +
> +typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
> +
> +dtpm_node_callback_t dtpm_node_callback[] = {
> + [DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
> + [DTPM_NODE_DT] = dtpm_setup_dt,
> +};
> +
> +static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
> + const struct dtpm_node *it, struct dtpm *parent)
> +{
> + struct dtpm *dtpm;
> + int i, ret;
> +
> + for (i = 0; hierarchy[i].name; i++) {
> +
> + if (hierarchy[i].parent != it)
> + continue;
> +
> + dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
> + if (!dtpm || IS_ERR(dtpm))

This can be tested with the "IS_ERR_OR_NULL()" macro.

> + continue;

We have discussed the error path previously. Just ignoring errors here
and continuing with the initialization, isn't normally how we design
good kernel code.

However, you have also explained that the error path is special and
somewhat non-trivial to manage in this case. I get that now and thanks
for clarifying.

Nevertheless, I think it deserves to be explained a bit with a comment
in the code of what goes on here. Otherwise another developer that
looks at this code in the future, may think it looks suspicious too.

> +
> + ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}

[...]

Other than the above, this looks good to me!

Kind regards
Uffe

2022-01-25 14:57:21

by Daniel Lezcano

[permalink] [raw]
Subject: Re: [PATCH v6 2/5] powercap/drivers/dtpm: Add hierarchy creation

On 24/01/2022 21:00, Ulf Hansson wrote:
> On Wed, 19 Jan 2022 at 09:58, Daniel Lezcano <[email protected]> wrote:
>>
>> The DTPM framework is available but without a way to configure it.
>>
>> This change provides a way to create a hierarchy of DTPM node where
>> the power consumption reflects the sum of the children's power
>> consumption.
>>
>> It is up to the platform to specify an array of dtpm nodes where each
>> element has a pointer to its parent, except the top most one. The type
>> of the node gives the indication of which initialization callback to
>> call. At this time, we can create a virtual node, where its purpose is
>> to be a parent in the hierarchy, and a DT node where the name
>> describes its path.
>>
>> In order to ensure a nice self-encapsulation, the DTPM subsys array
>> contains a couple of initialization functions, one to setup the DTPM
>> backend and one to initialize it up. With this approach, the DTPM
>> framework has a very few material to export.
>>
>> Signed-off-by: Daniel Lezcano <[email protected]>
>> ---
>> drivers/powercap/Kconfig | 1 +
>> drivers/powercap/dtpm.c | 168 ++++++++++++++++++++++++++++++++++++++-
>> include/linux/dtpm.h | 15 ++++
>> 3 files changed, 181 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
>> index 8242e8c5ed77..b1ca339957e3 100644
>> --- a/drivers/powercap/Kconfig
>> +++ b/drivers/powercap/Kconfig
>> @@ -46,6 +46,7 @@ config IDLE_INJECT
>>
>> config DTPM
>> bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
>> + depends on OF
>> help
>> This enables support for the power capping for the dynamic
>> thermal power management userspace engine.
>> diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c
>> index 0e5c93443c70..10032f7132c4 100644
>> --- a/drivers/powercap/dtpm.c
>> +++ b/drivers/powercap/dtpm.c
>> @@ -23,6 +23,7 @@
>> #include <linux/powercap.h>
>> #include <linux/slab.h>
>> #include <linux/mutex.h>
>> +#include <linux/of.h>
>>
>> #include "dtpm_subsys.h"
>>
>> @@ -463,14 +464,175 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
>> return 0;
>> }
>>
>> -static int __init init_dtpm(void)
>> +static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
>> + struct dtpm *parent)
>> {
>> + struct dtpm *dtpm;
>> + int ret;
>> +
>> + dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
>> + if (!dtpm)
>> + return ERR_PTR(-ENOMEM);
>> + dtpm_init(dtpm, NULL);
>> +
>> + ret = dtpm_register(hierarchy->name, dtpm, parent);
>> + if (ret) {
>> + pr_err("Failed to register dtpm node '%s': %d\n",
>> + hierarchy->name, ret);
>> + kfree(dtpm);
>> + return ERR_PTR(ret);
>> + }
>> +
>> + return dtpm;
>> +}
>> +
>> +static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
>> + struct dtpm *parent)
>> +{
>> + struct device_node *np;
>> + int i, ret;
>> +
>> + np = of_find_node_by_path(hierarchy->name);
>> + if (!np) {
>> + pr_err("Failed to find '%s'\n", hierarchy->name);
>> + return ERR_PTR(-ENXIO);
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
>> +
>> + if (!dtpm_subsys[i]->setup)
>> + continue;
>> +
>> + ret = dtpm_subsys[i]->setup(parent, np);
>> + if (ret) {
>> + pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
>> + of_node_put(np);
>> + return ERR_PTR(ret);
>> + }
>> + }
>> +
>> + of_node_put(np);
>> +
>> + /*
>> + * By returning a NULL pointer, we let know the caller there
>> + * is no child for us as we are a leaf of the tree
>> + */
>> + return NULL;
>> +}
>> +
>> +typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
>> +
>> +dtpm_node_callback_t dtpm_node_callback[] = {
>> + [DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
>> + [DTPM_NODE_DT] = dtpm_setup_dt,
>> +};
>> +
>> +static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
>> + const struct dtpm_node *it, struct dtpm *parent)
>> +{
>> + struct dtpm *dtpm;
>> + int i, ret;
>> +
>> + for (i = 0; hierarchy[i].name; i++) {
>> +
>> + if (hierarchy[i].parent != it)
>> + continue;
>> +
>> + dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
>> + if (!dtpm || IS_ERR(dtpm))
>
> This can be tested with the "IS_ERR_OR_NULL()" macro.
>
>> + continue;
>
> We have discussed the error path previously. Just ignoring errors here
> and continuing with the initialization, isn't normally how we design
> good kernel code.
>
> However, you have also explained that the error path is special and
> somewhat non-trivial to manage in this case. I get that now and thanks
> for clarifying.
>
> Nevertheless, I think it deserves to be explained a bit with a comment
> in the code of what goes on here. Otherwise another developer that
> looks at this code in the future, may think it looks suspicious too.
>
>> +
>> + ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>
> [...]
>
> Other than the above, this looks good to me!

With the above fixed, shall I add your reviewed-by ?


--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

2022-01-25 16:45:23

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v6 2/5] powercap/drivers/dtpm: Add hierarchy creation

On Tue, 25 Jan 2022 at 11:46, Daniel Lezcano <[email protected]> wrote:
>
> On 24/01/2022 21:00, Ulf Hansson wrote:
> > On Wed, 19 Jan 2022 at 09:58, Daniel Lezcano <[email protected]> wrote:
> >>
> >> The DTPM framework is available but without a way to configure it.
> >>
> >> This change provides a way to create a hierarchy of DTPM node where
> >> the power consumption reflects the sum of the children's power
> >> consumption.
> >>
> >> It is up to the platform to specify an array of dtpm nodes where each
> >> element has a pointer to its parent, except the top most one. The type
> >> of the node gives the indication of which initialization callback to
> >> call. At this time, we can create a virtual node, where its purpose is
> >> to be a parent in the hierarchy, and a DT node where the name
> >> describes its path.
> >>
> >> In order to ensure a nice self-encapsulation, the DTPM subsys array
> >> contains a couple of initialization functions, one to setup the DTPM
> >> backend and one to initialize it up. With this approach, the DTPM
> >> framework has a very few material to export.
> >>
> >> Signed-off-by: Daniel Lezcano <[email protected]>
> >> ---
> >> drivers/powercap/Kconfig | 1 +
> >> drivers/powercap/dtpm.c | 168 ++++++++++++++++++++++++++++++++++++++-
> >> include/linux/dtpm.h | 15 ++++
> >> 3 files changed, 181 insertions(+), 3 deletions(-)
> >>
> >> diff --git a/drivers/powercap/Kconfig b/drivers/powercap/Kconfig
> >> index 8242e8c5ed77..b1ca339957e3 100644
> >> --- a/drivers/powercap/Kconfig
> >> +++ b/drivers/powercap/Kconfig
> >> @@ -46,6 +46,7 @@ config IDLE_INJECT
> >>
> >> config DTPM
> >> bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
> >> + depends on OF
> >> help
> >> This enables support for the power capping for the dynamic
> >> thermal power management userspace engine.
> >> diff --git a/drivers/powercap/dtpm.c b/drivers/powercap/dtpm.c
> >> index 0e5c93443c70..10032f7132c4 100644
> >> --- a/drivers/powercap/dtpm.c
> >> +++ b/drivers/powercap/dtpm.c
> >> @@ -23,6 +23,7 @@
> >> #include <linux/powercap.h>
> >> #include <linux/slab.h>
> >> #include <linux/mutex.h>
> >> +#include <linux/of.h>
> >>
> >> #include "dtpm_subsys.h"
> >>
> >> @@ -463,14 +464,175 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
> >> return 0;
> >> }
> >>
> >> -static int __init init_dtpm(void)
> >> +static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
> >> + struct dtpm *parent)
> >> {
> >> + struct dtpm *dtpm;
> >> + int ret;
> >> +
> >> + dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
> >> + if (!dtpm)
> >> + return ERR_PTR(-ENOMEM);
> >> + dtpm_init(dtpm, NULL);
> >> +
> >> + ret = dtpm_register(hierarchy->name, dtpm, parent);
> >> + if (ret) {
> >> + pr_err("Failed to register dtpm node '%s': %d\n",
> >> + hierarchy->name, ret);
> >> + kfree(dtpm);
> >> + return ERR_PTR(ret);
> >> + }
> >> +
> >> + return dtpm;
> >> +}
> >> +
> >> +static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
> >> + struct dtpm *parent)
> >> +{
> >> + struct device_node *np;
> >> + int i, ret;
> >> +
> >> + np = of_find_node_by_path(hierarchy->name);
> >> + if (!np) {
> >> + pr_err("Failed to find '%s'\n", hierarchy->name);
> >> + return ERR_PTR(-ENXIO);
> >> + }
> >> +
> >> + for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
> >> +
> >> + if (!dtpm_subsys[i]->setup)
> >> + continue;
> >> +
> >> + ret = dtpm_subsys[i]->setup(parent, np);
> >> + if (ret) {
> >> + pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
> >> + of_node_put(np);
> >> + return ERR_PTR(ret);
> >> + }
> >> + }
> >> +
> >> + of_node_put(np);
> >> +
> >> + /*
> >> + * By returning a NULL pointer, we let know the caller there
> >> + * is no child for us as we are a leaf of the tree
> >> + */
> >> + return NULL;
> >> +}
> >> +
> >> +typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);
> >> +
> >> +dtpm_node_callback_t dtpm_node_callback[] = {
> >> + [DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
> >> + [DTPM_NODE_DT] = dtpm_setup_dt,
> >> +};
> >> +
> >> +static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
> >> + const struct dtpm_node *it, struct dtpm *parent)
> >> +{
> >> + struct dtpm *dtpm;
> >> + int i, ret;
> >> +
> >> + for (i = 0; hierarchy[i].name; i++) {
> >> +
> >> + if (hierarchy[i].parent != it)
> >> + continue;
> >> +
> >> + dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);
> >> + if (!dtpm || IS_ERR(dtpm))
> >
> > This can be tested with the "IS_ERR_OR_NULL()" macro.
> >
> >> + continue;
> >
> > We have discussed the error path previously. Just ignoring errors here
> > and continuing with the initialization, isn't normally how we design
> > good kernel code.
> >
> > However, you have also explained that the error path is special and
> > somewhat non-trivial to manage in this case. I get that now and thanks
> > for clarifying.
> >
> > Nevertheless, I think it deserves to be explained a bit with a comment
> > in the code of what goes on here. Otherwise another developer that
> > looks at this code in the future, may think it looks suspicious too.
> >
> >> +
> >> + ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
> >> + if (ret)
> >> + return ret;
> >> + }
> >> +
> >> + return 0;
> >> +}
> >
> > [...]
> >
> > Other than the above, this looks good to me!
>
> With the above fixed, shall I add your reviewed-by ?

Sure, that's fine!

Kind regards
Uffe