Add support for handling multiple FF-A versions
This is required because a single FF-A component (i.e. an SPMC) must be
able to support various FF-A versions at runtime. Also, implement some
of the new message types from FF-A v1.2 and add support for handling 18
registers instead of 8.
Signed-off-by: Balint Dobszay <balint.dobszay@arm.com>
Change-Id: Ia46dc40204698265dc4ea9ed3731275baad94949
diff --git a/README.md b/README.md
index e969932..691c7c2 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,8 @@
[FF-A Memory Management Protocol specification](https://developer.arm.com/documentation/den0140/latest/)
Library for handling common FF-A related functionality, create and parse interfaces and descriptors
-defined by FF-A.
+defined by FF-A. Starting from FF-A v1.2 the memory management related parts of the specification
+have been moved to a separate document (link above).
## Design goals
* Keep the code exception level agnostic by default. If exception level specific parts are
@@ -27,6 +28,17 @@
the spec (i.e. Table x.y or chapter x.y.z)
* The data structures should derive the necessary `zerocopy` traits.
+## FF-A version handling
+
+The FF-A specification allows different components of a system to use different versions of the
+specification. The version used at a specific FF-A instance (i.e. an interface between two FF-A
+components) is discovered at runtime, either by parsing FF-A manifests or using `FFA_VERSION`. An
+FF-A component might have to use multiple versions at runtime on its different interfaces, therefore
+this library must be able to support this and having a compile time fixed version is not possible.
+Because of this, most of the functions to create or parse FF-A messages and data structures require
+passing the FF-A version used at the instance where the serialized data was received from or will be
+sent to.
+
## Implemented features
* Supports converting FF-A interface types between Rust types and the FF-A register ABI.
@@ -38,6 +50,7 @@
* Implement missing features from FF-A v1.1 and later. Implementing FF-A v1.0 features that are
deprecated by v1.1 are low priority for now.
+ * Increase test coverage.
* Create more detailed documentation to capture which parts of FF-A are currently supported.
## License
diff --git a/src/boot_info.rs b/src/boot_info.rs
index c9c45fb..9d902cf 100644
--- a/src/boot_info.rs
+++ b/src/boot_info.rs
@@ -16,15 +16,18 @@
//! - S-EL2 SPMC and a S-EL0 SP.
//! - S-EL1 SPMC and a S-EL0 SP.
-use crate::{
- ffa_v1_1::{boot_info_descriptor, boot_info_header},
- Version,
-};
use core::ffi::CStr;
use thiserror::Error;
use uuid::Uuid;
use zerocopy::{FromBytes, IntoBytes};
+// This module uses FF-A v1.1 types by default.
+// FF-A v1.2 didn't introduce any changes to the data stuctures used by this module.
+use crate::{
+ ffa_v1_1::{boot_info_descriptor, boot_info_header},
+ Version,
+};
+
/// Rich error types returned by this module. Should be converted to [`crate::FfaError`] when used
/// with the `FFA_ERROR` interface.
#[derive(Debug, Error)]
@@ -244,7 +247,14 @@
/// virtual address where the buffer is mapped to). This is necessary since there are
/// self-references within the serialized data structure which must be described with an
/// absolute address according to the FF-A spec.
- pub fn pack(descriptors: &[BootInfo], buf: &mut [u8], mapped_addr: Option<usize>) {
+ pub fn pack(
+ version: Version,
+ descriptors: &[BootInfo],
+ buf: &mut [u8],
+ mapped_addr: Option<usize>,
+ ) {
+ assert!((Version(1, 1)..=Version(1, 2)).contains(&version));
+
// Offset from the base of the header to the first element in the boot info descriptor array
// Must be 8 byte aligned, but otherwise we're free to choose any value here.
// Let's just pack the array right after the header.
@@ -365,7 +375,7 @@
let header_raw = boot_info_header {
signature: 0x0ffa,
- version: Version(1, 1).into(),
+ version: version.into(),
boot_info_blob_size: total_offset as u32,
boot_info_desc_size: DESC_SIZE as u32,
boot_info_desc_count: desc_cnt as u32,
@@ -377,7 +387,7 @@
}
/// Validate and return the boot information header
- fn get_header(buf: &[u8]) -> Result<&boot_info_header, Error> {
+ fn get_header(version: Version, buf: &[u8]) -> Result<&boot_info_header, Error> {
let (header_raw, _) =
boot_info_header::ref_from_prefix(buf).map_err(|_| Error::InvalidHeader)?;
@@ -385,9 +395,9 @@
return Err(Error::InvalidSignature);
}
- let version = Version::from(header_raw.version);
- if version != Version(1, 1) {
- return Err(Error::InvalidVersion(version));
+ let header_version = header_raw.version.into();
+ if header_version != version {
+ return Err(Error::InvalidVersion(header_version));
}
Ok(header_raw)
@@ -397,8 +407,12 @@
/// consumer to map all of the boot information blob in its translation regime or copy it to
/// another memory location without parsing each element in the boot information descriptor
/// array.
- pub fn get_blob_size(buf: &[u8]) -> Result<usize, Error> {
- let header_raw = Self::get_header(buf)?;
+ pub fn get_blob_size(version: Version, buf: &[u8]) -> Result<usize, Error> {
+ if !(Version(1, 1)..=Version(1, 2)).contains(&version) {
+ return Err(Error::InvalidVersion(version));
+ }
+
+ let header_raw = Self::get_header(version, buf)?;
Ok(header_raw.boot_info_blob_size as usize)
}
@@ -414,8 +428,8 @@
impl<'a> BootInfoIterator<'a> {
/// Create an iterator of boot information descriptors from a buffer.
- pub fn new(buf: &'a [u8]) -> Result<Self, Error> {
- let header_raw = BootInfo::get_header(buf)?;
+ pub fn new(version: Version, buf: &'a [u8]) -> Result<Self, Error> {
+ let header_raw = BootInfo::get_header(version, buf)?;
if buf.len() < header_raw.boot_info_blob_size as usize {
return Err(Error::InvalidBufferSize);
@@ -551,8 +565,13 @@
let mut buf = [0u8; 0x1ff];
let buf_addr = buf.as_ptr() as usize;
- BootInfo::pack(&[desc1.clone(), desc2.clone()], &mut buf, Some(buf_addr));
- let mut descriptors = BootInfoIterator::new(&buf).unwrap();
+ BootInfo::pack(
+ Version(1, 1),
+ &[desc1.clone(), desc2.clone()],
+ &mut buf,
+ Some(buf_addr),
+ );
+ let mut descriptors = BootInfoIterator::new(Version(1, 1), &buf).unwrap();
let desc1_check = descriptors.next().unwrap().unwrap();
let desc2_check = descriptors.next().unwrap().unwrap();
diff --git a/src/ffa_v1_2.rs b/src/ffa_v1_2.rs
new file mode 100644
index 0000000..2d40344
--- /dev/null
+++ b/src/ffa_v1_2.rs
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: MIT OR Apache-2.0
+
+#![allow(non_camel_case_types)]
+
+use crate::ffa_v1_1;
+use zerocopy_derive::*;
+
+/// Table 6.1: Partition information descriptor
+/// Table 6.2: Partition properties descriptor
+/// The following changes are introduced by FF-A v1.2 to the partition properties field:
+/// - bit\[10:9\]: Has the following encoding if Bits\[5:4\] = b’00. Reserved (MBZ) otherwise.
+/// + bit\[9\] has the following encoding:
+/// * b’0: Cannot receive Direct requests via the FFA_MSG_SEND_DIRECT_REQ2 ABI.
+/// * b’1: Can receive Direct requests via the FFA_MSG_SEND_DIRECT_REQ2 ABI.
+/// + bit\[10\] has the following encoding:
+/// * b’0: Cannot send Direct requests via the FFA_MSG_SEND_DIRECT_REQ2 ABI.
+/// * b’1: Can send Direct requests via the FFA_MSG_SEND_DIRECT_REQ2 ABI.
+/// - bit\[31:11\]: Reserved (MBZ).
+///
+/// This doesn't change the descriptor format so we can just use an alias.
+#[allow(unused)]
+pub(crate) type partition_info_descriptor = ffa_v1_1::partition_info_descriptor;
+
+/// FF-A Memory Management Protocol Table 1.16: Endpoint memory access descriptor
+#[derive(Default, FromBytes, IntoBytes, KnownLayout, Immutable)]
+#[repr(C, packed)]
+pub(crate) struct endpoint_memory_access_descriptor {
+ /// Offset 0, length 4: Memory access permissions descriptor as specified in Table 10.15
+ pub(crate) access_perm_desc: ffa_v1_1::memory_access_permission_descriptor,
+ /// Offset 4, length 4: Offset to the composite memory region descriptor to which the endpoint
+ /// access permissions apply. Offset must be calculated from the base address of the data
+ /// structure this descriptor is included in. An offset value of 0 indicates that the endpoint
+ /// access permissions apply to a memory region description identified by the Handle parameter
+ /// specified in the data structure that includes this one.
+ pub(crate) composite_offset: u32,
+ /// Offset 8, length 16: Implementation defined information
+ pub(crate) impdef_info: [u8; 16],
+ /// Offset 24, length 8: Reserved (MBZ)
+ pub(crate) reserved: u64,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 466ec0a..53c1cb8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,6 +13,7 @@
pub mod boot_info;
mod ffa_v1_1;
+mod ffa_v1_2;
pub mod memory_management;
pub mod partition_info;
@@ -78,8 +79,10 @@
MsgSend2 = 0x84000086,
MsgSendDirectReq32 = 0x8400006f,
MsgSendDirectReq64 = 0xc400006f,
+ MsgSendDirectReq64_2 = 0xc400008d,
MsgSendDirectResp32 = 0x84000070,
MsgSendDirectResp64 = 0xc4000070,
+ MsgSendDirectResp64_2 = 0xc400008e,
MemDonate32 = 0x84000071,
MemDonate64 = 0xc4000071,
MemLend32 = 0x84000072,
@@ -99,6 +102,13 @@
ConsoleLog64 = 0xc400008a,
}
+impl FuncId {
+ /// Returns true if this is a 32-bit call, or false if it is a 64-bit call.
+ pub fn is_32bit(&self) -> bool {
+ u32::from(*self) & (1 << 30) != 0
+ }
+}
+
/// Error status codes used by the `FFA_ERROR` interface.
#[derive(Clone, Copy, Debug, Eq, Error, IntoPrimitive, PartialEq, TryFromPrimitive)]
#[num_enum(error_type(name = Error, constructor = Error::UnrecognisedErrorCode))]
@@ -151,10 +161,11 @@
pub enum SuccessArgs {
Result32([u32; 6]),
Result64([u64; 6]),
+ Result64_2([u64; 16]),
}
/// Version number of the FF-A implementation, `.0` is the major, `.1` is minor the version.
-#[derive(Clone, Copy, Eq, PartialEq)]
+#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
pub struct Version(pub u16, pub u16);
impl From<u32> for Version {
@@ -235,6 +246,10 @@
Args64([u64; 5]),
}
+/// Arguments for the `FFA_MSG_SEND_DIRECT_{REQ,RESP}2` interfaces.
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+pub struct DirectMsg2Args([u64; 14]);
+
/// Descriptor for a dynamically allocated memory buffer that contains the memory transaction
/// descriptor. Used by `FFA_MEM_{DONATE,LEND,SHARE,RETRIEVE_REQ}` interfaces, only when the TX
/// buffer is not used to transmit the transaction descriptor.
@@ -251,11 +266,11 @@
Addr64(u64),
}
-/// Argument for the `FFA_CONSOLE_LOG` interface. Currently only supports x0..x7 instead of x0..x17.
+/// Argument for the `FFA_CONSOLE_LOG` interface.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum ConsoleLogChars {
Reg32([u32; 6]),
- Reg64([u64; 6]),
+ Reg64([u64; 16]),
}
/// FF-A "message types", the terminology used by the spec is "interfaces". The interfaces are used
@@ -326,6 +341,17 @@
flags: u32,
args: DirectMsgArgs,
},
+ MsgSendDirectReq2 {
+ src_id: u16,
+ dst_id: u16,
+ uuid: Uuid,
+ args: DirectMsg2Args,
+ },
+ MsgSendDirectResp2 {
+ src_id: u16,
+ dst_id: u16,
+ args: DirectMsg2Args,
+ },
MemDonate {
total_len: u32,
frag_len: u32,
@@ -357,6 +383,7 @@
},
MemPermGet {
addr: MemAddr,
+ page_cnt: Option<u32>,
},
MemPermSet {
addr: MemAddr,
@@ -369,10 +396,116 @@
},
}
-impl TryFrom<[u64; 8]> for Interface {
- type Error = Error;
+impl Interface {
+ /// Returns the function ID for the call, if it has one.
+ pub fn function_id(&self) -> Option<FuncId> {
+ match self {
+ Interface::Error { .. } => Some(FuncId::Error),
+ Interface::Success { args, .. } => match args {
+ SuccessArgs::Result32(..) => Some(FuncId::Success32),
+ SuccessArgs::Result64(..) | SuccessArgs::Result64_2(..) => Some(FuncId::Success64),
+ },
+ Interface::Interrupt { .. } => Some(FuncId::Interrupt),
+ Interface::Version { .. } => Some(FuncId::Version),
+ Interface::VersionOut { .. } => None,
+ Interface::Features { .. } => Some(FuncId::Features),
+ Interface::RxAcquire { .. } => Some(FuncId::RxAcquire),
+ Interface::RxRelease { .. } => Some(FuncId::RxRelease),
+ Interface::RxTxMap { addr, .. } => match addr {
+ RxTxAddr::Addr32 { .. } => Some(FuncId::RxTxMap32),
+ RxTxAddr::Addr64 { .. } => Some(FuncId::RxTxMap64),
+ },
+ Interface::RxTxUnmap { .. } => Some(FuncId::RxTxUnmap),
+ Interface::PartitionInfoGet { .. } => Some(FuncId::PartitionInfoGet),
+ Interface::IdGet => Some(FuncId::IdGet),
+ Interface::SpmIdGet => Some(FuncId::SpmIdGet),
+ Interface::MsgWait => Some(FuncId::MsgWait),
+ Interface::Yield => Some(FuncId::Yield),
+ Interface::Run { .. } => Some(FuncId::Run),
+ Interface::NormalWorldResume => Some(FuncId::NormalWorldResume),
+ Interface::MsgSend2 { .. } => Some(FuncId::MsgSend2),
+ Interface::MsgSendDirectReq { args, .. } => match args {
+ DirectMsgArgs::Args32(_) => Some(FuncId::MsgSendDirectReq32),
+ DirectMsgArgs::Args64(_) => Some(FuncId::MsgSendDirectReq64),
+ },
+ Interface::MsgSendDirectResp { args, .. } => match args {
+ DirectMsgArgs::Args32(_) => Some(FuncId::MsgSendDirectResp32),
+ DirectMsgArgs::Args64(_) => Some(FuncId::MsgSendDirectResp64),
+ },
+ Interface::MsgSendDirectReq2 { .. } => Some(FuncId::MsgSendDirectReq64_2),
+ Interface::MsgSendDirectResp2 { .. } => Some(FuncId::MsgSendDirectResp64_2),
+ Interface::MemDonate { buf, .. } => match buf {
+ Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemDonate64),
+ _ => Some(FuncId::MemDonate32),
+ },
+ Interface::MemLend { buf, .. } => match buf {
+ Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemLend64),
+ _ => Some(FuncId::MemLend32),
+ },
+ Interface::MemShare { buf, .. } => match buf {
+ Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemShare64),
+ _ => Some(FuncId::MemShare32),
+ },
+ Interface::MemRetrieveReq { buf, .. } => match buf {
+ Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemRetrieveReq64),
+ _ => Some(FuncId::MemRetrieveReq32),
+ },
+ Interface::MemRetrieveResp { .. } => Some(FuncId::MemRetrieveResp),
+ Interface::MemRelinquish => Some(FuncId::MemRelinquish),
+ Interface::MemReclaim { .. } => Some(FuncId::MemReclaim),
+ Interface::MemPermGet { addr, .. } => match addr {
+ MemAddr::Addr32(_) => Some(FuncId::MemPermGet32),
+ MemAddr::Addr64(_) => Some(FuncId::MemPermGet64),
+ },
+ Interface::MemPermSet { addr, .. } => match addr {
+ MemAddr::Addr32(_) => Some(FuncId::MemPermSet32),
+ MemAddr::Addr64(_) => Some(FuncId::MemPermSet64),
+ },
+ Interface::ConsoleLog { char_lists, .. } => match char_lists {
+ ConsoleLogChars::Reg32(_) => Some(FuncId::ConsoleLog32),
+ ConsoleLogChars::Reg64(_) => Some(FuncId::ConsoleLog64),
+ },
+ }
+ }
- fn try_from(regs: [u64; 8]) -> Result<Self, Error> {
+ /// Returns true if this is a 32-bit call, or false if it is a 64-bit call.
+ pub fn is_32bit(&self) -> bool {
+ // TODO: self should always have a function ID?
+ self.function_id().unwrap().is_32bit()
+ }
+
+ /// Parse interface from register contents. The caller must ensure that the `regs` argument has
+ /// the correct length: 8 registers for FF-A v1.1 and lower, 18 registers for v1.2 and higher.
+ pub fn from_regs(version: Version, regs: &[u64]) -> Result<Self, Error> {
+ let reg_cnt = regs.len();
+
+ let msg = match reg_cnt {
+ 8 => {
+ assert!(version <= Version(1, 1));
+ Interface::unpack_regs8(version, regs.try_into().unwrap())?
+ }
+ 18 => {
+ assert!(version >= Version(1, 2));
+ match FuncId::try_from(regs[0] as u32)? {
+ FuncId::ConsoleLog64
+ | FuncId::Success64
+ | FuncId::MsgSendDirectReq64_2
+ | FuncId::MsgSendDirectResp64_2 => {
+ Interface::unpack_regs18(version, regs.try_into().unwrap())?
+ }
+ _ => Interface::unpack_regs8(version, regs[..8].try_into().unwrap())?,
+ }
+ }
+ _ => panic!(
+ "Invalid number of registers ({}) for FF-A version {}",
+ reg_cnt, version
+ ),
+ };
+
+ Ok(msg)
+ }
+
+ fn unpack_regs8(version: Version, regs: &[u64; 8]) -> Result<Self, Error> {
let fid = FuncId::try_from(regs[0] as u32)?;
let msg = match fid {
@@ -602,9 +735,19 @@
},
FuncId::MemPermGet32 => Self::MemPermGet {
addr: MemAddr::Addr32(regs[1] as u32),
+ page_cnt: if version >= Version(1, 3) {
+ Some(regs[2] as u32)
+ } else {
+ None
+ },
},
FuncId::MemPermGet64 => Self::MemPermGet {
addr: MemAddr::Addr64(regs[1]),
+ page_cnt: if version >= Version(1, 3) {
+ Some(regs[2] as u32)
+ } else {
+ None
+ },
},
FuncId::MemPermSet32 => Self::MemPermSet {
addr: MemAddr::Addr32(regs[1] as u32),
@@ -627,147 +770,78 @@
regs[7] as u32,
]),
},
- FuncId::ConsoleLog64 => Self::ConsoleLog {
- char_cnt: regs[1] as u8,
- char_lists: ConsoleLogChars::Reg64([
- regs[2], regs[3], regs[4], regs[5], regs[6], regs[7],
- ]),
- },
+ _ => panic!("Invalid number of registers (8) for function {:#x?}", fid),
};
Ok(msg)
}
-}
-impl Interface {
- /// Returns the function ID for the call, if it has one.
- pub fn function_id(&self) -> Option<FuncId> {
- match self {
- Interface::Error { .. } => Some(FuncId::Error),
- Interface::Success { args, .. } => match args {
- SuccessArgs::Result32(..) => Some(FuncId::Success32),
- SuccessArgs::Result64(..) => Some(FuncId::Success64),
- },
- Interface::Interrupt { .. } => Some(FuncId::Interrupt),
- Interface::Version { .. } => Some(FuncId::Version),
- Interface::VersionOut { .. } => None,
- Interface::Features { .. } => Some(FuncId::Features),
- Interface::RxAcquire { .. } => Some(FuncId::RxAcquire),
- Interface::RxRelease { .. } => Some(FuncId::RxRelease),
- Interface::RxTxMap { addr, .. } => match addr {
- RxTxAddr::Addr32 { .. } => Some(FuncId::RxTxMap32),
- RxTxAddr::Addr64 { .. } => Some(FuncId::RxTxMap64),
- },
- Interface::RxTxUnmap { .. } => Some(FuncId::RxTxUnmap),
- Interface::PartitionInfoGet { .. } => Some(FuncId::PartitionInfoGet),
- Interface::IdGet => Some(FuncId::IdGet),
- Interface::SpmIdGet => Some(FuncId::SpmIdGet),
- Interface::MsgWait => Some(FuncId::MsgWait),
- Interface::Yield => Some(FuncId::Yield),
- Interface::Run { .. } => Some(FuncId::Run),
- Interface::NormalWorldResume => Some(FuncId::NormalWorldResume),
- Interface::MsgSend2 { .. } => Some(FuncId::MsgSend2),
- Interface::MsgSendDirectReq { args, .. } => match args {
- DirectMsgArgs::Args32(_) => Some(FuncId::MsgSendDirectReq32),
- DirectMsgArgs::Args64(_) => Some(FuncId::MsgSendDirectReq64),
- },
- Interface::MsgSendDirectResp { args, .. } => match args {
- DirectMsgArgs::Args32(_) => Some(FuncId::MsgSendDirectResp32),
- DirectMsgArgs::Args64(_) => Some(FuncId::MsgSendDirectResp64),
- },
- Interface::MemDonate { buf, .. } => match buf {
- Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemDonate64),
- _ => Some(FuncId::MemDonate32),
- },
- Interface::MemLend { buf, .. } => match buf {
- Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemLend64),
- _ => Some(FuncId::MemLend32),
- },
- Interface::MemShare { buf, .. } => match buf {
- Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemShare64),
- _ => Some(FuncId::MemShare32),
- },
- Interface::MemRetrieveReq { buf, .. } => match buf {
- Some(MemOpBuf::Buf64 { .. }) => Some(FuncId::MemRetrieveReq64),
- _ => Some(FuncId::MemRetrieveReq32),
- },
- Interface::MemRetrieveResp { .. } => Some(FuncId::MemRetrieveResp),
- Interface::MemRelinquish => Some(FuncId::MemRelinquish),
- Interface::MemReclaim { .. } => Some(FuncId::MemReclaim),
- Interface::MemPermGet { addr, .. } => match addr {
- MemAddr::Addr32(_) => Some(FuncId::MemPermGet32),
- MemAddr::Addr64(_) => Some(FuncId::MemPermGet64),
- },
- Interface::MemPermSet { addr, .. } => match addr {
- MemAddr::Addr32(_) => Some(FuncId::MemPermSet32),
- MemAddr::Addr64(_) => Some(FuncId::MemPermSet64),
- },
- Interface::ConsoleLog { char_lists, .. } => match char_lists {
- ConsoleLogChars::Reg32(_) => Some(FuncId::ConsoleLog32),
- ConsoleLogChars::Reg64(_) => Some(FuncId::ConsoleLog64),
- },
- }
- }
+ fn unpack_regs18(version: Version, regs: &[u64; 18]) -> Result<Self, Error> {
+ assert!(version >= Version(1, 2));
- /// Returns true if this is a 32-bit call, or false if it is a 64-bit call.
- pub fn is_32bit(&self) -> bool {
- match self {
- Interface::Error { .. }
- | Interface::Interrupt { .. }
- | Interface::Version { .. }
- | Interface::VersionOut { .. }
- | Interface::Features { .. }
- | Interface::RxAcquire { .. }
- | Interface::RxRelease { .. }
- | Interface::RxTxUnmap { .. }
- | Interface::PartitionInfoGet { .. }
- | Interface::IdGet
- | Interface::SpmIdGet
- | Interface::MsgWait
- | Interface::Yield
- | Interface::Run { .. }
- | Interface::NormalWorldResume
- | Interface::MsgSend2 { .. }
- | Interface::MemRetrieveResp { .. }
- | Interface::MemRelinquish
- | Interface::MemReclaim { .. } => true,
- Interface::Success {
- args: SuccessArgs::Result32(..),
- ..
- } => true,
- Interface::RxTxMap {
- addr: RxTxAddr::Addr32 { .. },
- ..
- } => true,
- Interface::MsgSendDirectReq { args, .. }
- | Interface::MsgSendDirectResp { args, .. }
- if matches!(args, DirectMsgArgs::Args32(_)) =>
- {
- true
- }
- Interface::MemDonate { buf, .. }
- | Interface::MemLend { buf, .. }
- | Interface::MemShare { buf, .. }
- | Interface::MemRetrieveReq { buf, .. }
- if buf.is_none() || matches!(buf, Some(MemOpBuf::Buf32 { .. })) =>
- {
- true
- }
- Interface::MemPermGet { addr, .. } | Interface::MemPermSet { addr, .. }
- if matches!(addr, MemAddr::Addr32(_)) =>
- {
- true
- }
- Interface::ConsoleLog {
- char_lists: ConsoleLogChars::Reg32(_),
- ..
- } => true,
- _ => false,
- }
+ let fid = FuncId::try_from(regs[0] as u32)?;
+
+ let msg = match fid {
+ FuncId::Success64 => Self::Success {
+ target_info: regs[1] as u32,
+ args: SuccessArgs::Result64_2(regs[2..18].try_into().unwrap()),
+ },
+ FuncId::MsgSendDirectReq64_2 => Self::MsgSendDirectReq2 {
+ src_id: (regs[1] >> 16) as u16,
+ dst_id: regs[1] as u16,
+ uuid: Uuid::from_u64_pair(regs[2], regs[3]),
+ args: DirectMsg2Args(regs[4..18].try_into().unwrap()),
+ },
+ FuncId::MsgSendDirectResp64_2 => Self::MsgSendDirectResp2 {
+ src_id: (regs[1] >> 16) as u16,
+ dst_id: regs[1] as u16,
+ args: DirectMsg2Args(regs[4..18].try_into().unwrap()),
+ },
+ FuncId::ConsoleLog64 => Self::ConsoleLog {
+ char_cnt: regs[1] as u8,
+ char_lists: ConsoleLogChars::Reg64(regs[2..18].try_into().unwrap()),
+ },
+ _ => panic!("Invalid number of registers (18) for function {:#x?}", fid),
+ };
+
+ Ok(msg)
}
/// Create register contents for an interface.
- pub fn copy_to_array(&self, a: &mut [u64; 8]) {
+ pub fn to_regs(&self, version: Version, regs: &mut [u64]) {
+ let reg_cnt = regs.len();
+
+ match reg_cnt {
+ 8 => {
+ assert!(version <= Version(1, 1));
+ self.pack_regs8(version, (&mut regs[..8]).try_into().unwrap());
+ }
+ 18 => {
+ assert!(version >= Version(1, 2));
+
+ match self {
+ Interface::ConsoleLog {
+ char_lists: ConsoleLogChars::Reg64(_),
+ ..
+ }
+ | Interface::Success {
+ args: SuccessArgs::Result64_2(_),
+ ..
+ }
+ | Interface::MsgSendDirectReq2 { .. }
+ | Interface::MsgSendDirectResp2 { .. } => {
+ self.pack_regs18(version, regs.try_into().unwrap());
+ }
+ _ => {
+ self.pack_regs8(version, (&mut regs[..8]).try_into().unwrap());
+ }
+ }
+ }
+ _ => panic!("Invalid number of registers {}", reg_cnt),
+ }
+ }
+
+ fn pack_regs8(&self, version: Version, a: &mut [u64; 8]) {
a.fill(0);
if let Some(function_id) = self.function_id() {
a[0] = function_id as u64;
@@ -800,6 +874,7 @@
a[6] = regs[4];
a[7] = regs[5];
}
+ _ => panic!("{:#x?} requires 18 registers", args),
}
}
Interface::Interrupt {
@@ -980,11 +1055,17 @@
a[2] = handle_regs[1].into();
a[3] = flags.into();
}
- Interface::MemPermGet { addr } => {
+ Interface::MemPermGet { addr, page_cnt } => {
a[1] = match addr {
MemAddr::Addr32(addr) => addr.into(),
MemAddr::Addr64(addr) => addr,
};
+ a[2] = if version >= Version(1, 3) {
+ page_cnt.unwrap().into()
+ } else {
+ assert!(page_cnt.is_none());
+ 0
+ }
}
Interface::MemPermSet {
addr,
@@ -1012,16 +1093,60 @@
a[6] = regs[4].into();
a[7] = regs[5].into();
}
- ConsoleLogChars::Reg64(regs) => {
- a[2] = regs[0];
- a[3] = regs[1];
- a[4] = regs[2];
- a[5] = regs[3];
- a[6] = regs[4];
- a[7] = regs[5];
- }
+ _ => panic!("{:#x?} requires 18 registers", char_lists),
}
}
+ _ => panic!("{:#x?} requires 18 registers", self),
+ }
+ }
+
+ fn pack_regs18(&self, version: Version, a: &mut [u64; 18]) {
+ assert!(version >= Version(1, 2));
+
+ a.fill(0);
+ if let Some(function_id) = self.function_id() {
+ a[0] = function_id as u64;
+ }
+
+ match *self {
+ Interface::Success { target_info, args } => {
+ a[1] = target_info.into();
+ match args {
+ SuccessArgs::Result64_2(regs) => a[2..18].copy_from_slice(®s[..16]),
+ _ => panic!("{:#x?} requires 8 registers", args),
+ }
+ }
+ Interface::MsgSendDirectReq2 {
+ src_id,
+ dst_id,
+ uuid,
+ args,
+ } => {
+ a[1] = ((src_id as u64) << 16) | dst_id as u64;
+ (a[2], a[3]) = uuid.as_u64_pair();
+ a[4..18].copy_from_slice(&args.0[..14]);
+ }
+ Interface::MsgSendDirectResp2 {
+ src_id,
+ dst_id,
+ args,
+ } => {
+ a[1] = ((src_id as u64) << 16) | dst_id as u64;
+ a[2] = 0;
+ a[3] = 0;
+ a[4..18].copy_from_slice(&args.0[..14]);
+ }
+ Interface::ConsoleLog {
+ char_cnt,
+ char_lists,
+ } => {
+ a[1] = char_cnt.into();
+ match char_lists {
+ ConsoleLogChars::Reg64(regs) => a[2..18].copy_from_slice(®s[..16]),
+ _ => panic!("{:#x?} requires 8 registers", char_lists),
+ }
+ }
+ _ => panic!("{:#x?} requires 8 registers", self),
}
}
@@ -1047,10 +1172,8 @@
/// Maximum number of characters transmitted in a single `FFA_CONSOLE_LOG32` message.
pub const CONSOLE_LOG_32_MAX_CHAR_CNT: u8 = 24;
-/// Maximum number of characters transmitted in a single `FFA_CONSOLE_LOG64` message. Note: this
-/// value currently differs from the spec because the library currently only supports parsing 8
-/// registers instead of 18.
-pub const CONSOLE_LOG_64_MAX_CHAR_CNT: u8 = 48;
+/// Maximum number of characters transmitted in a single `FFA_CONSOLE_LOG64` message.
+pub const CONSOLE_LOG_64_MAX_CHAR_CNT: u8 = 128;
/// Helper function to convert the "Tightly packed list of characters" format used by the
/// `FFA_CONSOLE_LOG` interface into a byte slice.
diff --git a/src/partition_info.rs b/src/partition_info.rs
index 781f299..23e4c06 100644
--- a/src/partition_info.rs
+++ b/src/partition_info.rs
@@ -3,11 +3,21 @@
//! Implementation of FF-A partition discovery data structures.
-use crate::ffa_v1_1::partition_info_descriptor;
use thiserror::Error;
use uuid::Uuid;
use zerocopy::{FromBytes, IntoBytes};
+// This module uses FF-A v1.1 types by default.
+// FF-A v1.2 specified some previously reserved bits in the partition info properties field, but
+// this doesn't change the descriptor format.
+use crate::{ffa_v1_1::partition_info_descriptor, Version};
+
+// Sanity check to catch if the descriptor format is changed.
+const _: () = assert!(
+ size_of::<crate::ffa_v1_1::partition_info_descriptor>()
+ == size_of::<crate::ffa_v1_2::partition_info_descriptor>()
+);
+
/// Rich error types returned by this module. Should be converted to [`crate::FfaError`] when used
/// with the `FFA_ERROR` interface.
#[derive(Debug, Error)]
@@ -55,6 +65,12 @@
pub support_direct_req_rec: bool,
/// The partition can send direct requests.
pub support_direct_req_send: bool,
+ /// The partition supports receipt of direct requests via the FFA_MSG_SEND_DIRECT_REQ2 ABI.
+ /// Added in FF-A v1.2
+ pub support_direct_req2_rec: Option<bool>,
+ /// The partition can send direct requests via the FFA_MSG_SEND_DIRECT_REQ2 ABI.
+ /// Added in FF-A v1.2
+ pub support_direct_req2_send: Option<bool>,
/// The partition can send and receive indirect messages.
pub support_indirect_msg: bool,
/// The partition supports receipt of notifications.
@@ -75,113 +91,134 @@
const SUBSCRIBE_VM_CREATED_SHIFT: usize = 6;
const SUBSCRIBE_VM_DESTROYED_SHIFT: usize = 7;
const IS_AARCH64_SHIFT: usize = 8;
+ const SUPPORT_DIRECT_REQ2_REC_SHIFT: usize = 9;
+ const SUPPORT_DIRECT_REQ2_SEND_SHIFT: usize = 10;
}
-struct PartPropWrapper(PartitionIdType, PartitionProperties);
+fn create_partition_properties(
+ version: Version,
+ id_type: PartitionIdType,
+ properties: PartitionProperties,
+) -> (u32, u16) {
+ let exec_ctx_count_or_proxy_id = match id_type {
+ PartitionIdType::PeEndpoint {
+ execution_ctx_count,
+ } => execution_ctx_count,
+ PartitionIdType::SepidIndep => 0,
+ PartitionIdType::SepidDep { proxy_endpoint_id } => proxy_endpoint_id,
+ PartitionIdType::Aux => 0,
+ };
-impl From<PartPropWrapper> for (u32, u16) {
- fn from(value: PartPropWrapper) -> Self {
- let exec_ctx_count_or_proxy_id = match value.0 {
- PartitionIdType::PeEndpoint {
- execution_ctx_count,
- } => execution_ctx_count,
- PartitionIdType::SepidIndep => 0,
- PartitionIdType::SepidDep { proxy_endpoint_id } => proxy_endpoint_id,
- PartitionIdType::Aux => 0,
- };
+ let mut prop_bits = match id_type {
+ PartitionIdType::PeEndpoint { .. } => {
+ let mut p = PartitionIdType::PE_ENDPOINT << PartitionIdType::SHIFT;
- let mut props = match value.0 {
- PartitionIdType::PeEndpoint { .. } => {
- let mut p = PartitionIdType::PE_ENDPOINT << PartitionIdType::SHIFT;
-
- if value.1.support_direct_req_rec {
- p |= 1 << PartitionProperties::SUPPORT_DIRECT_REQ_REC_SHIFT;
- if value.1.subscribe_vm_created {
- // TODO: how to handle if ABI is invoked at NS phys instance?
- p |= 1 << PartitionProperties::SUBSCRIBE_VM_CREATED_SHIFT
- }
- if value.1.subscribe_vm_destroyed {
- // TODO: how to handle if ABI is invoked at NS phys instance?
- p |= 1 << PartitionProperties::SUBSCRIBE_VM_DESTROYED_SHIFT
- }
+ if properties.support_direct_req_rec {
+ p |= 1 << PartitionProperties::SUPPORT_DIRECT_REQ_REC_SHIFT;
+ if properties.subscribe_vm_created {
+ // TODO: how to handle if ABI is invoked at NS phys instance?
+ p |= 1 << PartitionProperties::SUBSCRIBE_VM_CREATED_SHIFT
}
- if value.1.support_direct_req_send {
- p |= 1 << PartitionProperties::SUPPORT_DIRECT_REQ_SEND_SHIFT
+ if properties.subscribe_vm_destroyed {
+ // TODO: how to handle if ABI is invoked at NS phys instance?
+ p |= 1 << PartitionProperties::SUBSCRIBE_VM_DESTROYED_SHIFT
}
- if value.1.support_indirect_msg {
- p |= 1 << PartitionProperties::SUPPORT_INDIRECT_MSG_SHIFT
- }
- if value.1.support_notif_rec {
- p |= 1 << PartitionProperties::SUPPORT_NOTIF_REC_SHIFT
- }
-
- p
}
- PartitionIdType::SepidIndep => PartitionIdType::SEPID_INDEP << PartitionIdType::SHIFT,
- PartitionIdType::SepidDep { .. } => {
- PartitionIdType::SEPID_DEP << PartitionIdType::SHIFT
- }
- PartitionIdType::Aux => PartitionIdType::AUX << PartitionIdType::SHIFT,
- };
- if value.1.is_aarch64 {
- props |= 1 << PartitionProperties::IS_AARCH64_SHIFT
+ if properties.support_direct_req_send {
+ p |= 1 << PartitionProperties::SUPPORT_DIRECT_REQ_SEND_SHIFT
+ }
+
+ // For v1.2 and later it's mandatory to specify these properties
+ if version >= Version(1, 2) {
+ if properties.support_direct_req2_rec.unwrap() {
+ p |= 1 << PartitionProperties::SUPPORT_DIRECT_REQ2_REC_SHIFT
+ }
+
+ if properties.support_direct_req2_send.unwrap() {
+ p |= 1 << PartitionProperties::SUPPORT_DIRECT_REQ2_SEND_SHIFT
+ }
+ }
+
+ if properties.support_indirect_msg {
+ p |= 1 << PartitionProperties::SUPPORT_INDIRECT_MSG_SHIFT
+ }
+
+ if properties.support_notif_rec {
+ p |= 1 << PartitionProperties::SUPPORT_NOTIF_REC_SHIFT
+ }
+
+ p
}
+ PartitionIdType::SepidIndep => PartitionIdType::SEPID_INDEP << PartitionIdType::SHIFT,
+ PartitionIdType::SepidDep { .. } => PartitionIdType::SEPID_DEP << PartitionIdType::SHIFT,
+ PartitionIdType::Aux => PartitionIdType::AUX << PartitionIdType::SHIFT,
+ };
- (props, exec_ctx_count_or_proxy_id)
+ if properties.is_aarch64 {
+ prop_bits |= 1 << PartitionProperties::IS_AARCH64_SHIFT
}
+
+ (prop_bits, exec_ctx_count_or_proxy_id)
}
-impl From<(u32, u16)> for PartPropWrapper {
- fn from(value: (u32, u16)) -> Self {
- let part_id_type = match (value.0 >> PartitionIdType::SHIFT) & PartitionIdType::MASK {
- PartitionIdType::PE_ENDPOINT => PartitionIdType::PeEndpoint {
- execution_ctx_count: value.1,
- },
- PartitionIdType::SEPID_INDEP => PartitionIdType::SepidIndep,
- PartitionIdType::SEPID_DEP => PartitionIdType::SepidDep {
- proxy_endpoint_id: value.1,
- },
- PartitionIdType::AUX => PartitionIdType::Aux,
- _ => panic!(), // The match is exhaustive for a 2-bit value
- };
+fn parse_partition_properties(
+ version: Version,
+ prop_bits: u32,
+ id_type: u16,
+) -> (PartitionIdType, PartitionProperties) {
+ let part_id_type = match (prop_bits >> PartitionIdType::SHIFT) & PartitionIdType::MASK {
+ PartitionIdType::PE_ENDPOINT => PartitionIdType::PeEndpoint {
+ execution_ctx_count: id_type,
+ },
+ PartitionIdType::SEPID_INDEP => PartitionIdType::SepidIndep,
+ PartitionIdType::SEPID_DEP => PartitionIdType::SepidDep {
+ proxy_endpoint_id: id_type,
+ },
+ PartitionIdType::AUX => PartitionIdType::Aux,
+ _ => panic!(), // The match is exhaustive for a 2-bit value
+ };
- let mut part_props = PartitionProperties::default();
+ let mut part_props = PartitionProperties::default();
- if (value.0 >> PartitionIdType::SHIFT) & PartitionIdType::MASK
- == PartitionIdType::PE_ENDPOINT
- {
- if (value.0 >> PartitionProperties::SUPPORT_DIRECT_REQ_REC_SHIFT) & 0b1 == 1 {
- part_props.support_direct_req_rec = true;
+ if matches!(part_id_type, PartitionIdType::PeEndpoint { .. }) {
+ if (prop_bits >> PartitionProperties::SUPPORT_DIRECT_REQ_REC_SHIFT) & 0b1 == 1 {
+ part_props.support_direct_req_rec = true;
- if (value.0 >> PartitionProperties::SUBSCRIBE_VM_CREATED_SHIFT) & 0b1 == 1 {
- part_props.subscribe_vm_created = true;
- }
-
- if (value.0 >> PartitionProperties::SUBSCRIBE_VM_DESTROYED_SHIFT) & 0b1 == 1 {
- part_props.subscribe_vm_destroyed = true;
- }
+ if (prop_bits >> PartitionProperties::SUBSCRIBE_VM_CREATED_SHIFT) & 0b1 == 1 {
+ part_props.subscribe_vm_created = true;
}
- if (value.0 >> PartitionProperties::SUPPORT_DIRECT_REQ_SEND_SHIFT) & 0b1 == 1 {
- part_props.support_direct_req_send = true;
- }
-
- if (value.0 >> PartitionProperties::SUPPORT_INDIRECT_MSG_SHIFT) & 0b1 == 1 {
- part_props.support_indirect_msg = true;
- }
-
- if (value.0 >> PartitionProperties::SUPPORT_NOTIF_REC_SHIFT) & 0b1 == 1 {
- part_props.support_notif_rec = true;
+ if (prop_bits >> PartitionProperties::SUBSCRIBE_VM_DESTROYED_SHIFT) & 0b1 == 1 {
+ part_props.subscribe_vm_destroyed = true;
}
}
- if (value.0 >> PartitionProperties::IS_AARCH64_SHIFT) & 0b1 == 1 {
- part_props.is_aarch64 = true;
+ if (prop_bits >> PartitionProperties::SUPPORT_DIRECT_REQ_SEND_SHIFT) & 0b1 == 1 {
+ part_props.support_direct_req_send = true;
}
- PartPropWrapper(part_id_type, part_props)
+ if version >= Version(1, 2) {
+ part_props.support_direct_req2_rec =
+ Some((prop_bits >> PartitionProperties::SUPPORT_DIRECT_REQ2_REC_SHIFT) & 0b1 == 1);
+ part_props.support_direct_req2_send =
+ Some((prop_bits >> PartitionProperties::SUPPORT_DIRECT_REQ2_SEND_SHIFT) & 0b1 == 1);
+ }
+
+ if (prop_bits >> PartitionProperties::SUPPORT_INDIRECT_MSG_SHIFT) & 0b1 == 1 {
+ part_props.support_indirect_msg = true;
+ }
+
+ if (prop_bits >> PartitionProperties::SUPPORT_NOTIF_REC_SHIFT) & 0b1 == 1 {
+ part_props.support_notif_rec = true;
+ }
}
+
+ if (prop_bits >> PartitionProperties::IS_AARCH64_SHIFT) & 0b1 == 1 {
+ part_props.is_aarch64 = true;
+ }
+
+ (part_id_type, part_props)
}
/// Partition information descriptor, returned by the `FFA_PARTITION_INFO_GET` interface.
@@ -194,11 +231,13 @@
}
impl PartitionInfo {
- const DESC_SIZE: usize = size_of::<partition_info_descriptor>();
+ pub const DESC_SIZE: usize = size_of::<partition_info_descriptor>();
/// Serialize a list of partition information descriptors into a buffer. The `fill_uuid`
/// parameter controls whether the UUID field of the descriptor will be filled.
- pub fn pack(descriptors: &[PartitionInfo], buf: &mut [u8], fill_uuid: bool) {
+ pub fn pack(version: Version, descriptors: &[PartitionInfo], buf: &mut [u8], fill_uuid: bool) {
+ assert!((Version(1, 1)..=Version(1, 2)).contains(&version));
+
let mut offset = 0;
for desc in descriptors {
@@ -210,7 +249,7 @@
(
desc_raw.partition_props,
desc_raw.exec_ctx_count_or_proxy_id,
- ) = PartPropWrapper(desc.partition_id_type, desc.props).into();
+ ) = create_partition_properties(version, desc.partition_id_type, desc.props);
if fill_uuid {
desc_raw.uuid.copy_from_slice(desc.uuid.as_bytes());
@@ -224,6 +263,7 @@
/// Iterator of partition information descriptors.
pub struct PartitionInfoIterator<'a> {
+ version: Version,
buf: &'a [u8],
offset: usize,
count: usize,
@@ -231,7 +271,9 @@
impl<'a> PartitionInfoIterator<'a> {
/// Create an iterator of partition information descriptors from a buffer.
- pub fn new(buf: &'a [u8], count: usize) -> Result<Self, Error> {
+ pub fn new(version: Version, buf: &'a [u8], count: usize) -> Result<Self, Error> {
+ assert!((Version(1, 1)..=Version(1, 2)).contains(&version));
+
let Some(total_size) = count.checked_mul(PartitionInfo::DESC_SIZE) else {
return Err(Error::InvalidBufferSize);
};
@@ -241,6 +283,7 @@
}
Ok(Self {
+ version,
buf,
offset: 0,
count,
@@ -265,18 +308,19 @@
let partition_id = desc_raw.partition_id;
- let wrapper = PartPropWrapper::from((
+ let (partition_id_type, props) = parse_partition_properties(
+ self.version,
desc_raw.partition_props,
desc_raw.exec_ctx_count_or_proxy_id,
- ));
+ );
let uuid = Uuid::from_bytes(desc_raw.uuid);
let desc = PartitionInfo {
uuid,
partition_id,
- partition_id_type: wrapper.0,
- props: wrapper.1,
+ partition_id_type,
+ props,
};
return Some(Ok(desc));
@@ -309,6 +353,8 @@
subscribe_vm_created: true,
subscribe_vm_destroyed: true,
is_aarch64: true,
+ support_direct_req2_rec: Some(true),
+ support_direct_req2_send: Some(true),
},
};
@@ -324,13 +370,15 @@
subscribe_vm_created: false,
subscribe_vm_destroyed: false,
is_aarch64: true,
+ support_direct_req2_rec: None,
+ support_direct_req2_send: None,
},
};
let mut buf = [0u8; 0xff];
- PartitionInfo::pack(&[desc1, desc2], &mut buf, true);
+ PartitionInfo::pack(Version(1, 2), &[desc1, desc2], &mut buf, true);
- let mut descriptors = PartitionInfoIterator::new(&buf, 2).unwrap();
+ let mut descriptors = PartitionInfoIterator::new(Version(1, 2), &buf, 2).unwrap();
let desc1_check = descriptors.next().unwrap().unwrap();
let desc2_check = descriptors.next().unwrap().unwrap();