浅析linux内核pm的应用小例
arch/arm/mach-pxa/pxa3xx_pm.c
suspend_set_ops(&pxa3xx_pm_ops);
pxa3xx_pm_ops电源管理操作集,当lcd熄灭之后,会调用pxa3xx_pm_enter()使得内核进入省电模式,至于进入哪种省电模式:
static int pxa3xx_pm_enter(suspend_state_t state)
{
if (state == PM_SUSPEND_MEM)
return pxa3xx_pm_enter_sleep(&pxa3xx_pm_regs);
else if (state == PM_SUSPEND_STANDBY)
return pxa3xx_pm_enter_standby(&pxa3xx_pm_regs);//standby模式,待机模式
else if (state == PM_SUSPEND_LCDREFRESH)
return pxa3xx_pm_enter_lcdrefresh(&pxa3xx_pm_regs);
else if (state == PM_SUSPEND_DEEPSLEEP)
return pxa3xx_pm_enter_deepsleep();
else
return -EINVAL;
}
//deepsleep这种模式就直接让kernel停下来了,
int pxa3xx_pm_enter_deepsleep(void)
{
unsigned int mode = PXA3xx_PM_DEEPSLEEP;
PMCR |= PMCR_BIE; /* Workaround of Micco reset */
pm_select_wakeup_src(mode, wakeup_src);//添加唤醒的条件,也就是从
pxa3xx_pm_set_cken();
pxa3xx_clear_pm_status(1);
__asm__ (
"mcr p14, 0, %0, c7, c0, 0\n"//cpu将在这里,直到pm_select_wakeup_src唤醒条件发生,比如按下键盘或者触摸了lcd屏幕
:
:"r"(mode)
);
return 0;
}
pm_power_off = pxa3xx_pm_poweroff;关闭电源,内核halt,等待唤醒条件满足,或者执行reboot的时候,也会执行pm_power_off函数
void pxa3xx_pm_poweroff(void)
{
unsigned int mode = PXA3xx_PM_DEEPSLEEP;
PMCR |= PMCR_BIE; /* Workaround of Micco reset */
/* Disable sleeptime interface at here */
pm_sleeptime = 0;
pm_select_wakeup_src(mode, wakeup_src);
// pxa3xx_pm_set_cken();
pxa3xx_clear_pm_status(1);
__asm__ (
"mcr p14, 0, %0, c7, c0, 0\n"
:
:"r"(mode)
);
}
因为有的时候,当lcd屏幕彻底关闭之后,并不一定要让kernel立即进入standby模式,让kernel停住,所以需要使用suspend_lock锁,阻止lcd屏幕关闭背光之后,kernel进入standby模式,比如某个内核线程正在执行,但是中途不希望因为电源的android_power_suspend的work_queue线程到达了超时时间,使kernel系统进入standby模式,所以可以调用android_lock_suspend_auto_expire(&micco_headset_lock, 5 * HZ);来锁住suspend工作队列,至于锁住多长时间,可以像这个例子一样,仅仅锁住5秒,也可以调用android_lock_suspend()一直锁住,直到调用android_unlock_suspend解锁.
android_power_suspend一个work_struct工作线程 首先检查USER_NOTIFICATION的链表,也就是g_active_full_wake_locks链表上的对象,知道所有登记的超时对象按照设定为最大超时的那个对象的超时时间为睡眠超时时间,调用wait_event_interruptible_timeout开始睡眠那么长时间, 调用while(g_user_suspend_state == USER_NOTIFICATION)直到g_active_full_wake_locks上的对象为空,完成之后会检查g_user_suspend_state是否还等于USER_NOTIFICATION,如果还是,说明用户在此期间没有强制为USER_AWAKE模式,检查g_active_full_wake_locks链表是否已经没有了对象,如果没有了,那么将执行, g_user_suspend_state = USER_SLEEP;标记系统进入sleep模式,在执行真正的sleep调用之前,首先会执行登记到g_early_suspend_handlers链表上的sleep之前用户打算提前执行的方法, list_for_each_entry(pos, &g_early_suspend_handlers, link) { if(pos->suspend != NULL) pos->suspend(pos); } 接下来执行sys_sync(),同步磁盘数据[其实没啥用,可以删掉],之后等待g_active_partial_wake_locks上的timeout对象超时,之后等待没有timeout属性的对象,当g_active_partial_wake_locks链表为空时,将进入下一个处理阶段,pxa3xx_enter_standby_mode()进入standby模式,中断禁止,kernel停止调度,开始睡眠, ENTRY(xxxx_cpu_standby) /* lr register will be the instruction just after invoke of pxa3xx_cpu_standby */ b pm_enter_standby 好了停在这了 直到pm_select_wakeup_src中选择的几个唤醒事件中的某个发生,之后cpu继续运行,使用local_irq_restore(flags);使能irq中断,kernel正常运行. struct xxxx_peripheral_wakeup_ops { int (*init)(pm_wakeup_src_t *src); int (*query)(unsigned int reg, pm_wakeup_src_t *src); int (*ext)(pm_wakeup_src_t src, int enable); int (*key)(pm_wakeup_src_t src, int enable); int (*mmc)(pm_wakeup_src_t src, int enable); int (*uart)(pm_wakeup_src_t src, int enable); int (*eth)(pm_wakeup_src_t src, int enable); int (*tsi)(pm_wakeup_src_t src, int enable); int (*usb)(pm_wakeup_src_t src, int enable); }; 当唤醒之后,会先调用, list_for_each_entry_reverse(pos, &g_early_suspend_handlers, link) { if(pos->resume != NULL) pos->resume(pos); } 然后退出该work_queue,不过这里设计上存在比较明显的问题,因为g_early_suspend_handlers上的动作并不会是第一个被内核执行的函数,因为当cpu唤醒之后,可能irq发生,于是pos->resume没有来得及执行,就被剥夺了执行权,别忘了2.6内核在适当的时候是运行发生内核抢占的.
ps:g_active_full_wake_locks是表示lcd亮时处理链表,g_active_partial_wake_locks是lcd熄灭之后,检查的链表.
|