不管任何方式的系统挂起,最终都会调用OEMPowerOff()函数来实现.OEMPowerOff()函数由OEM来完成,这个函数也许会位于 power.c或者off.c的文件中.OEMPowerOff()是OEM来实现的,代码和流程也许不同,但基本按照下面的方法来完成.
1.先进行平台相关的动作,比如清屏,设置AD,usb等.
4.呼叫OALCPUPowerOff()进行挂起.
6. 保存wakeup后的地址,MMU寄存器,进入各模式将sp和lr寄存器保存到内存RAM的某一个位置,这个位置是由config.bib指定保留的.为什么不象之前一样保存到堆栈呢?因为系统唤醒后跳转到reset开始执行,这时候堆栈还没有初始化.这也是poweroff过程复杂的原因.
8.禁止中断.
cpu的power off模式和其他睡眠模式不同,其他的睡眠模式唤醒后会从睡眠处继续运行,而power off模式唤醒后是从reset处执行.reset有3种可能情况,1.正常的上电冷启动,包括reset信号线有效造成的reset.2.看门狗失效造成的reset.3.power off之后被外部中断或者rtc中断唤醒的reset.在reset之后可以根据GSTATUS2寄存器来判断是否从power off唤醒.还有一个问题,不论何种方式reset,都是先执行bootloader的代码,所以唤醒过程需要bootloader的参与配合.具体流程:
2.bootloader中停止sdram的自刷新模式,然后跳到内核开始地址.有些bootloader会做的更多,因为前面我们把数据都保存到了ram中的某处,事实上只要知道这个ram地址就可以取得数据进入唤醒过程.所以有些bootloader会直接去唤醒.我认为这并不好,增加了bootloader的依赖性,层次间的耦合性也高了.
3.检查checksum,因为之前设置sdram处于自刷新状态,在poweroff期间sdram里面的数据会保持,增加checksum是有必要的安全措施.
4.从RAM取得之前保存的参数,其中包含了唤醒后应该跳转的地址,和MMU的配置数据以及各个模式的sp和lr.
6.跳到唤醒后的新地址.
7.进入各个模式恢复sp和lr.
9.跳转到lr,即相当于OALCPUPowerOff()返回,返回到OEMPowerOff()中.
10.打开kitl,恢复所有寄存器,恢复平台之前状态.
唤醒过程实际是一个挂起的逆过程.如此,系统成功唤醒,所有运行的应用程序不知道自己被系统挂起过而继续运行.
前两天有一个朋友问了我s3c2410WINCE挂起/唤醒的实现,这两天我研究了一下s3c2410的PowerButton驱动,对WINCE的中断流程又有了进一步的了解,下面我就写一下我的心得和大家交流一下。
首先和PowerButton驱动有关的文件为:
1)$(_WINCEROOT)\PLATFORM\smdk2410\DRIVERS\PWRBTN文件夹下面的三个文件pwrbtn2410.c、pwrbtn2410.h和pwrbtn2410.def,这个PowerButton驱动是通过流驱动实现的,实现的过程很简单,下面会详细说明。
2)$(_WINCEROOT)\PLATFORM\smdk2410\INC\oalintr.h,定义非内核模式的中断号( non-kernelinterrupt identifiers )
3)$(_WINCEROOT)\PLATFORM\smdk2410\KERNEL\HAL\cfw.c,这里面主要实现了OEMInitInterrupts、OEMInterruptEnable、OEMInterruptDisable、OEMInterruptDone这几个重要的中断函数。
4)$(_WINCEROOT)\PLATFORM\smdk2410\KERNEL\HAL\ARM\armint.c,这里主要实现了OEMInterruptHandler这个中断处理函数。
下面我简单分析一下中断处理过程。
a) 首先你为自己的硬件(键盘,按键等需要使用的中断)定义一个中断名称,比如这个电源按键就起了一个中断名称SYSINTR_POWER,然后在oalintr.h里面把它定义成SYSINTR_FIRMWARE+n的形式
比如: #defineSYSINTR_POWER (SYSINTR_FIRMWARE+13)
n必须小于SYSINTR_MAXUMUM or SYSINTR_FIRMWARE+23
b)在cfw.c的OEMInitInterrupts中进行一些中断初始化工作,主要就是屏蔽所有中断,清除中断挂起等工作,代码我就不详细说明了,比较简单。在OEMInterruptEnable(这个函数会被InterruptInitialize函数调用)函数中主要进行中断开启工作,当驱动使用InterruptInitialize的时候(比如InterruptInitialize(SYSINTR_POWER,gPwrButtonIntrEvent, 0,0))就会把SYSINTR_POWER中断号传入,然后开启EIN0这个中断,并且把SYSINTR_POWER中断和事件gPwrButtonIntrEvent连接起来,代码如下:
INTERRUPTS_OFF(); //关闭所有中断
switch (idInt)
{......
case SYSINTR_POWER:
s2410INT->rSRCPND = BIT_EINT0;
// S3C2410X Developer Notice(page 4) warns against writing a 1 to a 0 bit in the INTPNDregister.
if (s2410INT->rINTPND &BIT_EINT0) s2410INT->rINTPND = BIT_EINT0;
s2410INT->rINTMSK &=~BIT_EINT0;
s2410INT->rSRCPND = BIT_EINT2;
// S3C2410X Developer Notice(page 4) warns against writing a 1 to a 0 bit in the INTPNDregister.
if (s2410INT->rINTPND &BIT_EINT2) s2410INT->rINTPND = BIT_EINT2;
s2410INT->rINTMSK &=~BIT_EINT2;
break;
.....
}
INTERRUPTS_ON();//开启所有中断
这个文件里面还实现了OEMInterruptDisable函数用来禁止中断,与PowerButton相关的函数如下:
INTERRUPTS_OFF();
switch (idInt)
{....
case SYSINTR_POWER:
s2410INT->rINTMSK |=BIT_EINT0;
s2410INT->rINTMSK |=BIT_EINT2;
break;
.....
}
INTERRUPTS_ON();
在这个文件中,还实现了OEMInterruptDone函数,做一些中断处理结束后的事情,当驱动调用InterruptDone时会把中断号传到这个函数来使用这个函数,与PowerButton相关的函数如下:
INTERRUPTS_OFF();
switch (idInt)
{...
case SYSINTR_POWER:
s2410INT->rSRCPND =BIT_EINT0;
if (s2410INT->rINTPND &BIT_EINT0) s2410INT->rINTPND = BIT_EINT0;
s2410INT->rINTMSK &=~BIT_EINT0;
s2410INT->rSRCPND =BIT_EINT2;
if (s2410INT->rINTPND &BIT_EINT2) s2410INT->rINTPND = BIT_EINT2;
s2410INT->rINTMSK &=~BIT_EINT2;
break;
}
INTERRUPTS_ON();
以上几个中断函数都相当重要,而且功能我也讲得很清楚了,大家应该理解了吧^_^。
c)在armint.c中主要实现了OEMInterruptHandler这个中断处理函数,当有硬件中断来的时候会进入这个处理函数,我们看看与PowerButton有关的代码:
else if (IntPendVal ==INTSRC_EINT0) { // POWER BUTTON中断
s2410INT->rINTMSK |=BIT_EINT0;
s2410INT->rSRCPND = BIT_EINT0; /* InterruptClear */
if (s2410INT->rINTPND &BIT_EINT0) s2410INT->rINTPND = BIT_EINT0;
return(SYSINTR_POWER); //返回一个中断号通知系统发生了什么中断,系统通过这个中断产生一个事件//给IST使用。
在这里,我们把PowerButton和EINT0联系起来了,并且如果EINT0来了中断,就会返回系统一个中断号SYSINTR_POWER。
d)我们下面再来看看PowerButton驱动的实现。在pbut2410.c文件里,我们首先看看动态链接库的init实现:
PUBLIC DWORD
DSK_Init(DWORD dwContext)
{
…
do
{
gPwrButtonIntrThread =CreateThread(0, 0, (LPTHREAD_START_ROUTINE) PBT_IntrThread, 0, 0,&IDThread);//创建了一个PBT_IntrThread线程,这个就是PowerButton的IST
if (gPwrButtonIntrThread ==0)
{
break;
}
} while (0);
…
}
下面我们看看PBT_IntrThread的实现:
DWORD
PBT_IntrThread(PVOID pArg)
{
PBT_InitializeAddresses();//得到EINI0口的虚拟地址
PBT_EnableInterrupt(); //使能EINI0口中断
gPwrButtonIntrEvent = CreateEvent(NULL, FALSE,FALSE, NULL);//创建一个事件//PowerButton事件
if (!(InterruptInitialize(SYSINTR_POWER,gPwrButtonIntrEvent, 0, 0)))
//通知系统使能SYSINTR_POWER这个中断,并且当这个中断产生时产生一个gPwrButtonIntrEvent事件,//第一个参数为与这个IST连接的中断ID,第二个参数为中断产生是产生的事件
{
RETAILMSG(1, (TEXT(":::SYSINTR_POWER Init Fail\r\n")));
}
while (1)
{
WaitForSingleObject(gPwrButtonIntrEvent,INFINITE);//等待中断发生
if (gOffFlag == FALSE)
{
if(PBT_IsPushed()) /*To FilterNoise *///判断是否是噪声
{
Sleep(200);
if(PBT_IsPushed())
{
}
else
{//如果不是噪声则:
#if (WINCEOSVER >= 400)
if(gpfnSetSystemPowerState != NULL)
{
gpfnSetSystemPowerState(NULL, POWER_STATE_SUSPEND,POWER_FORCE);
}
else
{
PowerOffSystem();//调用PowerOffSystem函数,在这//个函数里面又会调用OEMPowerOff函数,这个函数在power.c里
}
#else
PowerOffSystem();
#endif
DriverSleep(0, FALSE);
}
}
InterruptDone(SYSINTR_POWER);//通知系统调用OEMInterruptDone
}
}
}
在看看一些函数
PRIVATE VOID
PBT_EnableInterrupt(VOID)
{
v_pIOPregs->rGPFCON &= ~(0x3 <<0); /* 设置GPF0) 为EINT0 */
v_pIOPregs->rGPFCON |= (0x2<< 0);
v_pIOPregs->rEXTINT0 &= ~(0x7 <<0); /*配置EINT0为下降沿模式 */
v_pIOPregs->rEXTINT0 |= (0x2 << 0);
}
PRIVATE BOOL
PBT_IsPushed(VOID)
{//判断GPF0是否被按下
return ((v_pIOPregs->rGPFDAT & (1 << 0)) ? FALSE :TRUE);
}
PRIVATE BOOL
PBT_InitializeAddresses(VOID)
{//分配EINT0的虚拟地址供驱动使用
BOOL RetValue =TRUE;
/* IO Register Allocation */
v_pIOPregs = (volatile IOPreg *)VirtualAlloc(0, sizeof(IOPreg),MEM_RESERVE, PAGE_NOACCESS);
if (v_pIOPregs == NULL)
{
ERRORMSG(1,(TEXT("For IOPregs : VirtualAlloc failed!\r\n")));
RetValue = FALSE;
}
else
{
if (!VirtualCopy((PVOID)v_pIOPregs, (PVOID)(IOP_BASE),sizeof(IOPreg), PAGE_READWRITE | PAGE_NOCACHE))
{
ERRORMSG(1,(TEXT("For IOPregs: VirtualCopy failed!\r\n")));
RetValue = FALSE;
}
}
if (!RetValue)
{
// RETAILMSG (1, (TEXT("::: PBT_InitializeAddresses - Fail!!\r\n")));
if (v_pIOPregs)
{
VirtualFree((PVOID) v_pIOPregs, 0, MEM_RELEASE);
}
v_pIOPregs = NULL;
}
else RETAILMSG (1, (TEXT("::: PBT_InitializeAddresses -Success\r\n") ));
return(RetValue);
}
总结:
从上面PowerButton这个驱动我们就能把WINCE的中断处理过程了解清楚,基本的过程是首先在oalinitr.h中把中断IDdefine成SYSINTR_FIRMWARE+N的形式,然后
OEMInitInterrupts(cfw.c) -> OEMInterruptEnable(cfw.c) ->硬件中断到达 -> OEMInterruptHandler(armint.c) ->自己写的中断服务线程(IST)-> OEMInterruptDone(cfw.c)
其中IST的流程一般为:
创建一个事件CreateEvent -> InterruptInitialize(SYSINTR_XXXX,XXXXXEvent, 0, 0)把中断ID和IST联系起来,并且把中断ID和事件XXXXXEvent联系起来 ->WaitForSingleObject(XXXXXEvent,INFINITE)等待这个事件的产生,由于事件和中断联系起来了,实际就是等待中断的产生-> 实际的中断处理过程 -> InterruptDone(SYSINTR_ XXXX)通过这个函数调用OEMInterruptDone
本文出自 “Mobile and Linux Deve..” 博客,请务必保留此出处http://buaadallas.blog.51cto.com/399160/80929
根据上面那篇PowerButton驱动分析的文章,我们应该清楚了,按下PowerButton就可以最后调用OEMPowerOff这个函数,其实也可以通过调用APIPowerOffSystem来进入OEMPowerOff。我们看看这个函数实现:
这个函数在$(_WINCEROOT)\PLATFORM\smdk2410\KERNEL\HAL下的power.c文件中
VOID OEMPowerOff(void)
{
volatile IOPreg *s2410IOP = (IOPreg*)IOP_BASE;
volatile INTreg *s2410INT =(INTreg *)INT_BASE;
volatile LCDreg *s2410LCD = (LCDreg*)LCD_BASE;
/* SaveCurrent Important CPU Regs... */
CPUSaveRegs(CPUBackupRegs);//保存寄存器的值
/* LCDControllerDisable */
CPULCDOff();//关闭LCD电源
/* Stop all GPIO */
ConfigStopGPIO();//停止IO口
/* Set misc register for power off */
ConfigMiscReg();
/* ActualPower-Off ModeEntry */
CPUPowerOff();//调用CPUPowerOff,这个函数在fw.s里面,调用完之后会进入挂起状态
/*Recover Process, Load CPURegs *///恢复过程
CPULoadRegs(CPUBackupRegs);//恢复寄存器值
/* Clear GSTATUS2 register : Write 1 to clear*/
s2410IOP->rGSTATUS2 =s2410IOP->rGSTATUS2;//清除GSTATUS2寄存器,这里面存放的是CPU挂起时PC指针的地址
/*InterruptClear *///清除中断
...
}
我们下面进入fw.s简单分析一下CPUPowerOff
LEAF_ENTRY CPUPowerOff//CPUPowerOff 函数实现
; 1.保存寄存器状态以及返回地址到栈里
;
stmdb sp!,{r4-r12}
stmdb sp!, {lr}
; 2. 保存MMU& CPU 寄存器到RAM.
;
ldr r3,=SLEEPDATA_BASE_VIRTUAL ; base of Sleep mode storage
ldr r2, =Awake_address
str r2, [r3],#4 ; save resume function address (virtual).
mrc p15, 0, r2, c1, c0, 0
ldr r0, =MMU_CTL_MASK
bic r2, r2, r0
str r2, [r3],#4 ; save MMU control data.
mrc p15, 0, r2, c2, c0, 0
ldr r0, =MMU_TTB_MASK
bic r2, r2, r0
str r2, [r3],#4 ; save TTB address.
mrc p15, 0, r2, c3, c0, 0
str r2, [r3],#4 ; save domain access control.
str sp, [r3],#4 ; save SVC mode stack pointer.
mrs r2, spsr
str r2, [r3],#4 ; save SVC status register.
mov r1,#Mode_FIQ:OR:I_Bit:OR:F_Bit ; enter FIQ mode, no interrupts.
msr cpsr, r1
mrs r2, spsr
stmia r3!, {r2, r8-r12, sp,lr} ; save the FIQ mode registers.
mov r1,#Mode_ABT:OR:I_Bit:OR:F_Bit ; enter ABT mode, no interrupts.
msr cpsr, r1
mrs r0, spsr
stmia r3!, {r0, sp,lr} ; save the ABT mode Registers.
mov r1,#Mode_IRQ:OR:I_Bit:OR:F_Bit ; enter IRQ mode, no interrupts.
msr cpsr, r1
mrs r0, spsr
stmia r3!, {r0, sp,lr} ; save the IRQ Mode Registers.
mov r1,#Mode_UND:OR:I_Bit:OR:F_Bit ; enter UND mode, no interrupts.
msr cpsr, r1
mrs r0, spsr
stmia r3!, {r0, sp,lr} ; save the UND mode Registers.
mov r1,#Mode_SYS:OR:I_Bit:OR:F_Bit ; enter SYS mode, no interrupts.
msr cpsr, r1
stmia r3!, {sp,lr} ; save the SYS mode Registers.
mov r1,#Mode_SVC:OR:I_Bit:OR:F_Bit ; back to SVC mode, no interrupts.
msr cpsr, r1
; 3. Compute the checksum onSleepData (verify integrity of data after resume).
;
ldr r3,=SLEEPDATA_BASE_VIRTUAL ; get pointer to SLEEPDATA.
mov r2, #0
ldr r0,=SLEEPDATA_SIZE ; get size of data structure (in words).
30
ldr r1, [r3],#4 ; compute the checksum.
and r1, r1, #0x1
mov r1, r1, LSL #31
orr r1, r1, r1, LSR #1
add r2, r2, r1
subs r0, r0,#1
bne %b30
ldr r0, =vGPIOBASE
str r2, [r0,#oGSTATUS3] ; save the checksum in the Power Manager Scratch pad register.
; 4.屏蔽和清除所有中断.
;
ldr r0, =vINTBASE
mvn r2, #0
str r2, [r0, #oINTMSK]
str r2, [r0, #oSRCPND]
str r2, [r0, #oINTPND]
......
; 6.设置外部唤醒中断(EINT0-2: power-button 和keyboard).
;
ldr r0, =vGPIOBASE
ldr r1, =0x550a
str r1, [r0, #oGPFCON]
ldr r1, =0x55550100
str r1, [r0, #oGPGCON]
; 7.转换到power-off 模式.
;
ldr r0,=vMISCCR ; hitthe TLB
ldr r0,[r0]
ldr r0, =vCLKCON
ldr r0,[r0]
; **Theseregisters are used later during power-off.
;
ldr r0, =vREFRESH
ldr r1,[r0] ; r1 = rREFRESH.
orr r1, r1, #(1 << 22)
; **Theseregisters are used later during power-off.
;
ldr r2, =vMISCCR
ldr r3, [r2]
orr r3, r3, #(7 <<17) ; make sure that SCLK0:SCLK->0, SCLK1:SCLK->0, SCKE=L duringboot-up.
; **Theseregisters are used later during power-off.
;
ldr r4, =vCLKCON
ldr r5,=0x7fff8 ; power-off mode.
ldr r8,=0xEA000000
add r8, r8,#0x3f0
add r8, r8,#0xe ; make value to0xEA0003FE
ldr r6,=0x92000000 ; make address to0x9200 1004 or 0x9200 0004
ldr r7, [r6] ;Check ROM Address data, if 0xEA0003FE, it is EBOOT
cmp r7,r8
bne %f50
add r6, r6,#0x1000 ; Because eboot startupcode is located at 0x1000.
50
add r6, r6,#0x4 ;
mov pc,r6 ;jump to Power off code in ROM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
b SelfRefreshAndPowerOff
ALIGN 32 ; for I-Cache Line(32Byte, 8 Word)
SelfRefreshAndPowerOff ; runwith Instruction Cache's code
str r1, [r0] ; 使能SDRAMself-refresh模式,这样RAM中的数据不会丢失!!
str r3,[r2] ; MISCCR Setting
str r5, [r4] ; Power Off !!
b .
上面的代码就实现了wince的挂起,下面我们分析唤醒的流程,通过外部中断,其实是让CPU复位,这样系统又从0地址开始执行,但是,通过下面的代码我们可以知道是冷启动还是唤醒!!!
ldr r1,=GSTATUS2 ; Determine Booting Mode
ldr r10,[r1]
tst r10,#0x2
beq %F2 ;if not wakeup from PowerOffmode如果第2位不为1则表示是唤醒
; Skip MISCCRsetting
b %F3 ;if wakeup from PowerOff mode
; goto Power-up code.
下面还有一段
tst r10,#0x2
beq BringUpWinCE ; 如果GSTATUS2为1则正常启动,否则进入唤醒
; Recover Process : Starting Point
; 1. Checksum Calculation saved Data
....
40
ldr r1, [r3],#4 ; pointerto SLEEPDATA
and r1, r1,#0x1
mov r1, r1,LSL #31
orr r1, r1,r1, LSR #1
add r2, r2,r1
subs r0, r0,#1 ;dec the count
bne %b40 ; loop till done
ldr r0,=GSTATUS3
ldr r3,[r0] ;get the Sleep data checksum from the Power Manager Scratch padregister
teq r2,r3 ; compare to what we saved before going to sleep
; bne BringUpWinCE ; bad news - do a cold boot - If emergency power off case, normalbooting.
bne JumpToRAM ;bad news - do a cold boot - If emergency power off case, normalbooting.
b MMUENABLE
JumpToRAM
ldr r2,=0x201000 ;offset into the RAM
ldr r3,=0x30000000 ;add physical base
add r2, r2,r3
mov pc,r2 ; & jump to StartUp address
MMUENABLE
; 2. MMU Enable
ldr r10, [r5, #SleepState_MMUDOMAIN] ; load the MMUdomain access info
ldr r9, [r5,#SleepState_MMUTTB] ; load theMMU TTB info
ldr r8, [r5,#SleepState_MMUCTL] ; load theMMU control info
ldr r7, [r5, #SleepState_WakeAddr] ; load the LR address
nop
nop
nop
nop
nop
....
; 唤醒过程
1
mcr p15, 0,r10, c3, c0, 0 ; setup access todomain 0
mcr p15, 0,r9, c2, c0,0 ; PT address
mcr p15, 0,r0, c8, c7,0 ; flush I+D TLBs
mcr p15, 0,r8, c1, c0,0 ; restore MMU control
; 3. 跳到内核fw.s(Awake_address)唤醒地址处!!!!!1
mov pc,r7 ; & jump to new virtual address (back up Power managementstack)跳到挂起时的地址
nop
唤醒时的执行地址如下:
Awake_address
; 1. Recover CPU Registers
ldr r3, =SLEEPDATA_BASE_VIRTUAL ;Sleep mode information data structure
add r2, r3, #SleepState_FIQ_SPSR
mov r1, #Mode_FIQ:OR:I_Bit:OR:F_Bit ; Enter FIQ mode, nointerrupts
msr cpsr, r1
ldr r0, [r2], #4
msr spsr, r0
ldr r8, [r2], #4
ldr r9, [r2], #4
ldr r10, [r2], #4
ldr r11, [r2], #4
ldr r12, [r2], #4
ldr sp, [r2], #4
ldr lr, [r2], #4
; mov r1, #Mode_ABT:OR:I_Bit:OR:F_Bit ; Enter ABT mode, nointerrupts
mov r1,#Mode_ABT:OR:I_Bit ;Enter ABT mode, no interrupts
msr cpsr, r1
ldr r0, [r2], #4
msr spsr, r0
ldr sp, [r2], #4
ldr lr, [r2], #4
; mov r1, #Mode_IRQ:OR:I_Bit:OR:F_Bit ; Enter IRQ mode, nointerrupts
mov r1,#Mode_IRQ:OR:I_Bit ;Enter IRQ mode, no interrupts
msr cpsr, r1
ldr r0, [r2], #4
msr spsr, r0
ldr sp, [r2], #4
ldr lr, [r2], #4
; mov r1, #Mode_UND:OR:I_Bit:OR:F_Bit ; Enter UND mode, nointerrupts
mov r1,#Mode_UND:OR:I_Bit ;Enter UND mode, no interrupts
msr cpsr, r1
ldr r0, [r2], #4
msr spsr, r0
ldr sp, [r2], #4
ldr lr, [r2], #4
; mov r1, #Mode_SYS:OR:I_Bit:OR:F_Bit ; Enter SYS mode, nointerrupts
mov r1,#Mode_SYS:OR:I_Bit ;Enter SYS mode, no interrupts
msr cpsr, r1
ldr sp, [r2], #4
ldr lr, [r2]
; mov r1, #Mode_SVC:OR:I_Bit:OR:F_Bit ; Enter SVC mode, nointerrupts
mov r1,#Mode_SVC:OR:I_Bit ;Enter SVC mode, no interrupts
msr cpsr, r1
ldr r0, [r3, #SleepState_SVC_SPSR]
msr spsr, r0
; 2. Recover Last mode's REG's, & go back to caller ofCPUPowerOff()
ldr sp, [r3, #SleepState_SVC_SP]
ldr lr, [sp], #4
ldmia sp!,{r4-r12}
mov pc,lr ; and now back to our sponsors
最后一句就是把返回地址装入PC,这个返回地址是什么,哈哈,就是在power.c的OEMPowerOff函数 CPUPowerOff()后应该执行的指令,也就是需要执行CPULoadRegs(CPUBackupRegs)了!!这下大家都明白了吧!!
本文出自 “Mobile and Linux Deve..” 博客,请务必保留此出处http://buaadallas.blog.51cto.com/399160/80930