UEFI中的S3实现

这段时间在龙芯平台上,调试S3的功能。目前已经基本完成,现将遇到的问题来做个总结,和简单的介绍一下S3的实现原理!

下面我们就从ACPI规范说起。。

计算机的状态

在ACPI 里面定义了很多个power state , 如果从用户的角度看,计算机一定是处理下图中的一个状态:

 

 

S0:实际上这就是我们平常的工作状态,所有设备全开,功耗一般会超过80W

S1:也称作为POS(power on suspend),这时通过CPU时钟控制器将CPU关闭外,其他外设都正常工作。

          功耗一般在30W以   下,其实一些CPU降温软件就是利用的这个原理。

S2:这个状态除了CPU被关闭之外,总线时钟也是处于关闭的状态,但是其他的外设还是都处于正常的工作状态的。

S3:就是我们熟悉的STR(Suspend to Ram),也就是待机到内存,CPU和外设电源被关闭,只有内存处于自刷新状态。就是笔记本合上屏幕的状态,功耗不超过10W。

S4:被称为STD(suspend to Disk),待机到硬盘,CPU和外设电源被关闭(硬盘处于工作状态),将内存的数据保存到硬盘中并可以被唤醒。

S5:就是关机的状态。

S3的实现原理

知道了S3的状态,其实也就明白了S3的大致实现原理,就拿笔记本合上屏幕的状态到打开屏幕的状态的切换过程为例,我们说说CPU都做了什么。

首先,合上屏幕的瞬间,应用程序要通知操作系统系统将处于待机状态,然后将系统当前的状态保留,主要就是将当前的所有进程的状态保留到内存,以及将外设寄存器的值保存到内存中,然后进程睡眠,最后将CPU的各个核也都睡眠,只留下一个核工作,然后将外设断电(内存除外,将内存配置成自刷新模式),最后将CPU也断电,这样就处于了待机状态。然后打开屏幕的瞬间,系统重新上电,系统重新启动,bios重新运行,当运行到摸一个阶段去检测ACPI电源管理的寄存器,发现这个寄存器被置位。便知道是S3唤醒状态,这样便跳转到S3的流程,和正常启动的流程并不相同。到S3流程之后,将内存中的数据重新写会到设备和唤醒CPU和进程,这样CPU就恢复了原来的状态。所以整个过程是需要BIOS和内核相互协调一起工作的。内核在整个过程中具体的实现这里不做介绍,详细请看内核代码。简单列举这部分代码的流程,这里不在介绍。

BIOS的传统启动流程和S3流程(UEFI中)

下图就是UEFI中常规启动流程和S3唤醒启动的流程:

在UEFI规范中,正常的启动流程的每个阶段都有自己固定的任务,这里不在想写的介绍,规范的S3实现是在DXE阶段会将外设的初始化以Boot Script Table的形式存储在UEFI的NVS内存中,然后当S3唤醒的时候,S3阶段是不执行正常启动的DXE阶段的,仅仅执行前期的SEC和Pei阶段,这两个阶段是在Cache中运行的,还有执行Boot Script Table中外设的配置脚本来初始化必要的外设,这部分是在NVS内存块中执行的,这部分内存是在BIOS传参的时候就预留的,内核是不会使用这段内存的。执行外之后就要跳转到内核。

上面是UEFI的标准实现,我们的实现并没有这么规范,但是原理大致相同。这里面首先要注意的几个问题:

(1)如何保证UEFI再次运行不破坏内核在内存中的现场数据

UEFI正常启动过程中使用的内存范围非常大,所以我们不能采用UEFI的内存和内核的内存分开的方案,这样太浪费内存。所以要使用内存复用就必须做到UEFI正常启动之后,将不在使用的内存释放掉给内核使用,这部分内存的范围要在给内核传参的时候告诉内核。然后在S3启动的过程中,UEFI运行的内存还不能使用内核使用的内存,不能破坏内核待机之前的现场数据。所以我们实现的方案就是在Cache阶段就实现了跳转,内存恢复之后就跳转到内核,这里我们并没有真的执行Boot Scriot Table ,只是在Sec和Pei阶段做了CPU和桥片的初始化工作之后,就跳转到了内核。正常来讲PCI设备的树的扫描也是必须要做的,但是我们这里还没有做,就可以跳回到内核,具体有什么隐患还未知,需要验证。所以我们的实现就是S3唤醒阶段,只执行了Sec和Pei的内存和桥片的初始化,严格来讲Pei阶段都没有执行完,执行完桥片的初始化之后,解锁cache之后,就去跳转内核了。所以,并没有执行DXE阶段,DXE阶段是在整个内存初始化好了之后,在内存中跑的,所以不会破坏内核在内存中的现场数据。

(2)内核和BIOS是如何完成跳转的

首先在BIOS阶段,也就是我们的UEFI的Sec阶段的汇编代码中,开机执行的第一条指令的地址是0x90000000bfc00000的地址,而这里UEFI中我们存放的是跳转执行令 j  0x90000000bfc10000,也就是跳转到了bfc10000的地址去执行,而这里才是我们Sec阶段代码的开始,这部分代码都是用汇编写的,我们在汇编的流程中,利用汇编中的  .align n(将以2的n次方的对齐方式编译代码,这个位置就基于开始位置偏移的,例如我们第一条指令的地址是64位虚拟地址0x90000000bfc10000,是跳转后执行的第一条指令的地址。在汇编代码上添加如下指令后,

.align 10/* bfc10400 */
move k0, ra #save ra
dla_a0, v200_msg
bal_stringserial
nop
b_exc_common

.align 10后面的指令将编译到地址0x90000000bfc10400的位置,这个位置怎么算出来的呢?就是其起始位置0x90000000bfc10000+2的十次方,十六进制就是0x400,所以就是0x90000000bfc10400这个地址) 指令实现对二进制的指定位置编译。这样我们利用这种方式将从内核跳转到UEFI的代码地址编写到0x90000000bfc10700的位置上,pc指针从内核跳转到这个地址来执行UEFI的代码。这部分跳转的函数是在内核的sleep.s中执行的。具体的实现看下面的代码:
 

LEAF(loongson_suspend_lowlevel)
__SETUP_SLEEP

__/* a0:address a1:L1_sets a2:L1_ways a3:L1_linesize */
__li a0, 0x80000000
__lw a1, loongson_pcache_sets
__lw a2, loongson_pcache_ways
__lw a3, loongson_pcache_linesz
flushL1:
__move  t0, a2
1:__cache 0, (a0)
__cache 1, (a0)
__addiu a0, a0, 1
__addiu t0, t0, -1
__bnez  t0, 1b
__subu  a0, a0, a2
__addu  a0, a0, a3
__addiu a1, a1, -1
__bnez  a1, flushL1

__/* a0:nr_nodes a1:address a2:L2_sets a3:L2_ways t8:L2_linesize */
__lw  a0, loongson_nr_nodes
__dli a1, 0x9800000000000000
__lw  a3, loongson_scache_ways
__lw  t8, loongson_scache_linesz
flushL2_all:
__lw  a2, loongson_scache_sets
__dli t9, 0x100000000000
flushL2_node:
__move   t0, a3
1:__cache  3, (a1)
__daddiu a1, a1, 1
__addiu  t0, t0, -1
__bnez   t0, 1b
__dsubu  a1, a1, a3
__daddu  a1, a1, t8
__addiu  a2, a2, -1
__bnez   a2, flushL2_node
__daddu  a1, a1, t9
__addiu  a0, a0, -1
__bnez   a0, flushL2_all

__/* Pass RA and SP to BIOS, for machines without CMOS RAM */
__daddi_a1, sp, 0
__dla_a0, wakeup_start
__ld v0, loongson_suspend_addr

/* Call BIOS's STR sleep routine */                                                                                                                  
__jr      v0
__nop
END(loongson_suspend_lowlevel)

/* This is where we return upon wakeup.
__ * Reload all of the registers and return.
__ */
LEAF(wakeup_start)
__lw__k0, PT_R8(sp)
__mtc0__k0, CP0_STATUS
__lw__k0, PT_R9(sp)
__mtc0__k0, CP0_CONFIG, 0
__lw__k0, PT_R10(sp)
__mtc0__k0, CP0_PAGEMASK, 0
__lw__k0, PT_R11(sp)
__mtc0__k0, CP0_PAGEMASK, 1
__ld__k0, PT_R12(sp)
__dmtc0_k0, CP0_CONTEXT
__ld__k0, PT_R13(sp)
__dmtc0_k0, CP0_XCONTEXT

__nop
__ld__$1, PT_R1(sp)
__ld__$2, PT_R2(sp)
__ld__$3, PT_R3(sp)
__ld__$4, PT_R4(sp)
__ld__$5, PT_R5(sp)
__ld__$6, PT_R6(sp)
__ld__$7, PT_R7(sp)
__ld__$16, PT_R16(sp)
__ld__$17, PT_R17(sp)
__ld__$18, PT_R18(sp)
__ld__$19, PT_R19(sp)
__ld__$20, PT_R20(sp)
__ld__$21, PT_R21(sp)
__ld__$22, PT_R22(sp)
__ld__$23, PT_R23(sp)
__ld__$26, PT_R26(sp)
__ld__$27, PT_R27(sp)
__ld__$28, PT_R28(sp)
__ld__$30, PT_R30(sp)
__ld__$31, PT_R31(sp)

__daddiu__sp, PT_SIZE
__jr__ra
END(wakeup_start)

oongson_suspend_lowlevel这个叶子函数就是内核在断电前执行的最后一个函数,这个函数的主要作用就是将cache中的内容刷新到了内存中,然后保存了当前通用寄存去的值到内从中,保存当前sp指针到a1寄存器中,跳转回来的函数地址保存在a0寄存器中,然后将loongson_suspend_addr这个地址加载到v0寄存器中,然后跳转到这个地址。loongson_suspend_addr值就是我们之前设置好的,内核跳转到UEFI执行的代码的地址即0x90000000bfc10700,内核是如何获取到这个地址的呢,是在UEFI给内核传参的时候,传给内核的。跳转到UEFI之后,UEFI首先要做的就是将a0和a1寄存器的值保存到指定的内存地址上,这个内存地址是UEFI不使用的,在UEFI跳转到内核的时候还需要使用这个地址。而唤醒后UEFI代码执行完,跳回内核的代码的地址就是函数wakeup_start对应的函数地址。wakeup_star叶子函数跳转回来之后第一个执行的内核代码,主要就是恢复函数栈和必要的CPU寄存器的值。这样控制权又返回给了内核。具体跳转到UEFI执行的第一条指令的和跳转到内核的代码如下:(不做详细介绍,简单列举)

内核跳转到UEFI执行的第一条指令的代码如下:
 

/*********************************************************************************************/
#if 1
/*
 * 3A7A S3 code start
 */
/* str debug */
.align  8            /* 0x900000001fc10700*/

.set__mips64

/*************************************************************************                                                                                                               
/* This Code Must Be Execute Before Memory SelfRefresh Begain,
/* Because Once We Enter SelfRefresh Mode,Memory Can't Be Accessed Any More
/* We Leave Poweroff Op Later(After Enter SelfRefresh Mode)
**************************************************************************/
/* store ra and sp to memory this addr will not be used in after untill wakeup from s3*/

dli_ta0, 0x900000000f030000  //dest
dli_ta1, 0x9000000000000000  //source
dli_ta2, 0x900000000f030400  //end

1:
lw__ta3, 0(ta1)
sw__ta3, 0(ta0)
daddu_ta0, 4
bne_ta2, ta0, 1b
daddu_ta1, 4

dli_t0, 0x900000000f0200a0
sd__a0, 0x0(t0) //store ra

dli_t1, 0x900000000f0200a8
sd__a1, 0x0(t1) //store sp

dli_t2, 0x900000000f0200b0

// 0x5a5a5a5a5a5a5a5a define by myself
dli_t0, 0x5a5a5a5a5a5a5a5a

UEFI执行完,准备跳转到内核的代码如下:

DisableInterrupts();
PlatFormCopyMem((UINT64 *)0x9800000000000000,(UINT64 *)0x900000000f030000,0x400);
__asm__ __volatile__(
    ".set_noat______\n"
    ".set_mips64______\n"
    "move_$t0, %0_____\n"
    "move_$t1, %1_____\n"
    "dli__$t2, 0x00000000ffffffff_\n"
    "and__$t1,$t2_____\n"
    "dsll_$t0,32______\n"
    "or_$sp, $t0,$t1____\n"
    "jr_%2______\n"
    "nop________\n"
    ".set_at______\n"
    : /* No outputs */
    :"r"(Sphigh), "r"(Splow),"r"(StrRa)
    );

(3)如何保证内核的异常向量的代码段和UEFI的异常向量的代码段不冲突

无论是在内核或者还是在UEFI中,都有异常处理程序的代码,当然内核和UEFI异常处理的代码是不同的。但是他们俩存放的地址是相同的(物理地址0x00000180,虚拟地址0x9000000000000180的位置)。所以当pc处于内核的进程上下文中,这部分地址存放的应该是内核的异常处理程序,当跳转到uefi之后,这部分地址应该存放的是UEFI的异常处理程序,而从UEFI跳回内核之后,还需要恢复内核的这部分代码。所以在内核跳转到UEFI的时候,需要将内核的这部分代码保存,也就是我们在跳转到UEFI的代码执行的前10行的代码,就是拷贝这部分异常处理程序的代码到0x900000000f030000这个位置,这部分地址是UEFI不使用的,所以保存在这是不会被破坏的。我们这里保存了0x400即1K大小的数据。(注意:这里面我们拷贝代码使用的是0x90开头的地址,也就是不经过cache的,因为这个时候内核已经将cache 中的数据刷新到内存上,我们不应该再向cache中写入数据,因此不要使用0x98来访问这部分地址。)然后在UEFI执行完之后,准备跳转到内核之前,就需要恢复内核异常向量的代码,也就是上面的代码中的PlatFormCopyMem这个函数,将异常向量的代码又拷贝回来。(注意,这个时候要回复内核的现场,UEFI阶段已经将cache解锁可以使用,我们需要使用0x98开头的地址来拷贝这部分代码,这样cache中就会有这部分代码的一份拷贝,和睡眠之前内核的现场保持一致。)我们之前没有想到上面的两个注意的问题,就会引起偶尔死机的问题,这个死机就是恢复之后cache中的数据和内存中的数据不能够保持一致。还有一点注意:那就是在执行PlatFormCopyMem函数之前,一定要将UEFI的中断关掉,否则在拷贝过程中出现异常处理跳转,跳转到异常处理程序的地址去执行,而这部分代码已经被拷贝过来的代码破坏,就会发生各种死机的现象。

(4)UEFI中,多核处理器的主核在跑BIOS的代码,其他核在做什么?

龙芯3a3000CPU四核处理器,我们设置0号核就是UEFI运行代码的CPU,123为从核,并没有真正的在跑代码,他们只是在cache的地址段上循环检测mailbox寄存器的状态,等待进入系统后内核来唤醒他们,执行内核的代码。所以UEFI是单线程的任务,整个UEFI的代码只有一个处理器在运行,而内核不一样。我们这里开始的时候没有设置从核的状态,从核处在死循环中,并没有去检测mailbox寄存器的状态。导致唤醒后从UEFI跳转到内核的时候,内核来唤醒从核不能唤醒起来,系统死在了内核里面。所以这里也需要注意。

(5)内核中的待机代码执行流程

内核中的流程分为内核公共部分的代码和系统平台两部分,这里面我们涉及到的平台的代码是mips分之下面的我们自己的代码。

平台部分的代码是在初始化的时候注册到内核的,下面我们来看看平台的代码是如何注册的。

平台注册到内核的过程:

内核在启动过程中会调用带有__init 标志的函数,这些函数是初始化的代码,代码在执行完之后是可以将这部分空间释放掉的。而平台下面的待机注册的代码也是以这样的方式实现的,下面就是代码


static const struct platform_suspend_ops loongson_pm_ops = {
__.valid__= loongson_pm_valid_state,
__.begin__= loongson_pm_begin,
__.enter__= loongson_pm_enter,
__.wake_= loongson_pm_wake,
__.end__= loongson_pm_end,
};


static int __init loongson_pm_init(void)
{
__suspend_set_ops(&loongson_pm_ops);

__return 0;
}
arch_initcall(loongson_pm_init);

arch_initcall(loongson_pm_init)在编译的时候就已经将这段代码编译到初始化的函数中,在start_kernel执行的时候就会调用loongson_pm_init这个函数,这个函数就是我们平台对应的函数。会将loongson_pm_ops这个结构注册到内核,然后内核调用平台的代码函数的时候,就会调用这里面的函数。这其中包含的函数就是平台实现的待机前做的准备工作和待机唤醒后需要做的工作,以及待机函数都是在平台中的代码实现的。这里面代码是如何实现的不再详细介绍。

下面是主要的代码

static int loongson_pm_enter(suspend_state_t state)
{
__mach_suspend(state);

__/* processor specific suspend */
__switch(state){
__case PM_SUSPEND_STANDBY:
____loongson_suspend_enter();
____break;
__case PM_SUSPEND_MEM:
#ifdef CONFIG_CPU_LOONGSON3
____loongson_nr_nodes = nr_nodes_loongson;
____loongson_suspend_addr = suspend_addr;
____loongson_pcache_ways = cpu_data[0].dcache.ways;
____loongson_scache_ways = cpu_data[0].scache.ways;
____loongson_pcache_sets = cpu_data[0].dcache.sets;
____loongson_scache_sets = cpu_data[0].scache.sets*4;
____loongson_pcache_linesz = cpu_data[0].dcache.linesz;
____loongson_scache_linesz = cpu_data[0].scache.linesz;
____loongson_suspend_lowlevel();
____cmos_write64(0x0, 0x40);  /* clear pc in cmos */
____cmos_write64(0x0, 0x48);  /* clear sp in cmos */
#else
____loongson_suspend_enter();
#endif
____break;
__}

__mach_resume(state);

__return 0;
}


static int loongson_pm_valid_state(suspend_state_t state)
{
__switch (state) {
__case PM_SUSPEND_ON:
____return 1;

__case PM_SUSPEND_STANDBY:
__case PM_SUSPEND_MEM:
____switch (mips_machtype) {
____case MACH_LEMOTE_ML2F7:
____case MACH_LEMOTE_YL2F89:
______return 1;
____case MACH_LOONGSON_GENERIC:
______return !!suspend_addr;
____default:
______return 0;
____}

__default:
____return 0;
__}
}

static int loongson_pm_begin(suspend_state_t state)
{
#ifdef CONFIG_LOONGSON3_CPUAUTOPLUG
__extern int autoplug_enabled;
__cached_autoplug_enabled = autoplug_enabled;
__autoplug_enabled = 0;
#endif
__return 0;
}

static void loongson_pm_wake(void)
{
#ifdef CONFIG_CPU_LOONGSON3
__disable_unused_cpus();
#endif
}

static void loongson_pm_end(void)
{
#ifdef CONFIG_LOONGSON3_CPUAUTOPLUG
__extern int autoplug_enabled;
__autoplug_enabled = cached_autoplug_enabled;
#endif
}

下面看看执行待机命令之后,代码的执行流程:echo "mem" >/sys/power/states

执行上面的命令之后,系统就会执行去待机的代码流程,首先执行的函数是state_store这个函数,这个函数位于kernel/power/main.c中,这是内核中公共部分的代码。那么为什么执行上面的命令之后,就会调用这个函数呢,下面我们来看看这个函数的注册过程。

首先待机过程是属于电源管理的一部分,这部分是在函数pm_init函数中注册的,pm_init又是如何被调用的呢?还是一样也是通过编译的时候就将代码编译到了初始化的代码段。下面是代码

static int __init pm_init(void)
{
__int error = pm_start_workqueue();
__if (error)
____return error;
__hibernate_image_size_init();
__hibernate_reserved_size_init();
__power_kobj = kobject_create_and_add("power", NULL);
__if (!power_kobj)
____return -ENOMEM;
__error = sysfs_create_group(power_kobj, &attr_group);
__if (error)
____return error;
__pm_print_times_init();
__return pm_autosleep_init();
}

core_initcall(pm_init);

在pm_init函数中调用 kobject_create_and_add函数,这个函数会依次调用下面的函数,最重将state_store注册进去,下面是函数的调用流程:

kobject_create_and_add-------> kobject_create------->kobject_init(kobj, &dynamic_kobj_ktype)这里面dynamic_kobj_ktype是个结构,state_store函数最终包含在这个结构中,下面是代码

static struct kobj_type dynamic_kobj_ktype = {                                                                                                                                           
__.release__= dynamic_kobj_release,
__.sysfs_ops__= &kobj_sysfs_ops,
};

const struct sysfs_ops kobj_sysfs_ops = {
__.show_= kobj_attr_show,
__.store__= kobj_attr_store,
};

static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr,                                                                                                             
______       const char *buf, size_t count)
{
__struct kobj_attribute *kattr;
__ssize_t ret = -EIO;

__kattr = container_of(attr, struct kobj_attribute, attr);
__if (kattr->store)
____ret = kattr->store(kobj, kattr, buf, count);
__return ret; 
}

kobj_attr_store函数中调用的kattr->store(kobj, kattr, buf, count)就是执行的state_store函数。这就是为什么这个函数会被调用了。

下面从state_store来看待机流程中,代码的执行流程:

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
______   const char *buf, size_t n)
{
__suspend_state_t state;
__int error;

__error = pm_autosleep_lock();
__if (error)
____return error;

__if (pm_autosleep_state() > PM_SUSPEND_ON) {
____error = -EBUSY;
____goto out;
__}

__state = decode_state(buf, n);
__if (state < PM_SUSPEND_MAX)
____error = pm_suspend(state);
__else if (state == PM_SUSPEND_MAX)
____error = hibernate();
__else
____error = -EINVAL;

 out:
__pm_autosleep_unlock();
__return error ? error : n;
}

从上面的代码可以看出,首先进来做了一些必要的操作,上锁和参数解析,如果没有问题,会调用函数pm_suspend,否则,进行错误处理回退流程。上报错误信息。

/**
 * pm_suspend - Externally visible function for suspending the system.
 * @state: System sleep state to enter.
 *
 * Check if the value of @state represents one of the supported states,
 * execute enter_state() and update system suspend statistics.
 */
int pm_suspend(suspend_state_t state)
{
__int error;

__if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
____return -EINVAL;

__error = enter_state(state);
__if (error) {
____suspend_stats.fail++;
____dpm_save_failed_errno(error);
__} else {
____suspend_stats.success++;
__}
__return error;
} 

在pm_suspend函数中,主要做的就是参数的检查,如果待机的参数都正确,没有问题这里会调用函数enter_state,如果没有错误,更新一下suspend_stats.success的值,如果出错,会进行错误的处理和更新suspend_stats.fail的值。

static int enter_state(suspend_state_t state)
{
__int error;

__if (state == PM_SUSPEND_FREEZE) {
#ifdef CONFIG_PM_DEBUG
____if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
______pr_warning("PM: Unsupported test mode for freeze state,"
________   "please choose none/freezer/devices/platform.\n");
______return -EAGAIN;
____}
#endif
__} else if (!valid_state(state)) {
____return -EINVAL;
__}
__if (!mutex_trylock(&pm_mutex))
____return -EBUSY;

__if (state == PM_SUSPEND_FREEZE)
____freeze_begin();

__printk(KERN_INFO "PM: Syncing filesystems ... ");
__sys_sync();
__printk("done.\n");

__pr_debug("PM: Preparing system for %s sleep\n", pm_states[state].label);
__error = suspend_prepare(state);
__if (error)
____goto Unlock;

__if (suspend_test(TEST_FREEZER))
____goto Finish;

__pr_debug("PM: Entering %s sleep\n", pm_states[state].label);
__pm_restrict_gfp_mask();
__error = suspend_devices_and_enter(state);
__pm_restore_gfp_mask();

 Finish:
__pr_debug("PM: Finishing wakeup.\n");
__suspend_finish();
 Unlock:
__mutex_unlock(&pm_mutex);                                                                                                                                                               
__return error;
}

这个函数主要调用了两个函数,一个是suspend_prepare,一个是suspend_devices_and_enter,这两个函数是最主要的函数,suspend_prepare主要是待机之前的准备工作,冻结用户进程,内核线程,workqueue等操作,然后调用suspend_devices_and_enter去执行设备的待机操作,然后进入真正的待机状态。

int suspend_devices_and_enter(suspend_state_t state)
{
__int error;
__bool wakeup = false;

__if (need_suspend_ops(state) && !suspend_ops)
____return -ENOSYS;

__trace_machine_suspend(state);
__if (need_suspend_ops(state) && suspend_ops->begin) {
____error = suspend_ops->begin(state);
____if (error)
______goto Close;
__}
__suspend_console();
__suspend_test_start();
__error = dpm_suspend_start(PMSG_SUSPEND);
__if (error) {
____pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
____goto Recover_platform;
__}
__suspend_test_finish("suspend devices");
__if (suspend_test(TEST_DEVICES))
____goto Recover_platform;

__do {
____error = suspend_enter(state, &wakeup);
__} while (!error && !wakeup && need_suspend_ops(state)
____&& suspend_ops->suspend_again && suspend_ops->suspend_again());

 Resume_devices:
__suspend_test_start();
__dpm_resume_end(PMSG_RESUME);
__suspend_test_finish("resume devices");
__resume_console();
 Close:
__if (need_suspend_ops(state) && suspend_ops->end)
____suspend_ops->end();
__trace_machine_suspend(PWR_EVENT_EXIT);
__return error;

 Recover_platform:
__if (need_suspend_ops(state) && suspend_ops->recover)
____suspend_ops->recover();                                                                                                                                                              
__goto Resume_devices;
}

这个函数首先会调用suspend_console()这个函数将控制台冻结,从此之后用printk函数打印的log将不会再有输出。然后调用

dpm_suspend_start这个函数,这是去执行设备的suspend的函数,设备的待机操作相关的函数都是这个函数实现的。下面是代码

/**
 * dpm_suspend_start - Prepare devices for PM transition and suspend them.
 * @state: PM transition of the system being carried out.
 *
 * Prepare all non-sysdev devices for system PM transition and execute "suspend"
 * callbacks for them.
 */

int dpm_suspend_start(pm_message_t state)
{
__int error;

__error= dpm_prepare(state);                                                                                                                                                            
__if (error) {
____suspend_stats.failed_prepare++;
____dpm_save_failed_step(SUSPEND_PREPARE);
__} else
____error = dpm_suspend(state);
__return error;
}
/**
 * dpm_prepare - Prepare all non-sysdev devices for a system PM transition.
 * @state: PM transition of the system being carried out.
 *
 * Execute the ->prepare() callback(s) for all devices.
 */
int dpm_prepare(pm_message_t state)
{
__int error = 0;

__might_sleep();

__mutex_lock(&dpm_list_mtx);
__while (!list_empty(&dpm_list)) {
____struct device *dev = to_device(dpm_list.next);

____get_device(dev);
____mutex_unlock(&dpm_list_mtx);

____error = device_prepare(dev, state);

____mutex_lock(&dpm_list_mtx);
____if (error) {
______if (error == -EAGAIN) {
________put_device(dev);
________error = 0;
________continue;
______}
______printk(KERN_INFO "PM: Device %s not prepared "
________"for power transition: code %d\n",
________dev_name(dev), error);
______put_device(dev);
______break;
____}
____dev->power.is_prepared = true;
____if (!list_empty(&dev->power.entry))
______list_move_tail(&dev->power.entry, &dpm_prepared_list);
____put_device(dev);
__}
__mutex_unlock(&dpm_list_mtx);
__return error;                                                                                                                                                                          
}

int dpm_suspend(pm_message_t state)
{
__ktime_t starttime = ktime_get();
__int error = 0;

__might_sleep();

__cpufreq_suspend();

__mutex_lock(&dpm_list_mtx);
__pm_transition = state;
__async_error = 0;
__while (!list_empty(&dpm_prepared_list)) {
____struct device *dev = to_device(dpm_prepared_list.prev);

____get_device(dev);
____mutex_unlock(&dpm_list_mtx);

____error = device_suspend(dev);

____mutex_lock(&dpm_list_mtx);
____if (error) {
______pm_dev_err(dev, state, "", error);
______dpm_save_failed_dev(dev_name(dev));
______put_device(dev);
______break;
____}
____if (!list_empty(&dev->power.entry))
______list_move(&dev->power.entry, &dpm_suspended_list);
____put_device(dev);
____if (async_error)
______break;
__}
__mutex_unlock(&dpm_list_mtx);
__async_synchronize_full();
__if (!error)
____error = async_error;
__if (error) {
____suspend_stats.failed_suspend++;
____dpm_save_failed_step(SUSPEND_SUSPEND);
__} else
____dpm_show_time(starttime, state, NULL);                                                                                                                                               
__return error;
}

从上面的代码可以看出,dpm_suspend_start函数主要调用了两个函数,分别是dpm_prepare和dpm_suspend,prepare函数就是执行的设备驱动中注册的suspend_prepare的函数,从上面的函数实现可知,函数调用的是device_prepare,这个函数的实现如下

static int device_prepare(struct device *dev, pm_message_t state)
{
__int (*callback)(struct device *) = NULL;                                                                                                                                               
__char *info = NULL;
__int error = 0;

__if (dev->power.syscore)
____return 0;

__/*
__ * If a device's parent goes into runtime suspend at the wrong time,
__ * it won't be possible to resume the device.  To prevent this we
__ * block runtime suspend here, during the prepare phase, and allow
__ * it again during the complete phase.
__ */
__pm_runtime_get_noresume(dev);

__device_lock(dev);

__dev->power.wakeup_path = device_may_wakeup(dev);

__if (dev->pm_domain) {
____info = "preparing power domain ";
____callback = dev->pm_domain->ops.prepare;
__} else if (dev->type && dev->type->pm) {
____info = "preparing type ";
____callback = dev->type->pm->prepare;
__} else if (dev->class && dev->class->pm) {
____info = "preparing class ";
____callback = dev->class->pm->prepare;
__} else if (dev->bus && dev->bus->pm) {
____info = "preparing bus ";
____callback = dev->bus->pm->prepare;
__}

__if (!callback && dev->driver && dev->driver->pm) {
____info = "preparing driver ";
____callback = dev->driver->pm->prepare;
__}

__if (callback) {
____error = callback(dev);
____suspend_report_result(callback, error);
__}

__device_unlock(dev);
__return error;
}

这个函数主要的功能就是获取设备的待机前prepare函数的指针,也就是代码中的callback函数指针的值。上面的代码详细的说明了函数指针是如何获取的,这里面每个设备的设备指针dev就是对应的每个驱动的设备结构,在设备驱动初始化的时候,已经将这个结构的成员注册,这里面只需要找到结构中的prepare函数,来执行即可。

而dpm_suspend函数就是去执行设备驱动的suspend函数,其原理和上面的prepare过程一样都是先通过全局变量&dpm_prepared_list来获取每个设备对应的dev结构指针,找到设备之后,调用函数device_suspend,代码如下

static int device_suspend(struct device *dev)                                                                                                                                            
{
__INIT_COMPLETION(dev->power.completion);

__if (pm_async_enabled && dev->power.async_suspend) {
____get_device(dev);
____async_schedule(async_suspend, dev);
____return 0;
__}

__return __device_suspend(dev, pm_transition, false);
}


static int __device_suspend(struct device *dev, pm_message_t state, bool async)
{
__pm_callback_t callback = NULL;
__char *info = NULL;
__int error = 0;

__dpm_wait_for_children(dev, async);

__if (async_error)
____goto Complete;

__/*
__ * If a device configured to wake up the system from sleep states
__ * has been suspended at run time and there's a resume request pending
__ * for it, this is equivalent to the device signaling wakeup, so the
__ * system suspend operation should be aborted.
__ */
__if (pm_runtime_barrier(dev) && device_may_wakeup(dev))
____pm_wakeup_event(dev, 0);

__if (pm_wakeup_pending()) {
____async_error = -EBUSY;
____goto Complete;
__}

__if (dev->power.syscore)
____goto Complete;

__device_lock(dev);

__if (dev->pm_domain) {
____info = "power domain ";
____callback = pm_op(&dev->pm_domain->ops, state);
____goto Run;
__}

__if (dev->type && dev->type->pm) {
____info = "type ";
____callback = pm_op(dev->type->pm, state);
____goto Run;
__}

__if (dev->class) {                                                                                                                                                                      
____if (dev->class->pm) {
______info = "class ";
______callback = pm_op(dev->class->pm, state);
____goto Run;
__}

__if (dev->type && dev->type->pm) {
____info = "type ";
____callback = pm_op(dev->type->pm, state);
____goto Run;
__}

__if (dev->class) {
____if (dev->class->pm) {
______info = "class ";
______callback = pm_op(dev->class->pm, state);
______goto Run;
____} else if (dev->class->suspend) {
______pm_dev_dbg(dev, state, "legacy class ");
______error = legacy_suspend(dev, state, dev->class->suspend);
______goto End;
____}
__}

__if (dev->bus) {
____if (dev->bus->pm) {
______info = "bus ";
______callback = pm_op(dev->bus->pm, state);
____} else if (dev->bus->suspend) {
______pm_dev_dbg(dev, state, "legacy bus ");
______error = legacy_suspend(dev, state, dev->bus->suspend);
______goto End;
____}
__}
 Run:
__if (!callback && dev->driver && dev->driver->pm) {
____info = "driver ";
____callback = pm_op(dev->driver->pm, state);
__}

__error = dpm_run_callback(callback, dev, state, info);

 End:
__if (!error) {
____dev->power.is_suspended = true;
____if (dev->power.wakeup_path
____    && dev->parent && !dev->parent->power.ignore_children)
______dev->parent->power.wakeup_path = true;
__}

__device_unlock(dev);

 Complete:
__complete_all(&dev->power.completion);
__if (error)
____async_error = error;

__return error;
}

static int dpm_run_callback(pm_callback_t cb, struct device *dev,
______    pm_message_t state, char *info)
{
__ktime_t calltime;
__int error;

__if (!cb)
____return 0;

__calltime = initcall_debug_start(dev);

__pm_dev_dbg(dev, state, info);
__error = cb(dev);
__suspend_report_result(cb, error);

__initcall_debug_report(dev, calltime, error);

__return error;
}

从代码可知,还是最重要的就是获取到设备驱动注册的suspend函数,找到后callback函数的指针赋值,然后调用dpm_run_callback函数去执行真正的suspend函数,这样一次遍历链表,将每个设备都suspend之后,然后到函数dpm_suspend_start中。如果没有出错,就会调用函数 suspend_enter去执行,函数从这里就调用到了平台注册的函数,下面是代码

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
__int error;

__if (need_suspend_ops(state) && suspend_ops->prepare) {
____error = suspend_ops->prepare();
____if (error)
______goto Platform_finish;
__}

__error = dpm_suspend_end(PMSG_SUSPEND);
__if (error) {
____printk(KERN_ERR "PM: Some devices failed to power down\n");
____goto Platform_finish;
__}

__if (need_suspend_ops(state) && suspend_ops->prepare_late) {
____error = suspend_ops->prepare_late();
____if (error)
______goto Platform_wake;
__}

__if (suspend_test(TEST_PLATFORM))
____goto Platform_wake;

__/*
__ * PM_SUSPEND_FREEZE equals
__ * frozen processes + suspended devices + idle processors.
__ * Thus we should invoke freeze_enter() soon after
__ * all the devices are suspended.
__ */
__if (state == PM_SUSPEND_FREEZE) {
____freeze_enter();
____goto Platform_wake;
__}

__error = disable_nonboot_cpus();
__if (error || suspend_test(TEST_CPUS))
____goto Enable_cpus;

__arch_suspend_disable_irqs();
__BUG_ON(!irqs_disabled());

__error = syscore_suspend();                                                                                                                                                             
__if (!error) {
____*wakeup = pm_wakeup_pending();
____if (!(suspend_test(TEST_CORE) || *wakeup)) {
______error = suspend_ops->enter(state);
______events_check_enabled = false;
____}
____syscore_resume();
__}

__arch_suspend_enable_irqs();
__BUG_ON(irqs_disabled());

 Enable_cpus:
__enable_nonboot_cpus();

 Platform_wake:
__if (need_suspend_ops(state) && suspend_ops->wake)
____suspend_ops->wake();

__dpm_resume_start(PMSG_RESUME);

 Platform_finish:
__if (need_suspend_ops(state) && suspend_ops->finish)
____suspend_ops->finish();                                                                                                                                                               

__return error;
}

从代码可知,函数会调用平台注册的prepare函数,做一些准备工作,然后调用dpm_suspend_end去执行设备驱动中的late函数。这个流程和上面的prepare一样都是通过操作设备链表来获取到设备的指针,然后通过每个设备找到设备注册时注册的设备late对应的函数去调用。代码这里不在列举。执行完之后调用平台的prepare函数然后禁止中断,然后调用平台的suspend_enter函数,去真正的跳转到bison中执行相应的代码。最后在BIOS中将设备断电,CPU断电。这样就完成了整个待机流程的睡眠过程。而唤醒过程与此相反,从上面的函数流程中一次执行唤醒的过程,然后依次退出。这样就完成了整个待机唤醒的过程。这里需要注意,在唤醒过程中,开启中断的时候,这个时候要将bios下面所触发的中断清掉,保证中断状态寄存器中没有中断置位,否则在打开中断之后,就会去跳转到中断处理程序中,然而这个时候所有的状态还没有恢复跳转过程很容易出现程序跑非或者死机等现象。

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页