Skip to main content

axplat_arm_peripherals/
psci.rs

1//! ARM Power State Coordination Interface.
2
3#![allow(dead_code)]
4
5use core::arch::asm;
6use core::sync::atomic::{AtomicBool, Ordering};
7
8const PSCI_0_2_FN_BASE: u32 = 0x84000000;
9const PSCI_0_2_64BIT: u32 = 0x40000000;
10const PSCI_0_2_FN_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + 1;
11const PSCI_0_2_FN_CPU_OFF: u32 = PSCI_0_2_FN_BASE + 2;
12const PSCI_0_2_FN_CPU_ON: u32 = PSCI_0_2_FN_BASE + 3;
13const PSCI_0_2_FN_MIGRATE: u32 = PSCI_0_2_FN_BASE + 5;
14const PSCI_0_2_FN_SYSTEM_OFF: u32 = PSCI_0_2_FN_BASE + 8;
15const PSCI_0_2_FN_SYSTEM_RESET: u32 = PSCI_0_2_FN_BASE + 9;
16const PSCI_0_2_FN64_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 1;
17const PSCI_0_2_FN64_CPU_ON: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 3;
18const PSCI_0_2_FN64_MIGRATE: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 5;
19
20static PSCI_METHOD_HVC: AtomicBool = AtomicBool::new(false);
21
22/// PSCI return values, inclusive of all PSCI versions.
23#[derive(PartialEq, Debug)]
24#[repr(i32)]
25enum PsciError {
26    NotSupported = -1,
27    InvalidParams = -2,
28    Denied = -3,
29    AlreadyOn = -4,
30    OnPending = -5,
31    InternalFailure = -6,
32    NotPresent = -7,
33    Disabled = -8,
34    InvalidAddress = -9,
35}
36
37impl From<i32> for PsciError {
38    fn from(code: i32) -> PsciError {
39        use PsciError::*;
40        match code {
41            -1 => NotSupported,
42            -2 => InvalidParams,
43            -3 => Denied,
44            -4 => AlreadyOn,
45            -5 => OnPending,
46            -6 => InternalFailure,
47            -7 => NotPresent,
48            -8 => Disabled,
49            -9 => InvalidAddress,
50            _ => panic!("Unknown PSCI error code: {}", code),
51        }
52    }
53}
54
55/// arm,psci method: smc
56/// when SMCCC_CONDUIT_SMC = 1
57fn arm_smccc_smc(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize {
58    let mut ret;
59    #[cfg(target_arch = "aarch64")]
60    unsafe {
61        asm!(
62            "smc #0",
63            inlateout("x0") func as usize => ret,
64            in("x1") arg0,
65            in("x2") arg1,
66            in("x3") arg2,
67        )
68    }
69    #[cfg(target_arch = "arm")]
70    unsafe {
71        asm!(
72            ".arch_extension sec",
73            "smc #0",
74            inlateout("r0") func => ret,
75            in("r1") arg0,
76            in("r2") arg1,
77            in("r3") arg2,
78        )
79    }
80    ret
81}
82
83/// psci "hvc" method call
84fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize {
85    let ret;
86    #[cfg(target_arch = "aarch64")]
87    unsafe {
88        asm!(
89            "hvc #0",
90            inlateout("x0") func as usize => ret,
91            in("x1") arg0,
92            in("x2") arg1,
93            in("x3") arg2,
94        )
95    }
96    #[cfg(target_arch = "arm")]
97    unsafe {
98        asm!(
99            ".arch_extension virt",
100            "hvc #0",
101            inlateout("r0") func => ret,
102            in("r1") arg0,
103            in("r2") arg1,
104            in("r3") arg2,
105        )
106    }
107    ret
108}
109
110fn psci_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> Result<(), PsciError> {
111    let ret = if PSCI_METHOD_HVC.load(Ordering::Acquire) {
112        psci_hvc_call(func, arg0, arg1, arg2)
113    } else {
114        arm_smccc_smc(func, arg0, arg1, arg2)
115    };
116    if ret == 0 {
117        Ok(())
118    } else {
119        Err(PsciError::from(ret as i32))
120    }
121}
122
123/// Initialize with the given PSCI method.
124///
125/// Method should be either "smc" or "hvc".
126pub fn init(method: &str) {
127    match method {
128        "smc" => PSCI_METHOD_HVC.store(false, Ordering::Release),
129        "hvc" => PSCI_METHOD_HVC.store(true, Ordering::Release),
130        _ => panic!("Unknown PSCI method: {}", method),
131    }
132}
133
134/// Shutdown the whole system, including all CPUs.
135pub fn system_off() -> ! {
136    info!("Shutting down...");
137    psci_call(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0).ok();
138    warn!("It should shutdown!");
139    loop {
140        axcpu::asm::halt();
141    }
142}
143
144/// Power up a core. This call is used to power up cores that either:
145///
146/// * Have not yet been booted into the calling supervisory software.
147/// * Have been previously powered down with a `cpu_off` call.
148///
149/// `target_cpu` contains a copy of the affinity fields of the MPIDR register.
150/// `entry_point` is the physical address of the secondary CPU's entry point.
151/// `arg` will be passed to the `X0` register of the secondary CPU.
152pub fn cpu_on(target_cpu: usize, entry_point: usize, arg: usize) {
153    info!("Starting CPU {:x} ON ...", target_cpu);
154    let fn_num = if cfg!(target_pointer_width = "64") {
155        PSCI_0_2_FN64_CPU_ON
156    } else {
157        PSCI_0_2_FN_CPU_ON
158    };
159    let res = psci_call(fn_num, target_cpu, entry_point, arg);
160    if let Err(e) = res {
161        error!("failed to boot CPU {:x} ({:?})", target_cpu, e);
162    }
163}
164
165/// Power down the calling core. This call is intended for use in hotplug. A
166/// core that is powered down by `cpu_off` can only be powered up again in
167/// response to a `cpu_on`.
168pub fn cpu_off() {
169    const PSCI_POWER_STATE_TYPE_STANDBY: u32 = 0;
170    const PSCI_POWER_STATE_TYPE_POWER_DOWN: u32 = 1;
171    const PSCI_0_2_POWER_STATE_TYPE_SHIFT: u32 = 16;
172    let state: u32 = PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT;
173    psci_call(PSCI_0_2_FN_CPU_OFF, state as usize, 0, 0).ok();
174}