From: Robert Lee <hidden> Date: 2012-01-24 04:37:27
This patch series adds a new common cpuidle interface to consolidate code
commonly duplicated by various platforms. A patch was then made for each
platform that could immediately take advantage of this code. The platform
specific changes are not required by the common code and are only made for
consoldation.
Maintainers and cpuidle idle developers of these platforms, please check to make
sure that you agree with the changes. Besides just code consolidation, a
default "WFI" state is now used with default parameters that different from your
original paramenters. The assumption is that if you have a WFI only idle state,
the parameters in the new default WFI are more realistic as a true WFI only
hardware state incurs minimal latency(<1us) or power penalty to enter and exit.
If your platform actually performs other platform specific functionality upon
entering WFI and the default parameters do not accurately reflect the
exit_latency and target_residency given in the common default state, please
say so. Also, the default state uses a common name and description value
which may differ from the previous platform specific values you used.
Lastly, a imx5 cpuidle implementation is added which uses the common cpuidle
interface.
Based on 3.3-rc1
Tested on i.MX51 Babbage Board
v2 submission can be found here:
http://comments.gmane.org/gmane.linux.ports.arm.kernel/144199
Changes since v2:
* Made various code organization and style changes as suggested in v1 review.
* Removed at91 use of common code. A separate effort is underway to clean
at91 code and the author has offered to convert to common interface as part
of those changes (if this common interface is accepted in time).
* Made platform cpuidle_driver objects __initdata and dynamically added one
persistent instance of this object in common code.
* Removed imx5 pm usage of gpc_dvfs clock as it is no longer needed after
being enabled during clock initialization.
* Re-organized patches.
v1 submission can be found here:
http://comments.gmane.org/gmane.linux.ports.arm.kernel/142791
Changes since v1:
* Common interface moved to drivers/cpuidle and made non arch-specific.
* Made various fixes and suggested additions to the common cpuidle
code from v1 review.
* Added callback for filling in driver_data field as needed.
* Modified the various platforms with these changes.
Robert Lee (7):
cpuidle: Add common init interface and idle functionality
ARM: exynos: Modify to use new common cpuidle code.
ARM: shmobile: Modify to use new common cpuidle code.
ARM: kirkwood: Modify to use new common cpuidle code.
ARM: davinci: Modify to use new common cpuidle code.
ARM: imx: Init imx5 gpc_dvfs clock for global use
ARM: imx: Add imx5 cpuidle implementation
arch/arm/mach-davinci/cpuidle.c | 135 ++++++++-----------------------
arch/arm/mach-exynos/cpuidle.c | 73 ++---------------
arch/arm/mach-kirkwood/cpuidle.c | 89 +++++---------------
arch/arm/mach-mx5/Makefile | 3 +-
arch/arm/mach-mx5/clock-mx51-mx53.c | 3 +
arch/arm/mach-mx5/cpuidle.c | 64 +++++++++++++++
arch/arm/mach-mx5/pm-imx5.c | 24 +-----
arch/arm/mach-shmobile/cpuidle.c | 51 ++----------
drivers/cpuidle/Makefile | 2 +-
drivers/cpuidle/common.c | 152 +++++++++++++++++++++++++++++++++++
include/linux/cpuidle.h | 24 ++++++
11 files changed, 319 insertions(+), 301 deletions(-)
create mode 100644 arch/arm/mach-mx5/cpuidle.c
create mode 100644 drivers/cpuidle/common.c
@@ -0,0 +1,152 @@+/*+*Copyright2011FreescaleSemiconductor,Inc.+*Copyright2011LinaroLtd.+*+*ThecodecontainedhereinislicensedundertheGNUGeneralPublic+*License.YoumayobtainacopyoftheGNUGeneralPublicLicense+*Version2orlateratthefollowinglocations:+*+*http://www.opensource.org/licenses/gpl-license.html+*http://www.gnu.org/copyleft/gpl.html+*/++/*+*Thiscodeperformsprovidessomecommonlyusedcpuidlesetupfunctionality+*usedbymanyARMSoCplatforms.Providingthisfunctionalityhere+*reducestheduplicationofthiscodeforeachARMplatformthatusesit.+*/++#include<linux/kernel.h>+#include<linux/io.h>+#include<linux/cpuidle.h>+#include<linux/hrtimer.h>+#include<linux/err.h>+#include<linux/slab.h>+#include<asm/proc-fns.h>++staticstructcpuidle_device__percpu*common_cpuidle_devices;++staticint(*do_idle[CPUIDLE_STATE_MAX])(structcpuidle_device*dev,+structcpuidle_driver*drv,intindex);++intcpuidle_def_idle(structcpuidle_device*dev,+structcpuidle_driver*drv,intindex)+{+cpu_do_idle();+returnindex;+}++staticintsimple_enter(structcpuidle_device*dev,+structcpuidle_driver*drv,intindex)+{+ktime_ttime_start,time_end;++local_irq_disable();++time_start=ktime_get();++index=do_idle[index](dev,drv,index);++time_end=ktime_get();++local_irq_enable();++dev->last_residency=+(int)ktime_to_us(ktime_sub(time_end,time_start));++returnindex;+}++voidcommon_cpuidle_devices_uninit(void)+{+intcpu_id;+structcpuidle_device*dev;++for_each_possible_cpu(cpu_id){+dev=per_cpu_ptr(common_cpuidle_devices,cpu_id);+cpuidle_unregister_device(dev);+}++free_percpu(common_cpuidle_devices);+}++/**+*common_cpuidle_init()-Providessomecommonlyusedinitfunctionality.+*@pdrvPointertoyourcpuidle_driverobject.+*@simpleUsethecommonsimple_enterwrapper?+*@driver_data_initPointertoyourplatformfunctiontoinitializeyour+*platformspecificdriverdata.UseNULLifplatform+*specificdataisnotneeded.+*+*Commoncpuidleinitinterfacetoprovidecommoncpuidlefunctionality+*usedbymanyplatforms.+*/+int__initcommon_cpuidle_init(structcpuidle_driver*pdrv,boolsimple,+void(*driver_data_init)(structcpuidle_device*dev))+{+structcpuidle_device*dev;+structcpuidle_driver*drv;+inti,cpu_id,ret;++if(!pdrv||pdrv->state_count>CPUIDLE_STATE_MAX){+pr_err("%s: Invalid Input\n",__func__);+return-EINVAL;+}++drv=kmalloc(sizeof(structcpuidle_driver),GFP_KERNEL);+if(!drv){+pr_err("%s: no memory for cpuidle driver\n",__func__);+return-ENOMEM;+}++*drv=*pdrv;++for(i=0;simple&&(i<drv->state_count);i++){+do_idle[i]=drv->states[i].enter;+drv->states[i].enter=simple_enter;+}++ret=cpuidle_register_driver(drv);+if(ret){+pr_err("%s: Failed to register cpuidle driver\n",__func__);+gotofree_drv;+}++common_cpuidle_devices=alloc_percpu(structcpuidle_device);+if(common_cpuidle_devices==NULL){+ret=-ENOMEM;+gotounregister_drv;+}++/* initialize state data for each cpuidle_device */+for_each_possible_cpu(cpu_id){+dev=per_cpu_ptr(common_cpuidle_devices,cpu_id);+dev->cpu=cpu_id;+dev->state_count=drv->state_count;++if(driver_data_init)+driver_data_init(dev);++ret=cpuidle_register_device(dev);+if(ret){+pr_err("%s: Failed to register cpu %u\n",+__func__,cpu_id);+gotouninit;+}+}++return0;+uninit:++common_cpuidle_devices_uninit();++unregister_drv:++cpuidle_unregister_driver(drv);++free_drv:++kfree(drv);++returnret;+}
@@ -56,6 +56,16 @@ struct cpuidle_state {#define CPUIDLE_DRIVER_FLAGS_MASK (0xFFFF0000)+/* Common ARM WFI state */+#define CPUIDLE_ARM_WFI_STATE {\+.enter=cpuidle_def_idle,\+.exit_latency=2,\+.target_residency=1,\+.flags=CPUIDLE_FLAG_TIME_VALID,\+.name="WFI",\+.desc="ARM core clock gating (WFI)",\+}+/***cpuidle_get_statedata-retrievesprivatedriverstatedata*@st_usage:thestateusagestatistics
@@ -141,6 +151,13 @@ extern void cpuidle_resume_and_unlock(void);externintcpuidle_enable_device(structcpuidle_device*dev);externvoidcpuidle_disable_device(structcpuidle_device*dev);+/* provide a default idle function */+externintcpuidle_def_idle(structcpuidle_device*dev,+structcpuidle_driver*drv,intindex);+externintcommon_cpuidle_init(structcpuidle_driver*drv,boolsimple,+void(*driver_data_init)(structcpuidle_device*dev));+externvoidcommon_cpuidle_devices_uninit(void);+#elsestaticinlinevoiddisable_cpuidle(void){}staticinlineintcpuidle_idle_call(void){return-ENODEV;}
From: Robert Lee <hidden> Date: 2012-01-24 04:37:29
Make necessary changes for consolidation with new common cpuidle code.
Signed-off-by: Robert Lee <redacted>
---
arch/arm/mach-exynos/cpuidle.c | 73 +++------------------------------------
1 files changed, 6 insertions(+), 67 deletions(-)
From: Robert Lee <hidden> Date: 2012-01-24 04:37:30
Make necessary changes for consolidation with new common cpuidle code.
Signed-off-by: Robert Lee <redacted>
---
arch/arm/mach-shmobile/cpuidle.c | 51 ++++++-------------------------------
1 files changed, 9 insertions(+), 42 deletions(-)
From: Robert Lee <hidden> Date: 2012-01-24 04:37:31
Make necessary changes for consolidation with new common cpuidle code.
Signed-off-by: Robert Lee <redacted>
---
arch/arm/mach-kirkwood/cpuidle.c | 89 ++++++++++----------------------------
1 files changed, 23 insertions(+), 66 deletions(-)
@@ -24,80 +24,37 @@#define KIRKWOOD_MAX_STATES 2-staticstructcpuidle_driverkirkwood_idle_driver={-.name="kirkwood_idle",-.owner=THIS_MODULE,-};--staticDEFINE_PER_CPU(structcpuidle_device,kirkwood_cpuidle_device);-/* Actual code that puts the SoC in different idle states */-staticintkirkwood_enter_idle(structcpuidle_device*dev,+staticintkirkwood_idle_ddr(structcpuidle_device*dev,structcpuidle_driver*drv,-intindex)+intindex){-structtimevalbefore,after;-intidle_time;--local_irq_disable();-do_gettimeofday(&before);-if(index==0)-/* Wait for interrupt state */-cpu_do_idle();-elseif(index==1){-/*-*FollowingwritewillputDDRinselfrefresh.-*Notethatwehave256cyclesbeforeDDRputsit-*selfinself-refresh,sothewait-for-interrupt-*callafterwardswon'tgettheDDRfromselfrefresh-*mode.-*/-writel(0x7,DDR_OPERATION_BASE);-cpu_do_idle();-}-do_gettimeofday(&after);-local_irq_enable();-idle_time=(after.tv_sec-before.tv_sec)*USEC_PER_SEC+-(after.tv_usec-before.tv_usec);--/* Update last residency */-dev->last_residency=idle_time;+writel(0x7,DDR_OPERATION_BASE);+cpu_do_idle();returnindex;}+staticstructcpuidle_driverkirkwood_idle_driver__initdata={+.name="kirkwood_idle",+.owner=THIS_MODULE,+.states[0]=CPUIDLE_ARM_WFI_STATE,+.states[1]={+.enter=kirkwood_idle_ddr,+.exit_latency=10,+.target_residency=10000,+.flags=CPUIDLE_FLAG_TIME_VALID,+.name="DDR SR",+.desc="WFI and DDR Self Refresh",+},+.state_count=KIRKWOOD_MAX_STATES,+};+/* Initialize CPU idle by registering the idle states */-staticintkirkwood_init_cpuidle(void)+staticint__initkirkwood_init_cpuidle(void){-structcpuidle_device*device;-structcpuidle_driver*driver=&kirkwood_idle_driver;--device=&per_cpu(kirkwood_cpuidle_device,smp_processor_id());-device->state_count=KIRKWOOD_MAX_STATES;-driver->state_count=KIRKWOOD_MAX_STATES;--/* Wait for interrupt state */-driver->states[0].enter=kirkwood_enter_idle;-driver->states[0].exit_latency=1;-driver->states[0].target_residency=10000;-driver->states[0].flags=CPUIDLE_FLAG_TIME_VALID;-strcpy(driver->states[0].name,"WFI");-strcpy(driver->states[0].desc,"Wait for interrupt");--/* Wait for interrupt and DDR self refresh state */-driver->states[1].enter=kirkwood_enter_idle;-driver->states[1].exit_latency=10;-driver->states[1].target_residency=10000;-driver->states[1].flags=CPUIDLE_FLAG_TIME_VALID;-strcpy(driver->states[1].name,"DDR SR");-strcpy(driver->states[1].desc,"WFI and DDR Self Refresh");--cpuidle_register_driver(&kirkwood_idle_driver);-if(cpuidle_register_device(device)){-printk(KERN_ERR"kirkwood_init_cpuidle: Failed registering\n");-return-EIO;-}-return0;+returncommon_cpuidle_init(&kirkwood_idle_driver,+true,+NULL);}-device_initcall(kirkwood_init_cpuidle);
From: Robert Lee <hidden> Date: 2012-01-24 04:37:32
Make necessary changes for consolidation with new common cpuidle code.
Signed-off-by: Robert Lee <redacted>
---
arch/arm/mach-davinci/cpuidle.c | 135 ++++++++++-----------------------------
1 files changed, 33 insertions(+), 102 deletions(-)
@@ -24,98 +24,57 @@#define DAVINCI_CPUIDLE_MAX_STATES 2-structdavinci_ops{-void(*enter)(u32flags);-void(*exit)(u32flags);-u32flags;-};--/* fields in davinci_ops.flags */-#define DAVINCI_CPUIDLE_FLAGS_DDR2_PWDN BIT(0)--staticstructcpuidle_driverdavinci_idle_driver={-.name="cpuidle-davinci",-.owner=THIS_MODULE,-};--staticDEFINE_PER_CPU(structcpuidle_device,davinci_cpuidle_device);+u32__initdataddr_reg_mask;staticvoid__iomem*ddr2_reg_base;-staticvoiddavinci_save_ddr_power(intenter,boolpdown)+/* idle that includes ddr low power */+staticintdavinci_idle_ddr(structcpuidle_device*dev,+structcpuidle_driver*drv,+intindex){u32val;val=__raw_readl(ddr2_reg_base+DDR2_SDRCR_OFFSET);-if(enter){-if(pdown)-val|=DDR2_SRPD_BIT;-else-val&=~DDR2_SRPD_BIT;-val|=DDR2_LPMODEN_BIT;-}else{-val&=~(DDR2_SRPD_BIT|DDR2_LPMODEN_BIT);-}+val|=(u32)dev->states_usage[index].driver_data;__raw_writel(val,ddr2_reg_base+DDR2_SDRCR_OFFSET);-}-staticvoiddavinci_c2state_enter(u32flags)-{-davinci_save_ddr_power(1,!!(flags&DAVINCI_CPUIDLE_FLAGS_DDR2_PWDN));-}+/* Wait for interrupt state */+cpu_do_idle();-staticvoiddavinci_c2state_exit(u32flags)-{-davinci_save_ddr_power(0,!!(flags&DAVINCI_CPUIDLE_FLAGS_DDR2_PWDN));+val&=~(DDR2_SRPD_BIT|DDR2_LPMODEN_BIT);+__raw_writel(val,ddr2_reg_base+DDR2_SDRCR_OFFSET);++returnindex;}-staticstructdavinci_opsdavinci_states[DAVINCI_CPUIDLE_MAX_STATES]={-[1]={-.enter=davinci_c2state_enter,-.exit=davinci_c2state_exit,+staticstructcpuidle_driverdavinci_idle_driver__initdata={+.name="cpuidle-davinci",+.owner=THIS_MODULE,+.states[0]=CPUIDLE_ARM_WFI_STATE,+.states[1]={+.enter=davinci_idle_ddr,+.exit_latency=10,+.target_residency=100000,+.flags=CPUIDLE_FLAG_TIME_VALID,+.name="DDR SR",+.desc="WFI and DDR Self Refresh",},+.state_count=DAVINCI_CPUIDLE_MAX_STATES,};-/* Actual code that puts the SoC in different idle states */-staticintdavinci_enter_idle(structcpuidle_device*dev,-structcpuidle_driver*drv,-intindex)+/* use drive_data field to hold the configured ddr low power bitmask */+staticvoid__initdavinci_cpuidle_dd_init(structcpuidle_device*dev){-structcpuidle_state_usage*state_usage=&dev->states_usage[index];-structdavinci_ops*ops=cpuidle_get_statedata(state_usage);-structtimevalbefore,after;-intidle_time;--local_irq_disable();-do_gettimeofday(&before);--if(ops&&ops->enter)-ops->enter(ops->flags);-/* Wait for interrupt state */-cpu_do_idle();-if(ops&&ops->exit)-ops->exit(ops->flags);--do_gettimeofday(&after);-local_irq_enable();-idle_time=(after.tv_sec-before.tv_sec)*USEC_PER_SEC+-(after.tv_usec-before.tv_usec);--dev->last_residency=idle_time;--returnindex;+dev->states_usage[1].driver_data=(void*)ddr_reg_mask;}staticint__initdavinci_cpuidle_probe(structplatform_device*pdev){intret;-structcpuidle_device*device;-structcpuidle_driver*driver=&davinci_idle_driver;structdavinci_cpuidle_config*pdata=pdev->dev.platform_data;-device=&per_cpu(davinci_cpuidle_device,smp_processor_id());-if(!pdata){dev_err(&pdev->dev,"cannot get platform data\n");return-ENOENT;
@@ -123,42 +82,15 @@ static int __init davinci_cpuidle_probe(struct platform_device *pdev)ddr2_reg_base=pdata->ddr2_ctlr_base;-/* Wait for interrupt state */-driver->states[0].enter=davinci_enter_idle;-driver->states[0].exit_latency=1;-driver->states[0].target_residency=10000;-driver->states[0].flags=CPUIDLE_FLAG_TIME_VALID;-strcpy(driver->states[0].name,"WFI");-strcpy(driver->states[0].desc,"Wait for interrupt");--/* Wait for interrupt and DDR self refresh state */-driver->states[1].enter=davinci_enter_idle;-driver->states[1].exit_latency=10;-driver->states[1].target_residency=10000;-driver->states[1].flags=CPUIDLE_FLAG_TIME_VALID;-strcpy(driver->states[1].name,"DDR SR");-strcpy(driver->states[1].desc,"WFI and DDR Self Refresh");if(pdata->ddr2_pdown)-davinci_states[1].flags|=DAVINCI_CPUIDLE_FLAGS_DDR2_PWDN;-cpuidle_set_statedata(&device->states_usage[1],&davinci_states[1]);+ddr_reg_mask=(DDR2_SRPD_BIT|DDR2_LPMODEN_BIT);+else+ddr_reg_mask=(DDR2_LPMODEN_BIT);-device->state_count=DAVINCI_CPUIDLE_MAX_STATES;-driver->state_count=DAVINCI_CPUIDLE_MAX_STATES;+ret=common_cpuidle_init(&davinci_idle_driver,true,+davinci_cpuidle_dd_init);-ret=cpuidle_register_driver(&davinci_idle_driver);-if(ret){-dev_err(&pdev->dev,"failed to register driver\n");-returnret;-}--ret=cpuidle_register_device(device);-if(ret){-dev_err(&pdev->dev,"failed to register device\n");-cpuidle_unregister_driver(&davinci_idle_driver);-returnret;-}--return0;+returnret;}staticstructplatform_driverdavinci_cpuidle_driver={
@@ -174,4 +106,3 @@ static int __init davinci_cpuidle_init(void)davinci_cpuidle_probe);}device_initcall(davinci_cpuidle_init);-
From: Robert Lee <hidden> Date: 2012-01-24 04:37:33
The gpc_dvfs clock consumes practically zero power and must be enabled
for various low power funcitonality. Now that a second user of this
clock is being added (cpuidle) for mx5, it is cleanest to just enable
this clock during clock initialization and leave it enabled.
Signed-off-by: Robert Lee <redacted>
---
arch/arm/mach-mx5/clock-mx51-mx53.c | 3 +++
arch/arm/mach-mx5/pm-imx5.c | 24 ++----------------------
2 files changed, 5 insertions(+), 22 deletions(-)
@@ -1572,6 +1573,7 @@ int __init mx51_clocks_init(unsigned long ckil, unsigned long osc,clk_enable(&cpu_clk);clk_enable(&main_bus_clk);+clk_enable(&gpc_dvfs_clk);clk_enable(&iim_clk);imx_print_silicon_rev("i.MX51",mx51_revision());
@@ -1615,6 +1617,7 @@ int __init mx53_clocks_init(unsigned long ckil, unsigned long osc,clk_set_parent(&uart_root_clk,&pll3_sw_clk);clk_enable(&cpu_clk);clk_enable(&main_bus_clk);+clk_enable(&gpc_dvfs_clk);clk_enable(&iim_clk);imx_print_silicon_rev("i.MX53",mx53_revision());
@@ -0,0 +1,64 @@+/*+*Copyright2011FreescaleSemiconductor,Inc.+*Copyright2011LinaroLtd.+*+*ThecodecontainedhereinislicensedundertheGNUGeneralPublic+*License.YoumayobtainacopyoftheGNUGeneralPublicLicense+*Version2orlateratthefollowinglocations:+*+*http://www.opensource.org/licenses/gpl-license.html+*http://www.gnu.org/copyleft/gpl.html+*/++#include<linux/kernel.h>+#include<linux/init.h>+#include<linux/export.h>+#include<linux/cpuidle.h>+#include<asm/proc-fns.h>+#include<mach/common.h>++intimx5_enter(structcpuidle_device*dev,+structcpuidle_driver*drv,+intindex)+{+mx5_cpu_lp_set((unsignedint)dev->states_usage[index].driver_data);+cpu_do_idle();+returnindex;+}++staticstructcpuidle_driverimx5_cpuidle_driver__initdata={+.name="imx5_cpuidle",+.owner=THIS_MODULE,+.states[0]={+.enter=imx5_enter,+.exit_latency=12,/* max latency at 160MHz */+.target_residency=1,+.flags=CPUIDLE_FLAG_TIME_VALID,+.name="IMX WFI",+.desc="CPU and related clocks gated",+},+.states[1]={+.enter=imx5_enter,+.exit_latency=20,/* max latency at 160MHz */+.target_residency=1,+.flags=CPUIDLE_FLAG_TIME_VALID,+.name="IMX SRPG",+.desc="CPU state retained,powered off",+},+.state_count=2,+};++/* use driver_data field to hold the mx5 idle parameter */+staticvoid__initimx5_dd_init(structcpuidle_device*dev)+{+dev->states_usage[0].driver_data=(void*)WAIT_UNCLOCKED;+dev->states_usage[1].driver_data=(void*)WAIT_UNCLOCKED_POWER_OFF;+}++staticint__initimx5_init_cpuidle(void)+{+returncommon_cpuidle_init(&imx5_cpuidle_driver,+true,+imx5_dd_init);+}+late_initcall(imx5_init_cpuidle);
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*
+ * This code performs provides some commonly used cpuidle setup functionality
+ * used by many ARM SoC platforms. Providing this functionality here
+ * reduces the duplication of this code for each ARM platform that uses it.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/cpuidle.h>
+#include <linux/hrtimer.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <asm/proc-fns.h>
+
+static struct cpuidle_device __percpu * common_cpuidle_devices;
+
+static int (*do_idle[CPUIDLE_STATE_MAX])(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int index);
+
+int cpuidle_def_idle(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int index)
+{
+ cpu_do_idle();
+ return index;
+}
+
+static int simple_enter(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int index)
+{
+ ktime_t time_start, time_end;
+
+ local_irq_disable();
+
+ time_start = ktime_get();
+
+ index = do_idle[index](dev, drv, index);
+
+ time_end = ktime_get();
+
+ local_irq_enable();
+
+ dev->last_residency =
+ (int)ktime_to_us(ktime_sub(time_end, time_start));
+
+ return index;
+}
+
+void common_cpuidle_devices_uninit(void)
+{
+ int cpu_id;
+ struct cpuidle_device *dev;
+
+ for_each_possible_cpu(cpu_id) {
+ dev = per_cpu_ptr(common_cpuidle_devices, cpu_id);
+ cpuidle_unregister_device(dev);
+ }
+
+ free_percpu(common_cpuidle_devices);
+}
+
+/**
+ * common_cpuidle_init() - Provides some commonly used init functionality.
+ * @pdrv Pointer to your cpuidle_driver object.
+ * @simple Use the common simple_enter wrapper?
remove the ?
+ * @driver_data_init Pointer to your platform function to initialize your
+ * platform specific driver data. Use NULL if platform
+ * specific data is not needed.
+ *
+ * Common cpuidle init interface to provide common cpuidle functionality
+ * used by many platforms.
+ */
+int __init common_cpuidle_init(struct cpuidle_driver *pdrv, bool simple,
+ void (*driver_data_init)(struct cpuidle_device *dev))
+{
+ struct cpuidle_device *dev;
+ struct cpuidle_driver *drv;
+ int i, cpu_id, ret;
+
+ if (!pdrv || pdrv->state_count > CPUIDLE_STATE_MAX) {
+ pr_err("%s: Invalid Input\n", __func__);
Using pr_fmt rather than function name is preferred.
@@ -56,6 +56,16 @@ struct cpuidle_state {#define CPUIDLE_DRIVER_FLAGS_MASK (0xFFFF0000)+/* Common ARM WFI state */+#define CPUIDLE_ARM_WFI_STATE {\+.enter=cpuidle_def_idle,\+.exit_latency=2,\+.target_residency=1,\+.flags=CPUIDLE_FLAG_TIME_VALID,\+.name="WFI",\+.desc="ARM core clock gating (WFI)",\+}+/***cpuidle_get_statedata-retrievesprivatedriverstatedata*@st_usage:thestateusagestatistics
@@ -141,6 +151,13 @@ extern void cpuidle_resume_and_unlock(void);externintcpuidle_enable_device(structcpuidle_device*dev);externvoidcpuidle_disable_device(structcpuidle_device*dev);+/* provide a default idle function */+externintcpuidle_def_idle(structcpuidle_device*dev,+structcpuidle_driver*drv,intindex);+externintcommon_cpuidle_init(structcpuidle_driver*drv,boolsimple,+void(*driver_data_init)(structcpuidle_device*dev));+externvoidcommon_cpuidle_devices_uninit(void);+#elsestaticinlinevoiddisable_cpuidle(void){}staticinlineintcpuidle_idle_call(void){return-ENODEV;}
You can use DECLARE_PER_CPU here.
Is there any particular reason to allocate these dynamically? You can
replace the code above with,
static DEFINE_PER_CPU(struct cpuidle_device, common_cpuidle_devices);
I might change the variable name to "cpu_cpuidle_device" in that case
since you are addressing a single CPU when using the per cpu accessor
functions and "common_cpuidle_devices" sounds like an array or a list
or something. No big deal to keep the current name though.
Sometimes an attempt to enter some C-state fails and the do_idle will
return immediately. What do you think about having do_idle return
-EERROR in this case and the conditionally setting last_residency to
zero in those cases? The point is that a C-state's total residency
time should not increase in the case where the hardware did not
successfully transition into that C-state. I've observed many times
where a specific low power state was actually achieved in the hardware
but /sys/devices/system/cpu/cpuN/cpuidle/stateM/time keeps
incrementing (albeit in very tiny increments). Something like,
if (IS_ERR(index))
dev->last_residency = 0;
else
...
Note: I haven't been through the CPUidle core in a while so maybe the
above suggestion violates some other requirements/assumptions...
If the registering sequence aborts, won't cpuidle_unregister_device
leads to a kernel warning as it could be specified with a cpu which has
*not* been registered yet ?
Perhaps we should pass the cpuid from where the cpu has failed an do a
reverse unregister sequence.
void common_cpuidle_devices_uninit(int cpu)
{
for (cpu--; cpu >= 0; cpu--) {
device = &per_cpu(common_cpuidle_devices, cpu);
cpuidle_unregister_device(device);
}
...
+
+/**
+ * common_cpuidle_init() - Provides some commonly used init functionality.
+ * @pdrv Pointer to your cpuidle_driver object.
+ * @simple Use the common simple_enter wrapper?
+ * @driver_data_init Pointer to your platform function to initialize your
+ * platform specific driver data. Use NULL if platform
+ * specific data is not needed.
+ *
+ * Common cpuidle init interface to provide common cpuidle functionality
+ * used by many platforms.
+ */
+int __init common_cpuidle_init(struct cpuidle_driver *pdrv, bool simple,
+ void (*driver_data_init)(struct cpuidle_device *dev))
+{
+ struct cpuidle_device *dev;
+ struct cpuidle_driver *drv;
+ int i, cpu_id, ret;
+
+ if (!pdrv || pdrv->state_count> CPUIDLE_STATE_MAX) {
+ pr_err("%s: Invalid Input\n", __func__);
+ return -EINVAL;
+ }
+
+ drv = kmalloc(sizeof(struct cpuidle_driver), GFP_KERNEL);
+ if (!drv) {
+ pr_err("%s: no memory for cpuidle driver\n", __func__);
+ return -ENOMEM;
+ }
+
+ *drv = *pdrv;
Rob can you explain why is needed to copy this structure ?
Maybe kmemdup is more adequate here.
drv = kmemdup(pdrv, sizeof(*drv), GFP_KERNEL);
Do we really need a 'simple' parameter ? Is there an idle enter function
which does not correspond to the 'simple' scheme except omap3/4 ?
Maybe I am wrong but that looks a bit hacky because we are trying to
override the functions the driver had previously defined and in order to
prevent to modify the cpuidle.c core and more code.
I am wondering if it is possible to move the usual:
[ local_irq_disable(), getnstimeofday(before), myidle,
getnstimeofday(after), local_irq_enable(), dev->last_residency =
after-before, return index ]
to cpuidle.c/cpuidle_idle_call and wrap the
entered_state = target_state->enter(dev, drv, next_state)
with these simple scheme.
Also I am not sure local_irq_disable is needed because AFAICT the idle
function is called with the local_irq_disable. For example, the
intel_idle driver does not do that and assume the enter_idle function is
called with the local irq disabled.
Looking at the code :
arch/arm/kernel/process.c : pm_idle is wrapped with local_irq_disable /
local_irq_enable.
arch/x86/kernel/process_32/64.c : pm_idle is called with
local_irq_disable but assumes the function will enable local irq
arch/ia64/kernel/process.c : the code assumes the idle function will
disable/enable the local irq.
etc ...
It seems the code with the different arch is non consistent except there
is a technical reason I don't know. May be making them consistent will
help to factor out this part of the code and make the common framework
more simple.
It is just a suggestion and IMO that could be done later on top of this
patchset.
quoted hunk
+ ret = cpuidle_register_driver(drv);
+ if (ret) {
+ pr_err("%s: Failed to register cpuidle driver\n", __func__);
+ goto free_drv;
+ }
+
+ common_cpuidle_devices = alloc_percpu(struct cpuidle_device);
+ if (common_cpuidle_devices == NULL) {
+ ret = -ENOMEM;
+ goto unregister_drv;
+ }
+
+ /* initialize state data for each cpuidle_device */
+ for_each_possible_cpu(cpu_id) {
+ dev = per_cpu_ptr(common_cpuidle_devices, cpu_id);
+ dev->cpu = cpu_id;
+ dev->state_count = drv->state_count;
+
+ if (driver_data_init)
+ driver_data_init(dev);
+
+ ret = cpuidle_register_device(dev);
+ if (ret) {
+ pr_err("%s: Failed to register cpu %u\n",
+ __func__, cpu_id);
+ goto uninit;
+ }
+ }
+
+ return 0;
+uninit:
+
+ common_cpuidle_devices_uninit();
+
+unregister_drv:
+
+ cpuidle_unregister_driver(drv);
+
+free_drv:
+
+ kfree(drv);
+
+ return ret;
+}
@@ -56,6 +56,16 @@ struct cpuidle_state {#define CPUIDLE_DRIVER_FLAGS_MASK (0xFFFF0000)+/* Common ARM WFI state */+#define CPUIDLE_ARM_WFI_STATE {\+.enter=cpuidle_def_idle,\+.exit_latency=2,\+.target_residency=1,\+.flags=CPUIDLE_FLAG_TIME_VALID,\+.name="WFI",\+.desc="ARM core clock gating (WFI)",\+}+/***cpuidle_get_statedata-retrievesprivatedriverstatedata*@st_usage:thestateusagestatistics
@@ -141,6 +151,13 @@ extern void cpuidle_resume_and_unlock(void);externintcpuidle_enable_device(structcpuidle_device*dev);externvoidcpuidle_disable_device(structcpuidle_device*dev);+/* provide a default idle function */+externintcpuidle_def_idle(structcpuidle_device*dev,+structcpuidle_driver*drv,intindex);+externintcommon_cpuidle_init(structcpuidle_driver*drv,boolsimple,+void(*driver_data_init)(structcpuidle_device*dev));+externvoidcommon_cpuidle_devices_uninit(void);+#elsestaticinlinevoiddisable_cpuidle(void){}staticinlineintcpuidle_idle_call(void){return-ENODEV;}
Ok. Do you mean that DECLARE_PER_CPU is preferred in this case?
Is there any particular reason to allocate these dynamically? ?You can
replace the code above with,
static DEFINE_PER_CPU(struct cpuidle_device, common_cpuidle_devices);
I was thinking of the single kernel with multiple platform support
case. In that particular case, it seems better to create the number
of device objects you need at run time.
I might change the variable name to "cpu_cpuidle_device" in that case
since you are addressing a single CPU when using the per cpu accessor
functions and "common_cpuidle_devices" sounds like an array or a list
or something. ?No big deal to keep the current name though.
Sometimes an attempt to enter some C-state fails and the do_idle will
return immediately. ?What do you think about having do_idle return
-EERROR in this case and the conditionally setting last_residency to
zero in those cases? ?The point is that a C-state's total residency
time should not increase in the case where the hardware did not
successfully transition into that C-state. ?I've observed many times
where a specific low power state was actually achieved in the hardware
but /sys/devices/system/cpu/cpuN/cpuidle/stateM/time keeps
incrementing (albeit in very tiny increments). ?Something like,
if (IS_ERR(index))
? ? ? ?dev->last_residency = 0;
else
? ? ? ?...
Note: I haven't been through the CPUidle core in a while so maybe the
above suggestion violates some other requirements/assumptions...
Good suggestion. I'll look into adding this to v4.
See note above about statically allocating the per-cpu variables.
quoted
+}
+
+/**
+ * common_cpuidle_init() - Provides some commonly used init functionality.
+ * @pdrv ? ? ? ? ? ? ? Pointer to your cpuidle_driver object.
+ * @simple ? ? ? ? ? ? Use the common simple_enter wrapper?
+ * @driver_data_init ? Pointer to your platform function to initialize your
+ * ? ? ? ? ? ? ? ? ? ? platform specific driver data. ?Use NULL if platform
+ * ? ? ? ? ? ? ? ? ? ? specific data is not needed.
+ *
+ * Common cpuidle init interface to provide common cpuidle functionality
+ * used by many platforms.
+ */
+int __init common_cpuidle_init(struct cpuidle_driver *pdrv, bool simple,
+ ? ? ? ? ? ? ? ? ? ? ? ?void (*driver_data_init)(struct cpuidle_device *dev))
+{
+ ? ? ? struct cpuidle_device *dev;
+ ? ? ? struct cpuidle_driver *drv;
+ ? ? ? int i, cpu_id, ret;
+
+ ? ? ? if (!pdrv || pdrv->state_count > CPUIDLE_STATE_MAX) {
+ ? ? ? ? ? ? ? pr_err("%s: Invalid Input\n", __func__);
+ ? ? ? ? ? ? ? return -EINVAL;
+ ? ? ? }
+
+ ? ? ? drv = kmalloc(sizeof(struct cpuidle_driver), GFP_KERNEL);
+ ? ? ? if (!drv) {
+ ? ? ? ? ? ? ? pr_err("%s: no memory for cpuidle driver\n", __func__);
+ ? ? ? ? ? ? ? return -ENOMEM;
+ ? ? ? }
+
+ ? ? ? *drv = *pdrv;
+
+ ? ? ? for (i = 0; simple && (i < drv->state_count); i++) {
+ ? ? ? ? ? ? ? do_idle[i] = drv->states[i].enter;
+ ? ? ? ? ? ? ? drv->states[i].enter = simple_enter;
+ ? ? ? }
+
+ ? ? ? ret = cpuidle_register_driver(drv);
+ ? ? ? if (ret) {
+ ? ? ? ? ? ? ? pr_err("%s: Failed to register cpuidle driver\n", __func__);
+ ? ? ? ? ? ? ? goto free_drv;
+ ? ? ? }
+
+ ? ? ? common_cpuidle_devices = alloc_percpu(struct cpuidle_device);
+ ? ? ? if (common_cpuidle_devices == NULL) {
+ ? ? ? ? ? ? ? ret = -ENOMEM;
+ ? ? ? ? ? ? ? goto unregister_drv;
+ ? ? ? }
See note above about statically allocating these.
Regards,
Mike
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at ?http://vger.kernel.org/majordomo-info.html
Daniel, thanks for your review. I think you and Mike timed sending
your responses :) Comments below.
On Tue, Jan 24, 2012 at 5:49 PM, Daniel Lezcano
[off-list ref] wrote:
On 01/24/2012 05:37 AM, Robert Lee wrote:
quoted
The patch adds some cpuidle initialization functionality commonly
duplicated by many platforms.
Signed-off-by: Robert Lee<redacted>
---
Hi Rob,
nice work. The result is interesting. I have a few comments below.
If the registering sequence aborts, won't cpuidle_unregister_device leads to
a kernel warning as it could be specified with a cpu which has *not* been
registered yet ?
I think you may have been looking at cpuidle_unregister_driver. Here
is cpuidle_unregister_device which seems to handle a device not yet
registered ok:
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();
if (dev->registered == 0)
return;
...
Perhaps we should pass the cpuid from where the cpu has failed an do a
reverse unregister sequence.
void common_cpuidle_devices_uninit(int cpu)
{
? ? ? ?for (cpu--; cpu >= 0; cpu--) {
? ? ? ? ? ? ? ?device = &per_cpu(common_cpuidle_devices, cpu);
? ? ? ? ? ? ? ?cpuidle_unregister_device(device);
? ? ? ?}
...
quoted
+
+/**
+ * common_cpuidle_init() - Provides some commonly used init
functionality.
+ * @pdrv ? ? ? ? ? ? ? Pointer to your cpuidle_driver object.
+ * @simple ? ? ? ? ? ? Use the common simple_enter wrapper?
+ * @driver_data_init ? Pointer to your platform function to initialize
your
+ * ? ? ? ? ? ? ? ? ? ? platform specific driver data. ?Use NULL if
platform
+ * ? ? ? ? ? ? ? ? ? ? specific data is not needed.
+ *
+ * Common cpuidle init interface to provide common cpuidle functionality
+ * used by many platforms.
+ */
+int __init common_cpuidle_init(struct cpuidle_driver *pdrv, bool simple,
+ ? ? ? ? ? ? ? ? ? ? ? ?void (*driver_data_init)(struct cpuidle_device
*dev))
+{
+ ? ? ? struct cpuidle_device *dev;
+ ? ? ? struct cpuidle_driver *drv;
+ ? ? ? int i, cpu_id, ret;
+
+ ? ? ? if (!pdrv || pdrv->state_count> ?CPUIDLE_STATE_MAX) {
+ ? ? ? ? ? ? ? pr_err("%s: Invalid Input\n", __func__);
+ ? ? ? ? ? ? ? return -EINVAL;
+ ? ? ? }
+
+ ? ? ? drv = kmalloc(sizeof(struct cpuidle_driver), GFP_KERNEL);
+ ? ? ? if (!drv) {
+ ? ? ? ? ? ? ? pr_err("%s: no memory for cpuidle driver\n", __func__);
+ ? ? ? ? ? ? ? return -ENOMEM;
+ ? ? ? }
+
+ ? ? ? *drv = *pdrv;
Rob can you explain why is needed to copy this structure ?
I made the original platform cpuidle_driver objects __initdata so I
need to copy over to the dynamically allocated structure.
Maybe kmemdup is more adequate here.
drv = kmemdup(pdrv, sizeof(*drv), GFP_KERNEL);
Is this preferred by the community over direct structure copies? Or
is there some other advantage?
Do we really need a 'simple' parameter ? Is there an idle enter function
which does not correspond to the 'simple' scheme except omap3/4 ?
Maybe I am wrong but that looks a bit hacky because we are trying to
override the functions the driver had previously defined and in order to
prevent to modify the cpuidle.c core and more code.
I am wondering if it is possible to move the usual:
[ local_irq_disable(), getnstimeofday(before), myidle,
getnstimeofday(after), local_irq_enable(), dev->last_residency =
after-before, return index ]
to cpuidle.c/cpuidle_idle_call and wrap the
? ? ? ?entered_state = target_state->enter(dev, drv, next_state)
with these simple scheme.
Yes, I considered the same thing and originally made a version of this
patch with direct changes to cpuidle_idle_call. But I concluded that
since this common code's main purpose is just to consolidate code
duplication on *some* (but not all) cpuidle implementations, it was
better to create the extra simple_enter wrapper than to add additional
code in cpuidle_idle_call that other platforms don't need. I'd be
happy to submit a version of this patch with cpuidle_idle_call changes
though and let the community decide. If anyone else thinks this is a
good or bad idea, please give your input.
Also I am not sure local_irq_disable is needed because AFAICT the idle
function is called with the local_irq_disable. For example, the intel_idle
driver does not do that and assume the enter_idle function is called with
the local irq disabled.
Looking at the code :
arch/arm/kernel/process.c : pm_idle is wrapped with local_irq_disable /
local_irq_enable.
arch/x86/kernel/process_32/64.c : pm_idle is called with local_irq_disable
but assumes the function will enable local irq
arch/ia64/kernel/process.c : the code assumes the idle function will
disable/enable the local irq.
etc ...
Agree. I considered this as well but ultimately decided to leave it
in. I can remove it for the next patch version though.
It seems the code with the different arch is non consistent except there is
a technical reason I don't know. May be making them consistent will help to
factor out this part of the code and make the common framework more simple.
It is just a suggestion and IMO that could be done later on top of this
patchset.
If the registering sequence aborts, won't cpuidle_unregister_device leads to
a kernel warning as it could be specified with a cpu which has *not* been
registered yet ?
I think you may have been looking at cpuidle_unregister_driver. Here
is cpuidle_unregister_device which seems to handle a device not yet
registered ok:
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();
if (dev->registered == 0)
return;
...
Ok, it is harmless. I could have looked at that ... :)
quoted
quoted
+
+ drv = kmalloc(sizeof(struct cpuidle_driver), GFP_KERNEL);
+ if (!drv) {
+ pr_err("%s: no memory for cpuidle driver\n", __func__);
+ return -ENOMEM;
+ }
+
+ *drv = *pdrv;
[ ... ]
quoted
Rob can you explain why is needed to copy this structure ?
I made the original platform cpuidle_driver objects __initdata so I
need to copy over to the dynamically allocated structure.
Yes, but why declare a static object to be freed and allocate a new one
and copy it ? Why don't just use the pdrv parameter of the function ?
quoted
Maybe kmemdup is more adequate here.
drv = kmemdup(pdrv, sizeof(*drv), GFP_KERNEL);
Is this preferred by the community over direct structure copies? Or
is there some other advantage?
It does kmalloc + memcpy. And *drv = *pdrv is converted to a memcpy by
the compiler. So using kmemdup generates the same code as kmalloc +
memcpy, or kmalloc + *drv = *pdrv
Do we really need a 'simple' parameter ? Is there an idle enter function
which does not correspond to the 'simple' scheme except omap3/4 ?
Maybe I am wrong but that looks a bit hacky because we are trying to
override the functions the driver had previously defined and in order to
prevent to modify the cpuidle.c core and more code.
I am wondering if it is possible to move the usual:
[ local_irq_disable(), getnstimeofday(before), myidle,
getnstimeofday(after), local_irq_enable(), dev->last_residency =
after-before, return index ]
to cpuidle.c/cpuidle_idle_call and wrap the
entered_state = target_state->enter(dev, drv, next_state)
with these simple scheme.
Yes, I considered the same thing and originally made a version of this
patch with direct changes to cpuidle_idle_call. But I concluded that
since this common code's main purpose is just to consolidate code
duplication on *some* (but not all) cpuidle implementations, it was
better to create the extra simple_enter wrapper than to add additional
code in cpuidle_idle_call that other platforms don't need. I'd be
happy to submit a version of this patch with cpuidle_idle_call changes
though and let the community decide. If anyone else thinks this is a
good or bad idea, please give your input.
[1]
quoted
Also I am not sure local_irq_disable is needed because AFAICT the idle
function is called with the local_irq_disable. For example, the intel_idle
driver does not do that and assume the enter_idle function is called with
the local irq disabled.
Looking at the code :
arch/arm/kernel/process.c : pm_idle is wrapped with local_irq_disable /
local_irq_enable.
arch/x86/kernel/process_32/64.c : pm_idle is called with local_irq_disable
but assumes the function will enable local irq
arch/ia64/kernel/process.c : the code assumes the idle function will
disable/enable the local irq.
etc ...
Agree. I considered this as well but ultimately decided to leave it
in. I can remove it for the next patch version though.