pub const PSCI_VERSION: u32 = 0x8400_0000;
pub const CPU_OFF: u32 = 0x8400_0002;
pub const CPU_ON: u32 = 0xC400_0003;
pub const AFFINITY_INFO: u32 = 0xC400_0004;
pub const MIGRATE_INFO_TYPE: u32 = 0x8400_0006;
pub const SYSTEM_OFF: u32 = 0x8400_0008;
pub const SYSTEM_RESET: u32 = 0x8400_0009;
pub const PSCI_FEATURES: u32 = 0x8400_000A;
pub const CPU_SUSPEND: u32 = 0xC400_0001;
pub const MIGRATE: u32 = 0xC400_0005;
pub const PSCI_RET_SUCCESS: i32 = 0;
pub const PSCI_RET_NOT_SUPPORTED: i32 = -1;
pub const PSCI_RET_INVALID_PARAMETERS: i32 = -2;
pub const PSCI_RET_ALREADY_ON: i32 = -4;
pub const PSCI_RET_ON_PENDING: i32 = -5;
pub const PSCI_RET_INTERNAL_FAILURE: i32 = -6;
pub const PSCI_VERSION_VALUE: u32 = 0x0001_0001;
pub mod affinity {
pub const ON: i32 = 0;
pub const OFF: i32 = 1;
pub const ON_PENDING: i32 = 2;
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum PsciFunction {
Version,
CpuOff,
CpuOn {
target_cpu: u64,
entry_point: u64,
context_id: u64,
},
AffinityInfo {
target_cpu: u64,
lowest_affinity_level: u32,
},
MigrateInfoType,
SystemOff,
SystemReset,
Features {
target_fn: u32,
},
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PsciOutcome {
Return(PsciReturn),
ParkCallerCpuOff,
BringUpSecondary {
target_cpu: u64,
entry_point: u64,
context_id: u64,
},
QueryAffinityInfo {
target_cpu: u64,
lowest_affinity_level: u32,
},
SystemOff,
SystemReset,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PsciReturn {
Success,
NotSupported,
InvalidParameters,
Word(u32),
}
impl PsciReturn {
#[must_use]
#[allow(clippy::cast_sign_loss)] pub const fn as_x0(self) -> u64 {
match self {
Self::Success => PSCI_RET_SUCCESS as u64,
Self::NotSupported => PSCI_RET_NOT_SUPPORTED as u64,
Self::InvalidParameters => PSCI_RET_INVALID_PARAMETERS as u64,
Self::Word(w) => w as u64,
}
}
}
#[must_use]
pub fn dispatch(func_id: u32, args: [u64; 3]) -> PsciOutcome {
match func_id {
PSCI_VERSION => PsciOutcome::Return(PsciReturn::Word(PSCI_VERSION_VALUE)),
CPU_OFF => PsciOutcome::ParkCallerCpuOff,
CPU_ON => PsciOutcome::BringUpSecondary {
target_cpu: args[0],
entry_point: args[1],
context_id: args[2],
},
AFFINITY_INFO => PsciOutcome::QueryAffinityInfo {
target_cpu: args[0],
#[allow(clippy::cast_possible_truncation)] lowest_affinity_level: args[1] as u32,
},
MIGRATE_INFO_TYPE => PsciOutcome::Return(PsciReturn::Word(2)),
SYSTEM_OFF => PsciOutcome::SystemOff,
SYSTEM_RESET => PsciOutcome::SystemReset,
PSCI_FEATURES => {
#[allow(clippy::cast_possible_truncation)] let queried = args[0] as u32;
let supported = matches!(
queried,
PSCI_VERSION
| CPU_OFF
| CPU_ON
| AFFINITY_INFO
| MIGRATE_INFO_TYPE
| SYSTEM_OFF
| SYSTEM_RESET
| PSCI_FEATURES
);
if supported {
PsciOutcome::Return(PsciReturn::Success)
} else {
PsciOutcome::Return(PsciReturn::NotSupported)
}
}
_ => PsciOutcome::Return(PsciReturn::NotSupported),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn psci_version_returns_one_one() {
let outcome = dispatch(PSCI_VERSION, [0; 3]);
assert_eq!(
outcome,
PsciOutcome::Return(PsciReturn::Word(PSCI_VERSION_VALUE))
);
assert_eq!(PsciReturn::Word(PSCI_VERSION_VALUE).as_x0(), 0x0001_0001);
}
#[test]
fn cpu_off_parks_caller() {
assert_eq!(dispatch(CPU_OFF, [0; 3]), PsciOutcome::ParkCallerCpuOff);
}
#[test]
fn cpu_on_carries_target_entry_context() {
let outcome = dispatch(CPU_ON, [0xAABB_CCDD, 0x8000_0000, 0xDEAD_BEEF]);
assert_eq!(
outcome,
PsciOutcome::BringUpSecondary {
target_cpu: 0xAABB_CCDD,
entry_point: 0x8000_0000,
context_id: 0xDEAD_BEEF,
}
);
}
#[test]
fn affinity_info_passes_through_args() {
let outcome = dispatch(AFFINITY_INFO, [0x100, 1, 0]);
assert_eq!(
outcome,
PsciOutcome::QueryAffinityInfo {
target_cpu: 0x100,
lowest_affinity_level: 1,
}
);
}
#[test]
fn migrate_info_type_returns_2() {
assert_eq!(
dispatch(MIGRATE_INFO_TYPE, [0; 3]),
PsciOutcome::Return(PsciReturn::Word(2))
);
}
#[test]
fn system_off_and_reset_route_correctly() {
assert_eq!(dispatch(SYSTEM_OFF, [0; 3]), PsciOutcome::SystemOff);
assert_eq!(dispatch(SYSTEM_RESET, [0; 3]), PsciOutcome::SystemReset);
}
#[test]
fn psci_features_says_yes_for_implemented_calls() {
assert_eq!(
dispatch(PSCI_FEATURES, [u64::from(PSCI_VERSION), 0, 0]),
PsciOutcome::Return(PsciReturn::Success)
);
assert_eq!(
dispatch(PSCI_FEATURES, [u64::from(SYSTEM_OFF), 0, 0]),
PsciOutcome::Return(PsciReturn::Success)
);
}
#[test]
fn psci_features_says_no_for_cpu_suspend() {
assert_eq!(
dispatch(PSCI_FEATURES, [u64::from(CPU_SUSPEND), 0, 0]),
PsciOutcome::Return(PsciReturn::NotSupported)
);
}
#[test]
fn unknown_function_id_returns_not_supported() {
for func_id in [0x0000_0000, 0x8400_00FF, 0xC400_00FF, 0xDEAD_BEEF, u32::MAX] {
let outcome = dispatch(func_id, [0; 3]);
assert_eq!(
outcome,
PsciOutcome::Return(PsciReturn::NotSupported),
"func_id {func_id:#010x}"
);
}
}
#[test]
fn cpu_suspend_and_migrate_route_to_not_supported_in_smc_view() {
assert_eq!(
dispatch(CPU_SUSPEND, [0; 3]),
PsciOutcome::Return(PsciReturn::NotSupported)
);
assert_eq!(
dispatch(MIGRATE, [0; 3]),
PsciOutcome::Return(PsciReturn::NotSupported)
);
}
#[test]
fn psci_return_x0_bit_pattern_matches_signed_constants() {
let not_supported_low = (PsciReturn::NotSupported.as_x0() & 0xFFFF_FFFF) as u32;
let invalid_low = (PsciReturn::InvalidParameters.as_x0() & 0xFFFF_FFFF) as u32;
assert_eq!(not_supported_low, 0xFFFF_FFFF);
assert_eq!(PsciReturn::Success.as_x0(), 0);
assert_eq!(invalid_low, 0xFFFF_FFFE);
}
}