Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Generic power capping sysfs interface configuration
#
menuconfig POWERCAP
bool "Generic powercap sysfs driver"
help
The power capping sysfs interface allows kernel subsystems to expose power
capping settings to user space in a consistent way. Usually, it consists
of multiple control types that determine which settings may be exposed and
power zones representing parts of the system that can be subject to power
capping.
If you want this code to be compiled in, say Y here.
if POWERCAP
# Client driver configurations go here.
config INTEL_RAPL_CORE
tristate
depends on PCI
select IOSF_MBI
config INTEL_RAPL
tristate "Intel RAPL Support via MSR Interface"
depends on X86 && PCI
select INTEL_RAPL_CORE
help
This enables support for the Intel Running Average Power Limit (RAPL)
technology via MSR interface, which allows power limits to be enforced
and monitored on modern Intel processors (Sandy Bridge and later).
In RAPL, the platform level settings are divided into domains for
fine grained control. These domains include processor package, DRAM
controller, CPU core (Power Plane 0), graphics uncore (Power Plane
1), etc.
config INTEL_RAPL_TPMI
tristate "Intel RAPL Support via TPMI Interface"
depends on X86
depends on INTEL_TPMI
select INTEL_RAPL_CORE
help
This enables support for the Intel Running Average Power Limit (RAPL)
technology via TPMI interface, which allows power limits to be enforced
and monitored.
In RAPL, the platform level settings are divided into domains for
fine grained control. These domains include processor package, DRAM
controller, platform, etc.
config IDLE_INJECT
bool "Idle injection framework"
depends on CPU_IDLE
default n
help
This enables support for the idle injection framework. It
provides a way to force idle periods on a set of specified
CPUs for power capping. Idle period can be injected
synchronously on a set of specified CPUs or alternatively
on a per CPU basis.
config ARM_SCMI_POWERCAP
tristate "ARM SCMI Powercap driver"
depends on ARM_SCMI_PROTOCOL
help
This enables support for the ARM Powercap based on ARM SCMI
Powercap protocol.
ARM SCMI Powercap protocol allows power limits to be enforced
and monitored against the SCMI Powercap domains advertised as
available by the SCMI platform firmware.
When compiled as module it will be called arm_scmi_powercap.ko.
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.
config DTPM_CPU
bool "Add CPU power capping based on the energy model"
depends on DTPM && ENERGY_MODEL && SMP
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
+10
View File
@@ -0,0 +1,10 @@
# 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
obj-$(CONFIG_INTEL_RAPL_TPMI) += intel_rapl_tpmi.o
obj-$(CONFIG_IDLE_INJECT) += idle_inject.o
obj-$(CONFIG_ARM_SCMI_POWERCAP) += arm_scmi_powercap.o
@@ -0,0 +1,550 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SCMI Powercap support.
*
* Copyright (C) 2022 ARM Ltd.
*/
#include <linux/device.h>
#include <linux/math.h>
#include <linux/limits.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/powercap.h>
#include <linux/scmi_protocol.h>
#include <linux/slab.h>
#define to_scmi_powercap_zone(z) \
container_of(z, struct scmi_powercap_zone, zone)
static const struct scmi_powercap_proto_ops *powercap_ops;
struct scmi_powercap_zone {
bool registered;
bool invalid;
unsigned int height;
struct device *dev;
struct scmi_protocol_handle *ph;
const struct scmi_powercap_info *info;
struct scmi_powercap_zone *spzones;
struct powercap_zone zone;
struct list_head node;
};
struct scmi_powercap_root {
unsigned int num_zones;
struct scmi_powercap_zone *spzones;
struct list_head *registered_zones;
struct list_head scmi_zones;
};
static struct powercap_control_type *scmi_top_pcntrl;
static int scmi_powercap_zone_release(struct powercap_zone *pz)
{
return 0;
}
static int scmi_powercap_get_max_power_range_uw(struct powercap_zone *pz,
u64 *max_power_range_uw)
{
*max_power_range_uw = U32_MAX;
return 0;
}
static int scmi_powercap_get_power_uw(struct powercap_zone *pz,
u64 *power_uw)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 avg_power, pai;
int ret;
if (!spz->info->powercap_monitoring)
return -EINVAL;
ret = powercap_ops->measurements_get(spz->ph, spz->info->id, &avg_power,
&pai);
if (ret)
return ret;
*power_uw = avg_power;
if (spz->info->powercap_scale_mw)
*power_uw *= 1000;
return 0;
}
static int scmi_powercap_zone_enable_set(struct powercap_zone *pz, bool mode)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
return powercap_ops->cap_enable_set(spz->ph, spz->info->id, mode);
}
static int scmi_powercap_zone_enable_get(struct powercap_zone *pz, bool *mode)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
return powercap_ops->cap_enable_get(spz->ph, spz->info->id, mode);
}
static const struct powercap_zone_ops zone_ops = {
.get_max_power_range_uw = scmi_powercap_get_max_power_range_uw,
.get_power_uw = scmi_powercap_get_power_uw,
.release = scmi_powercap_zone_release,
.set_enable = scmi_powercap_zone_enable_set,
.get_enable = scmi_powercap_zone_enable_get,
};
static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,
u64 power_limit_uw, u32 *norm)
{
bool scale_mw = spz->info->powercap_scale_mw;
u64 val;
val = scale_mw ? DIV_ROUND_UP_ULL(power_limit_uw, 1000) : power_limit_uw;
/*
* This cast is lossless since here @req_power is certain to be within
* the range [min_power_cap, max_power_cap] whose bounds are assured to
* be two unsigned 32bits quantities.
*/
*norm = clamp_t(u32, val, spz->info->min_power_cap,
spz->info->max_power_cap);
*norm = rounddown(*norm, spz->info->power_cap_step);
val = (scale_mw) ? *norm * 1000 : *norm;
if (power_limit_uw != val)
dev_dbg(spz->dev,
"Normalized %s:CAP - requested:%llu - normalized:%llu\n",
spz->info->name, power_limit_uw, val);
}
static int scmi_powercap_set_power_limit_uw(struct powercap_zone *pz, int cid,
u64 power_uw)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_power;
if (!spz->info->powercap_cap_config)
return -EINVAL;
scmi_powercap_normalize_cap(spz, power_uw, &norm_power);
return powercap_ops->cap_set(spz->ph, spz->info->id, norm_power, false);
}
static int scmi_powercap_get_power_limit_uw(struct powercap_zone *pz, int cid,
u64 *power_limit_uw)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 power;
int ret;
ret = powercap_ops->cap_get(spz->ph, spz->info->id, &power);
if (ret)
return ret;
*power_limit_uw = power;
if (spz->info->powercap_scale_mw)
*power_limit_uw *= 1000;
return 0;
}
static void scmi_powercap_normalize_time(const struct scmi_powercap_zone *spz,
u64 time_us, u32 *norm)
{
/*
* This cast is lossless since here @time_us is certain to be within the
* range [min_pai, max_pai] whose bounds are assured to be two unsigned
* 32bits quantities.
*/
*norm = clamp_t(u32, time_us, spz->info->min_pai, spz->info->max_pai);
*norm = rounddown(*norm, spz->info->pai_step);
if (time_us != *norm)
dev_dbg(spz->dev,
"Normalized %s:PAI - requested:%llu - normalized:%u\n",
spz->info->name, time_us, *norm);
}
static int scmi_powercap_set_time_window_us(struct powercap_zone *pz, int cid,
u64 time_window_us)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
u32 norm_pai;
if (!spz->info->powercap_pai_config)
return -EINVAL;
scmi_powercap_normalize_time(spz, time_window_us, &norm_pai);
return powercap_ops->pai_set(spz->ph, spz->info->id, norm_pai);
}
static int scmi_powercap_get_time_window_us(struct powercap_zone *pz, int cid,
u64 *time_window_us)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
int ret;
u32 pai;
ret = powercap_ops->pai_get(spz->ph, spz->info->id, &pai);
if (ret)
return ret;
*time_window_us = pai;
return 0;
}
static int scmi_powercap_get_max_power_uw(struct powercap_zone *pz, int cid,
u64 *max_power_uw)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
*max_power_uw = spz->info->max_power_cap;
if (spz->info->powercap_scale_mw)
*max_power_uw *= 1000;
return 0;
}
static int scmi_powercap_get_min_power_uw(struct powercap_zone *pz, int cid,
u64 *min_power_uw)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
*min_power_uw = spz->info->min_power_cap;
if (spz->info->powercap_scale_mw)
*min_power_uw *= 1000;
return 0;
}
static int scmi_powercap_get_max_time_window_us(struct powercap_zone *pz,
int cid, u64 *time_window_us)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
*time_window_us = spz->info->max_pai;
return 0;
}
static int scmi_powercap_get_min_time_window_us(struct powercap_zone *pz,
int cid, u64 *time_window_us)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
*time_window_us = (u64)spz->info->min_pai;
return 0;
}
static const char *scmi_powercap_get_name(struct powercap_zone *pz, int cid)
{
return "SCMI power-cap";
}
static const struct powercap_zone_constraint_ops constraint_ops = {
.set_power_limit_uw = scmi_powercap_set_power_limit_uw,
.get_power_limit_uw = scmi_powercap_get_power_limit_uw,
.set_time_window_us = scmi_powercap_set_time_window_us,
.get_time_window_us = scmi_powercap_get_time_window_us,
.get_max_power_uw = scmi_powercap_get_max_power_uw,
.get_min_power_uw = scmi_powercap_get_min_power_uw,
.get_max_time_window_us = scmi_powercap_get_max_time_window_us,
.get_min_time_window_us = scmi_powercap_get_min_time_window_us,
.get_name = scmi_powercap_get_name,
};
static void scmi_powercap_unregister_all_zones(struct scmi_powercap_root *pr)
{
int i;
/* Un-register children zones first starting from the leaves */
for (i = pr->num_zones - 1; i >= 0; i--) {
if (!list_empty(&pr->registered_zones[i])) {
struct scmi_powercap_zone *spz;
list_for_each_entry(spz, &pr->registered_zones[i], node)
powercap_unregister_zone(scmi_top_pcntrl,
&spz->zone);
}
}
}
static inline unsigned int
scmi_powercap_get_zone_height(struct scmi_powercap_zone *spz)
{
if (spz->info->parent_id == SCMI_POWERCAP_ROOT_ZONE_ID)
return 0;
return spz->spzones[spz->info->parent_id].height + 1;
}
static inline struct scmi_powercap_zone *
scmi_powercap_get_parent_zone(struct scmi_powercap_zone *spz)
{
if (spz->info->parent_id == SCMI_POWERCAP_ROOT_ZONE_ID)
return NULL;
return &spz->spzones[spz->info->parent_id];
}
static int scmi_powercap_register_zone(struct scmi_powercap_root *pr,
struct scmi_powercap_zone *spz,
struct scmi_powercap_zone *parent)
{
int ret = 0;
struct powercap_zone *z;
if (spz->invalid) {
list_del(&spz->node);
return -EINVAL;
}
z = powercap_register_zone(&spz->zone, scmi_top_pcntrl, spz->info->name,
parent ? &parent->zone : NULL,
&zone_ops, 1, &constraint_ops);
if (!IS_ERR(z)) {
spz->height = scmi_powercap_get_zone_height(spz);
spz->registered = true;
list_move(&spz->node, &pr->registered_zones[spz->height]);
dev_dbg(spz->dev, "Registered node %s - parent %s - height:%d\n",
spz->info->name, parent ? parent->info->name : "ROOT",
spz->height);
} else {
list_del(&spz->node);
ret = PTR_ERR(z);
dev_err(spz->dev,
"Error registering node:%s - parent:%s - h:%d - ret:%d\n",
spz->info->name,
parent ? parent->info->name : "ROOT",
spz->height, ret);
}
return ret;
}
/**
* scmi_zones_register- Register SCMI powercap zones starting from parent zones
*
* @dev: A reference to the SCMI device
* @pr: A reference to the root powercap zones descriptors
*
* When registering SCMI powercap zones with the powercap framework we should
* take care to always register zones starting from the root ones and to
* deregister starting from the leaves.
*
* Unfortunately we cannot assume that the array of available SCMI powercap
* zones provided by the SCMI platform firmware is built to comply with such
* requirement.
*
* This function, given the set of SCMI powercap zones to register, takes care
* to walk the SCMI powercap zones trees up to the root registering any
* unregistered parent zone before registering the child zones; at the same
* time each registered-zone height in such a tree is accounted for and each
* zone, once registered, is stored in the @registered_zones array that is
* indexed by zone height: this way will be trivial, at unregister time, to walk
* the @registered_zones array backward and unregister all the zones starting
* from the leaves, removing children zones before parents.
*
* While doing this, we prune away any zone marked as invalid (like the ones
* sporting an SCMI abstract power scale) as long as they are positioned as
* leaves in the SCMI powercap zones hierarchy: any non-leaf invalid zone causes
* the entire process to fail since we cannot assume the correctness of an SCMI
* powercap zones hierarchy if some of the internal nodes are missing.
*
* Note that the array of SCMI powercap zones as returned by the SCMI platform
* is known to be sane, i.e. zones relationships have been validated at the
* protocol layer.
*
* Return: 0 on Success
*/
static int scmi_zones_register(struct device *dev,
struct scmi_powercap_root *pr)
{
int ret = 0;
unsigned int sp = 0, reg_zones = 0;
struct scmi_powercap_zone *spz, **zones_stack;
zones_stack = kzalloc_objs(spz, pr->num_zones);
if (!zones_stack)
return -ENOMEM;
spz = list_first_entry_or_null(&pr->scmi_zones,
struct scmi_powercap_zone, node);
while (spz) {
struct scmi_powercap_zone *parent;
parent = scmi_powercap_get_parent_zone(spz);
if (parent && !parent->registered) {
zones_stack[sp++] = spz;
spz = parent;
} else {
ret = scmi_powercap_register_zone(pr, spz, parent);
if (!ret) {
reg_zones++;
} else if (sp) {
/* Failed to register a non-leaf zone.
* Bail-out.
*/
dev_err(dev,
"Failed to register non-leaf zone - ret:%d\n",
ret);
scmi_powercap_unregister_all_zones(pr);
reg_zones = 0;
goto out;
}
/* Pick next zone to process */
if (sp)
spz = zones_stack[--sp];
else
spz = list_first_entry_or_null(&pr->scmi_zones,
struct scmi_powercap_zone,
node);
}
}
out:
kfree(zones_stack);
dev_info(dev, "Registered %d SCMI Powercap domains !\n", reg_zones);
return ret;
}
static int scmi_powercap_probe(struct scmi_device *sdev)
{
int ret, i;
struct scmi_powercap_root *pr;
struct scmi_powercap_zone *spz;
struct scmi_protocol_handle *ph;
struct device *dev = &sdev->dev;
if (!sdev->handle)
return -ENODEV;
powercap_ops = sdev->handle->devm_protocol_get(sdev,
SCMI_PROTOCOL_POWERCAP,
&ph);
if (IS_ERR(powercap_ops))
return PTR_ERR(powercap_ops);
pr = devm_kzalloc(dev, sizeof(*pr), GFP_KERNEL);
if (!pr)
return -ENOMEM;
ret = powercap_ops->num_domains_get(ph);
if (ret < 0) {
dev_err(dev, "number of powercap domains not found\n");
return ret;
}
pr->num_zones = ret;
pr->spzones = devm_kcalloc(dev, pr->num_zones,
sizeof(*pr->spzones), GFP_KERNEL);
if (!pr->spzones)
return -ENOMEM;
/* Allocate for worst possible scenario of maximum tree height. */
pr->registered_zones = devm_kcalloc(dev, pr->num_zones,
sizeof(*pr->registered_zones),
GFP_KERNEL);
if (!pr->registered_zones)
return -ENOMEM;
INIT_LIST_HEAD(&pr->scmi_zones);
for (i = 0, spz = pr->spzones; i < pr->num_zones; i++, spz++) {
/*
* Powercap domains are validate by the protocol layer, i.e.
* when only non-NULL domains are returned here, whose
* parent_id is assured to point to another valid domain.
*/
spz->info = powercap_ops->info_get(ph, i);
spz->dev = dev;
spz->ph = ph;
spz->spzones = pr->spzones;
INIT_LIST_HEAD(&spz->node);
INIT_LIST_HEAD(&pr->registered_zones[i]);
list_add_tail(&spz->node, &pr->scmi_zones);
/*
* Forcibly skip powercap domains using an abstract scale.
* Note that only leaves domains can be skipped, so this could
* lead later to a global failure.
*/
if (!spz->info->powercap_scale_uw &&
!spz->info->powercap_scale_mw) {
dev_warn(dev,
"Abstract power scale not supported. Skip %s.\n",
spz->info->name);
spz->invalid = true;
continue;
}
}
/*
* Scan array of retrieved SCMI powercap domains and register them
* recursively starting from the root domains.
*/
ret = scmi_zones_register(dev, pr);
if (ret)
return ret;
dev_set_drvdata(dev, pr);
return ret;
}
static void scmi_powercap_remove(struct scmi_device *sdev)
{
struct device *dev = &sdev->dev;
struct scmi_powercap_root *pr = dev_get_drvdata(dev);
scmi_powercap_unregister_all_zones(pr);
}
static const struct scmi_device_id scmi_id_table[] = {
{ SCMI_PROTOCOL_POWERCAP, "powercap" },
{ },
};
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
static struct scmi_driver scmi_powercap_driver = {
.name = "scmi-powercap",
.probe = scmi_powercap_probe,
.remove = scmi_powercap_remove,
.id_table = scmi_id_table,
};
static int __init scmi_powercap_init(void)
{
int ret;
scmi_top_pcntrl = powercap_register_control_type(NULL, "arm-scmi", NULL);
if (IS_ERR(scmi_top_pcntrl))
return PTR_ERR(scmi_top_pcntrl);
ret = scmi_register(&scmi_powercap_driver);
if (ret)
powercap_unregister_control_type(scmi_top_pcntrl);
return ret;
}
module_init(scmi_powercap_init);
static void __exit scmi_powercap_exit(void)
{
scmi_unregister(&scmi_powercap_driver);
powercap_unregister_control_type(scmi_top_pcntrl);
}
module_exit(scmi_powercap_exit);
MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>");
MODULE_DESCRIPTION("ARM SCMI Powercap driver");
MODULE_LICENSE("GPL");
+647
View File
@@ -0,0 +1,647 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020 Linaro Limited
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
* The powercap based Dynamic Thermal Power Management framework
* provides to the userspace a consistent API to set the power limit
* on some devices.
*
* DTPM defines the functions to create a tree of constraints. Each
* parent node is a virtual description of the aggregation of the
* children. It propagates the constraints set at its level to its
* children and collect the children power information. The leaves of
* the tree are the real devices which have the ability to get their
* current power consumption and set their power limit.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/dtpm.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/powercap.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include "dtpm_subsys.h"
#define DTPM_POWER_LIMIT_FLAG 0
static const char *constraint_name[] = {
"Instantaneous",
};
static DEFINE_MUTEX(dtpm_lock);
static struct powercap_control_type *pct;
static struct dtpm *root;
static int get_time_window_us(struct powercap_zone *pcz, int cid, u64 *window)
{
return -ENOSYS;
}
static int set_time_window_us(struct powercap_zone *pcz, int cid, u64 window)
{
return -ENOSYS;
}
static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
{
struct dtpm *dtpm = to_dtpm(pcz);
*max_power_uw = dtpm->power_max - dtpm->power_min;
return 0;
}
static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)
{
struct dtpm *child;
u64 power;
int ret = 0;
if (dtpm->ops) {
*power_uw = dtpm->ops->get_power_uw(dtpm);
return 0;
}
*power_uw = 0;
list_for_each_entry(child, &dtpm->children, sibling) {
ret = __get_power_uw(child, &power);
if (ret)
break;
*power_uw += power;
}
return ret;
}
static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
{
return __get_power_uw(to_dtpm(pcz), power_uw);
}
static void __dtpm_rebalance_weight(struct dtpm *dtpm)
{
struct dtpm *child;
list_for_each_entry(child, &dtpm->children, sibling) {
pr_debug("Setting weight '%d' for '%s'\n",
child->weight, child->zone.name);
child->weight = DIV64_U64_ROUND_CLOSEST(
child->power_max * 1024, dtpm->power_max);
__dtpm_rebalance_weight(child);
}
}
static void __dtpm_sub_power(struct dtpm *dtpm)
{
struct dtpm *parent = dtpm->parent;
while (parent) {
parent->power_min -= dtpm->power_min;
parent->power_max -= dtpm->power_max;
parent->power_limit -= dtpm->power_limit;
parent = parent->parent;
}
}
static void __dtpm_add_power(struct dtpm *dtpm)
{
struct dtpm *parent = dtpm->parent;
while (parent) {
parent->power_min += dtpm->power_min;
parent->power_max += dtpm->power_max;
parent->power_limit += dtpm->power_limit;
parent = parent->parent;
}
}
/**
* dtpm_update_power - Update the power on the dtpm
* @dtpm: a pointer to a dtpm structure to update
*
* Function to update the power values of the dtpm node specified in
* parameter. These new values will be propagated to the tree.
*
* Return: zero on success, -EINVAL if the values are inconsistent
*/
int dtpm_update_power(struct dtpm *dtpm)
{
int ret;
__dtpm_sub_power(dtpm);
ret = dtpm->ops->update_power_uw(dtpm);
if (ret)
pr_err("Failed to update power for '%s': %d\n",
dtpm->zone.name, ret);
if (!test_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags))
dtpm->power_limit = dtpm->power_max;
__dtpm_add_power(dtpm);
if (root)
__dtpm_rebalance_weight(root);
return ret;
}
/**
* dtpm_release_zone - Cleanup when the node is released
* @pcz: a pointer to a powercap_zone structure
*
* Do some housecleaning and update the weight on the tree. The
* release will be denied if the node has children. This function must
* be called by the specific release callback of the different
* backends.
*
* Return: 0 on success, -EBUSY if there are children
*/
int dtpm_release_zone(struct powercap_zone *pcz)
{
struct dtpm *dtpm = to_dtpm(pcz);
struct dtpm *parent = dtpm->parent;
if (!list_empty(&dtpm->children))
return -EBUSY;
if (parent)
list_del(&dtpm->sibling);
__dtpm_sub_power(dtpm);
if (dtpm->ops)
dtpm->ops->release(dtpm);
else
kfree(dtpm);
return 0;
}
static int get_power_limit_uw(struct powercap_zone *pcz,
int cid, u64 *power_limit)
{
*power_limit = to_dtpm(pcz)->power_limit;
return 0;
}
/*
* Set the power limit on the nodes, the power limit is distributed
* given the weight of the children.
*
* The dtpm node lock must be held when calling this function.
*/
static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)
{
struct dtpm *child;
int ret = 0;
u64 power;
/*
* A max power limitation means we remove the power limit,
* otherwise we set a constraint and flag the dtpm node.
*/
if (power_limit == dtpm->power_max) {
clear_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
} else {
set_bit(DTPM_POWER_LIMIT_FLAG, &dtpm->flags);
}
pr_debug("Setting power limit for '%s': %llu uW\n",
dtpm->zone.name, power_limit);
/*
* Only leaves of the dtpm tree has ops to get/set the power
*/
if (dtpm->ops) {
dtpm->power_limit = dtpm->ops->set_power_uw(dtpm, power_limit);
} else {
dtpm->power_limit = 0;
list_for_each_entry(child, &dtpm->children, sibling) {
/*
* Integer division rounding will inevitably
* lead to a different min or max value when
* set several times. In order to restore the
* initial value, we force the child's min or
* max power every time if the constraint is
* at the boundaries.
*/
if (power_limit == dtpm->power_max) {
power = child->power_max;
} else if (power_limit == dtpm->power_min) {
power = child->power_min;
} else {
power = DIV_ROUND_CLOSEST_ULL(
power_limit * child->weight, 1024);
}
pr_debug("Setting power limit for '%s': %llu uW\n",
child->zone.name, power);
ret = __set_power_limit_uw(child, cid, power);
if (!ret)
ret = get_power_limit_uw(&child->zone, cid, &power);
if (ret)
break;
dtpm->power_limit += power;
}
}
return ret;
}
static int set_power_limit_uw(struct powercap_zone *pcz,
int cid, u64 power_limit)
{
struct dtpm *dtpm = to_dtpm(pcz);
int ret;
/*
* Don't allow values outside of the power range previously
* set when initializing the power numbers.
*/
power_limit = clamp_val(power_limit, dtpm->power_min, dtpm->power_max);
ret = __set_power_limit_uw(dtpm, cid, power_limit);
pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
dtpm->zone.name, dtpm->power_limit, dtpm->power_max);
return ret;
}
static const char *get_constraint_name(struct powercap_zone *pcz, int cid)
{
return constraint_name[cid];
}
static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
{
*max_power = to_dtpm(pcz)->power_max;
return 0;
}
static struct powercap_zone_constraint_ops constraint_ops = {
.set_power_limit_uw = set_power_limit_uw,
.get_power_limit_uw = get_power_limit_uw,
.set_time_window_us = set_time_window_us,
.get_time_window_us = get_time_window_us,
.get_max_power_uw = get_max_power_uw,
.get_name = get_constraint_name,
};
static struct powercap_zone_ops zone_ops = {
.get_max_power_range_uw = get_max_power_range_uw,
.get_power_uw = get_power_uw,
.release = dtpm_release_zone,
};
/**
* dtpm_init - Allocate and initialize a dtpm struct
* @dtpm: The dtpm struct pointer to be initialized
* @ops: The dtpm device specific ops, NULL for a virtual node
*/
void dtpm_init(struct dtpm *dtpm, struct dtpm_ops *ops)
{
if (dtpm) {
INIT_LIST_HEAD(&dtpm->children);
INIT_LIST_HEAD(&dtpm->sibling);
dtpm->weight = 1024;
dtpm->ops = ops;
}
}
/**
* dtpm_unregister - Unregister a dtpm node from the hierarchy tree
* @dtpm: a pointer to a dtpm structure corresponding to the node to be removed
*
* Call the underlying powercap unregister function. That will call
* the release callback of the powercap zone.
*/
void dtpm_unregister(struct dtpm *dtpm)
{
powercap_unregister_zone(pct, &dtpm->zone);
pr_debug("Unregistered dtpm node '%s'\n", dtpm->zone.name);
}
/**
* dtpm_register - Register a dtpm node in the hierarchy tree
* @name: a string specifying the name of the node
* @dtpm: a pointer to a dtpm structure corresponding to the new node
* @parent: a pointer to a dtpm structure corresponding to the parent node
*
* Create a dtpm node in the tree. If no parent is specified, the node
* is the root node of the hierarchy. If the root node already exists,
* then the registration will fail. The powercap controller must be
* initialized before calling this function.
*
* The dtpm structure must be initialized with the power numbers
* before calling this function.
*
* Return: zero on success, a negative value in case of error:
* -EAGAIN: the function is called before the framework is initialized.
* -EBUSY: the root node is already inserted
* -EINVAL: * there is no root node yet and @parent is specified
* * no all ops are defined
* * parent have ops which are reserved for leaves
* Other negative values are reported back from the powercap framework
*/
int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
{
struct powercap_zone *pcz;
if (!pct)
return -EAGAIN;
if (root && !parent)
return -EBUSY;
if (!root && parent)
return -EINVAL;
if (parent && parent->ops)
return -EINVAL;
if (!dtpm)
return -EINVAL;
if (dtpm->ops && !(dtpm->ops->set_power_uw &&
dtpm->ops->get_power_uw &&
dtpm->ops->update_power_uw &&
dtpm->ops->release))
return -EINVAL;
pcz = powercap_register_zone(&dtpm->zone, pct, name,
parent ? &parent->zone : NULL,
&zone_ops, MAX_DTPM_CONSTRAINTS,
&constraint_ops);
if (IS_ERR(pcz))
return PTR_ERR(pcz);
if (parent) {
list_add_tail(&dtpm->sibling, &parent->children);
dtpm->parent = parent;
} else {
root = dtpm;
}
if (dtpm->ops && !dtpm->ops->update_power_uw(dtpm)) {
__dtpm_add_power(dtpm);
dtpm->power_limit = dtpm->power_max;
}
pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
dtpm->zone.name, dtpm->power_min, dtpm->power_max);
return 0;
}
static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
struct dtpm *parent)
{
struct dtpm *dtpm;
int ret;
dtpm = kzalloc_obj(*dtpm);
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 *);
static 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);
/*
* A NULL pointer means there is no children, hence we
* continue without going deeper in the recursivity.
*/
if (!dtpm)
continue;
/*
* There are multiple reasons why the callback could
* fail. The generic glue is abstracting the backend
* and therefore it is not possible to report back or
* take a decision based on the error. In any case,
* if this call fails, it is not critical in the
* hierarchy creation, we can assume the underlying
* service is not found, so we continue without this
* branch in the tree but with a warning to log the
* information the node was not created.
*/
if (IS_ERR(dtpm)) {
pr_warn("Failed to create '%s' in the hierarchy\n",
hierarchy[i].name);
continue;
}
ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
if (ret)
return ret;
}
return 0;
}
/**
* dtpm_create_hierarchy - Create the dtpm hierarchy
* @dtpm_match_table: Pointer to the array of device ID structures
*
* 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 dtpm_node *hierarchy;
int i, ret;
mutex_lock(&dtpm_lock);
if (pct) {
ret = -EBUSY;
goto out_unlock;
}
pct = powercap_register_control_type(NULL, "dtpm", NULL);
if (IS_ERR(pct)) {
pr_err("Failed to register control type\n");
ret = PTR_ERR(pct);
goto out_pct;
}
hierarchy = of_machine_get_match_data(dtpm_match_table);
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 initialize '%s': %d",
dtpm_subsys[i]->name, ret);
}
mutex_unlock(&dtpm_lock);
return 0;
out_err:
powercap_unregister_control_type(pct);
out_pct:
pct = NULL;
out_unlock:
mutex_unlock(&dtpm_lock);
return ret;
}
EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);
static void __dtpm_destroy_hierarchy(struct dtpm *dtpm)
{
struct dtpm *child, *aux;
list_for_each_entry_safe(child, aux, &dtpm->children, sibling)
__dtpm_destroy_hierarchy(child);
/*
* At this point, we know all children were removed from the
* recursive call before
*/
dtpm_unregister(dtpm);
}
void dtpm_destroy_hierarchy(void)
{
int i;
mutex_lock(&dtpm_lock);
if (!pct)
goto out_unlock;
__dtpm_destroy_hierarchy(root);
for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {
if (!dtpm_subsys[i]->exit)
continue;
dtpm_subsys[i]->exit();
}
powercap_unregister_control_type(pct);
pct = NULL;
root = NULL;
out_unlock:
mutex_unlock(&dtpm_lock);
}
EXPORT_SYMBOL_GPL(dtpm_destroy_hierarchy);
+319
View File
@@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2020 Linaro Limited
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
* The DTPM CPU is based on the energy model. It hooks the CPU in the
* DTPM tree which in turns update the power number by propagating the
* power number from the CPU energy model information to the parents.
*
* The association between the power and the performance state, allows
* to set the power of the CPU at the OPP granularity.
*
* The CPU hotplug is supported and the power numbers will be updated
* if a CPU is hot plugged / unplugged.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cpumask.h>
#include <linux/cpufreq.h>
#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>
struct dtpm_cpu {
struct dtpm dtpm;
struct freq_qos_request qos_req;
int cpu;
};
static DEFINE_PER_CPU(struct dtpm_cpu *, dtpm_per_cpu);
static struct dtpm_cpu *to_dtpm_cpu(struct dtpm *dtpm)
{
return container_of(dtpm, struct dtpm_cpu, dtpm);
}
static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
{
struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
struct em_perf_domain *pd = em_cpu_get(dtpm_cpu->cpu);
struct em_perf_state *table;
unsigned long freq;
u64 power;
int i, nr_cpus;
nr_cpus = cpumask_weight_and(cpu_online_mask, to_cpumask(pd->cpus));
rcu_read_lock();
table = em_perf_state_from_pd(pd);
for (i = 0; i < pd->nr_perf_states; i++) {
power = table[i].power * nr_cpus;
if (power > power_limit)
break;
}
freq = table[i - 1].frequency;
power_limit = table[i - 1].power * nr_cpus;
rcu_read_unlock();
freq_qos_update_request(&dtpm_cpu->qos_req, freq);
return power_limit;
}
static u64 scale_pd_power_uw(struct cpumask *pd_mask, u64 power)
{
unsigned long max, sum_util = 0;
int cpu;
/*
* The capacity is the same for all CPUs belonging to
* the same perf domain.
*/
max = arch_scale_cpu_capacity(cpumask_first(pd_mask));
for_each_cpu_and(cpu, pd_mask, cpu_online_mask)
sum_util += sched_cpu_util(cpu);
return (power * ((sum_util << 10) / max)) >> 10;
}
static u64 get_pd_power_uw(struct dtpm *dtpm)
{
struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
struct em_perf_state *table;
struct em_perf_domain *pd;
struct cpumask *pd_mask;
unsigned long freq;
u64 power = 0;
int i;
pd = em_cpu_get(dtpm_cpu->cpu);
if (!pd)
return 0;
pd_mask = em_span_cpus(pd);
freq = cpufreq_quick_get(dtpm_cpu->cpu);
rcu_read_lock();
table = em_perf_state_from_pd(pd);
for (i = 0; i < pd->nr_perf_states; i++) {
if (table[i].frequency < freq)
continue;
power = scale_pd_power_uw(pd_mask, table[i].power);
break;
}
rcu_read_unlock();
return power;
}
static int update_pd_power_uw(struct dtpm *dtpm)
{
struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
struct em_perf_domain *em = em_cpu_get(dtpm_cpu->cpu);
struct em_perf_state *table;
int nr_cpus;
nr_cpus = cpumask_weight_and(cpu_online_mask, to_cpumask(em->cpus));
rcu_read_lock();
table = em_perf_state_from_pd(em);
dtpm->power_min = table[0].power;
dtpm->power_min *= nr_cpus;
dtpm->power_max = table[em->nr_perf_states - 1].power;
dtpm->power_max *= nr_cpus;
rcu_read_unlock();
return 0;
}
static void pd_release(struct dtpm *dtpm)
{
struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
struct cpufreq_policy *policy;
if (freq_qos_request_active(&dtpm_cpu->qos_req))
freq_qos_remove_request(&dtpm_cpu->qos_req);
policy = cpufreq_cpu_get(dtpm_cpu->cpu);
if (policy) {
for_each_cpu(dtpm_cpu->cpu, policy->related_cpus)
per_cpu(dtpm_per_cpu, dtpm_cpu->cpu) = NULL;
cpufreq_cpu_put(policy);
}
kfree(dtpm_cpu);
}
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 cpuhp_dtpm_cpu_offline(unsigned int cpu)
{
struct dtpm_cpu *dtpm_cpu;
dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
if (dtpm_cpu)
dtpm_update_power(&dtpm_cpu->dtpm);
return 0;
}
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;
struct em_perf_state *table;
struct em_perf_domain *pd;
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;
pd = em_cpu_get(cpu);
if (!pd || em_is_artificial(pd)) {
ret = -EINVAL;
goto release_policy;
}
dtpm_cpu = kzalloc_obj(*dtpm_cpu);
if (!dtpm_cpu) {
ret = -ENOMEM;
goto release_policy;
}
dtpm_init(&dtpm_cpu->dtpm, &dtpm_ops);
dtpm_cpu->cpu = cpu;
for_each_cpu(cpu, policy->related_cpus)
per_cpu(dtpm_per_cpu, cpu) = dtpm_cpu;
snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu);
ret = dtpm_register(name, &dtpm_cpu->dtpm, parent);
if (ret)
goto out_kfree_dtpm_cpu;
rcu_read_lock();
table = em_perf_state_from_pd(pd);
ret = freq_qos_add_request(&policy->constraints,
&dtpm_cpu->qos_req, FREQ_QOS_MAX,
table[pd->nr_perf_states - 1].frequency);
rcu_read_unlock();
if (ret < 0)
goto out_dtpm_unregister;
cpufreq_cpu_put(policy);
return 0;
out_dtpm_unregister:
dtpm_unregister(&dtpm_cpu->dtpm);
dtpm_cpu = NULL;
out_kfree_dtpm_cpu:
for_each_cpu(cpu, policy->related_cpus)
per_cpu(dtpm_per_cpu, cpu) = NULL;
kfree(dtpm_cpu);
release_policy:
cpufreq_cpu_put(policy);
return ret;
}
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;
/*
* The callbacks at CPU hotplug time are calling
* dtpm_update_power() which in turns calls update_pd_power().
*
* The function update_pd_power() uses the online mask to
* figure out the power consumption limits.
*
* At CPUHP_AP_ONLINE_DYN, the CPU is present in the CPU
* online mask when the cpuhp_dtpm_cpu_online function is
* called, but the CPU is still in the online mask for the
* tear down callback. So the power can not be updated when
* the CPU is unplugged.
*
* At CPUHP_AP_DTPM_CPU_DEAD, the situation is the opposite as
* above. The CPU online mask is not up to date when the CPU
* is plugged in.
*
* For this reason, we need to call the online and offline
* callbacks at different moments when the CPU online mask is
* consistent with the power numbers we want to update.
*/
ret = cpuhp_setup_state(CPUHP_AP_DTPM_CPU_DEAD, "dtpm_cpu:offline",
NULL, cpuhp_dtpm_cpu_offline);
if (ret < 0)
return ret;
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "dtpm_cpu:online",
cpuhp_dtpm_cpu_online, NULL);
if (ret < 0)
return ret;
return 0;
}
static void dtpm_cpu_exit(void)
{
cpuhp_remove_state_nocalls(CPUHP_AP_ONLINE_DYN);
cpuhp_remove_state_nocalls(CPUHP_AP_DTPM_CPU_DEAD);
}
struct dtpm_subsys_ops dtpm_cpu_ops = {
.name = KBUILD_MODNAME,
.init = dtpm_cpu_init,
.exit = dtpm_cpu_exit,
.setup = dtpm_cpu_setup,
};
+210
View File
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2021 Linaro Limited
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
* 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);
struct em_perf_state *table;
rcu_read_lock();
table = em_perf_state_from_pd(pd);
dtpm->power_min = table[0].power;
dtpm->power_max = table[pd->nr_perf_states - 1].power;
rcu_read_unlock();
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);
struct em_perf_state *table;
unsigned long freq;
int i;
rcu_read_lock();
table = em_perf_state_from_pd(pd);
for (i = 0; i < pd->nr_perf_states; i++) {
if (table[i].power > power_limit)
break;
}
freq = table[i - 1].frequency;
power_limit = table[i - 1].power;
rcu_read_unlock();
dev_pm_qos_update_request(&dtpm_devfreq->qos_req, freq);
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;
struct em_perf_state *table;
unsigned long freq;
u64 power = 0;
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);
rcu_read_lock();
table = em_perf_state_from_pd(pd);
for (i = 0; i < pd->nr_perf_states; i++) {
if (table[i].frequency < freq)
continue;
power = table[i].power;
power *= status.busy_time;
power >>= 10;
break;
}
rcu_read_unlock();
return power;
}
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_obj(*dtpm_devfreq);
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);
kfree(dtpm_devfreq);
return ret;
}
ret = dev_pm_qos_add_request(dev, &dtpm_devfreq->qos_req,
DEV_PM_QOS_MAX_FREQUENCY,
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
if (ret < 0) {
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);
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,
};
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2022 Linaro Ltd
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*/
#ifndef ___DTPM_SUBSYS_H__
#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
+420
View File
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018 Linaro Limited
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
* The idle injection framework provides a way to force CPUs to enter idle
* states for a specified fraction of time over a specified period.
*
* It relies on the smpboot kthreads feature providing common code for CPU
* hotplug and thread [un]parking.
*
* All of the kthreads used for idle injection are created at init time.
*
* Next, the users of the idle injection framework provide a cpumask via
* its register function. The kthreads will be synchronized with respect to
* this cpumask.
*
* The idle + run duration is specified via separate helpers and that allows
* idle injection to be started.
*
* The idle injection kthreads will call play_idle_precise() with the idle
* duration and max allowed latency specified as per the above.
*
* After all of them have been woken up, a timer is set to start the next idle
* injection cycle.
*
* The timer interrupt handler will wake up the idle injection kthreads for
* all of the CPUs in the cpumask provided by the user.
*
* Idle injection is stopped synchronously and no leftover idle injection
* kthread activity after its completion is guaranteed.
*
* It is up to the user of this framework to provide a lock for higher-level
* synchronization to prevent race conditions like starting idle injection
* while unregistering from the framework.
*/
#define pr_fmt(fmt) "ii_dev: " fmt
#include <linux/cpu.h>
#include <linux/hrtimer.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smpboot.h>
#include <linux/idle_inject.h>
#include <uapi/linux/sched/types.h>
/**
* struct idle_inject_thread - task on/off switch structure
* @tsk: task injecting the idle cycles
* @should_run: whether or not to run the task (for the smpboot kthread API)
*/
struct idle_inject_thread {
struct task_struct *tsk;
int should_run;
};
/**
* struct idle_inject_device - idle injection data
* @timer: idle injection period timer
* @idle_duration_us: duration of CPU idle time to inject
* @run_duration_us: duration of CPU run time to allow
* @latency_us: max allowed latency
* @update: Optional callback deciding whether or not to skip idle
* injection in the given cycle.
* @cpumask: mask of CPUs affected by idle injection
*
* This structure is used to define per instance idle inject device data. Each
* instance has an idle duration, a run duration and mask of CPUs to inject
* idle.
*
* Actual CPU idle time is injected by calling kernel scheduler interface
* play_idle_precise(). There is one optional callback that can be registered
* by calling idle_inject_register_full():
*
* update() - This callback is invoked just before waking up CPUs to inject
* idle. If it returns false, CPUs are not woken up to inject idle in the given
* cycle. It also allows the caller to readjust the idle and run duration by
* calling idle_inject_set_duration() for the next cycle.
*/
struct idle_inject_device {
struct hrtimer timer;
unsigned int idle_duration_us;
unsigned int run_duration_us;
unsigned int latency_us;
bool (*update)(void);
unsigned long cpumask[];
};
static DEFINE_PER_CPU(struct idle_inject_thread, idle_inject_thread);
static DEFINE_PER_CPU(struct idle_inject_device *, idle_inject_device);
/**
* idle_inject_wakeup - Wake up idle injection threads
* @ii_dev: target idle injection device
*
* Every idle injection task associated with the given idle injection device
* and running on an online CPU will be woken up.
*/
static void idle_inject_wakeup(struct idle_inject_device *ii_dev)
{
struct idle_inject_thread *iit;
unsigned int cpu;
for_each_cpu_and(cpu, to_cpumask(ii_dev->cpumask), cpu_online_mask) {
iit = per_cpu_ptr(&idle_inject_thread, cpu);
iit->should_run = 1;
wake_up_process(iit->tsk);
}
}
/**
* idle_inject_timer_fn - idle injection timer function
* @timer: idle injection hrtimer
*
* This function is called when the idle injection timer expires. It wakes up
* idle injection tasks associated with the timer and they, in turn, invoke
* play_idle_precise() to inject a specified amount of CPU idle time.
*
* Return: HRTIMER_RESTART.
*/
static enum hrtimer_restart idle_inject_timer_fn(struct hrtimer *timer)
{
unsigned int duration_us;
struct idle_inject_device *ii_dev =
container_of(timer, struct idle_inject_device, timer);
if (!ii_dev->update || ii_dev->update())
idle_inject_wakeup(ii_dev);
duration_us = READ_ONCE(ii_dev->run_duration_us);
duration_us += READ_ONCE(ii_dev->idle_duration_us);
hrtimer_forward_now(timer, us_to_ktime(duration_us));
return HRTIMER_RESTART;
}
/**
* idle_inject_fn - idle injection work function
* @cpu: the CPU owning the task
*
* This function calls play_idle_precise() to inject a specified amount of CPU
* idle time.
*/
static void idle_inject_fn(unsigned int cpu)
{
struct idle_inject_device *ii_dev;
struct idle_inject_thread *iit;
ii_dev = per_cpu(idle_inject_device, cpu);
iit = per_cpu_ptr(&idle_inject_thread, cpu);
/*
* Let the smpboot main loop know that the task should not run again.
*/
iit->should_run = 0;
play_idle_precise(READ_ONCE(ii_dev->idle_duration_us) * NSEC_PER_USEC,
READ_ONCE(ii_dev->latency_us) * NSEC_PER_USEC);
}
/**
* idle_inject_set_duration - idle and run duration update helper
* @ii_dev: idle injection control device structure
* @run_duration_us: CPU run time to allow in microseconds
* @idle_duration_us: CPU idle time to inject in microseconds
*/
void idle_inject_set_duration(struct idle_inject_device *ii_dev,
unsigned int run_duration_us,
unsigned int idle_duration_us)
{
if (run_duration_us + idle_duration_us) {
WRITE_ONCE(ii_dev->run_duration_us, run_duration_us);
WRITE_ONCE(ii_dev->idle_duration_us, idle_duration_us);
}
if (!run_duration_us)
pr_debug("CPU is forced to 100 percent idle\n");
}
EXPORT_SYMBOL_NS_GPL(idle_inject_set_duration, "IDLE_INJECT");
/**
* idle_inject_get_duration - idle and run duration retrieval helper
* @ii_dev: idle injection control device structure
* @run_duration_us: memory location to store the current CPU run time
* @idle_duration_us: memory location to store the current CPU idle time
*/
void idle_inject_get_duration(struct idle_inject_device *ii_dev,
unsigned int *run_duration_us,
unsigned int *idle_duration_us)
{
*run_duration_us = READ_ONCE(ii_dev->run_duration_us);
*idle_duration_us = READ_ONCE(ii_dev->idle_duration_us);
}
EXPORT_SYMBOL_NS_GPL(idle_inject_get_duration, "IDLE_INJECT");
/**
* idle_inject_set_latency - set the maximum latency allowed
* @ii_dev: idle injection control device structure
* @latency_us: set the latency requirement for the idle state
*/
void idle_inject_set_latency(struct idle_inject_device *ii_dev,
unsigned int latency_us)
{
WRITE_ONCE(ii_dev->latency_us, latency_us);
}
EXPORT_SYMBOL_NS_GPL(idle_inject_set_latency, "IDLE_INJECT");
/**
* idle_inject_start - start idle injections
* @ii_dev: idle injection control device structure
*
* The function starts idle injection by first waking up all of the idle
* injection kthreads associated with @ii_dev to let them inject CPU idle time
* sets up a timer to start the next idle injection period.
*
* Return: -EINVAL if the CPU idle or CPU run time is not set or 0 on success.
*/
int idle_inject_start(struct idle_inject_device *ii_dev)
{
unsigned int idle_duration_us = READ_ONCE(ii_dev->idle_duration_us);
unsigned int run_duration_us = READ_ONCE(ii_dev->run_duration_us);
if (!(idle_duration_us + run_duration_us))
return -EINVAL;
pr_debug("Starting injecting idle cycles on CPUs '%*pbl'\n",
cpumask_pr_args(to_cpumask(ii_dev->cpumask)));
idle_inject_wakeup(ii_dev);
hrtimer_start(&ii_dev->timer,
us_to_ktime(idle_duration_us + run_duration_us),
HRTIMER_MODE_REL);
return 0;
}
EXPORT_SYMBOL_NS_GPL(idle_inject_start, "IDLE_INJECT");
/**
* idle_inject_stop - stops idle injections
* @ii_dev: idle injection control device structure
*
* The function stops idle injection and waits for the threads to finish work.
* If CPU idle time is being injected when this function runs, then it will
* wait until the end of the cycle.
*
* When it returns, there is no more idle injection kthread activity. The
* kthreads are scheduled out and the periodic timer is off.
*/
void idle_inject_stop(struct idle_inject_device *ii_dev)
{
struct idle_inject_thread *iit;
unsigned int cpu;
pr_debug("Stopping idle injection on CPUs '%*pbl'\n",
cpumask_pr_args(to_cpumask(ii_dev->cpumask)));
hrtimer_cancel(&ii_dev->timer);
/*
* Stopping idle injection requires all of the idle injection kthreads
* associated with the given cpumask to be parked and stay that way, so
* prevent CPUs from going online at this point. Any CPUs going online
* after the loop below will be covered by clearing the should_run flag
* that will cause the smpboot main loop to schedule them out.
*/
cpu_hotplug_disable();
/*
* Iterate over all (online + offline) CPUs here in case one of them
* goes offline with the should_run flag set so as to prevent its idle
* injection kthread from running when the CPU goes online again after
* the ii_dev has been freed.
*/
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
iit = per_cpu_ptr(&idle_inject_thread, cpu);
iit->should_run = 0;
wait_task_inactive(iit->tsk, TASK_ANY);
}
cpu_hotplug_enable();
}
EXPORT_SYMBOL_NS_GPL(idle_inject_stop, "IDLE_INJECT");
/**
* idle_inject_setup - prepare the current task for idle injection
* @cpu: not used
*
* Called once, this function is in charge of setting the current task's
* scheduler parameters to make it an RT task.
*/
static void idle_inject_setup(unsigned int cpu)
{
sched_set_fifo(current);
}
/**
* idle_inject_should_run - function helper for the smpboot API
* @cpu: CPU the kthread is running on
*
* Return: whether or not the thread can run.
*/
static int idle_inject_should_run(unsigned int cpu)
{
struct idle_inject_thread *iit =
per_cpu_ptr(&idle_inject_thread, cpu);
return iit->should_run;
}
/**
* idle_inject_register_full - initialize idle injection on a set of CPUs
* @cpumask: CPUs to be affected by idle injection
* @update: This callback is called just before waking up CPUs to inject
* idle
*
* This function creates an idle injection control device structure for the
* given set of CPUs and initializes the timer associated with it. This
* function also allows to register update()callback.
* It does not start any injection cycles.
*
* Return: NULL if memory allocation fails, idle injection control device
* pointer on success.
*/
struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask,
bool (*update)(void))
{
struct idle_inject_device *ii_dev;
int cpu, cpu_rb;
ii_dev = kzalloc(sizeof(*ii_dev) + cpumask_size(), GFP_KERNEL);
if (!ii_dev)
return NULL;
cpumask_copy(to_cpumask(ii_dev->cpumask), cpumask);
hrtimer_setup(&ii_dev->timer, idle_inject_timer_fn, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ii_dev->latency_us = UINT_MAX;
ii_dev->update = update;
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
if (per_cpu(idle_inject_device, cpu)) {
pr_err("cpu%d is already registered\n", cpu);
goto out_rollback;
}
per_cpu(idle_inject_device, cpu) = ii_dev;
}
return ii_dev;
out_rollback:
for_each_cpu(cpu_rb, to_cpumask(ii_dev->cpumask)) {
if (cpu == cpu_rb)
break;
per_cpu(idle_inject_device, cpu_rb) = NULL;
}
kfree(ii_dev);
return NULL;
}
EXPORT_SYMBOL_NS_GPL(idle_inject_register_full, "IDLE_INJECT");
/**
* idle_inject_register - initialize idle injection on a set of CPUs
* @cpumask: CPUs to be affected by idle injection
*
* This function creates an idle injection control device structure for the
* given set of CPUs and initializes the timer associated with it. It does not
* start any injection cycles.
*
* Return: NULL if memory allocation fails, idle injection control device
* pointer on success.
*/
struct idle_inject_device *idle_inject_register(struct cpumask *cpumask)
{
return idle_inject_register_full(cpumask, NULL);
}
EXPORT_SYMBOL_NS_GPL(idle_inject_register, "IDLE_INJECT");
/**
* idle_inject_unregister - unregister idle injection control device
* @ii_dev: idle injection control device to unregister
*
* The function stops idle injection for the given control device,
* unregisters its kthreads and frees memory allocated when that device was
* created.
*/
void idle_inject_unregister(struct idle_inject_device *ii_dev)
{
unsigned int cpu;
idle_inject_stop(ii_dev);
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask))
per_cpu(idle_inject_device, cpu) = NULL;
kfree(ii_dev);
}
EXPORT_SYMBOL_NS_GPL(idle_inject_unregister, "IDLE_INJECT");
static struct smp_hotplug_thread idle_inject_threads = {
.store = &idle_inject_thread.tsk,
.setup = idle_inject_setup,
.thread_fn = idle_inject_fn,
.thread_comm = "idle_inject/%u",
.thread_should_run = idle_inject_should_run,
};
static int __init idle_inject_init(void)
{
return smpboot_register_percpu_thread(&idle_inject_threads);
}
early_initcall(idle_inject_init);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,602 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel Running Average Power Limit (RAPL) Driver via MSR interface
* Copyright (c) 2019, Intel Corporation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/log2.h>
#include <linux/bitmap.h>
#include <linux/delay.h>
#include <linux/sysfs.h>
#include <linux/cpu.h>
#include <linux/powercap.h>
#include <linux/suspend.h>
#include <linux/intel_rapl.h>
#include <linux/processor.h>
#include <linux/platform_device.h>
#include <linux/units.h>
#include <linux/bits.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/iosf_mbi.h>
#include <asm/msr.h>
/* Local defines */
#define MSR_PLATFORM_POWER_LIMIT 0x0000065C
#define MSR_VR_CURRENT_CONFIG 0x00000601
#define ENERGY_UNIT_SCALE 1000 /* scale from driver unit to powercap unit */
#define POWER_UNIT_OFFSET 0x00
#define POWER_UNIT_MASK GENMASK(3, 0)
#define ENERGY_UNIT_OFFSET 0x08
#define ENERGY_UNIT_MASK GENMASK(12, 8)
#define TIME_UNIT_OFFSET 0x10
#define TIME_UNIT_MASK GENMASK(19, 16)
/* bitmasks for RAPL MSRs, used by primitive access functions */
#define ENERGY_STATUS_MASK GENMASK(31, 0)
#define POWER_LIMIT1_MASK GENMASK(14, 0)
#define POWER_LIMIT1_ENABLE BIT(15)
#define POWER_LIMIT1_CLAMP BIT(16)
#define POWER_LIMIT2_MASK GENMASK_ULL(46, 32)
#define POWER_LIMIT2_ENABLE BIT_ULL(47)
#define POWER_LIMIT2_CLAMP BIT_ULL(48)
#define POWER_HIGH_LOCK BIT_ULL(63)
#define POWER_LOW_LOCK BIT(31)
#define POWER_LIMIT4_MASK GENMASK(12, 0)
#define TIME_WINDOW1_MASK GENMASK_ULL(23, 17)
#define TIME_WINDOW2_MASK GENMASK_ULL(55, 49)
#define POWER_INFO_MAX_MASK GENMASK_ULL(46, 32)
#define POWER_INFO_MIN_MASK GENMASK_ULL(30, 16)
#define POWER_INFO_MAX_TIME_WIN_MASK GENMASK_ULL(53, 48)
#define POWER_INFO_THERMAL_SPEC_MASK GENMASK(14, 0)
#define PERF_STATUS_THROTTLE_TIME_MASK GENMASK(31, 0)
#define PP_POLICY_MASK GENMASK(4, 0)
/*
* SPR has different layout for Psys Domain PowerLimit registers.
* There are 17 bits of PL1 and PL2 instead of 15 bits.
* The Enable bits and TimeWindow bits are also shifted as a result.
*/
#define PSYS_POWER_LIMIT1_MASK GENMASK_ULL(16, 0)
#define PSYS_POWER_LIMIT1_ENABLE BIT(17)
#define PSYS_POWER_LIMIT2_MASK GENMASK_ULL(48, 32)
#define PSYS_POWER_LIMIT2_ENABLE BIT_ULL(49)
#define PSYS_TIME_WINDOW1_MASK GENMASK_ULL(25, 19)
#define PSYS_TIME_WINDOW2_MASK GENMASK_ULL(57, 51)
/* Sideband MBI registers */
#define IOSF_CPU_POWER_BUDGET_CTL_BYT 0x02
#define IOSF_CPU_POWER_BUDGET_CTL_TNG 0xDF
/* private data for RAPL MSR Interface */
static struct rapl_if_priv *rapl_msr_priv;
static bool rapl_msr_pmu __ro_after_init;
static struct rapl_if_priv rapl_msr_priv_intel = {
.type = RAPL_IF_MSR,
.reg_unit.msr = MSR_RAPL_POWER_UNIT,
.regs[RAPL_DOMAIN_PACKAGE][RAPL_DOMAIN_REG_LIMIT].msr = MSR_PKG_POWER_LIMIT,
.regs[RAPL_DOMAIN_PACKAGE][RAPL_DOMAIN_REG_STATUS].msr = MSR_PKG_ENERGY_STATUS,
.regs[RAPL_DOMAIN_PACKAGE][RAPL_DOMAIN_REG_PERF].msr = MSR_PKG_PERF_STATUS,
.regs[RAPL_DOMAIN_PACKAGE][RAPL_DOMAIN_REG_INFO].msr = MSR_PKG_POWER_INFO,
.regs[RAPL_DOMAIN_PP0][RAPL_DOMAIN_REG_LIMIT].msr = MSR_PP0_POWER_LIMIT,
.regs[RAPL_DOMAIN_PP0][RAPL_DOMAIN_REG_STATUS].msr = MSR_PP0_ENERGY_STATUS,
.regs[RAPL_DOMAIN_PP0][RAPL_DOMAIN_REG_POLICY].msr = MSR_PP0_POLICY,
.regs[RAPL_DOMAIN_PP1][RAPL_DOMAIN_REG_LIMIT].msr = MSR_PP1_POWER_LIMIT,
.regs[RAPL_DOMAIN_PP1][RAPL_DOMAIN_REG_STATUS].msr = MSR_PP1_ENERGY_STATUS,
.regs[RAPL_DOMAIN_PP1][RAPL_DOMAIN_REG_POLICY].msr = MSR_PP1_POLICY,
.regs[RAPL_DOMAIN_DRAM][RAPL_DOMAIN_REG_LIMIT].msr = MSR_DRAM_POWER_LIMIT,
.regs[RAPL_DOMAIN_DRAM][RAPL_DOMAIN_REG_STATUS].msr = MSR_DRAM_ENERGY_STATUS,
.regs[RAPL_DOMAIN_DRAM][RAPL_DOMAIN_REG_PERF].msr = MSR_DRAM_PERF_STATUS,
.regs[RAPL_DOMAIN_DRAM][RAPL_DOMAIN_REG_INFO].msr = MSR_DRAM_POWER_INFO,
.regs[RAPL_DOMAIN_PLATFORM][RAPL_DOMAIN_REG_LIMIT].msr = MSR_PLATFORM_POWER_LIMIT,
.regs[RAPL_DOMAIN_PLATFORM][RAPL_DOMAIN_REG_STATUS].msr = MSR_PLATFORM_ENERGY_STATUS,
.limits[RAPL_DOMAIN_PACKAGE] = BIT(POWER_LIMIT2),
.limits[RAPL_DOMAIN_PLATFORM] = BIT(POWER_LIMIT2),
};
static struct rapl_if_priv rapl_msr_priv_amd = {
.type = RAPL_IF_MSR,
.reg_unit.msr = MSR_AMD_RAPL_POWER_UNIT,
.regs[RAPL_DOMAIN_PACKAGE][RAPL_DOMAIN_REG_STATUS].msr = MSR_AMD_PKG_ENERGY_STATUS,
.regs[RAPL_DOMAIN_PP0][RAPL_DOMAIN_REG_STATUS].msr = MSR_AMD_CORE_ENERGY_STATUS,
};
/* Handles CPU hotplug on multi-socket systems.
* If a CPU goes online as the first CPU of the physical package
* we add the RAPL package to the system. Similarly, when the last
* CPU of the package is removed, we remove the RAPL package and its
* associated domains. Cooling devices are handled accordingly at
* per-domain level.
*/
static int rapl_cpu_online(unsigned int cpu)
{
struct rapl_package *rp;
rp = rapl_find_package_domain_cpuslocked(cpu, rapl_msr_priv, true);
if (!rp) {
rp = rapl_add_package_cpuslocked(cpu, rapl_msr_priv, true);
if (IS_ERR(rp))
return PTR_ERR(rp);
if (rapl_msr_pmu)
rapl_package_add_pmu_locked(rp);
}
cpumask_set_cpu(cpu, &rp->cpumask);
return 0;
}
static int rapl_cpu_down_prep(unsigned int cpu)
{
struct rapl_package *rp;
int lead_cpu;
rp = rapl_find_package_domain_cpuslocked(cpu, rapl_msr_priv, true);
if (!rp)
return 0;
cpumask_clear_cpu(cpu, &rp->cpumask);
lead_cpu = cpumask_first(&rp->cpumask);
if (lead_cpu >= nr_cpu_ids) {
if (rapl_msr_pmu)
rapl_package_remove_pmu_locked(rp);
rapl_remove_package_cpuslocked(rp);
} else if (rp->lead_cpu == cpu) {
rp->lead_cpu = lead_cpu;
}
return 0;
}
static int rapl_msr_read_raw(int cpu, struct reg_action *ra, bool pmu_ctx)
{
/*
* When called from PMU context, perform MSR read directly using
* rdmsrq() without IPI overhead. Package-scoped MSRs are readable
* from any CPU in the package.
*/
if (pmu_ctx) {
rdmsrq(ra->reg.msr, ra->value);
goto out;
}
if (rdmsrq_safe_on_cpu(cpu, ra->reg.msr, &ra->value)) {
pr_debug("failed to read msr 0x%x on cpu %d\n", ra->reg.msr, cpu);
return -EIO;
}
out:
ra->value &= ra->mask;
return 0;
}
static void rapl_msr_update_func(void *info)
{
struct reg_action *ra = info;
u64 val;
ra->err = rdmsrq_safe(ra->reg.msr, &val);
if (ra->err)
return;
val &= ~ra->mask;
val |= ra->value;
ra->err = wrmsrq_safe(ra->reg.msr, val);
}
static int rapl_msr_write_raw(int cpu, struct reg_action *ra)
{
int ret;
ret = smp_call_function_single(cpu, rapl_msr_update_func, ra, 1);
if (WARN_ON_ONCE(ret))
return ret;
return ra->err;
}
static int rapl_check_unit_atom(struct rapl_domain *rd)
{
struct reg_action ra;
u32 value;
ra.reg = rd->regs[RAPL_DOMAIN_REG_UNIT];
ra.mask = ~0;
if (rapl_msr_read_raw(rd->rp->lead_cpu, &ra, false)) {
pr_err("Failed to read power unit REG 0x%llx on %s:%s, exit.\n",
ra.reg.val, rd->rp->name, rd->name);
return -ENODEV;
}
value = (ra.value & ENERGY_UNIT_MASK) >> ENERGY_UNIT_OFFSET;
rd->energy_unit = ENERGY_UNIT_SCALE * (1ULL << value);
value = (ra.value & POWER_UNIT_MASK) >> POWER_UNIT_OFFSET;
rd->power_unit = (1ULL << value) * MILLIWATT_PER_WATT;
value = (ra.value & TIME_UNIT_MASK) >> TIME_UNIT_OFFSET;
rd->time_unit = USEC_PER_SEC >> value;
pr_debug("Atom %s:%s energy=%dpJ, time=%dus, power=%duW\n",
rd->rp->name, rd->name, rd->energy_unit, rd->time_unit, rd->power_unit);
return 0;
}
static void set_floor_freq_atom(struct rapl_domain *rd, bool enable)
{
static u32 power_ctrl_orig_val;
const struct rapl_defaults *defaults = rd->rp->priv->defaults;
u32 mdata;
if (!defaults->floor_freq_reg_addr) {
pr_err("Invalid floor frequency config register\n");
return;
}
if (!power_ctrl_orig_val)
iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_CR_READ,
defaults->floor_freq_reg_addr,
&power_ctrl_orig_val);
mdata = power_ctrl_orig_val;
if (enable) {
mdata &= ~GENMASK(14, 8);
mdata |= BIT(8);
}
iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_CR_WRITE,
defaults->floor_freq_reg_addr, mdata);
}
static u64 rapl_compute_time_window_atom(struct rapl_domain *rd, u64 value,
bool to_raw)
{
if (to_raw)
return div64_u64(value, rd->time_unit);
/*
* Atom time unit encoding is straight forward val * time_unit,
* where time_unit is default to 1 sec. Never 0.
*/
return value ? value * rd->time_unit : rd->time_unit;
}
/* RAPL primitives for MSR I/F */
static struct rapl_primitive_info rpi_msr[NR_RAPL_PRIMITIVES] = {
/* name, mask, shift, msr index, unit divisor */
[POWER_LIMIT1] = PRIMITIVE_INFO_INIT(POWER_LIMIT1, POWER_LIMIT1_MASK, 0,
RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
[POWER_LIMIT2] = PRIMITIVE_INFO_INIT(POWER_LIMIT2, POWER_LIMIT2_MASK, 32,
RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
[POWER_LIMIT4] = PRIMITIVE_INFO_INIT(POWER_LIMIT4, POWER_LIMIT4_MASK, 0,
RAPL_DOMAIN_REG_PL4, POWER_UNIT, 0),
[ENERGY_COUNTER] = PRIMITIVE_INFO_INIT(ENERGY_COUNTER, ENERGY_STATUS_MASK, 0,
RAPL_DOMAIN_REG_STATUS, ENERGY_UNIT, 0),
[FW_LOCK] = PRIMITIVE_INFO_INIT(FW_LOCK, POWER_LOW_LOCK, 31,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[FW_HIGH_LOCK] = PRIMITIVE_INFO_INIT(FW_LOCK, POWER_HIGH_LOCK, 63,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[PL1_ENABLE] = PRIMITIVE_INFO_INIT(PL1_ENABLE, POWER_LIMIT1_ENABLE, 15,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[PL1_CLAMP] = PRIMITIVE_INFO_INIT(PL1_CLAMP, POWER_LIMIT1_CLAMP, 16,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[PL2_ENABLE] = PRIMITIVE_INFO_INIT(PL2_ENABLE, POWER_LIMIT2_ENABLE, 47,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[PL2_CLAMP] = PRIMITIVE_INFO_INIT(PL2_CLAMP, POWER_LIMIT2_CLAMP, 48,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[TIME_WINDOW1] = PRIMITIVE_INFO_INIT(TIME_WINDOW1, TIME_WINDOW1_MASK, 17,
RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
[TIME_WINDOW2] = PRIMITIVE_INFO_INIT(TIME_WINDOW2, TIME_WINDOW2_MASK, 49,
RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
[THERMAL_SPEC_POWER] = PRIMITIVE_INFO_INIT(THERMAL_SPEC_POWER,
POWER_INFO_THERMAL_SPEC_MASK, 0,
RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
[MAX_POWER] = PRIMITIVE_INFO_INIT(MAX_POWER, POWER_INFO_MAX_MASK, 32,
RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
[MIN_POWER] = PRIMITIVE_INFO_INIT(MIN_POWER, POWER_INFO_MIN_MASK, 16,
RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
[MAX_TIME_WINDOW] = PRIMITIVE_INFO_INIT(MAX_TIME_WINDOW,
POWER_INFO_MAX_TIME_WIN_MASK, 48,
RAPL_DOMAIN_REG_INFO, TIME_UNIT, 0),
[THROTTLED_TIME] = PRIMITIVE_INFO_INIT(THROTTLED_TIME,
PERF_STATUS_THROTTLE_TIME_MASK, 0,
RAPL_DOMAIN_REG_PERF, TIME_UNIT, 0),
[PRIORITY_LEVEL] = PRIMITIVE_INFO_INIT(PRIORITY_LEVEL, PP_POLICY_MASK, 0,
RAPL_DOMAIN_REG_POLICY, ARBITRARY_UNIT, 0),
[PSYS_POWER_LIMIT1] = PRIMITIVE_INFO_INIT(PSYS_POWER_LIMIT1, PSYS_POWER_LIMIT1_MASK, 0,
RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
[PSYS_POWER_LIMIT2] = PRIMITIVE_INFO_INIT(PSYS_POWER_LIMIT2, PSYS_POWER_LIMIT2_MASK,
32, RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
[PSYS_PL1_ENABLE] = PRIMITIVE_INFO_INIT(PSYS_PL1_ENABLE, PSYS_POWER_LIMIT1_ENABLE,
17, RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT,
0),
[PSYS_PL2_ENABLE] = PRIMITIVE_INFO_INIT(PSYS_PL2_ENABLE, PSYS_POWER_LIMIT2_ENABLE,
49, RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT,
0),
[PSYS_TIME_WINDOW1] = PRIMITIVE_INFO_INIT(PSYS_TIME_WINDOW1, PSYS_TIME_WINDOW1_MASK,
19, RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
[PSYS_TIME_WINDOW2] = PRIMITIVE_INFO_INIT(PSYS_TIME_WINDOW2, PSYS_TIME_WINDOW2_MASK,
51, RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
};
static const struct rapl_defaults rapl_defaults_core = {
.floor_freq_reg_addr = 0,
.check_unit = rapl_default_check_unit,
.set_floor_freq = rapl_default_set_floor_freq,
.compute_time_window = rapl_default_compute_time_window,
};
static const struct rapl_defaults rapl_defaults_hsw_server = {
.check_unit = rapl_default_check_unit,
.set_floor_freq = rapl_default_set_floor_freq,
.compute_time_window = rapl_default_compute_time_window,
.dram_domain_energy_unit = 15300,
};
static const struct rapl_defaults rapl_defaults_spr_server = {
.check_unit = rapl_default_check_unit,
.set_floor_freq = rapl_default_set_floor_freq,
.compute_time_window = rapl_default_compute_time_window,
.psys_domain_energy_unit = NANOJOULE_PER_JOULE,
.spr_psys_bits = true,
};
static const struct rapl_defaults rapl_defaults_byt = {
.floor_freq_reg_addr = IOSF_CPU_POWER_BUDGET_CTL_BYT,
.check_unit = rapl_check_unit_atom,
.set_floor_freq = set_floor_freq_atom,
.compute_time_window = rapl_compute_time_window_atom,
};
static const struct rapl_defaults rapl_defaults_tng = {
.floor_freq_reg_addr = IOSF_CPU_POWER_BUDGET_CTL_TNG,
.check_unit = rapl_check_unit_atom,
.set_floor_freq = set_floor_freq_atom,
.compute_time_window = rapl_compute_time_window_atom,
};
static const struct rapl_defaults rapl_defaults_ann = {
.floor_freq_reg_addr = 0,
.check_unit = rapl_check_unit_atom,
.set_floor_freq = NULL,
.compute_time_window = rapl_compute_time_window_atom,
};
static const struct rapl_defaults rapl_defaults_cht = {
.floor_freq_reg_addr = 0,
.check_unit = rapl_check_unit_atom,
.set_floor_freq = NULL,
.compute_time_window = rapl_compute_time_window_atom,
};
static const struct rapl_defaults rapl_defaults_amd = {
.check_unit = rapl_default_check_unit,
};
static const struct rapl_defaults rapl_defaults_core_pl4 = {
.floor_freq_reg_addr = 0,
.check_unit = rapl_default_check_unit,
.set_floor_freq = rapl_default_set_floor_freq,
.compute_time_window = rapl_default_compute_time_window,
.msr_pl4_support = 1,
};
static const struct rapl_defaults rapl_defaults_core_pl4_pmu = {
.floor_freq_reg_addr = 0,
.check_unit = rapl_default_check_unit,
.set_floor_freq = rapl_default_set_floor_freq,
.compute_time_window = rapl_default_compute_time_window,
.msr_pl4_support = 1,
.msr_pmu_support = 1,
};
static const struct x86_cpu_id rapl_ids[] = {
X86_MATCH_VFM(INTEL_SANDYBRIDGE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_SANDYBRIDGE_X, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_IVYBRIDGE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_IVYBRIDGE_X, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_HASWELL, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_HASWELL_L, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_HASWELL_G, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_HASWELL_X, &rapl_defaults_hsw_server),
X86_MATCH_VFM(INTEL_BROADWELL, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_BROADWELL_G, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_BROADWELL_D, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_BROADWELL_X, &rapl_defaults_hsw_server),
X86_MATCH_VFM(INTEL_SKYLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_SKYLAKE_L, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_SKYLAKE_X, &rapl_defaults_hsw_server),
X86_MATCH_VFM(INTEL_KABYLAKE_L, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_KABYLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_CANNONLAKE_L, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ICELAKE_L, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_ICELAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ICELAKE_NNPI, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ICELAKE_X, &rapl_defaults_hsw_server),
X86_MATCH_VFM(INTEL_ICELAKE_D, &rapl_defaults_hsw_server),
X86_MATCH_VFM(INTEL_COMETLAKE_L, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_COMETLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_TIGERLAKE_L, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_TIGERLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ROCKETLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ALDERLAKE, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_ALDERLAKE_L, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_ATOM_GRACEMONT, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_RAPTORLAKE, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_RAPTORLAKE_P, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_RAPTORLAKE_S, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_BARTLETTLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_METEORLAKE, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_METEORLAKE_L, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_SAPPHIRERAPIDS_X, &rapl_defaults_spr_server),
X86_MATCH_VFM(INTEL_EMERALDRAPIDS_X, &rapl_defaults_spr_server),
X86_MATCH_VFM(INTEL_LUNARLAKE_M, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &rapl_defaults_core_pl4_pmu),
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &rapl_defaults_core_pl4_pmu),
X86_MATCH_VFM(INTEL_NOVALAKE, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_NOVALAKE_L, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_ARROWLAKE_H, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_ARROWLAKE, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ARROWLAKE_U, &rapl_defaults_core_pl4),
X86_MATCH_VFM(INTEL_LAKEFIELD, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ATOM_SILVERMONT, &rapl_defaults_byt),
X86_MATCH_VFM(INTEL_ATOM_AIRMONT, &rapl_defaults_cht),
X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_MID, &rapl_defaults_tng),
X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_MID2, &rapl_defaults_ann),
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_PLUS, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_D, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ATOM_TREMONT, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ATOM_TREMONT_D, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_ATOM_TREMONT_L, &rapl_defaults_core),
X86_MATCH_VFM(INTEL_XEON_PHI_KNL, &rapl_defaults_hsw_server),
X86_MATCH_VFM(INTEL_XEON_PHI_KNM, &rapl_defaults_hsw_server),
X86_MATCH_VENDOR_FAM(AMD, 0x17, &rapl_defaults_amd),
X86_MATCH_VENDOR_FAM(AMD, 0x19, &rapl_defaults_amd),
X86_MATCH_VENDOR_FAM(AMD, 0x1A, &rapl_defaults_amd),
X86_MATCH_VENDOR_FAM(HYGON, 0x18, &rapl_defaults_amd),
{}
};
MODULE_DEVICE_TABLE(x86cpu, rapl_ids);
static int rapl_msr_probe(struct platform_device *pdev)
{
int ret;
switch (boot_cpu_data.x86_vendor) {
case X86_VENDOR_INTEL:
rapl_msr_priv = &rapl_msr_priv_intel;
break;
case X86_VENDOR_HYGON:
case X86_VENDOR_AMD:
rapl_msr_priv = &rapl_msr_priv_amd;
break;
default:
pr_err("intel-rapl does not support CPU vendor %d\n", boot_cpu_data.x86_vendor);
return -ENODEV;
}
rapl_msr_priv->read_raw = rapl_msr_read_raw;
rapl_msr_priv->write_raw = rapl_msr_write_raw;
rapl_msr_priv->defaults = (const struct rapl_defaults *)pdev->dev.platform_data;
rapl_msr_priv->rpi = rpi_msr;
if (rapl_msr_priv->defaults->msr_pl4_support) {
rapl_msr_priv->limits[RAPL_DOMAIN_PACKAGE] |= BIT(POWER_LIMIT4);
rapl_msr_priv->regs[RAPL_DOMAIN_PACKAGE][RAPL_DOMAIN_REG_PL4].msr =
MSR_VR_CURRENT_CONFIG;
pr_info("PL4 support detected (updated).\n");
}
if (rapl_msr_priv->defaults->msr_pmu_support) {
rapl_msr_pmu = true;
pr_info("MSR-based RAPL PMU support enabled (updated)\n");
}
rapl_msr_priv->control_type = powercap_register_control_type(NULL, "intel-rapl", NULL);
if (IS_ERR(rapl_msr_priv->control_type)) {
pr_debug("failed to register powercap control_type.\n");
return PTR_ERR(rapl_msr_priv->control_type);
}
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "powercap/rapl:online",
rapl_cpu_online, rapl_cpu_down_prep);
if (ret < 0)
goto out;
rapl_msr_priv->pcap_rapl_online = ret;
return 0;
out:
if (ret)
powercap_unregister_control_type(rapl_msr_priv->control_type);
return ret;
}
static void rapl_msr_remove(struct platform_device *pdev)
{
cpuhp_remove_state(rapl_msr_priv->pcap_rapl_online);
powercap_unregister_control_type(rapl_msr_priv->control_type);
}
static const struct platform_device_id rapl_msr_ids[] = {
{ .name = "intel_rapl_msr", },
{}
};
MODULE_DEVICE_TABLE(platform, rapl_msr_ids);
static struct platform_driver intel_rapl_msr_driver = {
.probe = rapl_msr_probe,
.remove = rapl_msr_remove,
.id_table = rapl_msr_ids,
.driver = {
.name = "intel_rapl_msr",
},
};
static struct platform_device *rapl_msr_platdev;
static int intel_rapl_msr_init(void)
{
const struct rapl_defaults *def;
const struct x86_cpu_id *id;
int ret;
ret = platform_driver_register(&intel_rapl_msr_driver);
if (ret)
return ret;
/* Create the MSR RAPL platform device for supported platforms */
id = x86_match_cpu(rapl_ids);
if (!id)
return 0;
def = (const struct rapl_defaults *)id->driver_data;
rapl_msr_platdev = platform_device_register_data(NULL, "intel_rapl_msr", 0, def,
sizeof(*def));
if (IS_ERR(rapl_msr_platdev))
pr_debug("intel_rapl_msr device register failed, ret:%ld\n",
PTR_ERR(rapl_msr_platdev));
return 0;
}
module_init(intel_rapl_msr_init);
static void intel_rapl_msr_exit(void)
{
platform_device_unregister(rapl_msr_platdev);
platform_driver_unregister(&intel_rapl_msr_driver);
}
module_exit(intel_rapl_msr_exit);
MODULE_DESCRIPTION("Driver for Intel RAPL (Running Average Power Limit) control via MSR interface");
MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("INTEL_RAPL");
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* intel_rapl_tpmi: Intel RAPL driver via TPMI interface
*
* Copyright (c) 2023, Intel Corporation.
* All Rights Reserved.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#include <linux/intel_rapl.h>
#include <linux/intel_tpmi.h>
#include <linux/intel_vsec.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/units.h>
#define TPMI_RAPL_MAJOR_VERSION 0
#define TPMI_RAPL_MINOR_VERSION 1
/* 1 header + 10 registers + 5 reserved. 8 bytes for each. */
#define TPMI_RAPL_DOMAIN_SIZE 128
enum tpmi_rapl_domain_type {
TPMI_RAPL_DOMAIN_INVALID,
TPMI_RAPL_DOMAIN_SYSTEM,
TPMI_RAPL_DOMAIN_PACKAGE,
TPMI_RAPL_DOMAIN_RESERVED,
TPMI_RAPL_DOMAIN_MEMORY,
TPMI_RAPL_DOMAIN_MAX,
};
enum tpmi_rapl_register {
TPMI_RAPL_REG_HEADER,
TPMI_RAPL_REG_UNIT,
TPMI_RAPL_REG_PL1,
TPMI_RAPL_REG_PL2,
TPMI_RAPL_REG_PL3,
TPMI_RAPL_REG_PL4,
TPMI_RAPL_REG_RESERVED,
TPMI_RAPL_REG_ENERGY_STATUS,
TPMI_RAPL_REG_PERF_STATUS,
TPMI_RAPL_REG_POWER_INFO,
TPMI_RAPL_REG_DOMAIN_INFO,
TPMI_RAPL_REG_INTERRUPT,
TPMI_RAPL_REG_MAX = 15,
};
struct tpmi_rapl_package {
struct rapl_if_priv priv;
struct oobmsm_plat_info *tpmi_info;
struct rapl_package *rp;
void __iomem *base;
struct list_head node;
};
static LIST_HEAD(tpmi_rapl_packages);
static DEFINE_MUTEX(tpmi_rapl_lock);
static struct powercap_control_type *tpmi_control_type;
/* bitmasks for RAPL TPMI, used by primitive access functions */
#define TPMI_POWER_LIMIT_MASK GENMASK_ULL(17, 0)
#define TPMI_POWER_LIMIT_ENABLE BIT_ULL(62)
#define TPMI_POWER_HIGH_LOCK BIT_ULL(63)
#define TPMI_TIME_WINDOW_MASK GENMASK_ULL(24, 18)
#define TPMI_INFO_SPEC_MASK GENMASK_ULL(17, 0)
#define TPMI_INFO_MIN_MASK GENMASK_ULL(35, 18)
#define TPMI_INFO_MAX_MASK GENMASK_ULL(53, 36)
#define TPMI_INFO_MAX_TIME_WIN_MASK GENMASK_ULL(60, 54)
#define TPMI_ENERGY_STATUS_MASK GENMASK(31, 0)
#define TPMI_PERF_STATUS_THROTTLE_TIME_MASK GENMASK(31, 0)
/* RAPL primitives for TPMI I/F */
static struct rapl_primitive_info rpi_tpmi[NR_RAPL_PRIMITIVES] = {
/* name, mask, shift, msr index, unit divisor */
[POWER_LIMIT1] = PRIMITIVE_INFO_INIT(POWER_LIMIT1, TPMI_POWER_LIMIT_MASK, 0,
RAPL_DOMAIN_REG_LIMIT, POWER_UNIT, 0),
[POWER_LIMIT2] = PRIMITIVE_INFO_INIT(POWER_LIMIT2, TPMI_POWER_LIMIT_MASK, 0,
RAPL_DOMAIN_REG_PL2, POWER_UNIT, 0),
[POWER_LIMIT4] = PRIMITIVE_INFO_INIT(POWER_LIMIT4, TPMI_POWER_LIMIT_MASK, 0,
RAPL_DOMAIN_REG_PL4, POWER_UNIT, 0),
[ENERGY_COUNTER] = PRIMITIVE_INFO_INIT(ENERGY_COUNTER, TPMI_ENERGY_STATUS_MASK, 0,
RAPL_DOMAIN_REG_STATUS, ENERGY_UNIT, 0),
[PL1_LOCK] = PRIMITIVE_INFO_INIT(PL1_LOCK, TPMI_POWER_HIGH_LOCK, 63,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[PL2_LOCK] = PRIMITIVE_INFO_INIT(PL2_LOCK, TPMI_POWER_HIGH_LOCK, 63,
RAPL_DOMAIN_REG_PL2, ARBITRARY_UNIT, 0),
[PL4_LOCK] = PRIMITIVE_INFO_INIT(PL4_LOCK, TPMI_POWER_HIGH_LOCK, 63,
RAPL_DOMAIN_REG_PL4, ARBITRARY_UNIT, 0),
[PL1_ENABLE] = PRIMITIVE_INFO_INIT(PL1_ENABLE, TPMI_POWER_LIMIT_ENABLE, 62,
RAPL_DOMAIN_REG_LIMIT, ARBITRARY_UNIT, 0),
[PL2_ENABLE] = PRIMITIVE_INFO_INIT(PL2_ENABLE, TPMI_POWER_LIMIT_ENABLE, 62,
RAPL_DOMAIN_REG_PL2, ARBITRARY_UNIT, 0),
[PL4_ENABLE] = PRIMITIVE_INFO_INIT(PL4_ENABLE, TPMI_POWER_LIMIT_ENABLE, 62,
RAPL_DOMAIN_REG_PL4, ARBITRARY_UNIT, 0),
[TIME_WINDOW1] = PRIMITIVE_INFO_INIT(TIME_WINDOW1, TPMI_TIME_WINDOW_MASK, 18,
RAPL_DOMAIN_REG_LIMIT, TIME_UNIT, 0),
[TIME_WINDOW2] = PRIMITIVE_INFO_INIT(TIME_WINDOW2, TPMI_TIME_WINDOW_MASK, 18,
RAPL_DOMAIN_REG_PL2, TIME_UNIT, 0),
[THERMAL_SPEC_POWER] = PRIMITIVE_INFO_INIT(THERMAL_SPEC_POWER, TPMI_INFO_SPEC_MASK, 0,
RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
[MAX_POWER] = PRIMITIVE_INFO_INIT(MAX_POWER, TPMI_INFO_MAX_MASK, 36,
RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
[MIN_POWER] = PRIMITIVE_INFO_INIT(MIN_POWER, TPMI_INFO_MIN_MASK, 18,
RAPL_DOMAIN_REG_INFO, POWER_UNIT, 0),
[MAX_TIME_WINDOW] = PRIMITIVE_INFO_INIT(MAX_TIME_WINDOW, TPMI_INFO_MAX_TIME_WIN_MASK,
54, RAPL_DOMAIN_REG_INFO, TIME_UNIT, 0),
[THROTTLED_TIME] = PRIMITIVE_INFO_INIT(THROTTLED_TIME,
TPMI_PERF_STATUS_THROTTLE_TIME_MASK,
0, RAPL_DOMAIN_REG_PERF, TIME_UNIT, 0),
};
static int tpmi_rapl_read_raw(int id, struct reg_action *ra, bool atomic)
{
if (!ra->reg.mmio)
return -EINVAL;
ra->value = readq(ra->reg.mmio);
ra->value &= ra->mask;
return 0;
}
static int tpmi_rapl_write_raw(int id, struct reg_action *ra)
{
u64 val;
if (!ra->reg.mmio)
return -EINVAL;
val = readq(ra->reg.mmio);
val &= ~ra->mask;
val |= ra->value;
writeq(val, ra->reg.mmio);
return 0;
}
static struct tpmi_rapl_package *trp_alloc(int pkg_id)
{
struct tpmi_rapl_package *trp;
int ret;
mutex_lock(&tpmi_rapl_lock);
if (list_empty(&tpmi_rapl_packages)) {
tpmi_control_type = powercap_register_control_type(NULL, "intel-rapl", NULL);
if (IS_ERR(tpmi_control_type)) {
ret = PTR_ERR(tpmi_control_type);
goto err_unlock;
}
}
trp = kzalloc_obj(*trp);
if (!trp) {
ret = -ENOMEM;
goto err_del_powercap;
}
list_add(&trp->node, &tpmi_rapl_packages);
mutex_unlock(&tpmi_rapl_lock);
return trp;
err_del_powercap:
if (list_empty(&tpmi_rapl_packages))
powercap_unregister_control_type(tpmi_control_type);
err_unlock:
mutex_unlock(&tpmi_rapl_lock);
return ERR_PTR(ret);
}
static void trp_release(struct tpmi_rapl_package *trp)
{
mutex_lock(&tpmi_rapl_lock);
list_del(&trp->node);
if (list_empty(&tpmi_rapl_packages))
powercap_unregister_control_type(tpmi_control_type);
kfree(trp);
mutex_unlock(&tpmi_rapl_lock);
}
/*
* Bit 0 of TPMI_RAPL_REG_DOMAIN_INFO indicates if the current package is a domain
* root or not. Only domain root packages can enumerate System (Psys) Domain.
*/
#define TPMI_RAPL_DOMAIN_ROOT BIT(0)
static int parse_one_domain(struct tpmi_rapl_package *trp, u32 offset)
{
u8 tpmi_domain_version;
enum rapl_domain_type domain_type;
enum tpmi_rapl_domain_type tpmi_domain_type;
enum tpmi_rapl_register reg_index;
enum rapl_domain_reg_id reg_id;
int tpmi_domain_size, tpmi_domain_flags;
u64 tpmi_domain_header = readq(trp->base + offset);
u64 tpmi_domain_info;
/* Domain Parent bits are ignored for now */
tpmi_domain_version = tpmi_domain_header & 0xff;
tpmi_domain_type = tpmi_domain_header >> 8 & 0xff;
tpmi_domain_size = tpmi_domain_header >> 16 & 0xff;
tpmi_domain_flags = tpmi_domain_header >> 32 & 0xffff;
if (tpmi_domain_version == TPMI_VERSION_INVALID) {
pr_debug("Invalid version, other instances may be valid\n");
return -ENODEV;
}
if (TPMI_MAJOR_VERSION(tpmi_domain_version) != TPMI_RAPL_MAJOR_VERSION) {
pr_warn(FW_BUG "Unsupported major version:%ld\n",
TPMI_MAJOR_VERSION(tpmi_domain_version));
return -ENODEV;
}
if (TPMI_MINOR_VERSION(tpmi_domain_version) > TPMI_RAPL_MINOR_VERSION)
pr_info("Ignore: Unsupported minor version:%ld\n",
TPMI_MINOR_VERSION(tpmi_domain_version));
/* Domain size: in unit of 128 Bytes */
if (tpmi_domain_size != 1) {
pr_warn(FW_BUG "Invalid Domain size %d\n", tpmi_domain_size);
return -EINVAL;
}
/* Unit register and Energy Status register are mandatory for each domain */
if (!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_UNIT)) ||
!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_ENERGY_STATUS))) {
pr_warn(FW_BUG "Invalid Domain flag 0x%x\n", tpmi_domain_flags);
return -EINVAL;
}
switch (tpmi_domain_type) {
case TPMI_RAPL_DOMAIN_PACKAGE:
domain_type = RAPL_DOMAIN_PACKAGE;
break;
case TPMI_RAPL_DOMAIN_SYSTEM:
if (!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_DOMAIN_INFO))) {
pr_warn(FW_BUG "System domain must support Domain Info register\n");
return -ENODEV;
}
tpmi_domain_info = readq(trp->base + offset + TPMI_RAPL_REG_DOMAIN_INFO * 8);
if (!(tpmi_domain_info & TPMI_RAPL_DOMAIN_ROOT))
return 0;
domain_type = RAPL_DOMAIN_PLATFORM;
break;
case TPMI_RAPL_DOMAIN_MEMORY:
domain_type = RAPL_DOMAIN_DRAM;
break;
default:
pr_warn(FW_BUG "Unsupported Domain type %d\n", tpmi_domain_type);
return -EINVAL;
}
if (trp->priv.regs[domain_type][RAPL_DOMAIN_REG_UNIT].mmio) {
pr_warn(FW_BUG "Duplicate Domain type %d\n", tpmi_domain_type);
return -EINVAL;
}
reg_index = TPMI_RAPL_REG_HEADER;
while (++reg_index != TPMI_RAPL_REG_MAX) {
if (!(tpmi_domain_flags & BIT(reg_index)))
continue;
switch (reg_index) {
case TPMI_RAPL_REG_UNIT:
reg_id = RAPL_DOMAIN_REG_UNIT;
break;
case TPMI_RAPL_REG_PL1:
reg_id = RAPL_DOMAIN_REG_LIMIT;
trp->priv.limits[domain_type] |= BIT(POWER_LIMIT1);
break;
case TPMI_RAPL_REG_PL2:
reg_id = RAPL_DOMAIN_REG_PL2;
trp->priv.limits[domain_type] |= BIT(POWER_LIMIT2);
break;
case TPMI_RAPL_REG_PL4:
reg_id = RAPL_DOMAIN_REG_PL4;
trp->priv.limits[domain_type] |= BIT(POWER_LIMIT4);
break;
case TPMI_RAPL_REG_ENERGY_STATUS:
reg_id = RAPL_DOMAIN_REG_STATUS;
break;
case TPMI_RAPL_REG_PERF_STATUS:
reg_id = RAPL_DOMAIN_REG_PERF;
break;
case TPMI_RAPL_REG_POWER_INFO:
reg_id = RAPL_DOMAIN_REG_INFO;
break;
default:
continue;
}
trp->priv.regs[domain_type][reg_id].mmio = trp->base + offset + reg_index * 8;
}
return 0;
}
/* TPMI Unit register has different layout */
#define TPMI_ENERGY_UNIT_SCALE 1000
#define TPMI_POWER_UNIT_OFFSET 0x00
#define TPMI_POWER_UNIT_MASK GENMASK(3, 0)
#define TPMI_ENERGY_UNIT_OFFSET 0x06
#define TPMI_ENERGY_UNIT_MASK GENMASK_ULL(10, 6)
#define TPMI_TIME_UNIT_OFFSET 0x0C
#define TPMI_TIME_UNIT_MASK GENMASK_ULL(15, 12)
static int rapl_check_unit_tpmi(struct rapl_domain *rd)
{
struct reg_action ra;
u32 value;
ra.reg = rd->regs[RAPL_DOMAIN_REG_UNIT];
ra.mask = ~0;
if (tpmi_rapl_read_raw(rd->rp->id, &ra, false)) {
pr_err("Failed to read power unit REG 0x%llx on %s:%s, exit.\n",
ra.reg.val, rd->rp->name, rd->name);
return -ENODEV;
}
value = (ra.value & TPMI_ENERGY_UNIT_MASK) >> TPMI_ENERGY_UNIT_OFFSET;
rd->energy_unit = (TPMI_ENERGY_UNIT_SCALE * MICROJOULE_PER_JOULE) >> value;
value = (ra.value & TPMI_POWER_UNIT_MASK) >> TPMI_POWER_UNIT_OFFSET;
rd->power_unit = MICROWATT_PER_WATT >> value;
value = (ra.value & TPMI_TIME_UNIT_MASK) >> TPMI_TIME_UNIT_OFFSET;
rd->time_unit = USEC_PER_SEC >> value;
pr_debug("Core CPU %s:%s energy=%dpJ, time=%dus, power=%duW\n",
rd->rp->name, rd->name, rd->energy_unit, rd->time_unit, rd->power_unit);
return 0;
}
static const struct rapl_defaults defaults_tpmi = {
.check_unit = rapl_check_unit_tpmi,
/* Reuse existing logic, ignore the PL_CLAMP failures and enable all Power Limits */
.set_floor_freq = rapl_default_set_floor_freq,
.compute_time_window = rapl_default_compute_time_window,
};
static int intel_rapl_tpmi_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct tpmi_rapl_package *trp;
struct oobmsm_plat_info *info;
struct resource *res;
u32 offset;
int ret;
info = tpmi_get_platform_data(auxdev);
if (!info)
return -ENODEV;
trp = trp_alloc(info->package_id);
if (IS_ERR(trp))
return PTR_ERR(trp);
if (tpmi_get_resource_count(auxdev) > 1) {
dev_err(&auxdev->dev, "does not support multiple resources\n");
ret = -EINVAL;
goto err;
}
res = tpmi_get_resource_at_index(auxdev, 0);
if (!res) {
dev_err(&auxdev->dev, "can't fetch device resource info\n");
ret = -EIO;
goto err;
}
trp->base = devm_ioremap_resource(&auxdev->dev, res);
if (IS_ERR(trp->base)) {
ret = PTR_ERR(trp->base);
goto err;
}
for (offset = 0; offset < resource_size(res); offset += TPMI_RAPL_DOMAIN_SIZE) {
ret = parse_one_domain(trp, offset);
if (ret)
goto err;
}
trp->tpmi_info = info;
trp->priv.type = RAPL_IF_TPMI;
trp->priv.read_raw = tpmi_rapl_read_raw;
trp->priv.write_raw = tpmi_rapl_write_raw;
trp->priv.control_type = tpmi_control_type;
trp->priv.defaults = &defaults_tpmi;
trp->priv.rpi = rpi_tpmi;
/* RAPL TPMI I/F is per physical package */
trp->rp = rapl_find_package_domain(info->package_id, &trp->priv, false);
if (trp->rp) {
dev_err(&auxdev->dev, "Domain for Package%d already exists\n", info->package_id);
ret = -EEXIST;
goto err;
}
trp->rp = rapl_add_package(info->package_id, &trp->priv, false);
if (IS_ERR(trp->rp)) {
dev_err(&auxdev->dev, "Failed to add RAPL Domain for Package%d, %ld\n",
info->package_id, PTR_ERR(trp->rp));
ret = PTR_ERR(trp->rp);
goto err;
}
rapl_package_add_pmu(trp->rp);
auxiliary_set_drvdata(auxdev, trp);
return 0;
err:
trp_release(trp);
return ret;
}
static void intel_rapl_tpmi_remove(struct auxiliary_device *auxdev)
{
struct tpmi_rapl_package *trp = auxiliary_get_drvdata(auxdev);
rapl_package_remove_pmu(trp->rp);
rapl_remove_package(trp->rp);
trp_release(trp);
}
static const struct auxiliary_device_id intel_rapl_tpmi_ids[] = {
{.name = "intel_vsec.tpmi-rapl" },
{ }
};
MODULE_DEVICE_TABLE(auxiliary, intel_rapl_tpmi_ids);
static struct auxiliary_driver intel_rapl_tpmi_driver = {
.probe = intel_rapl_tpmi_probe,
.remove = intel_rapl_tpmi_remove,
.id_table = intel_rapl_tpmi_ids,
};
module_auxiliary_driver(intel_rapl_tpmi_driver)
MODULE_IMPORT_NS("INTEL_RAPL");
MODULE_IMPORT_NS("INTEL_TPMI");
MODULE_DESCRIPTION("Intel RAPL TPMI Driver");
MODULE_LICENSE("GPL");
+684
View File
@@ -0,0 +1,684 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Power capping class
* Copyright (c) 2013, Intel Corporation.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kstrtox.h>
#include <linux/slab.h>
#include <linux/powercap.h>
#define to_powercap_zone(n) container_of(n, struct powercap_zone, dev)
#define to_powercap_control_type(n) \
container_of(n, struct powercap_control_type, dev)
/* Power zone show function */
#define define_power_zone_show(_attr) \
static ssize_t _attr##_show(struct device *dev, \
struct device_attribute *dev_attr,\
char *buf) \
{ \
u64 value; \
ssize_t len = -EINVAL; \
struct powercap_zone *power_zone = to_powercap_zone(dev); \
\
if (power_zone->ops->get_##_attr) { \
if (!power_zone->ops->get_##_attr(power_zone, &value)) \
len = sysfs_emit(buf, "%lld\n", value); \
} \
\
return len; \
}
/* The only meaningful input is 0 (reset), others are silently ignored */
#define define_power_zone_store(_attr) \
static ssize_t _attr##_store(struct device *dev,\
struct device_attribute *dev_attr, \
const char *buf, size_t count) \
{ \
int err; \
struct powercap_zone *power_zone = to_powercap_zone(dev); \
u64 value; \
\
err = kstrtoull(buf, 10, &value); \
if (err) \
return -EINVAL; \
if (value) \
return count; \
if (power_zone->ops->reset_##_attr) { \
if (!power_zone->ops->reset_##_attr(power_zone)) \
return count; \
} \
\
return -EINVAL; \
}
/* Power zone constraint show function */
#define define_power_zone_constraint_show(_attr) \
static ssize_t show_constraint_##_attr(struct device *dev, \
struct device_attribute *dev_attr,\
char *buf) \
{ \
u64 value; \
ssize_t len = -ENODATA; \
struct powercap_zone *power_zone = to_powercap_zone(dev); \
int id; \
struct powercap_zone_constraint *pconst;\
\
if (sscanf(dev_attr->attr.name, "constraint_%d_", &id) != 1) \
return -EINVAL; \
if (id >= power_zone->const_id_cnt) \
return -EINVAL; \
pconst = &power_zone->constraints[id]; \
if (pconst && pconst->ops && pconst->ops->get_##_attr) { \
if (!pconst->ops->get_##_attr(power_zone, id, &value)) \
len = sysfs_emit(buf, "%lld\n", value); \
} \
\
return len; \
}
/* Power zone constraint store function */
#define define_power_zone_constraint_store(_attr) \
static ssize_t store_constraint_##_attr(struct device *dev,\
struct device_attribute *dev_attr, \
const char *buf, size_t count) \
{ \
int err; \
u64 value; \
struct powercap_zone *power_zone = to_powercap_zone(dev); \
int id; \
struct powercap_zone_constraint *pconst;\
\
if (sscanf(dev_attr->attr.name, "constraint_%d_", &id) != 1) \
return -EINVAL; \
if (id >= power_zone->const_id_cnt) \
return -EINVAL; \
pconst = &power_zone->constraints[id]; \
err = kstrtoull(buf, 10, &value); \
if (err) \
return -EINVAL; \
if (pconst && pconst->ops && pconst->ops->set_##_attr) { \
if (!pconst->ops->set_##_attr(power_zone, id, value)) \
return count; \
} \
\
return -ENODATA; \
}
/* Power zone information callbacks */
define_power_zone_show(power_uw);
define_power_zone_show(max_power_range_uw);
define_power_zone_show(energy_uj);
define_power_zone_store(energy_uj);
define_power_zone_show(max_energy_range_uj);
/* Power zone attributes */
static DEVICE_ATTR_RO(max_power_range_uw);
static DEVICE_ATTR_RO(power_uw);
static DEVICE_ATTR_RO(max_energy_range_uj);
static DEVICE_ATTR_RW(energy_uj);
/* Power zone constraint attributes callbacks */
define_power_zone_constraint_show(power_limit_uw);
define_power_zone_constraint_store(power_limit_uw);
define_power_zone_constraint_show(time_window_us);
define_power_zone_constraint_store(time_window_us);
define_power_zone_constraint_show(max_power_uw);
define_power_zone_constraint_show(min_power_uw);
define_power_zone_constraint_show(max_time_window_us);
define_power_zone_constraint_show(min_time_window_us);
/* For one time seeding of constraint device attributes */
struct powercap_constraint_attr {
struct device_attribute power_limit_attr;
struct device_attribute time_window_attr;
struct device_attribute max_power_attr;
struct device_attribute min_power_attr;
struct device_attribute max_time_window_attr;
struct device_attribute min_time_window_attr;
struct device_attribute name_attr;
};
static struct powercap_constraint_attr
constraint_attrs[MAX_CONSTRAINTS_PER_ZONE];
/* A list of powercap control_types */
static LIST_HEAD(powercap_cntrl_list);
/* Mutex to protect list of powercap control_types */
static DEFINE_MUTEX(powercap_cntrl_list_lock);
#define POWERCAP_CONSTRAINT_NAME_LEN 30 /* Some limit to avoid overflow */
static ssize_t show_constraint_name(struct device *dev,
struct device_attribute *dev_attr,
char *buf)
{
const char *name;
struct powercap_zone *power_zone = to_powercap_zone(dev);
int id;
ssize_t len = -ENODATA;
struct powercap_zone_constraint *pconst;
if (sscanf(dev_attr->attr.name, "constraint_%d_", &id) != 1)
return -EINVAL;
if (id >= power_zone->const_id_cnt)
return -EINVAL;
pconst = &power_zone->constraints[id];
if (pconst && pconst->ops && pconst->ops->get_name) {
name = pconst->ops->get_name(power_zone, id);
if (name) {
len = sysfs_emit(buf, "%.*s\n",
POWERCAP_CONSTRAINT_NAME_LEN - 1, name);
}
}
return len;
}
static int create_constraint_attribute(int id, const char *name,
int mode,
struct device_attribute *dev_attr,
ssize_t (*show)(struct device *,
struct device_attribute *, char *),
ssize_t (*store)(struct device *,
struct device_attribute *,
const char *, size_t)
)
{
dev_attr->attr.name = kasprintf(GFP_KERNEL, "constraint_%d_%s",
id, name);
if (!dev_attr->attr.name)
return -ENOMEM;
dev_attr->attr.mode = mode;
dev_attr->show = show;
dev_attr->store = store;
return 0;
}
static void free_constraint_attributes(void)
{
int i;
for (i = 0; i < MAX_CONSTRAINTS_PER_ZONE; ++i) {
kfree(constraint_attrs[i].power_limit_attr.attr.name);
kfree(constraint_attrs[i].time_window_attr.attr.name);
kfree(constraint_attrs[i].name_attr.attr.name);
kfree(constraint_attrs[i].max_power_attr.attr.name);
kfree(constraint_attrs[i].min_power_attr.attr.name);
kfree(constraint_attrs[i].max_time_window_attr.attr.name);
kfree(constraint_attrs[i].min_time_window_attr.attr.name);
}
}
static int seed_constraint_attributes(void)
{
int i;
int ret;
for (i = 0; i < MAX_CONSTRAINTS_PER_ZONE; ++i) {
ret = create_constraint_attribute(i, "power_limit_uw",
S_IWUSR | S_IRUGO,
&constraint_attrs[i].power_limit_attr,
show_constraint_power_limit_uw,
store_constraint_power_limit_uw);
if (ret)
goto err_alloc;
ret = create_constraint_attribute(i, "time_window_us",
S_IWUSR | S_IRUGO,
&constraint_attrs[i].time_window_attr,
show_constraint_time_window_us,
store_constraint_time_window_us);
if (ret)
goto err_alloc;
ret = create_constraint_attribute(i, "name", S_IRUGO,
&constraint_attrs[i].name_attr,
show_constraint_name,
NULL);
if (ret)
goto err_alloc;
ret = create_constraint_attribute(i, "max_power_uw", S_IRUGO,
&constraint_attrs[i].max_power_attr,
show_constraint_max_power_uw,
NULL);
if (ret)
goto err_alloc;
ret = create_constraint_attribute(i, "min_power_uw", S_IRUGO,
&constraint_attrs[i].min_power_attr,
show_constraint_min_power_uw,
NULL);
if (ret)
goto err_alloc;
ret = create_constraint_attribute(i, "max_time_window_us",
S_IRUGO,
&constraint_attrs[i].max_time_window_attr,
show_constraint_max_time_window_us,
NULL);
if (ret)
goto err_alloc;
ret = create_constraint_attribute(i, "min_time_window_us",
S_IRUGO,
&constraint_attrs[i].min_time_window_attr,
show_constraint_min_time_window_us,
NULL);
if (ret)
goto err_alloc;
}
return 0;
err_alloc:
free_constraint_attributes();
return ret;
}
static int create_constraints(struct powercap_zone *power_zone,
int nr_constraints,
const struct powercap_zone_constraint_ops *const_ops)
{
int i;
int ret = 0;
int count;
struct powercap_zone_constraint *pconst;
if (!power_zone || !const_ops || !const_ops->get_power_limit_uw ||
!const_ops->set_power_limit_uw ||
!const_ops->get_time_window_us ||
!const_ops->set_time_window_us)
return -EINVAL;
count = power_zone->zone_attr_count;
for (i = 0; i < nr_constraints; ++i) {
pconst = &power_zone->constraints[i];
pconst->ops = const_ops;
pconst->id = power_zone->const_id_cnt;
power_zone->const_id_cnt++;
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].power_limit_attr.attr;
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].time_window_attr.attr;
if (pconst->ops->get_name)
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].name_attr.attr;
if (pconst->ops->get_max_power_uw)
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].max_power_attr.attr;
if (pconst->ops->get_min_power_uw)
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].min_power_attr.attr;
if (pconst->ops->get_max_time_window_us)
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].max_time_window_attr.attr;
if (pconst->ops->get_min_time_window_us)
power_zone->zone_dev_attrs[count++] =
&constraint_attrs[i].min_time_window_attr.attr;
}
power_zone->zone_attr_count = count;
return ret;
}
static bool control_type_valid(void *control_type)
{
struct powercap_control_type *pos = NULL;
bool found = false;
mutex_lock(&powercap_cntrl_list_lock);
list_for_each_entry(pos, &powercap_cntrl_list, node) {
if (pos == control_type) {
found = true;
break;
}
}
mutex_unlock(&powercap_cntrl_list_lock);
return found;
}
static ssize_t name_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct powercap_zone *power_zone = to_powercap_zone(dev);
return sysfs_emit(buf, "%s\n", power_zone->name);
}
static DEVICE_ATTR_RO(name);
/* Create zone and attributes in sysfs */
static void create_power_zone_common_attributes(
struct powercap_zone *power_zone)
{
int count = 0;
power_zone->zone_dev_attrs[count++] = &dev_attr_name.attr;
if (power_zone->ops->get_max_energy_range_uj)
power_zone->zone_dev_attrs[count++] =
&dev_attr_max_energy_range_uj.attr;
if (power_zone->ops->get_energy_uj) {
if (power_zone->ops->reset_energy_uj)
dev_attr_energy_uj.attr.mode = S_IWUSR | S_IRUSR;
else
dev_attr_energy_uj.attr.mode = S_IRUSR;
power_zone->zone_dev_attrs[count++] =
&dev_attr_energy_uj.attr;
}
if (power_zone->ops->get_power_uw)
power_zone->zone_dev_attrs[count++] =
&dev_attr_power_uw.attr;
if (power_zone->ops->get_max_power_range_uw)
power_zone->zone_dev_attrs[count++] =
&dev_attr_max_power_range_uw.attr;
power_zone->zone_dev_attrs[count] = NULL;
power_zone->zone_attr_count = count;
}
static void powercap_release(struct device *dev)
{
bool allocated;
if (dev->parent) {
struct powercap_zone *power_zone = to_powercap_zone(dev);
/* Store flag as the release() may free memory */
allocated = power_zone->allocated;
/* Remove id from parent idr struct */
idr_remove(power_zone->parent_idr, power_zone->id);
/* Destroy idrs allocated for this zone */
idr_destroy(&power_zone->idr);
kfree(power_zone->name);
kfree(power_zone->zone_dev_attrs);
kfree(power_zone->constraints);
if (power_zone->ops->release)
power_zone->ops->release(power_zone);
if (allocated)
kfree(power_zone);
} else {
struct powercap_control_type *control_type =
to_powercap_control_type(dev);
/* Store flag as the release() may free memory */
allocated = control_type->allocated;
idr_destroy(&control_type->idr);
mutex_destroy(&control_type->lock);
if (control_type->ops && control_type->ops->release)
control_type->ops->release(control_type);
if (allocated)
kfree(control_type);
}
}
static ssize_t enabled_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
bool mode = true;
/* Default is enabled */
if (dev->parent) {
struct powercap_zone *power_zone = to_powercap_zone(dev);
if (power_zone->ops->get_enable)
if (power_zone->ops->get_enable(power_zone, &mode))
mode = false;
} else {
struct powercap_control_type *control_type =
to_powercap_control_type(dev);
if (control_type->ops && control_type->ops->get_enable)
if (control_type->ops->get_enable(control_type, &mode))
mode = false;
}
return sysfs_emit(buf, "%d\n", mode);
}
static ssize_t enabled_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
bool mode;
if (kstrtobool(buf, &mode))
return -EINVAL;
if (dev->parent) {
struct powercap_zone *power_zone = to_powercap_zone(dev);
if (power_zone->ops->set_enable)
if (!power_zone->ops->set_enable(power_zone, mode))
return len;
} else {
struct powercap_control_type *control_type =
to_powercap_control_type(dev);
if (control_type->ops && control_type->ops->set_enable)
if (!control_type->ops->set_enable(control_type, mode))
return len;
}
return -ENOSYS;
}
static DEVICE_ATTR_RW(enabled);
static struct attribute *powercap_attrs[] = {
&dev_attr_enabled.attr,
NULL,
};
ATTRIBUTE_GROUPS(powercap);
static struct class powercap_class = {
.name = "powercap",
.dev_release = powercap_release,
.dev_groups = powercap_groups,
};
struct powercap_zone *powercap_register_zone(
struct powercap_zone *power_zone,
struct powercap_control_type *control_type,
const char *name,
struct powercap_zone *parent,
const struct powercap_zone_ops *ops,
int nr_constraints,
const struct powercap_zone_constraint_ops *const_ops)
{
int result;
int nr_attrs;
if (!name || !control_type || !ops ||
nr_constraints > MAX_CONSTRAINTS_PER_ZONE ||
(!ops->get_energy_uj && !ops->get_power_uw) ||
!control_type_valid(control_type))
return ERR_PTR(-EINVAL);
if (power_zone) {
if (!ops->release)
return ERR_PTR(-EINVAL);
memset(power_zone, 0, sizeof(*power_zone));
} else {
power_zone = kzalloc_obj(*power_zone);
if (!power_zone)
return ERR_PTR(-ENOMEM);
power_zone->allocated = true;
}
power_zone->ops = ops;
power_zone->control_type_inst = control_type;
if (!parent) {
power_zone->dev.parent = &control_type->dev;
power_zone->parent_idr = &control_type->idr;
} else {
power_zone->dev.parent = &parent->dev;
power_zone->parent_idr = &parent->idr;
}
power_zone->dev.class = &powercap_class;
mutex_lock(&control_type->lock);
/* Using idr to get the unique id */
result = idr_alloc(power_zone->parent_idr, NULL, 0, 0, GFP_KERNEL);
if (result < 0)
goto err_idr_alloc;
power_zone->id = result;
idr_init(&power_zone->idr);
result = -ENOMEM;
power_zone->name = kstrdup(name, GFP_KERNEL);
if (!power_zone->name)
goto err_name_alloc;
power_zone->constraints = kzalloc_objs(*power_zone->constraints,
nr_constraints);
if (!power_zone->constraints)
goto err_const_alloc;
nr_attrs = nr_constraints * POWERCAP_CONSTRAINTS_ATTRS +
POWERCAP_ZONE_MAX_ATTRS + 1;
power_zone->zone_dev_attrs = kcalloc(nr_attrs, sizeof(void *),
GFP_KERNEL);
if (!power_zone->zone_dev_attrs)
goto err_attr_alloc;
create_power_zone_common_attributes(power_zone);
result = create_constraints(power_zone, nr_constraints, const_ops);
if (result)
goto err_dev_ret;
power_zone->zone_dev_attrs[power_zone->zone_attr_count] = NULL;
power_zone->dev_zone_attr_group.attrs = power_zone->zone_dev_attrs;
power_zone->dev_attr_groups[0] = &power_zone->dev_zone_attr_group;
power_zone->dev_attr_groups[1] = NULL;
power_zone->dev.groups = power_zone->dev_attr_groups;
dev_set_name(&power_zone->dev, "%s:%x",
dev_name(power_zone->dev.parent),
power_zone->id);
result = device_register(&power_zone->dev);
if (result) {
put_device(&power_zone->dev);
mutex_unlock(&control_type->lock);
return ERR_PTR(result);
}
control_type->nr_zones++;
mutex_unlock(&control_type->lock);
return power_zone;
err_dev_ret:
kfree(power_zone->zone_dev_attrs);
err_attr_alloc:
kfree(power_zone->constraints);
err_const_alloc:
kfree(power_zone->name);
err_name_alloc:
idr_remove(power_zone->parent_idr, power_zone->id);
err_idr_alloc:
if (power_zone->allocated)
kfree(power_zone);
mutex_unlock(&control_type->lock);
return ERR_PTR(result);
}
EXPORT_SYMBOL_GPL(powercap_register_zone);
int powercap_unregister_zone(struct powercap_control_type *control_type,
struct powercap_zone *power_zone)
{
if (!power_zone || !control_type)
return -EINVAL;
mutex_lock(&control_type->lock);
control_type->nr_zones--;
mutex_unlock(&control_type->lock);
device_unregister(&power_zone->dev);
return 0;
}
EXPORT_SYMBOL_GPL(powercap_unregister_zone);
struct powercap_control_type *powercap_register_control_type(
struct powercap_control_type *control_type,
const char *name,
const struct powercap_control_type_ops *ops)
{
int result;
if (!name)
return ERR_PTR(-EINVAL);
if (control_type) {
if (!ops || !ops->release)
return ERR_PTR(-EINVAL);
memset(control_type, 0, sizeof(*control_type));
} else {
control_type = kzalloc_obj(*control_type);
if (!control_type)
return ERR_PTR(-ENOMEM);
control_type->allocated = true;
}
mutex_init(&control_type->lock);
control_type->ops = ops;
INIT_LIST_HEAD(&control_type->node);
control_type->dev.class = &powercap_class;
dev_set_name(&control_type->dev, "%s", name);
idr_init(&control_type->idr);
mutex_lock(&powercap_cntrl_list_lock);
list_add_tail(&control_type->node, &powercap_cntrl_list);
mutex_unlock(&powercap_cntrl_list_lock);
result = device_register(&control_type->dev);
if (result) {
mutex_lock(&powercap_cntrl_list_lock);
list_del(&control_type->node);
mutex_unlock(&powercap_cntrl_list_lock);
idr_destroy(&control_type->idr);
put_device(&control_type->dev);
return ERR_PTR(result);
}
return control_type;
}
EXPORT_SYMBOL_GPL(powercap_register_control_type);
int powercap_unregister_control_type(struct powercap_control_type *control_type)
{
struct powercap_control_type *pos = NULL;
if (control_type->nr_zones) {
dev_err(&control_type->dev, "Zones of this type still not freed\n");
return -EINVAL;
}
mutex_lock(&powercap_cntrl_list_lock);
list_for_each_entry(pos, &powercap_cntrl_list, node) {
if (pos == control_type) {
list_del(&control_type->node);
mutex_unlock(&powercap_cntrl_list_lock);
device_unregister(&control_type->dev);
return 0;
}
}
mutex_unlock(&powercap_cntrl_list_lock);
return -ENODEV;
}
EXPORT_SYMBOL_GPL(powercap_unregister_control_type);
static int __init powercap_init(void)
{
int result;
result = seed_constraint_attributes();
if (result)
return result;
return class_register(&powercap_class);
}
fs_initcall(powercap_init);
MODULE_DESCRIPTION("PowerCap sysfs Driver");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");