在內核中針對的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和硬件相關部分分隔開。

從上圖可知,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。

支持的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屬性

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

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還是)。

通過查看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