On Tuesday 25 of September 2012 00:43:54 Daniel Lezcano wrote:
> With the tegra3 and the big.LITTLE [1] new architectures, several cpus
> with different characteristics (latencies and states) can co-exists on the
> system.
>
> The cpuidle framework has the limitation of handling only identical cpus.
>
> This patch removes this limitation by introducing the multiple driver support
> for cpuidle.
>
> This option is configurable at compile time and should be enabled for the
> architectures mentioned above. So there is no impact for the other platforms
> if the option is disabled. The option defaults to 'n'. Note the multiple
> drivers
> support is also compatible with the existing drivers, even if just one driver
> is
> needed, all the cpu will be tied to this driver using an extra small chunk of
> processor memory.
>
> The multiple driver support use a per-cpu driver pointer instead of a global
> variable and the accessor to this variable are done from a cpu context.
>
> In order to keep the compatibility with the existing drivers, the function
> 'cpuidle_register_driver' and 'cpuidle_unregister_driver' will register
> the specified driver for all the cpus.
>
> The sysfs output for the 'current_driver' is changed when this option is
> set by giving the drivers per cpu.
>
> eg.
> cpu0: acpi_idle
> cpu1: acpi_idle
>
> but if the option is disabled, the output will remain the same.
>
> [1] http://lwn.net/Articles/481055/
>
>
> Signed-off-by: Daniel Lezcano <[email protected]>
> ---
> drivers/cpuidle/Kconfig | 9 +++
> drivers/cpuidle/cpuidle.c | 24 ++++++--
> drivers/cpuidle/driver.c | 136 +++++++++++++++++++++++++++++++++++++++++---
> drivers/cpuidle/sysfs.c | 50 +++++++++++++----
> include/linux/cpuidle.h | 8 ++-
> 5 files changed, 197 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
> index a76b689..234ae65 100644
> --- a/drivers/cpuidle/Kconfig
> +++ b/drivers/cpuidle/Kconfig
> @@ -9,6 +9,15 @@ config CPU_IDLE
>
> If you're using an ACPI-enabled platform, you should say Y here.
>
> +config CPU_IDLE_MULTIPLE_DRIVERS
> + bool "Support multiple cpuidle drivers"
> + depends on CPU_IDLE
> + default n
> + help
> + Allows the cpuidle framework to use different drivers for each CPU.
> + This is useful if you have a system with different CPU latencies and
> + states. If unsure say N.
> +
> config CPU_IDLE_GOV_LADDER
> bool
> depends on CPU_IDLE
> diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c
> index e28f6ea..c581b99 100644
> --- a/drivers/cpuidle/cpuidle.c
> +++ b/drivers/cpuidle/cpuidle.c
> @@ -68,7 +68,7 @@ static cpuidle_enter_t cpuidle_enter_ops;
> int cpuidle_play_dead(void)
> {
> struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices);
> - struct cpuidle_driver *drv = cpuidle_get_driver();
> + struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev->cpu);
It would be better to change that to be called as cpuidle_get_cpu_driver(dev),
since dev is a cpuidle_device already.
> int i, dead_state = -1;
> int power_usage = -1;
>
> @@ -128,7 +128,7 @@ int cpuidle_enter_state(struct cpuidle_device *dev,
> struct cpuidle_driver *drv,
> int cpuidle_idle_call(void)
> {
> struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices);
> - struct cpuidle_driver *drv = cpuidle_get_driver();
> + struct cpuidle_driver *drv;
> int next_state, entered_state;
>
> if (off)
> @@ -141,6 +141,8 @@ int cpuidle_idle_call(void)
> if (!dev || !dev->enabled)
> return -EBUSY;
>
> + drv = cpuidle_get_cpu_driver(dev->cpu);
> +
> /* ask the governor for the next state */
> next_state = cpuidle_curr_governor->select(drv, dev);
> if (need_resched()) {
> @@ -308,15 +310,19 @@ static void poll_idle_init(struct cpuidle_driver *drv)
> {}
> int cpuidle_enable_device(struct cpuidle_device *dev)
> {
> int ret, i;
> - struct cpuidle_driver *drv = cpuidle_get_driver();
> + struct cpuidle_driver *drv;
>
> if (!dev)
> return -EINVAL;
>
> if (dev->enabled)
> return 0;
> +
> + drv = cpuidle_get_cpu_driver(dev->cpu);
> +
> if (!drv || !cpuidle_curr_governor)
> return -EIO;
> +
> if (!dev->state_count)
> dev->state_count = drv->state_count;
>
> @@ -368,15 +374,18 @@ EXPORT_SYMBOL_GPL(cpuidle_enable_device);
> */
> void cpuidle_disable_device(struct cpuidle_device *dev)
> {
> + struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev->cpu);
> +
> if (!dev->enabled)
> return;
> - if (!cpuidle_get_driver() || !cpuidle_curr_governor)
> +
> + if (!drv || !cpuidle_curr_governor)
> return;
>
> dev->enabled = 0;
>
> if (cpuidle_curr_governor->disable)
> - cpuidle_curr_governor->disable(cpuidle_get_driver(), dev);
> + cpuidle_curr_governor->disable(drv, dev);
>
> cpuidle_remove_state_sysfs(dev);
> enabled_devices--;
> @@ -395,7 +404,8 @@ static int __cpuidle_register_device(struct
> cpuidle_device *dev)
> {
> int ret;
> struct device *cpu_dev = get_cpu_device((unsigned long)dev->cpu);
> - struct cpuidle_driver *cpuidle_driver = cpuidle_get_driver();
> + struct cpuidle_driver *cpuidle_driver =
> + cpuidle_get_cpu_driver(dev->cpu);
>
> if (!try_module_get(cpuidle_driver->owner))
> return -EINVAL;
> @@ -461,7 +471,7 @@ EXPORT_SYMBOL_GPL(cpuidle_register_device);
> void cpuidle_unregister_device(struct cpuidle_device *dev)
> {
> struct device *cpu_dev = get_cpu_device((unsigned long)dev->cpu);
> - struct cpuidle_driver *cpuidle_driver = cpuidle_get_driver();
> + struct cpuidle_driver *cpuidle_driver =
> cpuidle_get_cpu_driver(dev->cpu);
>
> if (dev->registered == 0)
> return;
> diff --git a/drivers/cpuidle/driver.c b/drivers/cpuidle/driver.c
> index 391f80f..6529b91 100644
> --- a/drivers/cpuidle/driver.c
> +++ b/drivers/cpuidle/driver.c
> @@ -14,7 +14,11 @@
>
> #include "cpuidle.h"
>
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> +DEFINE_PER_CPU(struct cpuidle_driver *, cpuidle_drivers);
> +#else
> static struct cpuidle_driver *cpuidle_curr_driver;
> +#endif
> DEFINE_SPINLOCK(cpuidle_driver_lock);
>
> static void set_power_states(struct cpuidle_driver *drv)
> @@ -47,12 +51,25 @@ static void __cpuidle_driver_init(struct cpuidle_driver
> *drv)
> set_power_states(drv);
> }
>
> -static void cpuidle_set_driver(struct cpuidle_driver *drv)
> +static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu)
> {
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> + per_cpu(cpuidle_drivers, cpu) = drv;
> +#else
> cpuidle_curr_driver = drv;
> +#endif
I'm not a big fan of #ifdef blocks inside of functions. In my opinion it's
better to put entire functions under #ifdef blocks. So, for example, in this
particular case I would do"
#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu)
{
per_cpu(cpuidle_drivers, cpu) = drv;
}
#else
static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu)
{
cpuidle_curr_driver = drv;
}
#endif
> }
>
> -static int __cpuidle_register_driver(struct cpuidle_driver *drv)
> +static struct cpuidle_driver *__cpuidle_get_cpu_driver(int cpu)
> +{
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> + return per_cpu(cpuidle_drivers, cpu);
> +#else
> + return cpuidle_curr_driver;
> +#endif
> +}
And here analogously.
> +
> +static int __cpuidle_register_driver(struct cpuidle_driver *drv, int cpu)
> {
> if (!drv || !drv->state_count)
> return -EINVAL;
> @@ -60,26 +77,102 @@ static int __cpuidle_register_driver(struct
> cpuidle_driver *drv)
> if (cpuidle_disabled())
> return -ENODEV;
>
> - if (cpuidle_get_driver())
> + if (__cpuidle_get_cpu_driver(cpu))
> return -EBUSY;
>
> __cpuidle_driver_init(drv);
>
> - cpuidle_set_driver(drv);
> + __cpuidle_set_cpu_driver(drv, cpu);
>
> return 0;
> }
>
> -static void __cpuidle_unregister_driver(struct cpuidle_driver *drv)
> +static void __cpuidle_unregister_driver(struct cpuidle_driver *drv, int cpu)
> {
> - if (drv != cpuidle_get_driver()) {
> + if (drv != __cpuidle_get_cpu_driver(cpu)) {
> WARN(1, "invalid cpuidle_unregister_driver(%s)\n",
> drv->name);
> return;
> }
>
> if (!WARN_ON(drv->refcnt > 0))
> - cpuidle_set_driver(NULL);
> + __cpuidle_set_cpu_driver(NULL, cpu);
> +}
> +
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> +static void __cpuidle_unregister_all_cpu_driver(struct cpuidle_driver *drv)
> +{
> + int cpu;
> + for_each_present_cpu(cpu)
> + __cpuidle_unregister_driver(drv, cpu);
> +}
> +
> +static int __cpuidle_register_all_cpu_driver(struct cpuidle_driver *drv)
> +{
> + int ret = 0;
> + int i, cpu;
> +
> + for_each_present_cpu(cpu) {
> + ret = __cpuidle_register_driver(drv, cpu);
> + if (!ret)
> + continue;
> + for (i = cpu - 1; i >= 0; i--)
I wonder if this is going to work in all cases. For example, is there any
guarantee that the CPU numbers start from 0 and that there are no gaps?
> + __cpuidle_unregister_driver(drv, i);
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int __cpuidle_for_each_driver(int (*cb)(int, struct cpuidle_driver *,
> + void *), void *data)
> +{
> + struct cpuidle_driver *drv;
> + int cpu, ret = 0;
> +
> + for_each_present_cpu(cpu) {
> + drv = __cpuidle_get_cpu_driver(cpu);
> + ret = cb(cpu, drv, data);
> + if (ret < 0)
> + break;
> + }
> + return ret;
> +}
> +
> +int cpuidle_register_cpu_driver(struct cpuidle_driver *drv, int cpu)
> +{
> + int ret;
> +
> + spin_lock(&cpuidle_driver_lock);
> + ret = __cpuidle_register_driver(drv, cpu);
> + spin_unlock(&cpuidle_driver_lock);
> +
> + return ret;
> +}
> +
> +void cpuidle_unregister_cpu_driver(struct cpuidle_driver *drv, int cpu)
> +{
> + spin_lock(&cpuidle_driver_lock);
> + __cpuidle_unregister_driver(drv, cpu);
> + spin_unlock(&cpuidle_driver_lock);
> +}
> +#endif
> +
> +int cpuidle_for_each_driver(int (*cb)(int, struct cpuidle_driver *, void *),
> + void *data)
> +{
> + int ret;
> +
> + spin_lock(&cpuidle_driver_lock);
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> + ret = __cpuidle_for_each_driver(cb, data);
> +#else
> + ret = cb(smp_processor_id(),
> + __cpuidle_get_cpu_driver(smp_processor_id()), data);
> +#endif
Here I'd make the definition of __cpuidle_for_each_driver() depend on
whether or not CONFIG_CPU_IDLE_MULTIPLE_DRIVERS is set.
> + spin_unlock(&cpuidle_driver_lock);
> +
> + return ret;
> }
>
> /**
> @@ -91,7 +184,11 @@ int cpuidle_register_driver(struct cpuidle_driver *drv)
> int ret;
>
> spin_lock(&cpuidle_driver_lock);
> - ret = __cpuidle_register_driver(drv);
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> + ret = __cpuidle_register_all_cpu_driver(drv);
> +#else
> + ret = __cpuidle_register_driver(drv, smp_processor_id());
> +#endif
And analogously here.
> spin_unlock(&cpuidle_driver_lock);
>
> return ret;
> @@ -103,18 +200,37 @@ EXPORT_SYMBOL_GPL(cpuidle_register_driver);
> */
> struct cpuidle_driver *cpuidle_get_driver(void)
> {
> - return cpuidle_curr_driver;
> + return __cpuidle_get_cpu_driver(smp_processor_id());
> }
> EXPORT_SYMBOL_GPL(cpuidle_get_driver);
>
> /**
> + * cpuidle_get_cpu_driver - return the driver tied with a cpu
> + */
> +struct cpuidle_driver *cpuidle_get_cpu_driver(int cpu)
> +{
> + struct cpuidle_driver *drv;
> +
> + spin_lock(&cpuidle_driver_lock);
> + drv = __cpuidle_get_cpu_driver(cpu);
> + spin_unlock(&cpuidle_driver_lock);
> +
> + return drv;
> +}
> +EXPORT_SYMBOL_GPL(cpuidle_get_cpu_driver);
> +
> +/**
> * cpuidle_unregister_driver - unregisters a driver
> * @drv: the driver
> */
> void cpuidle_unregister_driver(struct cpuidle_driver *drv)
> {
> spin_lock(&cpuidle_driver_lock);
> - __cpuidle_unregister_driver(drv);
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> + __cpuidle_unregister_all_cpu_driver(drv);
> +#else
> + __cpuidle_unregister_driver(drv, smp_processor_id());
> +#endif
I'm slightly cautious about using smp_processor_id() above.
get_cpu()/put_cpu() is the preferred way of doing this nowadays (although
this particular code is under the spinlock, so it should be OK).
> spin_unlock(&cpuidle_driver_lock);
> }
> EXPORT_SYMBOL_GPL(cpuidle_unregister_driver);
> diff --git a/drivers/cpuidle/sysfs.c b/drivers/cpuidle/sysfs.c
> index 5f809e3..2596422 100644
> --- a/drivers/cpuidle/sysfs.c
> +++ b/drivers/cpuidle/sysfs.c
> @@ -43,21 +43,46 @@ out:
> return i;
> }
>
> +struct cbarg {
> + char *buf;
> + ssize_t count;
> +};
> +
> +static int each_driver_cb(int cpu, struct cpuidle_driver *drv, void *data)
> +{
> + int ret;
> + struct cbarg *cbarg = data;
> +
> + if ((drv && (strlen(drv->name) + cbarg->count) >= PAGE_SIZE) ||
> + (!drv && (strlen("none") + cbarg->count) >= PAGE_SIZE))
> + return -EOVERFLOW;
> +
> +#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
> + ret = sprintf(cbarg->buf + cbarg->count, "cpu%d: %s\n",
> + cpu, drv ? drv->name : "none" );
> +#else
> + ret = sprintf(cbarg->buf, "%s\n", drv ? drv->name : "none");
> +#endif
> + if (ret < 0)
> + return ret;
> +
> + cbarg->count += ret;
> +
> + return 0;
> +}
> +
> static ssize_t show_current_driver(struct device *dev,
> struct device_attribute *attr,
> char *buf)
> {
> - ssize_t ret;
> - struct cpuidle_driver *cpuidle_driver = cpuidle_get_driver();
> + struct cbarg cbarg = { .buf = buf };
> + int ret;
>
> - spin_lock(&cpuidle_driver_lock);
> - if (cpuidle_driver)
> - ret = sprintf(buf, "%s\n", cpuidle_driver->name);
> - else
> - ret = sprintf(buf, "none\n");
> - spin_unlock(&cpuidle_driver_lock);
> + ret = cpuidle_for_each_driver(each_driver_cb, &cbarg);
> + if (ret < 0)
> + return ret;
>
> - return ret;
> + return cbarg.count;
> }
>
> static ssize_t show_current_governor(struct device *dev,
> @@ -363,10 +388,10 @@ int cpuidle_add_state_sysfs(struct cpuidle_device
> *device)
> {
> int i, ret = -ENOMEM;
> struct cpuidle_state_kobj *kobj;
> - struct cpuidle_driver *drv = cpuidle_get_driver();
> + struct cpuidle_driver *drv = cpuidle_get_cpu_driver(device->cpu);
>
> /* state statistics */
> - for (i = 0; i < device->state_count; i++) {
> + for (i = 0; i < drv->state_count; i++) {
> kobj = kzalloc(sizeof(struct cpuidle_state_kobj), GFP_KERNEL);
> if (!kobj)
> goto error_state;
> @@ -374,7 +399,8 @@ int cpuidle_add_state_sysfs(struct cpuidle_device *device)
> kobj->state_usage = &device->states_usage[i];
> init_completion(&kobj->kobj_unregister);
>
> - ret = kobject_init_and_add(&kobj->kobj, &ktype_state_cpuidle,
> &device->kobj,
> + ret = kobject_init_and_add(&kobj->kobj,
> + &ktype_state_cpuidle, &device->kobj,
> "state%d", i);
> if (ret) {
> kfree(kobj);
> diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h
> index a4ff9f8..0e0b0ad 100644
> --- a/include/linux/cpuidle.h
> +++ b/include/linux/cpuidle.h
> @@ -164,6 +164,13 @@ extern int cpuidle_wrap_enter(struct cpuidle_device *dev,
> struct cpuidle_driver *drv, int index));
> extern int cpuidle_play_dead(void);
>
> +extern int cpuidle_for_each_driver(
> + int (*cb)(int, struct cpuidle_driver *, void *), void *data);
> +
> +extern struct cpuidle_driver *cpuidle_get_cpu_driver(int cpu);
> +extern int cpuidle_register_cpu_driver(struct cpuidle_driver *drv, int cpu);
> +extern void cpuidle_unregister_cpu_driver(struct cpuidle_driver *drv, int
> cpu);
> +
> #else
> static inline void disable_cpuidle(void) { }
> static inline int cpuidle_idle_call(void) { return -ENODEV; }
> @@ -190,7 +197,6 @@ static inline int cpuidle_wrap_enter(struct
> cpuidle_device *dev,
> struct cpuidle_driver *drv, int index))
> { return -ENODEV; }
> static inline int cpuidle_play_dead(void) {return -ENODEV; }
> -
> #endif
>
> #ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
>
Thanks,
Rafael
--
I speak only for myself.
Rafael J. Wysocki, Intel Open Source Technology Center.
_______________________________________________
linaro-dev mailing list
[email protected]
http://lists.linaro.org/mailman/listinfo/linaro-dev