LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

Linux低功耗Suspend/Resume梳理(基于STM32MP1)

基于STM32MP1简单梳理Linux suspend/resume涉及到的内容:

  • 触发Suspend流程,以及唤醒手段和后续resume流程。
  • Linux kernel中Suspend/Resume流程。
  • TFA中冷启动、热启动、SMC处理、PSCI实现等等。
  • 其他低功耗相关:poweroff、reboot、fiq处理。
  • Power Domain Tree介绍;PSCI移植指导等。

1 STM32MP1低功耗手段

强制进入suspend:

echo mem > /sys/power/state

单核offline/online:

echo 0 > /sys/bus/cpu/devices/cpu1/online

设置RTC时钟,首先需要hwclock -w取系统时间设置RTC时钟,否则会报如下错误:

[ 4707.038805] rtc-pcf8563 3-0051: low voltage detected, date/time is not reliable.
[ 4707.046361] rtc-pcf8563 3-0051: low voltage detected, date/time is not reliable.
cat: read error: Invalid argument

rtc设置闹钟/唤醒闹钟:

rtc wkalmset
rtc almset
rtc aieon
rtc aieoff

2 Suspend/Resume流程

echo mem > /sys/power/state发起suspend/resume流程,示意图如下:

suspend/resume流程log:

[   25.498912] PM: suspend entry (deep)
[   25.539296] Filesystems sync: 0.038 seconds
[   25.545436] Freezing user space processes ... (elapsed 0.001 seconds) done.
[   25.552690] OOM killer disabled.
[   25.555557] Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done.
INFO:    arnoldlu psci_smc_handler smc_fid=0x8400000e [r1:r3]=0xc010f0ac 0x00000000 0x00000000
INFO:    arnoldlu psci_system_suspend
INFO:    arnoldlu stm32_validate_ns_entrypoint
INFO:    arnoldlu stm32_get_sys_suspend_power_state
INFO:    arnoldlu psci_cpu_suspend_start-163 idx=1, state_info=2, 2, is_power_down_state=1
INFO:    arnoldlu psci_cpu_suspend_start-177 idx=0, end_pwrlvl=1, parent_nodes[0]=0
INFO:    arnoldlu psci_cpu_suspend_start-185 end_pwrlvl=1, parent_nodes[0]=0
INFO:    arnoldlu psci_cpu_suspend_start-203 end_pwrlvl=1, local_pwr_state[0]=2
INFO:    arnoldlu stm32_enter_low_power, mode=0x00000002, nsec_addr=0xc010f0ac
INFO:    arnoldlu enter_cstop-133
INFO:    arnoldlu stm32_pwr_domain_pwr_down_wfi-173
INFO:    arnoldlu stm32_pwr_down_wfi-338
INFO:    arnoldlu stm32_pwr_down_wfi-343
INFO:    arnoldlu stm32_pwr_down_wfi-345
INFO:    arnoldlu stm32_pwr_down_wfi-348 interrupt=1023
INFO:    arnoldlu stm32_exit_cstop-246
INFO:    arnoldlu stm32_pwr_domain_pwr_down_wfi-184
INFO:    arnoldlu sp_min_warm_boot
INFO:    arnoldlu psci_warmboot_entrypoint
INFO:    arnoldlu psci_cpu_suspend_finish
INFO:    arnoldlu stm32_pwr_domain_suspend_finish
[   25.991364] usb 2-1: reset high-speed USB device number 2 using ehci-platform
[   26.164146] OOM killer enabled.
[   26.165833] Restarting tasks ... done.
[   26.173797] PM: suspend exit
# [   26.226977] mmc1: Problem switching card into high-speed mode!

3 Linux中低功耗代码分析

3.1 psci初始化

psci_dt_init()读取dts中psci的配置,并调用对应版本的初始化函数。比如“arm,psci-1.0”:

psci_1_0_init()
  ->psci_0_2_init()
    ->get_set_conduit_method()--根据dts配置,确定使用smc还是hvc处理psci任务。       ->set_donduit()--如果是smc,invoke_psci_fn对应的函数位__invoke_psci_fn_smc。如果是hvc,则对应函数为__invoke_psci_fn_hvc。     ->psci_probe()
      ->psci_get_version()
      ->psci_0_2_set_functions()
      ->psci_init_migrate()
      ->psci_init_smcc()
      ->psci_init_cpu_suspend()
      ->psci_init_system_suspend()
        ->psci_init_system_suspend()
          ->suspend_set_ops()--配置suspend_ops指向psci_suspend_ops,建立suspend层和psci层关联。
      ->psci_init_system_reset2()

3.2 suspend之内核通用层

pm_suspend()
  ->enter_state()
    ->valid_state()--判断输入的state是否有效,只支持PM_SUSPEND_MEM。
    ->suspend_prepare()
    ->suspend_devices_and_enter()
      ->platform_suspend_begin()
      ->suspend_console
      ->dpm_suspend_start
      ->suspend_enter
        platform_suspend_prepare()
        dpm_suspend_late()
        platform_suspend_prepare_late()
        dpm_suspend_noirq
        platform_suspend_prepare_noirq
        suspend_disable_secondary_cpus
        arch_suspend_disable_irqs
        syscore_suspend
        suspend_ops->enter()
          ->psci_system_suspend_enter--以psci_system_suspend作为参数,在下面展开。
        syscore_resume
        arch_suspend_enable_irqs
        suspend_enable_secondary_cpus
        platform_resume_noirq
        dpm_resume_noirq
        platform_resume_early
        dpm_resume_early
        platform_resume_finish
      dpm_resume_end
      resume_console
    ->suspend_finish()

3.3 suspend之psci层(纽带:invoke_psci_fn)

suspend_set_ops设置suspend_ops为psci_suspend_ops:
static const struct platform_suspend_ops psci_suspend_ops = {
    .valid          = suspend_valid_only_mem,--仅支持PM_SUSPEND_MEM模式的suspend。
    .enter          = psci_system_suspend_enter,
};

static int psci_system_suspend_enter(suspend_state_t state)
{
    return cpu_suspend(0, psci_system_suspend);
}

static int psci_system_suspend(unsigned long unused)
{
    return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND),
                  __pa_symbol(cpu_resume), 0, 0);--从PL3返回PL1的入口地址为cpu_resume的物理地址。
}

和suspend相关的调用有PSCI_SYSTEM_SUSPEND_AARCH32。

psci_system_suspend_enter--以psci_system_suspend作为参数。
  ->cpu_suspend
    ->cpu_logical_map
    ->__cpu_suspend--位于sleep.S中。
      ->保存pgd、virt sp、phys resume fn。
      ->__cpu_suspend_save
        ->取如下3个地址作为参数:idmap_pgd的物理地址;sp指针;取cpu_do_resume(指向cpu_v7_do_resume)的物理地址。
        ->cpu_do_suspend--指向cpu_v7_do_suspend函数。         ->flush_cache_louis         ->__cpuc_flush_dcache_area         ->outer_clear_range
        <==================================
        1. 如果CPU断电情况的suspend,上述Cache flush处理方式不够完整。
        2. 如果在bl32中关闭了I/D Cache,会造成部分数据丢失。在恢复的时候造成程序执行异常。
        3. 较好的方式flush_cache_all(),然后再cpu_proc_fin()关闭I/D Cache。
        ==================================>
      ->psci_system_suspend
--在__cpu_suspend结尾跳转到r1传入的参数,即本函数。通过__pa_symbol获取cpu_resume的物理地址。         ->invoke_psci_fn--调用smc命令,传递fid参数SYSTEM_SUSPEND,返回指针为cpu_resume对应的物理地址。           ->__invoke_psci_fn_smc             ->arm_smcc_smc               ->__arm_smcc_smc--执行smc指令,进入PL3。
<=================================================================
1. 此后通过smc,进入Monitor模式进行低功耗处理。
2. 从Monitor返回后没有立即进入下面代码执行,而是先进入cpu_resume(即cpu_v7_do_resume)处理。
3. 返回到如下代码首先判断返回值是否为0,正确后执行resume流程,首先切换MMU。
=================================================================>     ->cpu_switch_mm
--指向cpu_v7_switch_mm。     ->local_flush_bp_all     ->local_flush_tlb_all     ->check_other_bugs       ->cpu_check_bugs         ->PROC_VTABLE(check_bugs)--指向cpu_v7_bugs_init函数。           ->cpu_v7_spectre_init

唤醒后的入口函数为cpu_resume:

cpu_resume
  ->根据保存的resume fn地址,即cpu_do_resume(指向cpu_v7_do_resume)。
  ->cpu_v7_do_resume
    ->cpu_resume_mmu
      ->cpu_resume_after_mm
        ->cpu_init
        ->写0到r0,表示返回值为0;

3.4 发起SMC系统调用:invoke_psci_fn

invoke_psci_fn第一个参数为SMC ID,后面跟上3个参数。

static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
            unsigned long arg0, unsigned long arg1,
            unsigned long arg2)
{
    struct arm_smccc_res res;

    arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
    return res.a0;
}

    .macro SMCCC_SMC
    __SMC(0)
    .endm

    .macro SMCCC_HVC
    __HVC(0)
    .endm

    .macro SMCCC instr
UNWIND(    .fnstart)
    mov    r12, sp
    push    {r4-r7}
UNWIND(    .save    {r4-r7})
    ldm    r12, {r4-r7}
    \instr
    pop    {r4-r7}
    ldr    r12, [sp, #(4 * 4)]
    stm    r12, {r0-r3}
    bx    lr
UNWIND(    .fnend)
    .endm

4 TFA低功耗代码分析

4.1 BL32启动流程

BL32向量表:

vector_base sp_min_vector_table
    b    sp_min_entrypoint
    b    plat_panic_handler    /* Undef */
    b    sp_min_handle_smc    /* Syscall */
    b    plat_panic_handler    /* Prefetch abort */
    b    plat_panic_handler    /* Data abort */
    b    plat_panic_handler    /* Reserved */
    b    plat_panic_handler    /* IRQ */
    b    sp_min_handle_fiq    /* FIQ */

BL32仅对Reset、SMC调用、FIQ进行了异常处理,plat_panic_handler为死循环。

  • sp_min_entrypoint为BL32的入口函数。
  • sp_min_handle_smc为BL32响应smc调用的处理函数。
  • sp_min_handle_fiq为BL32响应FIQ中断的处理函数。

4.2 冷启动流程注释(sp_min_entrypoint)

启动流程函数如下:

sp_min_entrypoint
  ->el3_entrypoint_common
  ->sp_min_early_platform_setup2--BL32中进行平台特定的操作。
    ->mmap_add_region--对代码段进行等地址映射。
    ->configure_mmu
      ->mmap_add--增加MMU映射区间。
      ->init_xlat_tables--初始化转换页表。
      ->enable_mmu_svc_mon--打开Monitor模式MMU配置。
  ->sp_min_plat_arch_setup
    ->setup_page_tables--配置MMU页表等。
    ->enable_mmu_svc_mon--使能Monitor下MMU。
  ->sp_min_main-进行平台和PSCI库设置,并初始化运行时服务。
    ->sp_min_platform_setup--初始化MMU、GIC、唤醒中断等等。
      ->ddr_save_sr_mode--进入当前DDR SelfRefresh模式。
      ->stm32mp1_security_setup--初始化TrustZone Controller。
      ->generic_delay_timer_init--初始化GenericTimer作为udelay/mdelay使用。
      ->stm32_gic_init
        ->gicv2_driver_init--检查GIC版本号,并将gicv2_driver_data_t赋给driver_data。
        ->gicv2_distif_init--ColdBoot主CPU进行GIC Distributor初始化。
        ->stm32_gic_pcpu_init--每CPU相关的GIC初始化。
          ->gicv2_pcpu_distif_init--每CPU相关Distributor初始化。
          ->gicv2_set_pe_target_mask
          ->gicv2_cpuif_enable--每CPU的Interface初始化。
      ->configure_wakeup_interrupt
        ->plat_ic_set_interrupt_priority
          ->gicv2_set_interrupt_priority
            ->gicd_set_ipriorityr--设置某一中断的优先级,此优先级高于Mask优先级则可以送达CPU,作为唤醒中断。
    ->runtime_svc_init--遍历rt_svc_descs段中的各个结构体init()函数。
    ->sp_min_prepare_next_image_entry--初始化BL33非安全CPU上下文,并将相关寄存器拷贝到smc上下文。
    ->sp_min_plat_runtime_setup
    ->console_flush
  ->clean_dcache_range(.data)
  ->clean_dcache_range(.bss)--清.data和.bss段DCache。
  ->smc_get_next_ctx--获取非安全上下文smc_ctx_t地址到r0寄存器中。
  ->sp_min_exit
    ->monitor_exit--退出到非安全世界。

4.2.1 sp_min_exit

退出sp_min,此时r0存放SMC上下文。sp_min_exit调用monitor_exit,将SMC上下文恢复。

sp_min_exit被如下函数调用:

  • sp_min_entrypoint:冷启动入口,进行必要初始化后主动退出sp_min,进入非安全启动。
  • sp_min_handle_smc:处理smc调用。因smc异常进入sp_min,处理完smc请求后,主动退出sp_min。
  • sp_min_handle_fiq:因fiq异常进入sp_min,在电泳sp_min_fiq处理fiq之后,主动退出sp_min。
  • sp_min_warm_entrypoint:在系统进入suspend之后,唤醒进入Warm Boot流程。在流程结束后主动退出sp_min。

详细流程分析如下:

  • 恢复scr、pmcr寄存器。
  • 恢复sp_usr、lr_usr,以及irq/firq/svc/abt/und/mon的spsr、sp、lr寄存器。
  • 恢复r0-r12寄存器。
  • 恢复lr_mon到lr寄存器,并执行eret退出sp_min到非安全环境。
    .macro monitor_exit
    str    sp, [r0, #SMC_CTX_SP_MON]
    mov    sp, r0--将当前sp保存到ctx的sp_mon中,并将r0写到sp寄存器。

    ldr    r1, [r0, #SMC_CTX_SCR]
    stcopr    r1, SCR--从ctx中恢复scr到scr寄存器。
    isb

    /*
     * Restore PMCR when returning to Non-secure state
     */
    tst    r1, #SCR_NS_BIT
    beq    2f

    ldr    r1, [r0, #SMC_CTX_PMCR]
    stcopr    r1, PMCR--恢复pmcr寄存器。
2:
    /* Restore the banked registers including the current SPSR */
    add    r1, r0, #SMC_CTX_SP_USR--r1指向sp_usr。

#if ARM_ARCH_MAJOR == 7 && !defined(ARMV7_SUPPORTS_VIRTUALIZATION)
    /* Must be in secure state to restore Monitor mode */
    ldcopr    r4, SCR
    bic    r2, r4, #SCR_NS_BIT
    stcopr    r2, SCR
    isb

    cps    #MODE32_sys
    ldm    r1!, {sp, lr}--切换到sys模式,将sp_usr和lr_usr写到寄存器sp和lr中。

    cps    #MODE32_irq--切换到irq模式,设置spsr_fsxc域、sp、lr寄存器。下面一次操作fq、svc、abt、und、mon模式下spsr_fsxc。
    ldm    r1!, {r2, sp, lr}--更新sp_irq、lr_irq到sp、lr中,spsr_irq到r2中。
    msr    spsr_fsxc, r2
...

    stcopr    r4, SCR
    isb
#else
#endif

    /* Restore the LR */
    ldr    lr, [r0, #SMC_CTX_LR_MON]--将lr_mon恢复到lr寄存器。

    /* Restore the rest of the general purpose registers */
    ldm    r0, {r0-r12}--更新ctx中r0-r12到r0-r12寄存器中。
    eret--eret即跳转到lr指向的地址。
    .endm

4.2.2 冷启动log

从冷启动log可以看出:

  • BL2中加载了BL32、BL33,从启动参数中获取信息准备好跳转到BL32的上下文信息,跳转到BL32。
  • BL32中进行RuntimeService初始化后,根据启动参数跳转到下一级BL33。
  • BL33从外设中读取Kernel镜像,跳转到Kernel。
  • 在内核启动PSCI初始化阶段,psci_probe()中进行4次SMC调用,获取PSCI是否支持CPU_SYSPEND/SYSTEM_SUSPEND/SYSTEM_RESET2。
  • 多核环境,调用CPU_ON启动其他核。
=====================================BL2=====================================
NOTICE:  CPU: STM32MP157DAA Rev.Z
...
INFO:    BL2: Loading image id 4--BL2加载BL32镜像。
INFO:    Loading image id=4 at address 0x2ffed000
INFO:    Image id=4 loaded: 0x2ffed000 - 0x2ffff000
INFO:    BL2: Loading image id 5--BL2加载BL33镜像。
INFO:    Loading image id=5 at address 0xc0100000
INFO:    STM32 Image size : 866980
INFO:    Image id=5 loaded: 0xc0100000 - 0xc01d3aa4
WARNING: Skip signature check (header option)
NOTICE:  ROTPK is not deployed on platform. Skipping ROTPK verification.
NOTICE:  BL2: Booting BL32--BL2跳转到BL32。
INFO:    Entry point address = 0x2ffed000
INFO:    SPSR = 0x1d3
INFO:    Cannot find st,stpmic1 node in DT
=====================================BL32=====================================
NOTICE:  SP_MIN: v2.2-r1.0(debug):a5d819a-dirty--进入BL32。
NOTICE:  SP_MIN: Built : 08:59:04, Jul 21 2023
...
INFO:    SP_MIN: Initializing runtime services--启动RuntimeService。
arnoldlu enter std_svc_setup
arnoldlu enter psci_arch_setup
arnoldlu enter stm32mp1_svc_setup
INFO:    SP_MIN: Preparing exit to normal world--退出BL32到非安全BL33。
...
arnoldlu enter smc_get_next_ctx
=====================================BL33=====================================
U-Boot 2020.01-stm32mp-r1-gd05f10ca-dirty (Nov 15 2022 - 09:20:59 +0800)--进入BL33,即uboot。
...
Starting kernel ...--BL33启动Linux kernel。
=====================================Kernel=====================================
INFO:    arnoldlu enter psci_smc_handler smc_fid=0x84000000 [r1:r3]=0x00000000 0x00000000 0x00000000
INFO:    arnoldlu exit psci_smc_handler--Kernel中调用psci_get_version()获取PSCI版本号。
INFO:    arnoldlu enter psci_smc_handler smc_fid=0x8400000a [r1:r3]=0x80000000 0x00000000 0x00000000
INFO:    arnoldlu exit psci_smc_handler--psci_init_smccc()检查PSCI是否支持ARM_SMCCC_VERSION_FUNC_ID。
INFO:    arnoldlu enter psci_smc_handler smc_fid=0x8400000a [r1:r3]=0x84000001 0x00000000 0x00000000
INFO:    arnoldlu exit psci_smc_handler--psci_init_cpu_suspend()检查PSCI是否支持PSCI_0_2_FN_CPU_SUSPEND。
INFO:    arnoldlu enter psci_smc_handler smc_fid=0x8400000a [r1:r3]=0x8400000e 0x00000000 0x00000000
INFO:    arnoldlu exit psci_smc_handler--psci_init_system_suspend()检查PSCI是否支持PSCI_1_0_FN_SYSTEM_SUSPEND。
INFO:    arnoldlu enter psci_smc_handler smc_fid=0x8400000a [r1:r3]=0x84000012 0x00000000 0x00000000
INFO:    arnoldlu exit psci_smc_handler--psci_init_system_reset2()检查PSCI是否支持PSCI_1_1_FN_SYSTEM_RESET2。
INFO:    arnoldlu enter psci_smc_handler smc_fid=0x84000003 [r1:r3]=0x00000001 0xc0102600 0x00000000--对于多核,psci_cpu_on调用PSCI_0_2_FN_CPU_ON启动其他核。
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] psci: probing for conduit method from DT.
[    0.000000] psci: PSCIv1.1 detected in firmware.
[    0.000000] psci: Using standard PSCI v0.2 function IDs
[    0.000000] psci: MIGRATE_INFO_TYPE not supported.
[    0.000000] psci: SMC Calling Convention v1.0
...

4.3 热启动流程(sp_min_warm_entrypoint)

sp_min_warm_entrypoint()为Warm Boot的入口函数:

sp_min_warm_entrypoint
  el3_entrypoint_common
  bl32_plat_enable_mmu--使能MMU,但是否打开DCACHE取决于配置。
    ->enable_mmu_svc_mon
      ->setup_mmu_cfg
      ->enable_mmu_direct_svc_mon(enable_mmu.S)
  ->sp_min_warm_boot
    ->psci_warmboot_entrypoint
      ->psci_cpu_suspend_finish
        ->psci_plat_pm_ops->pwr_domain_suspend_finish--即stm32_pwr_domain_suspend_finish。
        ->psci_set_suspend_pwrlvl
        ->cm_prepare_el3_exit
      ->psci_set_pwr_domains_to_run
    ->smc_set_next_ctx--空函数。
    ->copy_cpu_ctx_to_smc_stx--将regs_t内容拷贝到smc_ctx_t的r0/r1/r2/lr_mon/spsr_mon/scr中。
      ->cm_get_context
  ->smc_get_next_ctx--将当前CPU的smc_ctx_t地址放入到r0寄存器。
  ->sp_min_exit
    ->monitor_exit--根据smc_ctx_t恢复通用寄存器以及banked mode寄存器,从Monitor模式退出。

4.4 SMC注册

SMC Calling Convention (SMCCC) (arm.com)介绍了SMC相关定义,TFA通过DECLARE_RT_SVC()注册了SMC不同SMC ID的操作函数。

#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch)    \
    static const rt_svc_desc_t __svc_desc_ ## _name            \
        __section("rt_svc_descs") __used = {            \
            .start_oen = (_start),                \
            .end_oen = (_end),                \
            .call_type = (_type),                \
            .name = #_name,                    \
            .init = (_setup),                \
            .handle = (_smch)                \
        }

将定义的结构体放入rt_svc_descs段中:

SECTIONS
{
...
    ro . : {
        /* Ensure 4-byte alignment for descriptors and ensure inclusion */
        . = ALIGN(4);
        __RT_SVC_DESCS_START__ = .;
        KEEP(*(rt_svc_descs))
        __RT_SVC_DESCS_END__ = .;
    } >RAM
...
}

4.5 std_svc注册

std_svc对应OEN为4的调用,即Standard Secure Service Calls。

通过DECLARE_RT_SVC将结构体放入rt_svc_descs段中。

DECLARE_RT_SVC(
        std_svc,

        OEN_STD_START,
        OEN_STD_END,
        SMC_TYPE_FAST,
        std_svc_setup,
        std_svc_smc_handler
);

init函数为std_svc_setup(),在runtime_svc_init()中被调用。handle函数为std_svc_smc_handler(),在handle_runtime_svc()中被调用。

std_svc_setup()
  ->get_arm_std_svc_args()
  ->psci_setup()

初始化psci_args结构体变量,其中mailbox_ep指向sp_min_warm_entrypoint()。

4.6 PSCI初始化:psci_setup()

std_svc_setup()调用psci_setup()在BL32启动时对后续PSCI调用做好准备。

psci_setup()
  psci_arch_setup()
    write_cntfrq_el0(plat_get_syscnt_freq2())
    init_cpu_ops()--初始化,并获取(struct cpu_data)->cpu_ops_ptr,
    print_errata_status--输出ERRATA信息。
  plat_get_power_domaiin_tree_desc()--获取平台定义的电源域树。
  populate_power_domain_tree()--根据输入的电源域拓扑结构,生成BL32使用的数据结构。
    psci_init_pwr_domain_node--初始化psci_non_cpu_pd_nodes或者psci_cpu_pd_nodes。
  psci_update_pwrlvl_limits()--需要搞明白PowerDomainTree以及PowerLevel。
  psci_init_req_local_pwr_states()
  psci_set_pwr_domains_to_run()
  plat_setup_psci_ops()--将具体CPU的psci操作函数赋值给平台psci_plat_pm_ops使用,同时指定Warm启动入口函数。
  psci_flush_dcache_range()--刷psci_plat_pm_ops对应的dcache,以免多核需要使用。
  psci_caps--在psci_smc_handler()中会对SMC调用做检查,psci_caps提供判断标准。

psci_setup()函数调用plat_setup_psci_ops()将特定平台的plat_psci_ops_t函数集赋给psci_plat_pm_ops。psci_caps记录了psci_plat_pm_ops支持的函数能力。

STM32低功耗实现核心函数集:

static const plat_psci_ops_t stm32_psci_ops = {
    .cpu_standby = stm32_cpu_standby,
    .pwr_domain_on = stm32_pwr_domain_on,
    .pwr_domain_off = stm32_pwr_domain_off,
    .pwr_domain_suspend = stm32_pwr_domain_suspend,
    .pwr_domain_on_finish = stm32_pwr_domain_on_finish,
    .pwr_domain_suspend_finish = stm32_pwr_domain_suspend_finish,
    .pwr_domain_pwr_down_wfi = stm32_pwr_domain_pwr_down_wfi,
    .system_off = stm32_system_off,
    .system_reset = stm32_system_reset,
    .validate_power_state = stm32_validate_power_state,
    .validate_ns_entrypoint = stm32_validate_ns_entrypoint,
    .get_node_hw_state = stm32_node_hw_state,
    .get_sys_suspend_power_state = stm32_get_sys_suspend_power_state,
};

deepest_system_suspend_mode和system_off_mode在stm32mp157d-atk.dtsi中进行配置,为:

    system_suspend_supported_soc_modes = <
        STM32_PM_CSLEEP_RUN
        STM32_PM_CSTOP_ALLOW_LP_STOP
//        STM32_PM_CSTOP_ALLOW_LPLV_STOP
//        STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR
    >;
    system_off_soc_mode = <STM32_PM_CSTOP_ALLOW_STANDBY_DDR_OFF>;

stm32mp1_get_lp_soc_mode()根据psci_mode返回stm32的低功耗模式,分别从stm32mp157d-atk.dtsi中获取。

4.7 CPU操作函数集cpu_ops:复位和下电操作

declare_cpu_ops定义了cpu_ops段内容,里面包含MIDR以及一系列操作函数。

declare_cpu_ops cortex_a7, CORTEX_A7_MIDR, \
    cortex_a7_reset_func, \
    cortex_a7_core_pwr_dwn, \
    cortex_a7_cluster_pwr_dwn

    .macro declare_cpu_ops _name:req, _midr:req, _resetfunc:req, \
        _power_down_ops:vararg
    .section cpu_ops, "a"
    .align 2
    .type cpu_ops_\_name, %object
    .word \_midr
#if defined(IMAGE_AT_EL3)
    .word \_resetfunc
#endif
#ifdef IMAGE_BL32
    /* Insert list of functions */
    fill_constants CPU_MAX_PWR_DWN_OPS, \_power_down_ops
#endif
...
    .endm

对于A7,最终输出结果为:

800090ac <__CPU_OPS_START__>:
800090ac:    410fc070     .word    0x410fc070--A7 MIDR
800090b0:    80008588     .word    0x80008588--cortex_a7_reset_func
800090b4:    8000858c     .word    0x8000858c--cortex_a7_core_pwr_dwn
800090b8:    800085a0     .word    0x800085a0--cortex_a7_cluster_pwr_dwn
800090bc <__CPU_OPS_END__>:

cortex_a7_reset_func

cortex_a7_core_pwr_dwn

cortex_a7_cluster_pwr_dwn

func cortex_a7_cluster_pwr_dwn
    push    {r12, lr}
    assert_cache_enabled--此时确保cache已经关闭。
    mov    r0, #DC_OP_CISW
    bl    dcsw_op_level1--刷L1 Cache。
    bl    plat_disable_acp--对于A7来说为空函数。
    /* Exit cluster coherency */
    pop    {r12, lr}
    b    cortex_a7_disable_smp--对ACTLR.SMP位清0,关闭到处理器的一致性其请求。
endfunc cortex_a7_cluster_pwr_dwn

4.8 SMC处理流程

smc_ctx_t为SMC调用上下文结构体,再进入SMC处理函数时需要将一系列寄存器保存,返回的时候恢复。

smc_ctx_t结构体如下:

typedef struct smc_ctx {
    u_register_t r0;
...
    u_register_t r12;
    /* spsr_usr doesn't exist */
    u_register_t sp_usr;
    u_register_t lr_usr;
...
    u_register_t spsr_mon;
    /*
     * `sp_mon` will point to the C runtime stack in monitor mode. But prior
     * to exit from SMC, this will point to the `smc_ctx_t` so that
     * on next entry due to SMC, the `smc_ctx_t` can be easily accessed.
     */
    u_register_t sp_mon;
    u_register_t lr_mon;
    u_register_t scr;
    u_register_t pmcr;
    u_register_t pad;
} smc_ctx_t __aligned(8);

sp_min_handle_smc进行smc调用处理:

func sp_min_handle_smc
    str    lr, [sp, #SMC_CTX_LR_MON]--将lr寄存器保存到smc_ctx_t中。
    smccc_save_gp_mode_regs--保存r0-r12/spsr/lr/sp/scr寄存器到SMC context中。
    clrex_on_monitor_entry--执行clrex指令,清除exclusive access。
    /*
     * `sp` still points to `smc_ctx_t`. Save it to a register
     * and restore the C runtime stack pointer to `sp`.
     */
    mov    r2, sp    /* handle */
    ldr    sp, [r2, #SMC_CTX_SP_MON]--从ctx中取Monitor SP到sp寄存器中。

    ldr    r0, [r2, #SMC_CTX_SCR]
    and    r3, r0, #SCR_NS_BIT    /* flags */

    /* Switch to Secure Mode*/
    bic    r0, #SCR_NS_BIT
    stcopr    r0, SCR--设置SCR.NS位,切换到安全状态。
    isb

    ldr    r0, [r2, #SMC_CTX_GPREG_R0]    /* smc_fid */--R0寄存器存放SMC调用的ID。
    /* Check whether an SMC64 is issued */
    tst    r0, #(FUNCID_CC_MASK << FUNCID_CC_SHIFT)--SMC ID的bit30表示32位还是64位,0为32位。
    beq    1f
    /* SMC32 is not detected. Return error back to caller */
    mov    r0, #SMC_UNK
    str    r0, [r2, #SMC_CTX_GPREG_R0]
    mov    r0, r2
    b    sp_min_exit--退出Monitor模式,返回smc调用之前模式。
1:
    mov    r1, #0    /* cookie */
    bl    handle_runtime_svc--进入SMC命令处理。

    /* `r0` points to `smc_ctx_t` */
    b    sp_min_exit--退出Monitor模式,返回smc调用之前模式。
endfunc sp_min_handle_smc

handle_runtime_svc()第一个参数为SMC Function ID。

uintptr_t handle_runtime_svc(uint32_t smc_fid,
         void *cookie,
         void *handle,
         unsigned int flags)
{
    u_register_t x1, x2, x3, x4;
    unsigned int index;
    unsigned int idx;
    const rt_svc_desc_t *rt_svc_descs;

    assert(handle != NULL);
    idx = get_unique_oen_from_smc_fid(smc_fid);--根据OEN和Call type确定rt_svc_descs_indices[]中序号。
    assert(idx < MAX_RT_SVCS);

    index = rt_svc_descs_indices[idx];--确定对应smc_fid在rt_svc_descs段中的序号。
    if (index >= RT_SVC_DECS_NUM)
    SMC_RET1(handle, SMC_UNK);

    rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;--rt_svc_descs段的起始地址。

    get_smc_params_from_ctx(handle, x1, x2, x3, x4);--获取SMC上下文中的x1/x2/x3/x4寄存器。

    return rt_svc_descs[index].handle(smc_fid, x1, x2, x3, x4, cookie,
    handle, flags);--根据index和rt_svc_descs找到smc_fid对应的结构体,然后找到handler函数,将获取的smc_fid/x1/x2/x3/x4作为参数输入。
}

4.9 PSCI命令分发处理:std_svc_smc_handler()->psci_smc_handler()

std_svc_smc_hander()是SMC异常处理入口函数,首先处理PSCI请求,然后在处理安全请求。

static uintptr_t std_svc_smc_handler(uint32_t smc_fid,
         u_register_t x1,
         u_register_t x2,
         u_register_t x3,
         u_register_t x4,
         void *cookie,
         void *handle,
         u_register_t flags)
{
    if (is_psci_fid(smc_fid)) {--psci的ID范围是0x84000000-0x8400001f,如果再次范围则进行PSCI处理。
    uint64_t ret;
    ret = psci_smc_handler(smc_fid, x1, x2, x3, x4,
        cookie, handle, flags);
    SMC_RET1(handle, ret);
    }

    switch (smc_fid) {--进行Standard Secure Service处理。
...
    }
}

其中psci_smc_handler()函数对PSCI协议中规定的功能进行处理。Linux内核函数、PSCI ID、TF-A函数对应关系:

 

Linux Caller(psci_ops)

SMC FID

TFA Handler

 

psci_get_version

PSCI_VERSION

psci_version()

psci_ops

psci_ops.cpu_off = psci_cpu_off

PSCI_CPU_OFF

psci_cpu_off()
  -->psci_do_cpu_off()
    -->psci_plat_pm_ops->pwr_domain_off
    -->psci_plat_pm_ops->pwr_domain_pwr_down_wfi/psci_power_down_wfi

psci_ops.cpu_suspend = psci_cpu_suspend

PSCI_CPU_SUSPEND_AARCH32

psci_cpu_suspend(r1, r2, r3)
  -->psci_validate_power_state
    -->psci_plat_pm_ops->validate_power_state
  -->psci_validate_entry_point
    -->psci_plat_pm_ops->validate_ns_entrypoint
  -->psci_cpu_suspend_start
    -->psci_suspend_to_pwrdown_start
    -->psci_plat_pm_ops->pwr_domain_suspend
    -->psci_plat_pm_ops->pwr_domain_pwr_down_wfi
    -->psci_suspend_to_standby_finisher
      -->psci_plat_pm_ops->pwr_domain_suspend_finish

psci_ops.cpu_on = psci_cpu_on

PSCI_CPU_ON_AARCH32

psci_cpu_on(r1, r2, r3)
  -->psci_validate_entry_point
    -->psci_plat_pm_ops->validate_ns_entrypoint
  -->psci_cpu_on_start()
    -->psci_plat_pm_ops->pwr_domain_on

psci_ops.affinity_info = psci_affinity_info

PSCI_AFFINITY_INFO_AARCH32

psci_affinity_info(r1, r2)

psci_ops.migrate = psci_migrate

PSCI_MIG_AARCH32

psci_migrate(r1)

psci_ops.migrate_info_type = psci_migrate_info_type

PSCI_MIG_INFO_TYPE

psci_migrate_info_type()

 

psci_migrate_info_up_cpu

PSCI_MIG_INFO_UP_CPU_AARCH32

psci_migrate_info_up_cpu()

   

PSCI_NODE_HW_STATE_AARCH32

psci_node_hw_state(r1, r2)
  -->psci_plat_pm_ops->get_node_hw_state

 

psci_system_suspend

PSCI_SYSTEM_SUSPEND_AARCH32

psci_system_suspend(r1, r2)
  -->psci_validate_entry_point
    -->psci_plat_pm_ops->validate_ns_entrypoint
  -->psci_query_sys_suspend_pwrstate
    -->psci_plat_pm_ops->get_sys_suspend_power_state
  -->psci_cpu_suspend_start
    -->psci_suspend_to_pwrdown_start
    -->psci_plat_pm_ops->pwr_domain_suspend
    -->psci_plat_pm_ops->pwr_domain_pwr_down_wfi
    -->psci_suspend_to_standby_finisher
      -->psci_plat_pm_ops->pwr_domain_suspend_finish

pm_power_off

psci_sys_poweroff

PSCI_SYSTEM_OFF

psci_system_off()
  -->psci_plat_pm_ops->system_off
  -->stm32_pwr_down_wfi

arm_pm_restart

psci_sys_reset

PSCI_SYSTEM_RESET

psci_system_reset()
  -->psci_plat_pm_ops->system_reset

 

psci_features

PSCI_FEATURES

psci_features(r1)

arm_pm_restart

psci_sys_reset

PSCI_SYSTEM_RESET2_AARCH32

psci_system_reset2(r1, r2)
  -->psci_plat_pm_ops->system_reset2

     

sp_min_warm_boot
  -->psci_warmboot_entrypoint
    -->psci_cpu_on_finish
      -->psci_plat_pm_ops->pwr_domain_on_finish
    -->psci_cpu_suspend_finish
      -->psci_plat_pm_ops->pwr_domain_suspend_finish

4.10 TFA suspend流程

TFA的psci_system_suspend负责更底层的suspend流程。

psci_smc_handler
  psci_system_suspend(PSCI_SYSTEM_SUSPEND_AARCH32)--确认是最后一个CPU,才会进入system suspend。
    psci_validate_entry_point
      stm32_validate_ns_entrypoint--判断entrypoint地址是否有效。
    psci_get_ns_ep_info--记录pc、spsr、args、ep_attr等到ep中。
  psci_query_sys_suspend_pwrstate
    stm32_get_sys_suspend_power_state
  psci_cpu_suspend_start
    psci_get_parent_pwr_domain_nodes
    psci_acquire_pwr_domain_locks
    psci_do_state_coordination
    read_isr_el1--读取CP15 ISR寄存器,获取A/I/F三种异常是否有Pending。判断是否有pending中断,如果有则停止后续的wfi。
    psci_suspend_to_pwrdown_start
      cm_init_my_context--获取当前CPU的cpu_context_t并初始化,为后续返回非安全状态做准备。
        cm_setup_context--保存r0-r3/lr/scr/spsr/sctlr等寄存器。
    psci_plat_pm_ops->pwr_domain_suspend--stm32_pwr_domain_suspend。
    stm32mp1_get_lp_soc_mode
    stm32_enter_low_power
      enter_cstop--低功耗设置。
        ddr_set_sr_mode--进入DDR SSR模式。
        stm32_save_ddr_training_area--保存DDR training数据。
        plat_ic_set_priority_mask--保存GIC mask寄存器到gicc_pmr。
        ddr_standby_sr_entry--
          ddr_sw_self_refresh_in
    psci_plat_pm_ops->pwr_domain_pwr_down_wfi--stm32_pwr_domain_pwr_down_wfi。
      stm32_pwr_down_wfi
        stm32mp1_calib_set_wakeup
        wfi_svc_int_enable
        --------------------->KEY0/KEY1 Press,之前为suspend流程,之后为resume流程。
        gicv2_acknowledge_interrupt--读到的中断ID为1023
        stm32_iwdg_refresh--重新喂狗。
      stm32_exit_cstop
        ddr_sw_self_refresh_exit--DDR退出SW SelfRefresh模式。
        ddr_restore_sr_mode--返回到保存的SR模式:SSR(Software Self-Refresh)、ASR(Automatic Self-Refresh)、FSR(Full Automatic Self-Refresh)。
        plat_ic_set_priority_mask--恢复gicc pmr寄存器值。
      disable_mmu_icache_secure
      <==================================================
      1. 关闭MMU、I/D Cache并不会对Cache Flush。
      2. 如果wfi之后对CPU断电,Cache中数据会丢失。唤醒后,可能造成数据不一致。
      3. 需要在此之前Flush D-Cache。
      ==================================================>       warm_entrypoint
--即stm32_sec_entrypoint,这里对应函数为sp_min_warm_entrypoint。     wfi     psci_suspend_to_standby_finisher       psci_plat_pm_ops->pwr_domain_suspend_finish--stm32_pwr_domain_suspend_finish

stm32_sec_entrypoint指向psci_args->mailbox_ep,psci_args->mailbox_ep指向sp_min_warm_entrypoint():完成suspend处理,然后恢复上下文退出pl3到Linux中执行Linux的Resume流程。

plat_my_core_pos获取当前core id序号,core id用于唯一标识一个Core。

func plat_stm32mp1_get_core_pos
    and    r1, r0, #MPIDR_CPU_MASK
    and    r0, r0, #MPIDR_CLUSTER_MASK
    add    r0, r1, r0, LSR #6--r1存放CoreId,r0存放ClusterId。r0右移6位,加上r1等同于ClusterID*4+CoreId。
    bx    lr
endfunc plat_stm32mp1_get_core_pos

func plat_my_core_pos
    ldcopr    r0, MPIDR--从CP15协处理器中读取MPIDR值到r0中。
    b    plat_stm32mp1_get_core_pos
endfunc plat_my_core_pos

wfi_svc_int_enable()将sp_mon和scr保存到r0/r4中,切换到svc模式打开DataAbort和FIQ进入wfi。从wfi恢复后关闭DataAbort和FIQ使能,将r8恢复到sp中。跳转到lr中地址。

func wfi_svc_int_enable
    push    {r4,r8,lr}--将r4/r8/lr压栈。
    ldcopr    r4, SCR--将SCR值写入r4。
    mov    r8, sp--将sp移到r8。
    mov    sp, r0--将r0指向的地址写入到sp,sp即使用第一个参数作为栈。
    add    r0, r0, #STM32MP_INT_STACK_SIZE--r0指向栈顶。
    str    r0, [sp, #SMC_CTX_SP_MON]--将smc_ctx_t的sp_mon写入到r0中。
    str    r4, [sp, #SMC_CTX_SCR]--将smc_ctx_t的SCR写入到r4中。
    cps    #MODE32_svc--切换处理器模式到SVC。
    cpsie    af--打开Data Abort和FIQ的使能。
    dsb
    isb
    wfi
    cpsid    af--关闭Data Abort和FIQ的使能。
    cps    #MODE32_mon--切换到Monitor模式。
    mov    sp, r8
    pop    {r4,r8,lr}--恢复栈,r4/r8和lr寄存器。
    bx    lr
endfunc wfi_svc_int_enable

5 Poweroff流程

poweroff执行log:

# Stopping network: OK
Saving random seed: OK
Stopping mdev... stopped process in pidfile '/var/run/mdev.pid' (pid 188)
OK
Stopping klogd: OK
Stopping syslogd: OK
umount: devtmpfs busy - remounted read-only
[   14.605608] EXT4-fs (mmcblk1p5): re-mounted. Opts: (null)
The system is going down NOW!
Sent SIGTERM to all processes
Sent SIGKILL to all processes
Requesting system poweroff
[   16.669279] reboot: Power down
INFO:    arnoldlu psci_smc_handler smc_fid=0x84000008 [r1:r3]=0x00000000 0x00000000 0x00000000
INFO:    arnoldlu psci_system_off
INFO:    PSCI Power Domain Map:
INFO:      Domain Node : Level 1, parent_node -1, State ON (0x0)
INFO:      Domain Node : Level 0, parent_node 0, State ON (0x0)
INFO:      CPU Node : MPID 0x0, parent_node 0, State ON (0x0)
INFO:      CPU Node : MPID 0xffffffff, parent_node 0, State OFF (0x2)
INFO:    arnoldlu stm32_system_off
INFO:    arnoldlu stm32_enter_low_power, mode=0x00000005, nsec_addr=0x00000000
INFO:    arnoldlu enter_cstop-133
INFO:    arnoldlu stm32_pwr_down_wfi-338
INFO:    arnoldlu stm32_pwr_down_wfi-343

Linux执行poweroff命令后,调用reboot系统调用:

reboot
  kernel_power_off
    kernel_shutdown_prepare
      blocking_notifier_call_chain--通知所有reboot_notifier_list上所有成员SYS_HALT或者SYS_POWER_OFF事件发生。
      usermodehelper_disable--关闭从内核空间启动用户空间程序的机制。
      device_shutdown--遍历所有device_kset->list上成员,调用有则一次调用class->shutdown_pre、bus->shutdown、driver->shutdown函数。
    pm_power_off_prepare--如果注册,则调用自定函数。
    migrate_to_reboot_cpu
      cpu_hotplug_disable--关闭CPU hotplug功能。
      cpu_online--确保reboot_cpu处于online状态,后续使用此CPU执行reboot流程。
    syscore_shutdown--调用syscore_ops_list成员的shutdown()函数。     machine_power_off       local_irq_disable       smp_send_stop         pm_power_off
--使能PSCI后,即调用psci_sys_poweroff,通过SMC发起SYSMTEM_OFF调用。

BL32中处理SYSRTEM_OFF的流程:

std_svc_smc_handler
  psci_smc_handler
    psci_system_off(PSCI_SYSTEM_OFF)
      psci_plat_pm_ops->system_off--stm32_system_off
        stm32_enter_low_power
          enter_cstop
        stm32_pwr_down_wfi

6 Reboot

执行reboot命令后,首先Linux内核执行reboot流程,然后将reboot交给BL32。

Linux reboot log如下:

Stopping network: OK
Saving random seed: OK
Stopping mdev... stopped process in pidfile '/var/run/mdev.pid' (pid 188)
OK
Stopping klogd: OK
Stopping syslogd: OK
umount: devtmpfs busy - remounted read-only
[  440.664728] EXT4-fs (mmcblk1p5): re-mounted. Opts: (null)
The system is going down NOW!
Sent SIGTERM to all processes
Sent SIGKILL to all processes
Requesting system reboot
[  442.779231] mmc2: switch to bus width 8 failed
[  442.802316] reboot: Restarting system

Linux reboot命令流程分析:

reboot--系统调用。
  kernel_restart
    kernel_restart_prepare
    migrate_to_reboot_cpu
    syscore_shutdown
    machine_restart
      local_irq_disable
      smp_send_stop
      arm_pm_restart--使能PSCI后即调用psci_sys_reset,触发SMC调用进入BL32。

BL32中PSCI的处理流程:

std_svc_smc_handler
  psci_smc_handler
    psci_system_reset(PSCI_SYSTEM_RESET)
      psci_plat_pm_ops->system_reset--stm32_system_reset写平台相关的复位寄存器。

7 FIQ处理

 FIQ中断入口函数为sp_min_handle_fiq:

/*
 * Secure Interrupts handling function for SP_MIN.
 */
func sp_min_handle_fiq
#if !SP_MIN_WITH_SECURE_FIQ
    b plat_panic_handler
#else
    /* FIQ has a +4 offset for lr compared to preferred return address */
    sub    lr, lr, #4
    /* On SMC entry, `sp` points to `smc_ctx_t`. Save `lr`. */
    str    lr, [sp, #SMC_CTX_LR_MON]

    smccc_save_gp_mode_regs

    clrex_on_monitor_entry

    /* load run-time stack */
    mov    r2, sp
    ldr    sp, [r2, #SMC_CTX_SP_MON]

    /* Switch to Secure Mode */
    ldr    r0, [r2, #SMC_CTX_SCR]
    bic    r0, #SCR_NS_BIT
    stcopr    r0, SCR
    isb

    push    {r2, r3}
    bl    sp_min_fiq
    pop    {r0, r3}

    b    sp_min_exit
#endif
endfunc sp_min_handle_fiq

sp_min_firq进行安全中断处理:

sp_min_fiq
  plat_ic_acknowledge_interrupt--读取中断号。
  sp_min_plat_fiq_handler--根据中断号进行中断处理。
  plat_ic_end_of_interrupt--中断EOI响应。

8 PSCI Power Domain Tree Structure

5. PSCI Power Domain Tree Structure》定义了TFA中使用的Power Domain Tree。

5.1 Requirements

Requirement 1: 平台提供plat_get_aff_count和plat_get_aff_state接口,以达到导出电源域架构的描述。

Requirement 2: 支持生成MPIDR,用于找到Power Domain Tree中的节点。

Requirement 3: 需要在Power Domain Tree中进行二进制搜索以找到特定的节点。

Requirement 4: Core电源域属性和上一层电源域属性有差异。

5.2  Design

5.2.1. Describing a power domain tree

为了满足Requirement 1,必须定义unsigned char数组:

  • 数组第一成员确定highest power level电源域数目。
  • 每个子条目关联一个电源域,并且包含直接子电源域数目。
  • 数组大小减去第一个条目等于非叶节点电源域数目。
  • 数组中每条目的值用于查找下一级条目的数目。
                               +-+
                               |0|-----------------------------level0
                               +-+
                              /   \
                             /     \
                            /       \
                           /         \
                          /           \
                         /             \
                        /               \
                       /                 \
                      /                   \
                     /                     \
                  +-+                       +-+
                  |1|                       |2|----------------level1
                  +-+                       +-+
                 /   \                     /   \
                /     \                   /     \
               /       \                 /       \
              /         \               /         \
           +-+           +-+         +-+           +-+
           |3|           |4|         |5|           |6|---------level2
           +-+           +-+         +-+           +-+
  +---+-----+    +----+----|     +----+----+     +----+-----+-----+
  |   |     |    |    |    |     |    |    |     |    |     |     |
  |   |     |    |    |    |     |    |    |     |    |     |     |
  v   v     v    v    v    v     v    v    v     v    v     v     v
+-+  +-+   +-+  +-+  +-+  +-+   +-+  +-+  +-+   +-+  +--+  +--+  +--+
|0|  |1|   |2|  |3|  |4|  |5|   |6|  |7|  |8|   |9|  |10|  |11|  |12|---level3

上述电源域树对应的描述符为:{1(number of highest power level), 2(level 0 node 0 has 2 subsequent), 2(level 1 node 1 has 2 subsequent), 2(level1 node 2 has 2 subsequent), 3(level 2 node 3 has 3 subsequent), 3(level 2 node 4 has 3 subsequent), 3(level 2 node 5 has 3 subsequent), 4(level 2 node 6 has 4 subsequent)}。

最终Core数量为13,电源域数量为13+7=20。

5.2.2. Removing assumptions about MPIDRs used in a platform

对每一个Core电源域分配一个唯一的值:从0~(PLAT_CORE_COUNT - 1)。

plat_core_pos_by_mpidr:根据MPIDR返回Cluster中Core  ID。

plat_my_core_pos:平台定义的函数。如A7,返回的ID=Cluster*4+Core ID。

psci_setup()中根据plat_get_power_domain_tree_desc()获取的电源域树描述符,生成psci_non_cpu_pd_nodes和psci_cpu_pd_nodes

{1, 2}对应的拓扑结构如下:

                               +-+
                               |0|-----------------------------level1
                               +-+
                              /   \
                             /     \
                            /       \
                           /         \
                          /           \
                         /             \
                        /               \
                       /                 \
                      /                   \
                     /                     \
                  +-+                       +-+
                  |0|                       |1|----------------level0(CPU Core)
                  +-+                       +-+

需要定义的相关变量有:

#define PLAT_MAX_PWR_LVL    U(1)
#define PSCI_CPU_PWR_LVL    U(0)--CPU对应的Power Level。
#define PSCI_NUM_PWR_DOMAINS--当前架构中所有电源域数量。
#define PSCI_NUM_NON_CPU_PWR_DOMAINS--当前架构中所有非CPU Core电源域数量。
#define PLATFORM_CORE_COUNT--当前架构中CPU Core数量。

typedef struct non_cpu_pwr_domain_node {
    unsigned int cpu_start_idx;--当前节点的Level0 CPU电源域节点序号。
    unsigned int ncpus;--当前节点CPU电源域数量。
    unsigned int parent_node;--当前电源域父节点。
    plat_local_state_t local_state;--当前电源域所处低功耗状态。
    unsigned char level;--当前电源域Power Level。
    unsigned char lock_index;
} non_cpu_pd_node_t;

typedef struct cpu_pwr_domain_node {
    u_register_t mpidr;--当前CPU Core MPIDR。
    unsigned int parent_node;--父节点的序号。
    spinlock_t cpu_lock;
} cpu_pd_node_t;

non_cpu_pd_node_t psci_non_cpu_pd_nodes[PSCI_NUM_NON_CPU_PWR_DOMAINS]--非CPU电源域节点信息。
cpu_pd_node_t psci_cpu_pd_nodes[PLATFORM_CORE_COUNT]--CPU Core电源域节点信息。

const plat_psci_ops_t *psci_plat_pm_ops;--平台相关低功耗函数集。

5.2.4. Populating the power domain tree

populate_power_domain_tree()根据psci_non_cpu_pd_nodes和psci_cpu_pd_nodes导出电源域树。

PSCI的power domain被组织为一个树形结构,每个power domain通过域中cpu序号和power domain等级确定。

关于power domain等级:

  • 0 处理实体,一般为CPU核。
  • 1 一组CPU核,一个cluster。
  • 2 一组cluster,表示整个系统。

关于PSCI Power Domain《5. PSCI Power Domain Tree Structure — Trusted Firmware-A documentation》。

9 TFA PSCI Porting Guide

以下内容来自于《4.13 Power State Coordination Interface(in BL31) — Trusted Firmware-A documentation》。

4.13.5. Function : plat_get_power_domain_tree_desc() [mandatory]

4.13.6. Function : plat_setup_psci_ops() [mandatory]

给psci_plat_pm_ops指定平台相关的函数集。

int plat_setup_psci_ops(uintptr_t sec_entrypoint,
            const plat_psci_ops_t **psci_ops)
{
    stm32_sec_entrypoint = sec_entrypoint;
    *psci_ops = &stm32_psci_ops;

    return 0;
}

4.13.6.1. plat_psci_ops.cpu_standby()

让当前CPU进入指定的plat_local_state_t状态。

4.13.6.2. plat_psci_ops.pwr_domain_on()

对指定MPIDR Core执行平台特定上电流程。

4.13.6.7. plat_psci_ops.pwr_domain_on_finish()

在调用CPU被CPU_ON之后,且释放复位之后调用。执行必要平台相关初始化,以进入normal world和提供安全运行服务。

4.13.6.3. plat_psci_ops.pwr_domain_off()

被PSCI_CPU_OFF调用,根据输入的psci_power_state_t关闭调用CPU及其上层电源域。

4.13.6.5. plat_psci_ops.pwr_domain_suspend()

PSCI CPU_SUSPEND命令调用此函数。

suspend和关闭power domain区别是:

关闭power domain后,在重新上电后需要重新初始化状态。通过pwr_domain_on_finish()实现。

suspend时,重新上电后需要恢复之前保存的状态。通过pwr_domain_suspend_finish()实现。

4.13.6.9. plat_psci_ops.pwr_domain_suspend_finish()

4.13.6.14. plat_psci_ops.get_sys_suspend_power_state()

填充psci_power_state_t结构体,返回系统支持的不同PLAT_MAX_PWR_LVL对应的本地power状态。可选的为:

/* Local power state for power domains in Run state. */
#define ARM_LOCAL_STATE_RUN    U(0)
/* Local power state for retention. Valid only for CPU power domains */
#define ARM_LOCAL_STATE_RET    U(1)
/* Local power state for power-down. Valid for CPU and cluster power domains */
#define ARM_LOCAL_STATE_OFF    U(2)

4.13.6.6. plat_psci_ops.pwr_domain_pwr_down_wfi()

4.13.6.10. plat_psci_ops.system_off()

在通知Secure Payload Dispatcher后,执行平台特定的power off流程。

4.13.6.11. plat_psci_ops.system_reset()

在通知Secure Payload Dispatcher后,执行平台特定的复位流程。

4.13.6.12. plat_psci_ops.validate_power_state()

4.13.6.13. plat_psci_ops.validate_ns_entrypoint()

验证非安全入口函数是否合法。

4.13.6.17. plat_psci_ops.get_node_hw_state()

posted on 2024-06-01 23:59  ArnoldLu  阅读(1037)  评论(0编辑  收藏  举报

导航