--- drivers/Makefile | 1 + drivers/cpuoffline/Kconfig | 26 ++ drivers/cpuoffline/Makefile | 2 + drivers/cpuoffline/cpuoffline.c | 488 +++++++++++++++++++++++++++++++++++++++ include/linux/cpuoffline.h | 82 +++++++ 5 files changed, 599 insertions(+), 0 deletions(-) create mode 100644 drivers/cpuoffline/Kconfig create mode 100644 drivers/cpuoffline/Makefile create mode 100644 drivers/cpuoffline/cpuoffline.c create mode 100644 include/linux/cpuoffline.h
diff --git a/drivers/Makefile b/drivers/Makefile index dde8076..d41e183 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -95,6 +95,7 @@ obj-$(CONFIG_EISA) += eisa/ obj-y += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ +obj-$(CONFIG_CPU_OFFLINE) += cpuoffline/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-y += leds/ diff --git a/drivers/cpuoffline/Kconfig b/drivers/cpuoffline/Kconfig new file mode 100644 index 0000000..57057d4 --- /dev/null +++ b/drivers/cpuoffline/Kconfig @@ -0,0 +1,26 @@ +config CPU_OFFLINE + bool "CPUoffline framework" + help + CPUoffline provides a framework that allows for taking CPUs + offline via an in-kernel governor. The governor itself can + implement any number of policies for deciding to offline a + core. Though primarily used for power capping, CPUoffline can + also be used to implement a thermal duty to prevent core + over-heating, etc. + + For details please see <file:Documentation/cpuoffline>. + + If in doubt, say N. + +config CPU_OFFLINE_DEFAULT_DRIVER + bool "CPUoffline default driver" + depends on CPU_OFFLINE + help + A default driver that creates a single partition containing + all possible CPUs. The benefit of this driver is that a + platform does not need any new code to make use of the + CPUoffline framework. Do not select this if your platform + implements it's own driver for registering partitions and CPUs + with the CPUoffline framework. + + If in doubt, say N. diff --git a/drivers/cpuoffline/Makefile b/drivers/cpuoffline/Makefile new file mode 100644 index 0000000..0b5aa59 --- /dev/null +++ b/drivers/cpuoffline/Makefile @@ -0,0 +1,2 @@ +# CPUoffline core +obj-$(CONFIG_CPU_OFFLINE) += cpuoffline.o diff --git a/drivers/cpuoffline/cpuoffline.c b/drivers/cpuoffline/cpuoffline.c new file mode 100644 index 0000000..0427df3 --- /dev/null +++ b/drivers/cpuoffline/cpuoffline.c @@ -0,0 +1,488 @@ +/* + * CPU Offline framework core + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Mike Turquette <mturque...@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/mutex.h> +#include <linux/cpuoffline.h> +#include <linux/slab.h> +//#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/err.h> + +#define MAX_CPU_LEN 8 + +static int nr_partitions = 0; + +static struct cpuoffline_driver *cpuoffline_driver; +DEFINE_MUTEX(cpuoffline_driver_mutex); + +static LIST_HEAD(cpuoffline_governor_list); +static DEFINE_MUTEX(cpuoffline_governor_mutex); + +static DEFINE_PER_CPU(struct cpuoffline_partition *, cpuoffline_partition); + +struct kobject *cpuoffline_global_kobject; +EXPORT_SYMBOL(cpuoffline_global_kobject); + +/* sysfs interfaces */ + +static struct cpuoffline_governor *__find_governor(const char *str_governor) +{ + struct cpuoffline_governor *gov; + + list_for_each_entry(gov, &cpuoffline_governor_list, governor_list) + if (!strnicmp(str_governor, gov->name, MAX_NAME_LEN)) + return gov; + + return NULL; +} + +static ssize_t current_governor_show(struct cpuoffline_partition *partition, + char *buf) +{ + struct cpuoffline_governor *gov; + + gov = partition->governor; + + if (!gov) + return 0; + + return snprintf(buf, MAX_NAME_LEN, "%s\n", gov->name); +} + +static ssize_t current_governor_store(struct cpuoffline_partition *partition, + const char *buf, size_t count) +{ + int ret; + char govstring[MAX_NAME_LEN]; + struct cpuoffline_governor *gov, *tempgov; + + gov = partition->governor; + + ret = sscanf(buf, "%15s", govstring); + + if (ret != 1) + return -EINVAL; + + tempgov = __find_governor(govstring); + + if (!tempgov) + return -EINVAL; + + if (!try_module_get(tempgov->owner)) + return -EINVAL; + + /* XXX should gov->stop handle the module put? probably not */ + if (gov) { + gov->stop(partition); + module_put(gov->owner); + } + + /* XXX kfree the governor? is this a memleak? */ + partition->governor = gov = tempgov; + + gov->start(partition); + + return count; +} + +static ssize_t available_governors_show(struct cpuoffline_partition *partition, + char *buf) +{ + ssize_t ret = 0; + struct cpuoffline_governor *gov; + + list_for_each_entry(gov, &cpuoffline_governor_list, governor_list) + ret += snprintf(buf, MAX_NAME_LEN, "%s\n", gov->name); + + return ret; +} + +static ssize_t partition_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct cpuoffline_partition *partition; + struct cpuoffline_attribute *c_attr; + ssize_t ret; + + partition = container_of(kobj, struct cpuoffline_partition, kobj); + c_attr = container_of(attr, struct cpuoffline_attribute, attr); + + if (!partition || !c_attr) + return -EINVAL; + + mutex_lock(&partition->mutex); + /* refcount++ */ + kobject_get(&partition->kobj); + + if (c_attr->show) + ret = c_attr->show(partition, buf); + else + ret = -EIO; + + /* refcount-- */ + kobject_put(&partition->kobj); + + mutex_unlock(&partition->mutex); + return ret; +} + +static ssize_t partition_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct cpuoffline_partition *partition; + struct cpuoffline_attribute *c_attr; + ssize_t ret = -EINVAL; + + partition = container_of(kobj, struct cpuoffline_partition, kobj); + c_attr = container_of(attr, struct cpuoffline_attribute, attr); + + if (!partition || !c_attr) + goto out; + + mutex_lock(&partition->mutex); + /* refcount++ */ + kobject_get(&partition->kobj); + + if(c_attr->store) + ret = c_attr->store(partition, buf, count); + else + ret = -EIO; + + /* refcount-- */ + kobject_put(&partition->kobj); + +out: + mutex_unlock(&partition->mutex); + return ret; +} + + +static struct cpuoffline_attribute current_governor = + __ATTR(current_governor, (S_IRUGO | S_IWUSR), current_governor_show, + current_governor_store); + +static struct cpuoffline_attribute available_governors = + __ATTR_RO(available_governors); + +static struct attribute *partition_default_attrs[] = { + ¤t_governor.attr, + &available_governors.attr, + NULL, +}; + +static const struct sysfs_ops partition_ops = { + .show = partition_show, + .store = partition_store, +}; + +static void cpuoffline_partition_release(struct kobject *kobj) +{ + struct cpuoffline_partition *partition; + + partition = container_of(kobj, struct cpuoffline_partition, kobj); + + complete(&partition->kobj_unregister); +} + +static struct kobj_type partition_ktype = { + .sysfs_ops = &partition_ops, + .default_attrs = partition_default_attrs, + .release = cpuoffline_partition_release, +}; + +/* cpu class sysdev device registration */ + +static int cpuoffline_add_dev_interface(struct cpuoffline_partition *partition, + struct sys_device *sys_dev) +{ + int ret = 0; + char name[MAX_CPU_LEN]; + struct kobject *kobj; + + /* create cpuoffline directory for this CPU */ + /*ret = kobject_init_and_add(&kobj, &ktype_device, + &sys_dev->kobj, "%s", "cpuoffline");*/ + kobj = kobject_create_and_add("cpuoffline", &sys_dev->kobj); + + if (!kobj) { + pr_warning("%s: failed to create cpuoffline dir for cpu %d\n", + __func__, sys_dev->id); + return -ENOMEM; + } + +#ifdef CONFIG_CPU_OFFLINE_STATISTICS + /* XXX set up per-CPU statistics here, which is ktype_device */ + /* create directory for cpuoffline stats */ +#endif + + /* create a symlink from this cpu to its partition */ + ret = sysfs_create_link(kobj, &partition->kobj, "partition"); + + if (ret) + pr_warning("%s: failed to create symlink from cpu %d to partition %d\n", + __func__, sys_dev->id, partition->id); + + /* create a symlink from this cpu's partition to itself */ + snprintf(name, MAX_CPU_LEN, "cpu%d", sys_dev->id); + ret = sysfs_create_link(&partition->kobj, kobj, name); + + if (ret) + pr_warning("%s: failed to create symlink from partition %d to cpu %d\n", + __func__, partition->id, sys_dev->id); + + return 0; +} + +static int cpuoffline_add_partition_interface( + struct cpuoffline_partition *partition) +{ + return kobject_init_and_add(&partition->kobj, &partition_ktype, + cpuoffline_global_kobject, "%s%d", "partition", + partition->id); +} + +struct cpuoffline_partition *cpuoffline_partition_init(unsigned int cpu) +{ + int ret = -ENOMEM; + struct cpuoffline_partition *partition; + + partition = kzalloc(sizeof(struct cpuoffline_partition), + GFP_KERNEL); + if (!partition) + goto out; + + if (!zalloc_cpumask_var(&partition->cpus, GFP_KERNEL)) + goto err_free_partition; + + /* start populating ->cpus with this cpu first */ + cpumask_copy(partition->cpus, cpumask_of(cpu)); + + mutex_init(&partition->mutex); + + /* helps sysfs look pretty */ + partition->id = nr_partitions++; + + ret = cpuoffline_driver->init(partition); + + if (ret) { + pr_err("%s: failed to init driver\n", __func__); + goto err_free_cpus; + } + + /* create directory in sysfs for this partition */ + ret = cpuoffline_add_partition_interface(partition); + + /* decrement partition->kobj if the above returns error */ + if (ret) { + pr_warn("%s: failed to create partition interface\n", __func__); + kobject_put(&partition->kobj); + } + + return partition; + +err_free_cpus: + nr_partitions--; + free_cpumask_var(partition->cpus); +err_free_partition: + kfree(partition); +out: + return (void *)ret; +} + +/* does not need locking because sequence is synchronous and orderly */ +static int cpuoffline_add_dev(struct sys_device *sys_dev) +{ + unsigned int cpu = sys_dev->id; + int ret = 0; + struct cpuoffline_partition *partition; + + /* sanity checks */ + if (cpu_is_offline(cpu)) + pr_notice("%s: CPU%d is offline\n", __func__, cpu); + + if (!cpuoffline_driver) + return -EINVAL; + + partition = per_cpu(cpuoffline_partition, cpu); + + /* + * The first cpu in each partition to hit this function will allocate + * partition and populate partition's address into the per-cpu data for + * each of the CPUs in the same. It is up to the driver->init function + * to do this since only the CPUoffline platform driver knows the + * desired topology. + * + * When the other CPUs in a partition hit this path, their partition + * wll have already been allocated. Only thing left to do is set up + * sysfs entries. + */ + if (!partition) { + partition = cpuoffline_partition_init(cpu); + + if (IS_ERR(partition)) { + pr_warn("%s: failed to create partition\n", __func__); + return -ENOMEM; + } + } + + ret = cpuoffline_add_dev_interface(partition, sys_dev); + + return ret; +} + +static int cpuoffline_remove_dev(struct sys_device *sys_dev) +{ + pr_err("%s: GETTING REMOVED!\n", __func__); + return 0; +} + +static struct sysdev_driver cpuoffline_sysdev_driver = { + .add = cpuoffline_add_dev, + .remove = cpuoffline_remove_dev, +}; + +/* driver registration API */ + +int cpuoffline_register_driver(struct cpuoffline_driver *driver) +{ + int ret = 0; + + pr_info("CPUoffline: registering %s driver", driver->name); + + if (!driver) + return -EINVAL; + + mutex_lock(&cpuoffline_driver_mutex); + + /* there can only be one */ + if (cpuoffline_driver) + ret = -EBUSY; + else + cpuoffline_driver = driver; + + mutex_unlock(&cpuoffline_driver_mutex); + + if (ret) + goto out; + + /* register every CPUoffline device */ + ret = sysdev_driver_register(&cpu_sysdev_class, + &cpuoffline_sysdev_driver); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(cpuoffline_register_driver); + +/* FIXME - should this be allowed? */ +int cpuoffline_unregister_driver(struct cpuoffline_driver *driver) +{ + pr_info("CPUoffline: unregistering %s driver\n", driver->name); + + return 0; +} +EXPORT_SYMBOL_GPL(cpuoffline_unregister_driver); + +/* default driver - single partition containing all CPUs */ + +#ifdef CONFIG_CPU_OFFLINE_DEFAULT_DRIVER +/** + * cpuoffline_default_driver_init - create a single partition with all CPUs + * @partition: CPUoffline partition that is yet to be populated + * + * A CPUoffline driver's init function is responsible for two pieces of data. + * First, for every CPU that should be in @partition, the driver init function + * must populate a per-cpu pointer to that partition. Second, for every CPU + * that should be in @partition, the driver init function must set that bit in + * the @partition->cpus cpumask. + */ +int cpuoffline_default_driver_init(struct cpuoffline_partition *partition) +{ + unsigned int cpu; + + /* sanity checks */ + if (!partition) + return -EINVAL; + + cpu = cpumask_first(partition->cpus); + + /* CPU0 should be the only CPU in the mask */ + if (cpu) + return -EINVAL; + + for_each_possible_cpu(cpu) { + per_cpu(cpuoffline_partition, cpu) = partition; + cpumask_set_cpu(cpu, partition->cpus); + } + + return 0; +} + +int cpuoffline_default_driver_exit(struct cpuoffline_partition *partition) +{ + return 0; +} + +static struct cpuoffline_driver cpuoffline_default_driver = { + .name = "default", + .init = cpuoffline_default_driver_init, + .exit = cpuoffline_default_driver_exit, +}; + +static int __init cpuoffline_register_default_driver(void) +{ + return cpuoffline_register_driver(&cpuoffline_default_driver); +} +late_initcall(cpuoffline_register_default_driver); +#endif + +/* CPUoffline governor registration */ +int cpuoffline_register_governor(struct cpuoffline_governor *governor) +{ + int ret; + + if (!governor) + return -EINVAL; + + mutex_lock(&cpuoffline_governor_mutex); + + ret = -EBUSY; + if (__find_governor(governor->name) == NULL) { + ret = 0; + list_add(&governor->governor_list, &cpuoffline_governor_list); + } + + mutex_unlock(&cpuoffline_governor_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(cpuoffline_register_governor); + + +/* CPUoffline core initialization */ + +static int __init cpuoffline_core_init(void) +{ + int cpu; + + pr_info("%s\n", __func__); + for_each_possible_cpu(cpu) { + per_cpu(cpuoffline_partition, cpu) = NULL; + } + + cpuoffline_global_kobject = kobject_create_and_add("cpuoffline", + &cpu_sysdev_class.kset.kobj); + + WARN_ON(!cpuoffline_global_kobject); + /*register_syscore_ops(&cpuoffline_syscore_ops);*/ + + return 0; +} +core_initcall(cpuoffline_core_init); diff --git a/include/linux/cpuoffline.h b/include/linux/cpuoffline.h new file mode 100644 index 0000000..0c5b9a5 --- /dev/null +++ b/include/linux/cpuoffline.h @@ -0,0 +1,82 @@ +/* + * cpuoffline.h + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Mike Turquette <mturque...@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/cpu.h> +#include <linux/mutex.h> + +#ifndef _LINUX_CPUOFFLINE_H +#define _LINUX_CPUOFFLINE_H + +#define MAX_NAME_LEN 16 + +//DECLARE_PER_CPU(struct cpuoffline_partition *, cpuoffline_partition); +//DECLARE_PER_CPU(int, cpuoffline_can_offline); + +struct cpuoffline_partition; + +struct cpuoffline_governor { + char name[MAX_NAME_LEN]; + struct list_head governor_list; + /*struct mutex mutex;*/ + struct module *owner; + int (*start)(struct cpuoffline_partition *partition); + int (*stop)(struct cpuoffline_partition *partition); + struct kobject kobj; +}; + +/** + * cpuoffline_parition - set of CPUs affected by a CPUoffline governor + * + * @cpus - bitmask of CPUs managed by this partition + * @cpus_can_offline - bitmask of CPUs in this partition that can go offline + * @min_cpus_online - limit how many CPUs are offline for performance + * @max_cpus_online - limits how many CPUs are online for power capping + * @cpuoffline_governor - governor policy for hotplugging CPUs + */ +struct cpuoffline_partition { + int id; + char name[MAX_NAME_LEN]; + cpumask_var_t cpus; + /*cpumask_var_t cpus_can_offline;*/ + int min_cpus_online; + /*int max_cpus_online;*/ + struct cpuoffline_governor *governor; + + struct kobject kobj; + struct completion kobj_unregister; + + struct mutex mutex; + + void * private_data; +}; + +struct cpuoffline_driver { + char name[MAX_NAME_LEN]; + int (*init)(struct cpuoffline_partition *partition); + int (*exit)(struct cpuoffline_partition *partition); +}; + +/* kobject/sysfs definitions */ +struct cpuoffline_attribute { + struct attribute attr; + ssize_t (*show)(struct cpuoffline_partition *partition, char *buf); + ssize_t (*store)(struct cpuoffline_partition *partition, + const char *buf, size_t count); +}; + +/* registration functions */ + +int cpuoffline_register_governor(struct cpuoffline_governor *governor); +void cpuoffline_unregister_governor(struct cpuoffline_governor *governor); + +int cpuoffline_register_driver(struct cpuoffline_driver *driver); +int cpuoffline_unregister_driver(struct cpuoffline_driver *driver); +#endif -- 1.7.4.1 _______________________________________________ linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev