cpu_ops、suspend_ops、arm_idle_driver以及machine_restart/machine_power_off到底層PSCI Firmware分析


在內核中針對的cpu的操作,比如arm_cpuidle_init、arm_cpuidle_suspend、boot_secondary、secondary_start_kernel、op_cpu_disable、op_cpu_kill、cpu_die、smp_cpu_setup、smp_prepare_cpus的都會回落到對cpu_ops的調用。

cpu_ops將針對底層cpu的操作抽象為一系列回調函數,以統一的形式向上層提供API。

cpu_psci_ops作為cpu_ops的一個特殊實現,將cpu_ops關聯到PSCI的psci_ops。

psci_ops的函數在PSCI Firmware中實現,提供一系列基於Function ID的調用。

這種分層思想將內核通用cpu_operations和硬件相關部分分隔開。

image

從上圖可知,cpu_ops和suspend_ops將內核通用API和底層arch-specific代碼區隔開;cpu_ops和suspend_ops分別調用cpu_psci_ops和psci_suspend_ops,這些回調函數最終都會回落到PSCI Firmware提供的接口。machine_restart/machine_power_off直接調用PSCI提供的接口。

cpu_operations及應用場景

首先分析一些cpu_operations這個結構體:

struct cpu_operations {
    const char    *name;
    int        (*cpu_init)(unsigned int);  讀取必要的數據准備初始化。
    int        (*cpu_prepare)(unsigned int);  啟動前准備工作
    int        (*cpu_boot)(unsigned int);  啟動一個CPU
    void        (*cpu_postboot)(void);  執行boot后的清理工作
#ifdef CONFIG_HOTPLUG_CPU
    int        (*cpu_disable)(unsigned int cpu);  關閉CPU之前的准備工作
    void        (*cpu_die)(unsigned int cpu);  關閉CPU
    int        (*cpu_kill)(unsigned int cpu);  確認是否關閉
#endif
#ifdef CONFIG_CPU_IDLE
    int        (*cpu_init_idle)(unsigned int);  讀取CPU idle狀態的參數
    int        (*cpu_suspend)(unsigned long);  suspend一個CPU,並且保存上下文
#endif
};

cpu_init

static int __init smp_cpu_setup(int cpu)
{
    if (cpu_read_ops(cpu))
        return -ENODEV;

    if (cpu_ops[cpu]->cpu_init(cpu))
        return -ENODEV;

    set_cpu_possible(cpu, true);

    return 0;
}

獲取指定cpu的cpu_ops,執行cpu_init回調函數進行初始化。並將此cpu設置為possible。

cpu_prepare

void __init smp_prepare_cpus(unsigned int max_cpus)
{
    int err;
    unsigned int cpu, ncores = num_possible_cpus();

    init_cpu_topology();  填充cpu_topology結構體數組

    smp_store_cpu_info(smp_processor_id());

    /*
     * are we trying to boot more cores than exist?
     */
    if (max_cpus > ncores)  不能超過possible cpu數目
        max_cpus = ncores;

    /* Don't bother if we're effectively UP */
    if (max_cpus <= 1)
        return;

    /*
     * Initialise the present map (which describes the set of CPUs
     * actually populated at the present time) and release the
     * secondaries from the bootloader.
     *
     * Make sure we online at most (max_cpus - 1) additional CPUs.
     */
    max_cpus--;
    for_each_possible_cpu(cpu) {
        if (max_cpus == 0)
            break;

        if (cpu == smp_processor_id())
            continue;

        if (!cpu_ops[cpu])
            continue;

        err = cpu_ops[cpu]->cpu_prepare(cpu);  執行.cpu_prepare回調函數,將指定cpu設置為present。
        if (err)
            continue;

        set_cpu_present(cpu, true);
        max_cpus--;
    }
}

cpu_boot

static int boot_secondary(unsigned int cpu, struct task_struct *idle)
{
    if (cpu_ops[cpu]->cpu_boot)
        return cpu_ops[cpu]->cpu_boot(cpu);

    return -EOPNOTSUPP;
}

cpu_postboot

asmlinkage void secondary_start_kernel(void)  被匯編調用,作為secondary CPU的啟動入口
{
    struct mm_struct *mm = &init_mm;
    unsigned int cpu = smp_processor_id();

    /*
     * All kernel threads share the same mm context; grab a
     * reference and switch to it.
     */
    atomic_inc(&mm->mm_count);
    current->active_mm = mm;

    set_my_cpu_offset(per_cpu_offset(smp_processor_id()));

    /*
     * TTBR0 is only used for the identity mapping at this stage. Make it
     * point to zero page to avoid speculatively fetching new entries.
     */
    cpu_set_reserved_ttbr0();
    local_flush_tlb_all();
    cpu_set_default_tcr_t0sz();

    preempt_disable();
    trace_hardirqs_off();

    /*
     * If the system has established the capabilities, make sure
     * this CPU ticks all of those. If it doesn't, the CPU will
     * fail to come online.
     */
    verify_local_cpu_capabilities();

    if (cpu_ops[cpu]->cpu_postboot)
        cpu_ops[cpu]->cpu_postboot();

    /*
     * Log the CPU info before it is marked online and might get read.
     */
    cpuinfo_store_cpu();

    /*
     * Enable GIC and timers.
     */
    notify_cpu_starting(cpu);

    smp_store_cpu_info(cpu);

    /*
     * OK, now it's safe to let the boot CPU continue.  Wait for
     * the CPU migration code to notice that the CPU is online
     * before we continue.
     */
    pr_info("CPU%u: Booted secondary processor [%08x]\n",
                     cpu, read_cpuid_id());
    set_cpu_online(cpu, true); 至此CPU可以設置為online狀態
    complete(&cpu_running);

    local_dbg_enable();
    local_irq_enable();
    local_async_enable();

    /*
     * OK, it's off to the idle thread for us
     */
    cpu_startup_entry(CPUHP_ONLINE);
}

cpu_disable

static int op_cpu_disable(unsigned int cpu)
{
    /*
     * If we don't have a cpu_die method, abort before we reach the point
     * of no return. CPU0 may not have an cpu_ops, so test for it.
     */
    if (!cpu_ops[cpu] || !cpu_ops[cpu]->cpu_die)
        return -EOPNOTSUPP;

    /*
     * We may need to abort a hot unplug for some other mechanism-specific
     * reason.
     */
    if (cpu_ops[cpu]->cpu_disable)
        return cpu_ops[cpu]->cpu_disable(cpu);

    return 0;
}

cpu_die

void cpu_die(void)
{
    unsigned int cpu = smp_processor_id();

    idle_task_exit();

    local_irq_disable();

    /* Tell __cpu_die() that this CPU is now safe to dispose of */
    (void)cpu_report_death();

    /*
     * Actually shutdown the CPU. This must never fail. The specific hotplug
     * mechanism must perform all required cache maintenance to ensure that
     * no dirty lines are lost in the process of shutting down the CPU.
     */
    cpu_ops[cpu]->cpu_die(cpu);

    BUG();
}

cpu_kill

static int op_cpu_kill(unsigned int cpu)
{
    /*
     * If we have no means of synchronising with the dying CPU, then assume
     * that it is really dead. We can only wait for an arbitrary length of
     * time and hope that it's dead, so let's skip the wait and just hope.
     */
    if (!cpu_ops[cpu]->cpu_kill)
        return 0;

    return cpu_ops[cpu]->cpu_kill(cpu);
}

cpu_init_idle和cpu_suspend

這兩個回調函數主要用於idle初始化和進入idle狀態。

arm_idle_init解析DeviceTree的"arm,idle-state",注冊ARM的cpuidle驅動arm_idle_driver,

static int __init arm_idle_init(void)
{
    int cpu, ret;
    struct cpuidle_driver *drv = &arm_idle_driver;
    struct cpuidle_device *dev;

    /*
     * Initialize idle states data, starting at index 1.
     * This driver is DT only, if no DT idle states are detected (ret == 0)
     * let the driver initialization fail accordingly since there is no
     * reason to initialize the idle driver if only wfi is supported.
     */
    ret = dt_init_idle_driver(drv, arm_idle_state_match, 1);
    if (ret <= 0)
        return ret ? : -ENODEV;

    ret = cpuidle_register_driver(drv);  注冊arm_idle_driver驅動函數
    if (ret) {
        pr_err("Failed to register cpuidle driver\n");
        return ret;
    }

    /*
     * Call arch CPU operations in order to initialize
     * idle states suspend back-end specific data
     */
    for_each_possible_cpu(cpu) {
        ret = arm_cpuidle_init(cpu);  獲取arch-specific的idle處理參數,這里對應cpu_psci_cpu_init_idle。

        /*
         * Skip the cpuidle device initialization if the reported
         * failure is a HW misconfiguration/breakage (-ENXIO).
         */
        if (ret == -ENXIO)
            continue;

        if (ret) {
            pr_err("CPU %d failed to init idle CPU ops\n", cpu);
            goto out_fail;
        }

        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev) {
            pr_err("Failed to allocate cpuidle device\n");
            goto out_fail;
        }
        dev->cpu = cpu;

        ret = cpuidle_register_device(dev);
        if (ret) {
            pr_err("Failed to register cpuidle device for CPU %d\n",
                   cpu);
            kfree(dev);
            goto out_fail;
        }
    }

    return 0;
out_fail:
    while (--cpu >= 0) {
        dev = per_cpu(cpuidle_devices, cpu);
        cpuidle_unregister_device(dev);
        kfree(dev);
    }

    cpuidle_unregister_driver(drv);

    return ret;
}

arm_cpuidle_init調用.cpu_init_idle回調函數。

int __init arm_cpuidle_init(unsigned int cpu)
{
    int ret = -EOPNOTSUPP;

    if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_init_idle)
        ret = cpu_ops[cpu]->cpu_init_idle(cpu);

    return ret;
}

arm_enter_idle_state根據參數idx使CPU進入特定的idle狀態,

static int arm_enter_idle_state(struct cpuidle_device *dev,
                struct cpuidle_driver *drv, int idx)
{
    int ret;

    if (!idx) {
        cpu_do_idle();  如果idx為0,則cpu_do_idle。
        return idx;
    }

    ret = cpu_pm_enter();
    if (!ret) {
        /*
         * Pass idle state index to cpu_suspend which in turn will
         * call the CPU ops suspend protocol with idle index as a
         * parameter.
         */
        ret = arm_cpuidle_suspend(idx);  調用底層arch-specific處理函數。

        cpu_pm_exit();
    }

    return ret ? -1 : idx;
}

cpu_do_idle使CPU進入WFI狀態。

ENTRY(cpu_do_idle)
    dsb    sy                // WFI may enter a low-power mode
    wfi
    ret
ENDPROC(cpu_do_idle)

 

int arm_cpuidle_suspend(int index)
{
    int cpu = smp_processor_id();

    /*
     * If cpu_ops have not been registered or suspend
     * has not been initialized, cpu_suspend call fails early.
     */
    if (!cpu_ops[cpu] || !cpu_ops[cpu]->cpu_suspend)
        return -EOPNOTSUPP;
    return cpu_ops[cpu]->cpu_suspend(index);
}

 

cpu_ops到arch-dependent的關聯

以start_kernel為起點,查看從內核開始到獲取cpu_ops的路徑如下:

start_kernel
    -->setup_arch
        -->cpu_read_bootcpu_ops  只獲取bootcpu的cpu_ops
            -->cpu_read_bootcpu_ops
                -->cpu_read_ops(0)
        -->smp_init_cpus  獲取nonboot cpu的cpu_ops
            -->smp_cpu_setup
                -->cpu_read_ops

cpu_read_ops是獲取cpu_ops的關鍵,參數是cpu的序列號,輸出是cpu_ops[cpu]。

int __init cpu_read_ops(int cpu)
{
    const char *enable_method = cpu_read_enable_method(cpu);  從DeviceTree獲取enable_method字符串

    if (!enable_method)
        return -ENODEV;

    cpu_ops[cpu] = cpu_get_ops(enable_method);  根據enable_method字符串在supported_cpu_ops獲取指針
    if (!cpu_ops[cpu]) {
        pr_warn("Unsupported enable-method: %s\n", enable_method);
        return -EOPNOTSUPP;
    }

    return 0;
}

通過cpu0的DeviceTree可以看出enable-method為pcsi。

image

支持的cpu_operations有:

static const struct cpu_operations *supported_cpu_ops[] __initconst = {
    &smp_spin_table_ops,
    &cpu_psci_ops,
    NULL,
};

所以cpu_ops=&cpu_psci_ops。

suspend_ops

在enter_state—>suspend_devices_and_enter—>suspend_enter有針對suspend_ops->enter的調用,suspend_ops的賦值在psci_init_system_suspend中:

static void __init psci_init_system_suspend(void)
{
    int ret;

    if (!IS_ENABLED(CONFIG_SUSPEND))
        return;

    ret = psci_features(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND));

    if (ret != PSCI_RET_NOT_SUPPORTED)
        suspend_set_ops(&psci_suspend_ops);  suspend_ops指向psci_suspend_ops
}

psci_suspend_ops是platform_suspend_ops類型的函數結構體,這里只有兩個成員。

static const struct platform_suspend_ops psci_suspend_ops = {
    .valid          = suspend_valid_only_mem,  返回系統支持的suspend類型
    .enter          = psci_system_suspend_enter,  進入suspend狀態,參數是狀態值,這里只能有mem
};

psci_system_suspend_enter調用cpu_suspend並且給出結束回調函數。

cpu_suspend調用__cpu_suspend_enter,並進行TTBR0、TLB、TCR、MM等的操作,這些都涉及到匯編處理。

__cpu_suspend_enter保存當前CPU狀態,其中x0保存結束回調函數的參數,x1是結束回調函數指針地址。

ENTRY(__cpu_suspend_enter)
    stp    x29, lr, [sp, #-96]!
    stp    x19, x20, [sp,#16]
    stp    x21, x22, [sp,#32]
    stp    x23, x24, [sp,#48]
    stp    x25, x26, [sp,#64]
    stp    x27, x28, [sp,#80]
    /*
     * Stash suspend finisher and its argument in x20 and x19
     */
    mov    x19, x0
    mov    x20, x1
    mov    x2, sp
    sub    sp, sp, #CPU_SUSPEND_SZ    // allocate cpu_suspend_ctx
    mov    x0, sp
    /*
     * x0 now points to struct cpu_suspend_ctx allocated on the stack
     */
    str    x2, [x0, #CPU_CTX_SP]
    ldr    x1, =sleep_save_sp
    ldr    x1, [x1, #SLEEP_SAVE_SP_VIRT]
    mrs    x7, mpidr_el1
    ldr    x9, =mpidr_hash
    ldr    x10, [x9, #MPIDR_HASH_MASK]
    /*
     * Following code relies on the struct mpidr_hash
     * members size.
     */
    ldp    w3, w4, [x9, #MPIDR_HASH_SHIFTS]
    ldp    w5, w6, [x9, #(MPIDR_HASH_SHIFTS + 8)]
    compute_mpidr_hash x8, x3, x4, x5, x6, x7, x10
    add    x1, x1, x8, lsl #3
    bl    __cpu_suspend_save
    /*
     * Grab suspend finisher in x20 and its argument in x19
     */
    mov    x0, x19  將備份的arg和fn恢復到x0, x1
    mov    x1, x20
    /*
     * We are ready for power down, fire off the suspend finisher
     * in x1, with argument in x0
     */
    blr    x1  執行suspend結束函數回調,這里指的是psci_system_suspend。
        /*
     * Never gets here, unless suspend finisher fails.
     * Successful cpu_suspend should return from cpu_resume, returning
     * through this code path is considered an error
     * If the return value is set to 0 force x0 = -EOPNOTSUPP
     * to make sure a proper error condition is propagated
     */
    cmp    x0, #0
    mov    x3, #-EOPNOTSUPP
    csel    x0, x3, x0, eq
    add    sp, sp, #CPU_SUSPEND_SZ    // rewind stack pointer
    ldp    x19, x20, [sp, #16]
    ldp    x21, x22, [sp, #32]
    ldp    x23, x24, [sp, #48]
    ldp    x25, x26, [sp, #64]
    ldp    x27, x28, [sp, #80]
    ldp    x29, lr, [sp], #96
    ret
ENDPROC(__cpu_suspend_enter)

psci_system_suspend作為cpu_suspend收尾函數,調用psci的suspend函數,讓CPU進入suspend。

static int psci_system_suspend(unsigned long unused)
{
    return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND),
                  virt_to_phys(cpu_resume), 0, 0);
}

psci的SYSTEM_SUSPEND entry_point參數是cpu_resume,這個函數會在CPU喚醒之后執行的入口點。

cpu_resume在arc/arm64/kernel/sleep.S中定義,和__cpu_suspend_enter是相反的過程,恢復sp指針、pc指針、MMU等。

ENTRY(cpu_resume)
    bl    el2_setup        // if in EL2 drop to EL1 cleanly
    mrs    x1, mpidr_el1
    adrp    x8, mpidr_hash
    add x8, x8, #:lo12:mpidr_hash // x8 = struct mpidr_hash phys address
        /* retrieve mpidr_hash members to compute the hash */
    ldr    x2, [x8, #MPIDR_HASH_MASK]
    ldp    w3, w4, [x8, #MPIDR_HASH_SHIFTS]
    ldp    w5, w6, [x8, #(MPIDR_HASH_SHIFTS + 8)]
    compute_mpidr_hash x7, x3, x4, x5, x6, x1, x2
        /* x7 contains hash index, let's use it to grab context pointer */
    ldr_l    x0, sleep_save_sp + SLEEP_SAVE_SP_PHYS
    ldr    x0, [x0, x7, lsl #3]
    /* load sp from context */
    ldr    x2, [x0, #CPU_CTX_SP]
    /* load physical address of identity map page table in x1 */
    adrp    x1, idmap_pg_dir
    mov    sp, x2
    /*
     * cpu_do_resume expects x0 to contain context physical address
     * pointer and x1 to contain physical address of 1:1 page tables
     */
    bl    cpu_do_resume        // PC relative jump, MMU off
    b    cpu_resume_mmu        // Resume MMU, never returns
ENDPROC(cpu_resume)

cpu_psci_ops分析

cpu_psci_ops結構體可以說是cpu_operations和psci_operations的橋梁,他講cpu_operations的一些列回調函數,映射到psci_operations。

const struct cpu_operations cpu_psci_ops = {
    .name        = "psci",
#ifdef CONFIG_CPU_IDLE
    .cpu_init_idle    = cpu_psci_cpu_init_idle,  從DeviceTree獲取CPU idle狀態數據
    .cpu_suspend    = cpu_psci_cpu_suspend,  根據是否丟失上下文來選擇是psci_ops.cpu_suspend還是cpu_suspend
#endif
    .cpu_init    = cpu_psci_cpu_init,  為空
    .cpu_prepare    = cpu_psci_cpu_prepare,  只是判斷psci_ops.cpu_on是否存在,不存在則返回錯誤。
    .cpu_boot    = cpu_psci_cpu_boot,  調用psci_ops.cpu_on
#ifdef CONFIG_HOTPLUG_CPU
    .cpu_disable    = cpu_psci_cpu_disable,  檢查是否支持psci_ops.cpu_off。
    .cpu_die    = cpu_psci_cpu_die,  調用psci_ops.cpu_off
    .cpu_kill    = cpu_psci_cpu_kill,  檢查指定cpu是否已經被kill
#endif
}

cpu_psci_cpu_boot

static int cpu_psci_cpu_boot(unsigned int cpu)
{
    int err = psci_ops.cpu_on(cpu_logical_map(cpu), __pa(secondary_entry));
    if (err)
        pr_err("failed to boot CPU%d (%d)\n", cpu, err);

    return err;
}

CPU_ON用於secondary boot、hotplug或者big.LITTLE遷移。如果需要從一個核啟動另一個核,通過CPU_ON提供一個入口地址和上下文標識。

PCSI提供必要的操作啟動一個核,並且在提供的入口地址開始執行,上下文標識必須存在R0或者W0中。這里的入口地址就對應secondary_entry。

在arch/arm64/kernel/head.S中:

secondary_entry—>secondary_startup—>__secondary_switched—>secondary_start_kernel

ENTRY(secondary_entry)
    bl    el2_setup            // Drop to EL1
    bl    set_cpu_boot_mode_flag
    b    secondary_startup
ENDPROC(secondary_entry)

ENTRY(secondary_startup)
    /*
     * Common entry point for secondary CPUs.
     */
    adrp    x25, idmap_pg_dir
    adrp    x26, swapper_pg_dir
    bl    __cpu_setup            // initialise processor

    ldr    x21, =secondary_data
    ldr    x27, =__secondary_switched    // address to jump to after enabling the MMU
    b    __enable_mmu
ENDPROC(secondary_startup)

ENTRY(__secondary_switched)
    ldr    x0, [x21]            // get secondary_data.stack
    mov    sp, x0
    mov    x29, #0
    b    secondary_start_kernel
ENDPROC(__secondary_switched)

在secondary_start_kernel將CPU設置為online,並調用.cpu_postboot回調函數,進行boot后處理。然后cpu_startup_entry啟動idle線程。

cpu_psci_cpu_init_idle

static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu)
{
    int i, ret, count = 0;
    u32 *psci_states;
    struct device_node *state_node, *cpu_node;

    cpu_node = of_get_cpu_node(cpu, NULL);
    if (!cpu_node)
        return -ENODEV;

    /*
     * If the PSCI cpu_suspend function hook has not been initialized
     * idle states must not be enabled, so bail out
     */
    if (!psci_ops.cpu_suspend)
        return -EOPNOTSUPP;

    /* Count idle states */
    while ((state_node = of_parse_phandle(cpu_node, "cpu-idle-states",
                          count))) {
        count++;
        of_node_put(state_node);
    }

    if (!count)
        return -ENODEV;

    psci_states = kcalloc(count, sizeof(*psci_states), GFP_KERNEL);
    if (!psci_states)
        return -ENOMEM;

    for (i = 0; i < count; i++) {
        u32 state;

        state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i);

        ret = of_property_read_u32(state_node,
                       "arm,psci-suspend-param",
                       &state);
        if (ret) {
            pr_warn(" * %s missing arm,psci-suspend-param property\n",
                state_node->full_name);
            of_node_put(state_node);
            goto free_mem;
        }

        of_node_put(state_node);
        pr_debug("psci-power-state %#x index %d\n", state, i);
        if (!psci_power_state_is_valid(state)) {
            pr_warn("Invalid PSCI power state %#x\n", state);
            ret = -EINVAL;
            goto free_mem;
        }
        psci_states[i] = state;
    }
    /* Idle states parsed correctly, initialize per-cpu pointer */
    per_cpu(psci_power_state, cpu) = psci_states;
    return 0;

free_mem:
    kfree(psci_states);
    return ret;
}

1.解析DeviceTree中cpu下的cpu-idle-states屬性

image

2.從每個state中獲取arm,psci-suspend-param的參數,並驗證是否有效。

image

3.初始化per-CPU類型的指針psci_power_state。

cpu_psci_cpu_suspend

static int __maybe_unused cpu_psci_cpu_suspend(unsigned long index)
{
    int ret;
    u32 *state = __this_cpu_read(psci_power_state);  從psci_power_state中讀取suspend的state參數。
    /*
     * idle state index 0 corresponds to wfi, should never be called
     * from the cpu_suspend operations
     */
    if (WARN_ON_ONCE(!index))
        return -EINVAL;

    if (!psci_power_state_loses_context(state[index - 1]))
        ret = psci_ops.cpu_suspend(state[index - 1], 0);
    else
        ret = cpu_suspend(index, psci_suspend_finisher);

    return ret;
}

 

psci_ops

由於acpi_disabled,所以psci通過DeviceTree獲取相關參數。

start_kernel
    -->setup_arch
        -->psci_dt_init  這個函數在cpu_ops之前,因為cpu_ops依賴psci_ops

psci有不同版本,需要通過DeviceTree獲取版本信息和使用的method(是smc還是)。

image

通過查看DeviceTree可以看到對應的是psci_0_2_init。

static const struct of_device_id const psci_of_match[] __initconst = {
    { .compatible = "arm,psci",    .data = psci_0_1_init},
    { .compatible = "arm,psci-0.2",    .data = psci_0_2_init},
    { .compatible = "arm,psci-1.0",    .data = psci_0_2_init},
    {},
};

psci_dt_init解析DeviceTree執行對應psci版本的初始化函數。

int __init psci_dt_init(void)
{
    struct device_node *np;
    const struct of_device_id *matched_np;
    psci_initcall_t init_fn;

    np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);

    if (!np)
        return -ENODEV;

    init_fn = (psci_initcall_t)matched_np->data;
    return init_fn(np);
}

psci_0_2_init設置method,然后調用psci_probe:

static int __init psci_0_2_init(struct device_node *np)
{
    int err;

    err = get_set_conduit_method(np);  從DeviceTree可知invoke_psci_fn = __invoke_psci_fn_smc

    if (err)
        goto out_put_node;
    /*
     * Starting with v0.2, the PSCI specification introduced a call
     * (PSCI_VERSION) that allows probing the firmware version, so
     * that PSCI function IDs and version specific initialization
     * can be carried out according to the specific version reported
     * by firmware
     */
    err = psci_probe();

out_put_node:
    of_node_put(np);
    return err;
}

psci_probe設置版本高於0.2的PSCI回調函數,以及arm_pm_restart和pm_power_off。

static void __init psci_0_2_set_functions(void)
{
    pr_info("Using standard PSCI v0.2 function IDs\n");
    psci_function_id[PSCI_FN_CPU_SUSPEND] =
                    PSCI_FN_NATIVE(0_2, CPU_SUSPEND);
    psci_ops.cpu_suspend = psci_cpu_suspend;

    psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF;
    psci_ops.cpu_off = psci_cpu_off;

    psci_function_id[PSCI_FN_CPU_ON] = PSCI_FN_NATIVE(0_2, CPU_ON);
    psci_ops.cpu_on = psci_cpu_on;

    psci_function_id[PSCI_FN_MIGRATE] = PSCI_FN_NATIVE(0_2, MIGRATE);
    psci_ops.migrate = psci_migrate;

    psci_ops.affinity_info = psci_affinity_info;

    psci_ops.migrate_info_type = psci_migrate_info_type;

    arm_pm_restart = psci_sys_reset;

    pm_power_off = psci_sys_poweroff;
}

這些函數都有一個共性invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0),着這里invoke_psci_fn指向__invoke_psci_fn_smc

__invoke_psci_fn_smc指向arch/arm64/kernel/psci-call.S定義的函數:

/* int __invoke_psci_fn_smc(u64 function_id, u64 arg0, u64 arg1, u64 arg2) */
ENTRY(__invoke_psci_fn_smc)
    smc    #0
    ret
ENDPROC(__invoke_psci_fn_smc)

http://infocenter.arm.com/help/topic/com.arm.doc.den0022c/DEN0022C_Power_State_Coordination_Interface.pdf Chapter5有PSCI函數圓形和相關參數返回值的介紹。

第一個參數是Function ID,后面三個參數作為Function ID的參數。如果使用的是32位的參數,后三個參數通過r0-r3傳遞給Function ID,r0存放返回值;如果使用64位的參數,后三個參數通過W0-W3傳遞,w0存放返回值。這些Function ID的實現,在對應的Firmware中,但是可以通過上述pdf查看輸入輸出細節。

PSCI除了提供psci_ops的回調函數之外,還提供以restart和power off的arch-dependent函數arm_pm_restart和pm_power_off

比如machine_power_off和machine_restart調用:

void machine_power_off(void)
{
    local_irq_disable();
    smp_send_stop();
    if (pm_power_off)
        pm_power_off();
}

void machine_restart(char *cmd)
{
    /* Disable interrupts first */
    local_irq_disable();
    smp_send_stop();

    /*
     * UpdateCapsule() depends on the system being reset via
     * ResetSystem().
     */
    if (efi_enabled(EFI_RUNTIME_SERVICES))
        efi_reboot(reboot_mode, NULL);

    /* Now call the architecture specific reboot code. */
    if (arm_pm_restart)
        arm_pm_restart(reboot_mode, cmd);
    else
        do_kernel_restart(cmd);

    /*
     * Whoops - the architecture was unable to reboot.
     */
    printk("Reboot failed -- System halted\n");
    while (1);
}

 

參考文檔

Linux CPU core的電源管理(3)_cpu ops:http://www.wowotech.net/pm_subsystem/cpu_ops.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM