LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
From: Trinabh Gupta <trinabh@linux.vnet.ibm.com>
To: arjan@linux.intel.com, peterz@infradead.org, lenb@kernel.org,
	suresh.b.siddha@intel.com, benh@kernel.crashing.org,
	venki@google.com, ak@linux.intel.com
Cc: linux-kernel@vger.kernel.org
Subject: [RFC PATCH V3 2/3] cpuidle: list based cpuidle driver registration and selection
Date: Tue, 08 Feb 2011 16:22:04 +0530	[thread overview]
Message-ID: <20110208105203.9998.35678.stgit@tringupt.in.ibm.com> (raw)
In-Reply-To: <20110208105146.9998.22103.stgit@tringupt.in.ibm.com>

A cpuidle_driver structure represents a cpuidle driver like
acpi_idle, intel_idle providing low level idle routines.
A cpuidle_driver is global in nature as it provides routines
for all the CPUS. Each CPU registered with the cpuidle subsystem is
represented as a cpuidle_device. A cpuidle_device structure
points to the low level idle routines for that CPU provided by
a certain driver. In other words, a cpuidle driver creates a
cpuidle_device structure for each CPU that it registers with the
cpuidle subsystem. Whenever cpuidle idle loop is called, the cpuidle
subsystem picks the cpuidle_device structure for that cpu and
calls one of the low level idle routines through that structure.

In the current design, only one cpuidle_driver may be registered
and registration of any subsequent driver fails. The same registered
driver provides low level idle routines for each cpuidle_device.

A list based registration for cpuidle_driver provides a clean
mechanism for multiple subsystems/modules to register their own idle
routines and thus avoids using pm_idle.

This patch implements a list based registration for cpuidle
driver. Different drivers can be registered and the driver to
be used is selected based on a per driver priority. On a
cpuidle driver registration or unregistration cpuidle_device
structure for each CPU is changed. Each cpuidle_device points
to its driver to facilitate this registration and unregistration.

        ---------                        ---------
        |cpuidle|                        |cpuidle|
        |driver |----------------------- |driver |
        |default|                        |acpi   |
        ---------                        ---------
            ^                                ^
            |                                |
----------------------------      ---------------------------
^                    ^            ^                ^
|                    |            |                |
|                    |            |                |
---------    ---------            ---------    ----------
|cpuidle|    |cpuidle|            |cpuidle|    |cpuidle |
|device |    |device | ....       |device |    |device  | .....
|  CPU0 |    | CPU1  |            |CPU0   |    |CPU1    |
---------    ---------            ---------    ----------

Signed-off-by: Trinabh Gupta <trinabh@linux.vnet.ibm.com>
---

 drivers/acpi/processor_idle.c |    2 +
 drivers/cpuidle/Kconfig       |    6 ++
 drivers/cpuidle/cpuidle.c     |   41 ++++++++++++---
 drivers/cpuidle/driver.c      |  114 ++++++++++++++++++++++++++++++++++++++---
 include/linux/cpuidle.h       |    3 +
 5 files changed, 152 insertions(+), 14 deletions(-)

diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c
index dcb38f8..b53c7fb 100644
--- a/drivers/acpi/processor_idle.c
+++ b/drivers/acpi/processor_idle.c
@@ -964,6 +964,7 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev,
 struct cpuidle_driver acpi_idle_driver = {
 	.name =		"acpi_idle",
 	.owner =	THIS_MODULE,
+	.priority =	10,
 };
 
 /**
@@ -985,6 +986,7 @@ static int acpi_processor_setup_cpuidle(struct acpi_processor *pr)
 	}
 
 	dev->cpu = pr->id;
+	dev->drv = &acpi_idle_driver;
 	for (i = 0; i < CPUIDLE_STATE_MAX; i++) {
 		dev->states[i].name[0] = '\0';
 		dev->states[i].desc[0] = '\0';
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index e67c258..8cc73c8 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -19,3 +19,9 @@ config CPU_IDLE_GOV_MENU
 	bool
 	depends on CPU_IDLE && NO_HZ
 	default y
+
+config ARCH_USES_PMIDLE
+	bool
+	depends on CPUIDLE
+	depends on !X86
+	default y
diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c
index 9bf4640..dc472d8 100644
--- a/drivers/cpuidle/cpuidle.c
+++ b/drivers/cpuidle/cpuidle.c
@@ -109,6 +109,7 @@ void cpuidle_idle_call(void)
 	trace_power_end(smp_processor_id());
 }
 
+#ifdef CONFIG_ARCH_USES_PMIDLE
 /**
  * cpuidle_install_idle_handler - installs the cpuidle idle loop handler
  */
@@ -131,6 +132,10 @@ void cpuidle_uninstall_idle_handler(void)
 		cpuidle_kick_cpus();
 	}
 }
+#else
+void cpuidle_install_idle_handler(void) {}
+void cpuidle_uninstall_idle_handler(void) {}
+#endif
 
 /**
  * cpuidle_pause_and_lock - temporarily disables CPUIDLE
@@ -284,9 +289,21 @@ static int __cpuidle_register_device(struct cpuidle_device *dev)
 	struct sys_device *sys_dev = get_cpu_sysdev((unsigned long)dev->cpu);
 	struct cpuidle_driver *cpuidle_driver = cpuidle_get_driver();
 
+	if (dev->registered)
+		return 0;
 	if (!sys_dev)
 		return -EINVAL;
-	if (!try_module_get(cpuidle_driver->owner))
+	/*
+	 * This will maintain compatibility with old cpuidle_device
+	 * structure where driver pointer is not set.
+	 *
+	 * To Do: Change all call sites to set dev->drv pointer.
+	 * This can be changed to BUG() later in case somebody
+	 * registers without a driver pointer.
+	 */
+	if (!dev->drv)
+		dev->drv = cpuidle_driver;
+	if (!try_module_get(dev->drv->owner))
 		return -EINVAL;
 
 	init_completion(&dev->kobj_unregister);
@@ -313,10 +330,11 @@ static int __cpuidle_register_device(struct cpuidle_device *dev)
 			dev->states[i].power_usage = -1 - i;
 	}
 
-	per_cpu(cpuidle_devices, dev->cpu) = dev;
+	if (cpuidle_driver == dev->drv)
+		per_cpu(cpuidle_devices, dev->cpu) = dev;
 	list_add(&dev->device_list, &cpuidle_detected_devices);
 	if ((ret = cpuidle_add_sysfs(sys_dev))) {
-		module_put(cpuidle_driver->owner);
+		module_put(dev->drv->owner);
 		return ret;
 	}
 
@@ -367,13 +385,13 @@ void cpuidle_unregister_device(struct cpuidle_device *dev)
 	cpuidle_disable_device(dev);
 
 	cpuidle_remove_sysfs(sys_dev);
-	list_del(&dev->device_list);
 	wait_for_completion(&dev->kobj_unregister);
-	per_cpu(cpuidle_devices, dev->cpu) = NULL;
+	if (cpuidle_driver == dev->drv)
+		per_cpu(cpuidle_devices, dev->cpu) = NULL;
 
 	cpuidle_resume_and_unlock();
 
-	module_put(cpuidle_driver->owner);
+	module_put(dev->drv->owner);
 }
 
 EXPORT_SYMBOL_GPL(cpuidle_unregister_device);
@@ -413,6 +431,15 @@ static inline void latency_notifier_init(struct notifier_block *n)
 
 #endif /* CONFIG_SMP */
 
+#ifdef CONFIG_ARCH_USES_PMIDLE
+static inline void save_pm_idle(void)
+{
+	pm_idle_old = pm_idle;
+}
+#else
+static inline void save_pm_idle(void) {}
+#endif
+
 /**
  * cpuidle_init - core initializer
  */
@@ -420,7 +447,7 @@ static int __init cpuidle_init(void)
 {
 	int ret;
 
-	pm_idle_old = pm_idle;
+	save_pm_idle();
 
 	ret = cpuidle_add_class_sysfs(&cpu_sysdev_class);
 	if (ret)
diff --git a/drivers/cpuidle/driver.c b/drivers/cpuidle/driver.c
index fd1601e..c235286 100644
--- a/drivers/cpuidle/driver.c
+++ b/drivers/cpuidle/driver.c
@@ -14,8 +14,83 @@
 
 #include "cpuidle.h"
 
+#define MAX_PRIORITY 1000
+#define DEFAULT_PRIORITY 100
+
 static struct cpuidle_driver *cpuidle_curr_driver;
 DEFINE_SPINLOCK(cpuidle_driver_lock);
+LIST_HEAD(registered_cpuidle_drivers);
+
+static struct cpuidle_driver *select_cpuidle_driver(void)
+{
+	struct cpuidle_driver *item = NULL, *selected = NULL;
+	unsigned int min_priority = MAX_PRIORITY;
+
+	list_for_each_entry(item, &registered_cpuidle_drivers,
+		driver_list) {
+		if (item->priority <= min_priority) {
+			selected = item;
+			min_priority = item->priority;
+		}
+	}
+	return selected;
+}
+
+static void set_current_cpuidle_driver(struct cpuidle_driver *drv)
+{
+	struct cpuidle_device *item = NULL;
+	if (drv == cpuidle_curr_driver)
+		return;
+
+	/* Unregister the previous drivers devices */
+	/* Do we need to take cpuidle_lock ? {un}register_device takes lock*/
+	if (cpuidle_curr_driver) {
+		list_for_each_entry(item, &cpuidle_detected_devices,
+			device_list) {
+			if (item->drv == cpuidle_curr_driver)
+				cpuidle_unregister_device(item);
+		}
+	}
+
+	cpuidle_curr_driver = drv;
+
+	if (drv == NULL)
+		return;
+	else {
+		/* Register the new driver devices */
+		list_for_each_entry(item, &cpuidle_detected_devices,
+			device_list) {
+			if (item->drv == drv)
+				cpuidle_register_device(item);
+		}
+	}
+}
+
+static inline int arch_allow_register(void)
+{
+#ifdef CONFIG_ARCH_USES_PMIDLE
+	if (cpuidle_curr_driver)
+		return -EPERM;
+	else
+		return 0;
+#else
+	return 0;
+#endif
+}
+
+static inline int arch_allow_unregister(struct cpuidle_driver *drv)
+{
+#ifdef CONFIG_ARCH_USES_PMIDLE
+	if (drv != cpuidle_curr_driver) {
+		WARN(1, "invalid cpuidle_unregister_driver(%s)\n",
+			drv->name);
+		return -EPERM;
+	} else
+		return 0;
+#else
+	return 0;
+#endif
+}
 
 /**
  * cpuidle_register_driver - registers a driver
@@ -23,15 +98,29 @@ DEFINE_SPINLOCK(cpuidle_driver_lock);
  */
 int cpuidle_register_driver(struct cpuidle_driver *drv)
 {
+	struct cpuidle_driver *item = NULL;
 	if (!drv)
 		return -EINVAL;
+	if (drv->priority == 0)
+		drv->priority = DEFAULT_PRIORITY;
 
 	spin_lock(&cpuidle_driver_lock);
-	if (cpuidle_curr_driver) {
+
+	if (arch_allow_register()) {
 		spin_unlock(&cpuidle_driver_lock);
 		return -EBUSY;
 	}
-	cpuidle_curr_driver = drv;
+
+	/* Check if driver already registered */
+	list_for_each_entry(item, &registered_cpuidle_drivers, driver_list) {
+		if (item == drv) {
+			spin_unlock(&cpuidle_driver_lock);
+			return -EINVAL;
+		}
+	}
+
+	list_add(&drv->driver_list, &registered_cpuidle_drivers);
+	set_current_cpuidle_driver(select_cpuidle_driver());
 	spin_unlock(&cpuidle_driver_lock);
 
 	return 0;
@@ -54,14 +143,25 @@ EXPORT_SYMBOL_GPL(cpuidle_get_driver);
  */
 void cpuidle_unregister_driver(struct cpuidle_driver *drv)
 {
-	if (drv != cpuidle_curr_driver) {
-		WARN(1, "invalid cpuidle_unregister_driver(%s)\n",
-			drv->name);
+	struct cpuidle_device *item = NULL;
+
+	if (arch_allow_unregister(drv))
 		return;
-	}
 
 	spin_lock(&cpuidle_driver_lock);
-	cpuidle_curr_driver = NULL;
+
+	/* Set some other driver as current */
+	list_del(&drv->driver_list);
+	set_current_cpuidle_driver(select_cpuidle_driver());
+
+	/* Delete all devices corresponding to this driver */
+	mutex_lock(&cpuidle_lock);
+	list_for_each_entry(item, &cpuidle_detected_devices, device_list) {
+		if (item->drv == drv)
+			list_del(&item->device_list);
+	}
+	mutex_unlock(&cpuidle_lock);
+
 	spin_unlock(&cpuidle_driver_lock);
 }
 
diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h
index 1be416b..cbb504c 100644
--- a/include/linux/cpuidle.h
+++ b/include/linux/cpuidle.h
@@ -96,6 +96,7 @@ struct cpuidle_device {
 	struct cpuidle_state	*last_state;
 
 	struct list_head 	device_list;
+	struct cpuidle_driver	*drv;
 	struct kobject		kobj;
 	struct completion	kobj_unregister;
 	void			*governor_data;
@@ -125,6 +126,8 @@ static inline int cpuidle_get_last_residency(struct cpuidle_device *dev)
 struct cpuidle_driver {
 	char			name[CPUIDLE_NAME_LEN];
 	struct module 		*owner;
+	unsigned int		priority;
+	struct list_head	driver_list;
 };
 
 #ifdef CONFIG_CPU_IDLE


  parent reply	other threads:[~2011-02-08 10:52 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-02-08 10:51 [RFC PATCH V3 0/3] cpuidle: Cleanup pm_idle and include driver/cpuidle.c in-kernel Trinabh Gupta
2011-02-08 10:51 ` [RFC PATCH V3 1/3] cpuidle: Remove pm_idle pointer for x86 Trinabh Gupta
2011-02-08 10:52 ` Trinabh Gupta [this message]
2011-02-09 11:17   ` [RFC PATCH V3 2/3] cpuidle: list based cpuidle driver registration and selection Peter Zijlstra
2011-02-10  7:00     ` Vaidyanathan Srinivasan
2011-02-10  9:53       ` Peter Zijlstra
2011-02-10 17:16         ` Vaidyanathan Srinivasan
2011-02-08 10:52 ` [RFC PATCH V3 3/3] cpuidle: default idle driver for x86 Trinabh Gupta
2011-02-09 11:19   ` Peter Zijlstra
2011-02-09 11:21 ` [RFC PATCH V3 0/3] cpuidle: Cleanup pm_idle and include driver/cpuidle.c in-kernel Peter Zijlstra
2011-02-10 15:10   ` Trinabh Gupta

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20110208105203.9998.35678.stgit@tringupt.in.ibm.com \
    --to=trinabh@linux.vnet.ibm.com \
    --cc=ak@linux.intel.com \
    --cc=arjan@linux.intel.com \
    --cc=benh@kernel.crashing.org \
    --cc=lenb@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=peterz@infradead.org \
    --cc=suresh.b.siddha@intel.com \
    --cc=venki@google.com \
    --subject='Re: [RFC PATCH V3 2/3] cpuidle: list based cpuidle driver registration and selection' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).