---
 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[] = {
+       &current_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

Reply via email to