From patchwork Fri Aug 19 19:38:17 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mike Turquette X-Patchwork-Id: 3581 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 46A7A23F26 for ; Fri, 19 Aug 2011 19:40:00 +0000 (UTC) Received: from mail-gw0-f52.google.com (mail-gw0-f52.google.com [74.125.83.52]) by fiordland.canonical.com (Postfix) with ESMTP id D8E4BA1800B for ; Fri, 19 Aug 2011 19:39:59 +0000 (UTC) Received: by mail-gw0-f52.google.com with SMTP id 15so2733900gwj.11 for ; Fri, 19 Aug 2011 12:39:59 -0700 (PDT) Received: by 10.150.2.16 with SMTP id 16mr123907ybb.333.1313782799605; Fri, 19 Aug 2011 12:39:59 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.150.157.17 with SMTP id f17cs113591ybe; Fri, 19 Aug 2011 12:39:59 -0700 (PDT) Received: by 10.224.190.193 with SMTP id dj1mr122697qab.321.1313782799032; Fri, 19 Aug 2011 12:39:59 -0700 (PDT) Received: from na3sys009aog105.obsmtp.com ([74.125.149.75]) by mx.google.com with SMTP id u10si8289132wfk.60.2011.08.19.12.39.58 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 19 Aug 2011 12:39:58 -0700 (PDT) Received-SPF: pass (google.com: domain of mturquette@ti.com designates 74.125.149.75 as permitted sender) client-ip=74.125.149.75; Authentication-Results: mx.google.com; spf=pass (google.com: domain of mturquette@ti.com designates 74.125.149.75 as permitted sender) smtp.mail=mturquette@ti.com Received: from mail-gy0-f170.google.com ([209.85.160.170]) (using TLSv1) by na3sys009aob105.postini.com ([74.125.148.12]) with SMTP ID DSNKTk68DVdkRqKFirZzGETeANO9zSjAI7uv@postini.com; Fri, 19 Aug 2011 12:39:58 PDT Received: by mail-gy0-f170.google.com with SMTP id 5so4181123gyd.1 for ; Fri, 19 Aug 2011 12:39:57 -0700 (PDT) Received: by 10.236.184.132 with SMTP id s4mr889585yhm.53.1313782797786; Fri, 19 Aug 2011 12:39:57 -0700 (PDT) Received: from localhost.localdomain (dragon.ti.com [192.94.94.33]) by mx.google.com with ESMTPS id e21sm1179802yhn.35.2011.08.19.12.39.55 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 19 Aug 2011 12:39:57 -0700 (PDT) From: Mike Turquette To: linaro-dev@lists.linaro.org, patches@linaro.org Cc: Mike Turquette Subject: [PATCH 4/6] cpuoffline core Date: Fri, 19 Aug 2011 12:38:17 -0700 Message-Id: <1313782699-29411-5-git-send-email-mturquette@ti.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1313782699-29411-1-git-send-email-mturquette@ti.com> References: <1313782699-29411-1-git-send-email-mturquette@ti.com> --- 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 . + + 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 + * + * 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 +#include +#include +//#include +#include +#include + +#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 + * + * 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 +#include + +#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