一、设备IRQ的suspend和resume
本末节首要处理这样一个问题:在体系休眠进程中,怎么suspend设备中止(IRQ)?在从休眠中唤醒的进程中,怎么resume设备IRQ?
一般来说,在体系suspend进程的后期,各个设备的IRQ (interrupt request line)会被disable掉。详细的时刻点是在各个设备的late suspend阶段之后。代码如下(删除了部分无关代码):
static int suspend_enter(suspend_state_t state, bool *wakeup)
{……
error = dpm_suspend_late(PMSG_SUSPEND);-----late suspend阶段
error = platform_suspend_prepare_late(state);
下面的代码中会disable各个设备的irq
error = dpm_suspend_noirq(PMSG_SUSPEND);----进入noirq的阶段
error = platform_suspend_prepare_noirq(state);
……
}
在dpm_suspend_noirq函数中,会针对体系中的每一个device,顺次调用device_suspend_noirq来履行该设备noirq状况下的suspend callback函数,当然,在此之前会调用suspend_device_irqs函数来disable一切设备的irq。
之所以这么做,其思路是这样的:在各个设备驱动完结了late suspend之后,按理说这些现已被suspend的设备不该该再触发中止了。假如还有一些设备没有被正确的suspend,那么咱们最好的战略是mask该设备的irq,然后阻挠中止的递送。此外,在曩昔的代码中(指interrupt handler),咱们对设备同享IRQ的状况处理的不是很好,存在这样的问题:在同享IRQ的设备们完结suspend之后,假如有中止触发,这时分设备驱动的interrupt handler并没有预备好。在有些场景下,interrupt handler会拜访现已suspend设备的IO地址空间,然后导致不行预知的issue。这些issue很难debug,因而,咱们引入了suspend_device_irqs()以及设备noirq阶段的callback函数。
体系resume进程中,在各个设备的early resume进程之前,各个设备的IRQ会被从头翻开,详细代码如下(删除了部分无关代码):
staTIc int suspend_enter(suspend_state_t state, bool *wakeup)
{……
platform_resume_noirq(state);----首要履行noirq阶段的resume
dpm_resume_noirq(PMSG_RESUME);------在这里会康复irq,然后进入early resume阶段
platform_resume_early(state);
dpm_resume_early(PMSG_RESUME);
……}
在dpm_resume_noirq函数中,会调用各个设备驱动的noirq callback,在此之后,调用resume_device_irqs函数,完结各个设备irq的enable。
二、关于IRQF_NO_SUSPEND Flag
当然,有些中止需求在整个体系的suspend-resume进程中(包含在noirq阶段,包含将nonboot CPU推送到offline状况以及体系resume后,将其从头设置为online的阶段)坚持能够触发的状况。一个简略的比如便是TImer中止,此外IPI以及一些特别意图设备中止也需求如此。
在中止请求的时分,IRQF_NO_SUSPEND flag能够用来奉告IRQ subsystem,这个中止便是上一段文字中描绘的那种中止:需求在体系的suspend-resume进程中坚持enable状况。有了这个flag,suspend_device_irqs并不会disable该IRQ,然后让该中止在随后的suspend和resume进程中,坚持中止敞开。当然,这并不能确保该中止能够将体系唤醒。假如想要到达唤醒的意图,请调用enable_irq_wake。
需求留意的是:IRQF_NO_SUSPEND flag影响运用该IRQ的一切外设(一个IRQ能够被多个外设同享,不过ARM中不会这么用)。假如一个IRQ被多个外设同享,而且各个外设都注册了对应的interrupt handler,假如其一在请求中止的时分运用了IRQF_NO_SUSPEND flag,那么在体系suspend的时分(指suspend_device_irqs之后,按理说各个IRQ现已被disable了),一切该IRQ上的各个设备的interrupt handler都能够被正常的被触发履行,即使是有些设备在调用request_irq(或许其他中止注册函数)的时分没有设定IRQF_NO_SUSPEND flag。正因为如此,咱们应该尽或许的防止一起运用IRQF_NO_SUSPEND 和IRQF_SHARED这两个flag。
三、体系中止唤醒接口:enable_irq_wake() 和 disable_irq_wake()
有些中止能够将体系从睡觉状况中唤醒,咱们称之“能够唤醒体系的中止”,当然,“能够唤醒体系的中止”需求装备才干发动唤醒体系这样的功用。这样的中止一般在作业状况的时分便是作为一般I/O interrupt呈现,只要在预备使能唤醒体系功用的时分,才会建议一些特别的装备和设定。
这样的装备和设定有或许是和硬件体系(例如SOC)上的信号处理逻辑相关的,咱们能够考虑下面的HW block图:
外设的中止信号被送到“通用的中止信号处理模块”和“特定中止信号接纳模块”。正常作业的时分,咱们会turn on“通用的中止信号处理模块”的处理逻辑,而turn off“特定中止信号接纳模块” 的处理逻辑。可是,在体系进入睡觉状况的时分,有或许“通用的中止信号处理模块”现已off了,这时分,咱们需求发动“特定中止信号接纳模块”来接纳中止信号,然后让体系suspend-resume模块(它往往是suspend状况时分仅有能够作业的HW block了)能够正常的被该中止信号唤醒。一旦唤醒,咱们最好是turn off“特定中止信号接纳模块”,让外设的中止处理回到正常的作业形式,一起,也防止了体系suspend-resume模块收到不必要的搅扰。
IRQ子体系供给了两个接口函数来完结这个功用:enable_irq_wake()函数用来翻开该外设中止线通往体系电源办理模块(也便是上面的suspend-resume模块)之路,别的一个接口是disable_irq_wake(),用来封闭该外设中止线通往体系电源办理模块途径上的各种HW block。
调用了enable_irq_wake会影响体系suspend进程中的suspend_device_irqs处理,代码如下:
staTIc bool suspend_device_irq(struct irq_desc *desc)
{
……
if (irqd_is_wakeup_set(&desc->irq_data)) {
irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
return true;
}
省掉Disable 中止的代码
}
也便是说,一旦调用enable_irq_wake设定了该设备的中止作为体系suspend的唤醒源,那么在该外设的中止不会被disable,仅仅被符号一个IRQD_WAKEUP_ARMED的符号。关于那些不是wakeup source的中止,在suspend_device_irq 函数中会符号IRQS_SUSPENDED并disable该设备的irq。在体系唤醒进程中(resume_device_irqs),被diable的中止会从头enable。
当然,假如在suspend的进程中发生了某些事情(例如wakeup source产生了有用信号),然后导致本次suspend abort,那么这个abort事情也会告诉到PM core模块。事情并不需求被马上告诉到PM core模块,一般来说,suspend thread会在某些点上去查看pending的wakeup event。
在体系suspend的进程中,每一个来自wakeup source的中止都会停止suspend进程或许将体系唤醒(假如体系现已进入suspend状况)。可是,在履行了suspend_device_irqs之后,一般的中止被屏蔽了,这时分,即使HW触发了中止信号也无法履行其interrupt handler。作为wakeup source的IRQ会怎样呢?尽管它的中止没有被mask掉,可是其interrupt handler也不会履行(这时分的HW Signal仅仅用来唤醒体系)。仅有有时机履行的interrupt handler是那些符号IRQF_NO_SUSPEND flag的IRQ,因为它们的中止始终是enable的。当然,这些中止不该该调用enable_irq_wake进行唤醒源的设定。
四、Interrupts and Suspend-to-Idle
Suspend-to-idle (也被称为”freeze” 状况)是一个相对比较新的体系电源办理状况,相关代码如下:
staTIc int suspend_enter(suspend_state_t state, bool *wakeup)
{
……
各个设备的late suspend阶段
各个设备的noirq suspend阶段
if (state == PM_SUSPEND_FREEZE) {
freeze_enter();
goto Platform_wake;
}
……
}
Freeze和suspend的前面的操作基本是相同的:首要冻住体系中的进程,然后是suspend体系中的五花八门的device,不相同的当地在noirq suspend完结之后,freeze不会disable那些non-BSP的处理器和syscore suspend阶段,而是调用freeze_enter函数,把一切的处理器推送到idle状况。这时分,任何的enable的中止都能够将体系唤醒。而这也就意味着那些符号IRQF_NO_SUSPEND(其IRQ没有在suspend_device_irqs进程中被mask掉)是有才能将处理器从idle状况中唤醒(不过,需求留意的是:这种信号并不会触发一个体系唤醒信号),而一般中止因为其IRQ被disable了,因而无法唤醒idle状况中的处理器。
那些能够唤醒体系的wakeup interrupt呢?因为其中止没有被mask掉,因而也能够将体系从suspend-to-idle状况中唤醒。整个进程和将体系从suspend状况中唤醒相同,仅有不同的是:将体系从freeze状况唤醒走的中止处理途径,而将体系从suspend状况唤醒走的唤醒处理途径,需求电源办理HW BLOCK中特别的中止处理逻辑的参加。
五、IRQF_NO_SUSPEND 标志和enable_irq_wake函数不能一起运用
针对一个设备,在请求中止的时分运用IRQF_NO_SUSPEND flag,又一起调用enable_irq_wake设定唤醒源是不合理的,首要原因如下:
1、假如IRQ没有同享,运用IRQF_NO_SUSPEND flag阐明你想要在整个体系的suspend-resume进程中(包含suspend_device_irqs之后的阶段)坚持中止翻开以便正常的调用其interrupt handler。而调用enable_irq_wake函数则阐明你想要将该设备的irq信号设定为中止源,因而并不希望调用其interrupt handler。而这两个需求显着是互斥的。
2、IRQF_NO_SUSPEND 标志和enable_irq_wake函数都不是针对一个interrupt handler的,而是针对该IRQ上的一切注册的handler的。在一个IRQ上同享唤醒源以及no suspend中止源是比较荒唐的。
不过,在十分特别的场合下,一个IRQ能够被设定为wakeup source,一起也设定IRQF_NO_SUSPEND 标志。为了代码逻辑正确,该设备的驱动代码需求满意一些特别的需求。