684 lines · 18.6 KB · C
RawDownload
// 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)

Lines 1–500 of 684.