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 <daniel.lezcano@xxxxxxxxxx> > --- > 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. -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html