Cover PSCI crate with unit tests

Validate the behavior of the parser and builder functions.

Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: Ife2dc1c042af9d9d0638f6841910ddf5cb324456
diff --git a/src/lib.rs b/src/lib.rs
index e834c25..387a1c5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -982,3 +982,780 @@
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    const MPIDR32: Mpidr = Mpidr {
+        aff0: 0x11,
+        aff1: 0x22,
+        aff2: 0x33,
+        aff3: None,
+    };
+    const MPIDR32_VALUE: u32 = 0x00332211;
+
+    const MPIDR64: Mpidr = Mpidr {
+        aff0: 0x11,
+        aff1: 0x22,
+        aff2: 0x33,
+        aff3: Some(0x44),
+    };
+
+    const MPIDR64_VALUE: u64 = 0x00000044_00332211;
+
+    const POWER_STATE: PowerState = PowerState::PowerDown(0xf12_3456);
+
+    const POWER_STATE_VALUE: u32 = 0x4f12_3456;
+
+    macro_rules! validate_function {
+        ( $regs:expr, $function:expr, $function_id:expr ) => {
+            assert_eq!(Ok($function), Function::try_from($regs));
+            assert_eq!($function_id, ($function).function_id());
+
+            let mut built_regs = [0; 4];
+            $function.copy_to_array(&mut built_regs);
+            assert_eq!($regs, built_regs);
+        };
+    }
+
+    #[test]
+    fn test_error() {
+        assert_eq!(
+            ErrorCode::NotSupported,
+            ErrorCode::from(Error::UnrecognisedFunctionId(0))
+        );
+
+        assert_eq!(
+            ErrorCode::InvalidParameters,
+            ErrorCode::from(Error::InvalidVersion(0))
+        );
+    }
+
+    #[test]
+    fn test_return_code() {
+        assert_eq!(
+            Err(Error::UnrecognisedErrorCode(-100)),
+            ReturnCode::try_from(-100)
+        );
+
+        assert_eq!(Ok(ReturnCode::Success), ReturnCode::try_from(0));
+        assert_eq!(
+            Ok(ReturnCode::Error(ErrorCode::NotSupported)),
+            ReturnCode::try_from(-1)
+        );
+
+        assert_eq!(0, i32::from(ReturnCode::Success));
+        assert_eq!(-1, i32::from(ReturnCode::Error(ErrorCode::NotSupported)));
+    }
+
+    #[test]
+    fn test_error_code() {
+        assert_eq!(-1, i32::from(ErrorCode::NotSupported));
+        assert_eq!(-2, i32::from(ErrorCode::InvalidParameters));
+        assert_eq!(-3, i32::from(ErrorCode::Denied));
+        assert_eq!(-4, i32::from(ErrorCode::AlreadyOn));
+        assert_eq!(-5, i32::from(ErrorCode::OnPending));
+        assert_eq!(-6, i32::from(ErrorCode::InternalFailure));
+        assert_eq!(-7, i32::from(ErrorCode::NotPresent));
+        assert_eq!(-8, i32::from(ErrorCode::Disabled));
+        assert_eq!(-9, i32::from(ErrorCode::InvalidAddress));
+    }
+
+    #[test]
+    fn test_version() {
+        assert_eq!(
+            Err(Error::InvalidVersion(0xffff_ffff)),
+            Version::try_from(0xffff_ffff)
+        );
+
+        assert_eq!(
+            Ok(Version {
+                major: 0x7123,
+                minor: 0x4567
+            }),
+            Version::try_from(0x7123_4567)
+        );
+
+        assert_eq!(
+            0x789a_bcde,
+            u32::from(Version {
+                major: 0x789a,
+                minor: 0xbcde
+            })
+        );
+    }
+
+    #[test]
+    fn test_power_state() {
+        assert_eq!(
+            Err(Error::InvalidPowerState(0x8000_0000)),
+            PowerState::try_from(0x8000_0000)
+        );
+
+        assert_eq!(
+            Err(Error::InvalidPowerState(0x3000_0000)),
+            PowerState::try_from(0x3000_0000)
+        );
+
+        assert_eq!(
+            Ok(PowerState::StandbyOrRetention(0xfff_ffff),),
+            PowerState::try_from(0x0fff_ffff)
+        );
+
+        assert_eq!(
+            Ok(PowerState::PowerDown(0x123_ffff)),
+            PowerState::try_from(0x4123_ffff)
+        );
+
+        assert_eq!(
+            0x0123_4567,
+            u32::from(PowerState::StandbyOrRetention(0x123_4567))
+        );
+
+        assert_eq!(0x4123_4567, u32::from(PowerState::PowerDown(0x123_4567)));
+    }
+
+    #[test]
+    fn test_mpidr() {
+        assert_eq!(
+            Err(Error::InvalidMpidr32(0xff00_0000)),
+            Mpidr::try_from(0xff00_0000u32)
+        );
+
+        assert_eq!(
+            Ok(Mpidr {
+                aff0: 0x33,
+                aff1: 0x22,
+                aff2: 0x11,
+                aff3: None
+            }),
+            Mpidr::try_from(0x0011_2233u32)
+        );
+
+        assert_eq!(
+            Err(Error::InvalidMpidr64(0xff00_0000_0000_0000)),
+            Mpidr::try_from(0xff00_0000_0000_0000u64)
+        );
+
+        assert_eq!(
+            Ok(Mpidr {
+                aff0: 0x33,
+                aff1: 0x22,
+                aff2: 0x11,
+                aff3: Some(0x44)
+            }),
+            Mpidr::try_from(0x0000_0044_0011_2233u64)
+        );
+
+        assert_eq!(
+            0x0011_2233u32,
+            Mpidr {
+                aff0: 0x33,
+                aff1: 0x22,
+                aff2: 0x11,
+                aff3: None
+            }
+            .into()
+        );
+
+        assert_eq!(
+            0x0000_0044_0011_2233u64,
+            Mpidr {
+                aff0: 0x33,
+                aff1: 0x22,
+                aff2: 0x11,
+                aff3: Some(0x44)
+            }
+            .into()
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_mpidr_32_invalid() {
+        let _ = u32::from(Mpidr {
+            aff0: 0x33,
+            aff1: 0x22,
+            aff2: 0x11,
+            aff3: Some(0x44),
+        });
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_mpidr_64_invalid() {
+        let _ = u64::from(Mpidr {
+            aff0: 0x33,
+            aff1: 0x22,
+            aff2: 0x11,
+            aff3: None,
+        });
+    }
+
+    #[test]
+    fn test_affinity_info_value() {
+        assert_eq!(0, u32::from(AffinityInfo::On));
+        assert_eq!(1, u32::from(AffinityInfo::Off));
+        assert_eq!(2, u32::from(AffinityInfo::OnPending));
+    }
+
+    #[test]
+    fn test_migration_info_type() {
+        assert_eq!(0, u32::from(MigrateInfoType::MigrateCapable));
+        assert_eq!(1, u32::from(MigrateInfoType::NotMigrateCapable));
+        assert_eq!(2, u32::from(MigrateInfoType::MigrationNotRequired));
+    }
+
+    #[test]
+    fn test_reset_type() {
+        assert_eq!(
+            Err(Error::UnrecognisedSystemReset2Type(0x1234_5678)),
+            ResetType::try_from(0x1234_5678)
+        );
+
+        assert_eq!(
+            Ok(ResetType::Architectural(
+                ArchitecturalResetType::SystemWarmReset
+            )),
+            ResetType::try_from(0x0000_0000)
+        );
+
+        assert_eq!(
+            Ok(ResetType::VendorSpecific(0x0000_0001)),
+            ResetType::try_from(0x8000_0001)
+        );
+
+        assert_eq!(
+            0x0000_0000u32,
+            ResetType::Architectural(ArchitecturalResetType::SystemWarmReset).into()
+        );
+        assert_eq!(
+            0x8000_0001u32,
+            ResetType::VendorSpecific(0x0000_0001).into()
+        );
+    }
+
+    #[test]
+    fn test_psci_feature() {
+        assert_eq!(
+            Err(Error::UnrecognisedFunctionId(0x1234_5678)),
+            PsciFeature::try_from(0x1234_5678)
+        );
+
+        assert_eq!(
+            Ok(PsciFeature::SmcccVersion),
+            PsciFeature::try_from(0x8000_0000)
+        );
+
+        assert_eq!(
+            Ok(PsciFeature::PsciFunction(FunctionId::PsciVersion)),
+            PsciFeature::try_from(0x8400_0000)
+        );
+
+        assert_eq!(0x8000_0000u32, PsciFeature::SmcccVersion.into());
+        assert_eq!(
+            0x8400_0000u32,
+            PsciFeature::PsciFunction(FunctionId::PsciVersion).into()
+        );
+    }
+
+    #[test]
+    fn test_feature_flags_suspend() {
+        assert_eq!(
+            Err(Error::UnrecognisedPsciFeaturesFlags(0x0000_0004)),
+            FeatureFlagsCpuSuspend::try_from(0x0000_0004)
+        );
+
+        assert_eq!(
+            Ok(FeatureFlagsCpuSuspend::empty()),
+            FeatureFlagsCpuSuspend::try_from(0x0000_0000)
+        );
+
+        assert_eq!(
+            Ok(FeatureFlagsCpuSuspend::OS_INITIATED_MODE),
+            FeatureFlagsCpuSuspend::try_from(0x0000_0001)
+        );
+
+        assert_eq!(
+            Ok(FeatureFlagsCpuSuspend::EXTENDED_POWER_STATE),
+            FeatureFlagsCpuSuspend::try_from(0x0000_0002)
+        );
+
+        assert_eq!(
+            Ok(FeatureFlagsCpuSuspend::OS_INITIATED_MODE
+                | FeatureFlagsCpuSuspend::EXTENDED_POWER_STATE),
+            FeatureFlagsCpuSuspend::try_from(0x0000_0003)
+        );
+
+        assert_eq!(
+            0x0000_0003,
+            u32::from(
+                FeatureFlagsCpuSuspend::OS_INITIATED_MODE
+                    | FeatureFlagsCpuSuspend::EXTENDED_POWER_STATE
+            )
+        );
+    }
+
+    #[test]
+    fn test_feature_flags_system_off2() {
+        assert_eq!(
+            Err(Error::UnrecognisedPsciFeaturesFlags(0x0000_0002)),
+            FeatureFlagsSystemOff2::try_from(0x0000_0002)
+        );
+
+        assert_eq!(
+            Ok(FeatureFlagsSystemOff2::empty()),
+            FeatureFlagsSystemOff2::try_from(0x0000_0000)
+        );
+
+        assert_eq!(
+            Ok(FeatureFlagsSystemOff2::HIBERNATE_OFF),
+            FeatureFlagsSystemOff2::try_from(0x0000_0001)
+        );
+
+        assert_eq!(0x0000_0000u32, FeatureFlagsSystemOff2::empty().into());
+
+        assert_eq!(0x0000_0001u32, FeatureFlagsSystemOff2::HIBERNATE_OFF.into());
+    }
+
+    #[test]
+    fn test_hw_state() {
+        assert_eq!(0, u32::from(HwState::On));
+        assert_eq!(1, u32::from(HwState::Off));
+        assert_eq!(2, u32::from(HwState::Standby));
+    }
+
+    #[test]
+    fn test_function_version() {
+        validate_function!(
+            [0x8400_0000, 0, 0, 0],
+            Function::Version,
+            FunctionId::PsciVersion
+        );
+    }
+
+    #[test]
+    fn test_cpu_suspend() {
+        validate_function!(
+            [
+                0x8400_0001,
+                POWER_STATE_VALUE.into(),
+                0xabcdef01,
+                0x23456789
+            ],
+            Function::CpuSuspend {
+                state: POWER_STATE,
+                entry: EntryPoint::Entry32 {
+                    entry_point_address: 0xabcdef01,
+                    context_id: 0x23456789
+                }
+            },
+            FunctionId::CpuSuspend32
+        );
+
+        validate_function!(
+            [
+                0xc400_0001,
+                POWER_STATE_VALUE.into(),
+                0xabcdef01_23456789,
+                0x23456789_abcdef01
+            ],
+            Function::CpuSuspend {
+                state: POWER_STATE,
+                entry: EntryPoint::Entry64 {
+                    entry_point_address: 0xabcdef01_23456789,
+                    context_id: 0x23456789_abcdef01
+                }
+            },
+            FunctionId::CpuSuspend64
+        );
+    }
+
+    #[test]
+    fn test_function_cpu_off() {
+        validate_function!([0x8400_0002, 0, 0, 0], Function::CpuOff, FunctionId::CpuOff);
+    }
+
+    #[test]
+    fn test_function_cpu_on() {
+        validate_function!(
+            [0x8400_0003, MPIDR32_VALUE.into(), 0xabcdef01, 0x23456789],
+            Function::CpuOn {
+                target_cpu: MPIDR32,
+                entry: EntryPoint::Entry32 {
+                    entry_point_address: 0xabcdef01,
+                    context_id: 0x23456789,
+                },
+            },
+            FunctionId::CpuOn32
+        );
+
+        validate_function!(
+            [
+                0xc400_0003,
+                MPIDR64_VALUE,
+                0x01234567_89abcdef,
+                0x89abcdef_01234567,
+            ],
+            Function::CpuOn {
+                target_cpu: MPIDR64,
+                entry: EntryPoint::Entry64 {
+                    entry_point_address: 0x01234567_89abcdef,
+                    context_id: 0x89abcdef_01234567,
+                },
+            },
+            FunctionId::CpuOn64
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_function_cpu_on_mixed_32_64() {
+        let mut regs = [0u64; 4];
+        Function::CpuOn {
+            target_cpu: MPIDR64,
+            entry: EntryPoint::Entry32 {
+                entry_point_address: 1,
+                context_id: 2,
+            },
+        }
+        .copy_to_array(&mut regs);
+    }
+
+    #[test]
+    fn test_function_affinity_info() {
+        validate_function!(
+            [0x8400_0004, MPIDR32_VALUE.into(), 2, 0],
+            Function::AffinityInfo {
+                mpidr: MPIDR32,
+                lowest_affinity_level: 2,
+            },
+            FunctionId::AffinityInfo32
+        );
+
+        validate_function!(
+            [0xc400_0004, MPIDR64_VALUE, 2, 0],
+            Function::AffinityInfo {
+                mpidr: MPIDR64,
+                lowest_affinity_level: 2,
+            },
+            FunctionId::AffinityInfo64
+        );
+
+        assert_eq!(
+            Err(Error::InvalidLowerAffinityLevel(3)),
+            Function::try_from([0x8400_0004, MPIDR32_VALUE.into(), 3, 0])
+        );
+
+        assert_eq!(
+            Err(Error::InvalidLowerAffinityLevel(4)),
+            Function::try_from([0xc400_0004, MPIDR64_VALUE, 4, 0])
+        );
+    }
+
+    #[test]
+    fn test_function_migrate() {
+        validate_function!(
+            [0x8400_0005, MPIDR32_VALUE.into(), 0, 0],
+            Function::Migrate {
+                target_affinity: MPIDR32,
+            },
+            FunctionId::Migrate32
+        );
+
+        validate_function!(
+            [0xc400_0005, MPIDR64_VALUE, 0, 0],
+            Function::Migrate {
+                target_affinity: MPIDR64,
+            },
+            FunctionId::Migrate64
+        );
+    }
+
+    #[test]
+    fn test_function_migrate_info_type() {
+        validate_function!(
+            [0x8400_0006, 0, 0, 0],
+            Function::MigrateInfoType,
+            FunctionId::MigrateInfoType
+        );
+    }
+
+    #[test]
+    fn test_function_migrate_info_up_cpu() {
+        validate_function!(
+            [0x8400_0007, 0, 0, 0],
+            Function::MigrateInfoUpCpu { is_32bit: true },
+            FunctionId::MigrateInfoUpCpu32
+        );
+
+        validate_function!(
+            [0xc400_0007, 0, 0, 0],
+            Function::MigrateInfoUpCpu { is_32bit: false },
+            FunctionId::MigrateInfoUpCpu64
+        );
+    }
+
+    #[test]
+    fn test_function_system_off() {
+        validate_function!(
+            [0x8400_0008, 0, 0, 0],
+            Function::SystemOff,
+            FunctionId::SystemOff
+        );
+    }
+
+    #[test]
+    fn test_function_system_off2() {
+        validate_function!(
+            [0x8400_0015, 0x0000_0001, 0xabcdef01, 0],
+            Function::SystemOff2 {
+                off_type: SystemOff2Type::HibernateOff,
+                cookie: Cookie::Cookie32(0xabcdef01),
+            },
+            FunctionId::SystemOff232
+        );
+
+        validate_function!(
+            [0xc400_0015, 0x0000_0001, 0xabcdef01_23456789, 0],
+            Function::SystemOff2 {
+                off_type: SystemOff2Type::HibernateOff,
+                cookie: Cookie::Cookie64(0xabcdef01_23456789),
+            },
+            FunctionId::SystemOff264
+        );
+    }
+
+    #[test]
+    fn test_function_system_reset() {
+        validate_function!(
+            [0x8400_0009, 0, 0, 0],
+            Function::SystemReset,
+            FunctionId::SystemReset
+        );
+    }
+
+    #[test]
+    fn test_function_system_reset2() {
+        validate_function!(
+            [0x8400_0012, 0, 0xabcdef01, 0],
+            Function::SystemReset2 {
+                reset_type: ResetType::Architectural(ArchitecturalResetType::SystemWarmReset),
+                cookie: Cookie::Cookie32(0xabcdef01),
+            },
+            FunctionId::SystemReset232
+        );
+
+        validate_function!(
+            [0xc400_0012, 0, 0xabcdef01_23456789, 0],
+            Function::SystemReset2 {
+                reset_type: ResetType::Architectural(ArchitecturalResetType::SystemWarmReset),
+                cookie: Cookie::Cookie64(0xabcdef01_23456789),
+            },
+            FunctionId::SystemReset264
+        );
+    }
+
+    #[test]
+    fn test_function_mem_protect() {
+        validate_function!(
+            [0x8400_0013, 0x0000_0001, 0, 0],
+            Function::MemProtect { enabled: true },
+            FunctionId::MemProtect
+        );
+
+        validate_function!(
+            [0x8400_0013, 0x0000_0000, 0, 0],
+            Function::MemProtect { enabled: false },
+            FunctionId::MemProtect
+        );
+    }
+
+    #[test]
+    fn test_function_mem_protect_check_range() {
+        validate_function!(
+            [0x8400_0014, 0xabcdef01, 0x23456789, 0],
+            Function::MemProtectCheckRange {
+                range: MemProtectRange::Range32 {
+                    base: 0xabcdef01,
+                    length: 0x23456789,
+                },
+            },
+            FunctionId::MemProtectCheckRange32
+        );
+
+        validate_function!(
+            [0xc400_0014, 0xabcdef01_23456789, 0x23456789_abcdef01, 0],
+            Function::MemProtectCheckRange {
+                range: MemProtectRange::Range64 {
+                    base: 0xabcdef01_23456789,
+                    length: 0x23456789_abcdef01,
+                },
+            },
+            FunctionId::MemProtectCheckRange64
+        );
+    }
+
+    #[test]
+    fn test_function_features() {
+        validate_function!(
+            [0x8400_000a, 0x8000_0000, 0, 0],
+            Function::Features {
+                psci_func_id: PsciFeature::SmcccVersion,
+            },
+            FunctionId::PsciFeatures
+        );
+
+        validate_function!(
+            [0x8400_000a, 0x8400_0001, 0, 0],
+            Function::Features {
+                psci_func_id: PsciFeature::PsciFunction(FunctionId::CpuSuspend32),
+            },
+            FunctionId::PsciFeatures
+        );
+    }
+
+    #[test]
+    fn test_function_cpu_freeze() {
+        validate_function!(
+            [0x8400_000b, 0, 0, 0],
+            Function::CpuFreeze,
+            FunctionId::CpuFreeze
+        );
+    }
+
+    #[test]
+    fn test_function_cpu_default_suspend() {
+        validate_function!(
+            [0x8400_000c, 0xabcdef01, 0x23456789, 0],
+            Function::CpuDefaultSuspend {
+                entry: EntryPoint::Entry32 {
+                    entry_point_address: 0xabcdef01,
+                    context_id: 0x23456789,
+                },
+            },
+            FunctionId::CpuDefaultSuspend32
+        );
+
+        validate_function!(
+            [0xc400_000c, 0xabcdef01_23456789, 0x23456789_abcdef01, 0],
+            Function::CpuDefaultSuspend {
+                entry: EntryPoint::Entry64 {
+                    entry_point_address: 0xabcdef01_23456789,
+                    context_id: 0x23456789_abcdef01,
+                },
+            },
+            FunctionId::CpuDefaultSuspend64
+        );
+    }
+
+    #[test]
+    fn test_function_node_hw_state() {
+        validate_function!(
+            [0x8400_000d, MPIDR32_VALUE.into(), 0xabcdef01, 0],
+            Function::NodeHwState {
+                target_cpu: MPIDR32,
+                power_level: 0xabcdef01,
+            },
+            FunctionId::NodeHwState32
+        );
+
+        validate_function!(
+            [0xc400_000d, MPIDR64_VALUE, 0xabcdef01, 0],
+            Function::NodeHwState {
+                target_cpu: MPIDR64,
+                power_level: 0xabcdef01,
+            },
+            FunctionId::NodeHwState64
+        );
+    }
+
+    #[test]
+    fn test_function_system_suspend() {
+        validate_function!(
+            [0x8400_000e, 0xabcdef01, 0x23456789, 0],
+            Function::SystemSuspend {
+                entry: EntryPoint::Entry32 {
+                    entry_point_address: 0xabcdef01,
+                    context_id: 0x23456789,
+                },
+            },
+            FunctionId::SystemSuspend32
+        );
+
+        validate_function!(
+            [0xc400_000e, 0xabcdef01_23456789, 0x23456789_abcdef01, 0],
+            Function::SystemSuspend {
+                entry: EntryPoint::Entry64 {
+                    entry_point_address: 0xabcdef01_23456789,
+                    context_id: 0x23456789_abcdef01,
+                },
+            },
+            FunctionId::SystemSuspend64
+        );
+    }
+
+    #[test]
+    fn test_function_set_suspend_mode() {
+        validate_function!(
+            [0x8400_000f, 0x0000_0001, 0, 0],
+            Function::SetSuspendMode {
+                mode: SuspendMode::OsInitiated,
+            },
+            FunctionId::PsciSetSuspendMode
+        );
+    }
+
+    #[test]
+    fn test_function_stat_residency() {
+        validate_function!(
+            [
+                0x8400_0010,
+                MPIDR32_VALUE.into(),
+                POWER_STATE_VALUE.into(),
+                0,
+            ],
+            Function::StatResidency {
+                target_cpu: MPIDR32,
+                power_state: POWER_STATE,
+            },
+            FunctionId::PsciStatResidency32
+        );
+
+        validate_function!(
+            [0xc400_0010, MPIDR64_VALUE, POWER_STATE_VALUE.into(), 0],
+            Function::StatResidency {
+                target_cpu: MPIDR64,
+                power_state: POWER_STATE,
+            },
+            FunctionId::PsciStatResidency64
+        );
+    }
+
+    #[test]
+    fn test_function_stat_count() {
+        validate_function!(
+            [
+                0x8400_0011,
+                MPIDR32_VALUE.into(),
+                POWER_STATE_VALUE.into(),
+                0,
+            ],
+            Function::StatCount {
+                target_cpu: MPIDR32,
+                power_state: POWER_STATE,
+            },
+            FunctionId::PsciStatCount32
+        );
+
+        validate_function!(
+            [0xc400_0011, MPIDR64_VALUE, POWER_STATE_VALUE.into(), 0],
+            Function::StatCount {
+                target_cpu: MPIDR64,
+                power_state: POWER_STATE,
+            },
+            FunctionId::PsciStatCount64
+        );
+    }
+}