blob: deebc474344a1f21729877371c8a30c4a5c62806 [file] [log] [blame]
// SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Implementation of FF-A partition discovery data structures.
use thiserror::Error;
use uuid::Uuid;
use zerocopy::{transmute, 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, PartitionInfoGetFlags, SuccessArgs, 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)]
pub enum Error {
#[error("Invalid buffer size")]
InvalidBufferSize,
#[error("Malformed descriptor")]
MalformedDescriptor,
}
impl From<Error> for crate::FfaError {
fn from(_value: Error) -> Self {
Self::InvalidParameters
}
}
/// Type of partition identified by the partition ID.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PartitionIdType {
/// Partition ID is a PE endpoint ID. Contains the number of execution contexts implemented by
/// this partition.
PeEndpoint { execution_ctx_count: u16 },
/// Partition ID is a SEPID for an independent peripheral device.
SepidIndep,
/// Partition ID is a SEPID for an dependent peripheral device. Contains the ID of the proxy
/// endpoint for a dependent peripheral device.
SepidDep { proxy_endpoint_id: u16 },
/// Partition ID is an auxiliary ID.
Aux,
}
impl PartitionIdType {
const SHIFT: usize = 4;
const MASK: u32 = 0b11;
const PE_ENDPOINT: u32 = 0b00;
const SEPID_INDEP: u32 = 0b01;
const SEPID_DEP: u32 = 0b10;
const AUX: u32 = 0b11;
}
/// Properties of a partition.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PartitionProperties {
/// The partition supports receipt of direct requests.
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.
pub support_notif_rec: bool,
/// The partition must be informed about each VM that is created by the Hypervisor.
pub subscribe_vm_created: bool,
/// The partition must be informed about each VM that is destroyed by the Hypervisor.
pub subscribe_vm_destroyed: bool,
/// The partition runs in the AArch64 execution state.
pub is_aarch64: bool,
}
impl PartitionProperties {
const SUPPORT_DIRECT_REQ_REC_SHIFT: usize = 0;
const SUPPORT_DIRECT_REQ_SEND_SHIFT: usize = 1;
const SUPPORT_INDIRECT_MSG_SHIFT: usize = 2;
const SUPPORT_NOTIF_REC_SHIFT: usize = 3;
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;
}
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,
};
let mut prop_bits = match id_type {
PartitionIdType::PeEndpoint { .. } => {
let mut p = PartitionIdType::PE_ENDPOINT << PartitionIdType::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 properties.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_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,
};
if properties.is_aarch64 {
prop_bits |= 1 << PartitionProperties::IS_AARCH64_SHIFT
}
(prop_bits, exec_ctx_count_or_proxy_id)
}
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();
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 (prop_bits >> PartitionProperties::SUBSCRIBE_VM_CREATED_SHIFT) & 0b1 == 1 {
part_props.subscribe_vm_created = true;
}
if (prop_bits >> PartitionProperties::SUBSCRIBE_VM_DESTROYED_SHIFT) & 0b1 == 1 {
part_props.subscribe_vm_destroyed = true;
}
}
if (prop_bits >> PartitionProperties::SUPPORT_DIRECT_REQ_SEND_SHIFT) & 0b1 == 1 {
part_props.support_direct_req_send = true;
}
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.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PartitionInfo {
pub uuid: Uuid,
pub partition_id: u16,
pub partition_id_type: PartitionIdType,
pub props: PartitionProperties,
}
impl PartitionInfo {
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(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 {
let mut desc_raw = partition_info_descriptor {
partition_id: desc.partition_id,
..Default::default()
};
(
desc_raw.partition_props,
desc_raw.exec_ctx_count_or_proxy_id,
) = create_partition_properties(version, desc.partition_id_type, desc.props);
if fill_uuid {
desc_raw.uuid.copy_from_slice(desc.uuid.as_bytes());
}
desc_raw.write_to_prefix(&mut buf[offset..]).unwrap();
offset += Self::DESC_SIZE;
}
}
}
/// `FFA_PARTITION_INFO_GET` specific success args structure.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct SuccessArgsPartitionInfoGet {
pub count: u32,
pub size: Option<u32>,
}
impl From<SuccessArgsPartitionInfoGet> for SuccessArgs {
fn from(value: SuccessArgsPartitionInfoGet) -> Self {
SuccessArgs::Args32([value.count, value.size.unwrap_or(0), 0, 0, 0, 0])
}
}
impl TryFrom<(PartitionInfoGetFlags, SuccessArgs)> for SuccessArgsPartitionInfoGet {
type Error = crate::Error;
fn try_from(value: (PartitionInfoGetFlags, SuccessArgs)) -> Result<Self, Self::Error> {
let (flags, value) = value;
let args = value.try_get_args32()?;
let size = if !flags.count_only {
Some(args[1])
} else {
None
};
Ok(Self {
count: args[0],
size,
})
}
}
/// `FFA_PARTITION_INFO_GET_REGS` specific success args structure.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct SuccessArgsPartitionInfoGetRegs {
pub last_index: u16,
pub current_index: u16,
pub info_tag: u16,
pub descriptor_data: [u8; Self::DESCRIPTOR_REG_COUNT * 8],
}
impl SuccessArgsPartitionInfoGetRegs {
const DESCRIPTOR_REG_COUNT: usize = 15;
const LAST_INDEX_SHIFT: usize = 0;
const CURRENT_INDEX_SHIFT: usize = 16;
const INFO_TAG_SHIFT: usize = 32;
const SIZE_SHIFT: usize = 48;
}
impl From<SuccessArgsPartitionInfoGetRegs> for SuccessArgs {
fn from(value: SuccessArgsPartitionInfoGetRegs) -> Self {
let mut args = [0; 16];
args[0] = ((value.last_index as u64) << SuccessArgsPartitionInfoGetRegs::LAST_INDEX_SHIFT)
| ((value.current_index as u64)
<< SuccessArgsPartitionInfoGetRegs::CURRENT_INDEX_SHIFT)
| ((value.info_tag as u64) << SuccessArgsPartitionInfoGetRegs::INFO_TAG_SHIFT)
| ((PartitionInfo::DESC_SIZE as u64) << SuccessArgsPartitionInfoGetRegs::SIZE_SHIFT);
let descriptor_regs: [u64; SuccessArgsPartitionInfoGetRegs::DESCRIPTOR_REG_COUNT] =
transmute!(value.descriptor_data);
args[1..].copy_from_slice(&descriptor_regs);
Self::Args64_2(args)
}
}
impl TryFrom<SuccessArgs> for SuccessArgsPartitionInfoGetRegs {
type Error = crate::Error;
fn try_from(value: SuccessArgs) -> Result<Self, Self::Error> {
let args = value.try_get_args64_2()?;
// Validate size
let size = (args[0] >> Self::SIZE_SHIFT) as u16;
if size as usize != PartitionInfo::DESC_SIZE {
return Err(Self::Error::InvalidPartitionInfoGetRegsResponse);
}
// Validate inidices
let last_index = (args[0] >> Self::LAST_INDEX_SHIFT) as u16;
let current_index = (args[0] >> Self::CURRENT_INDEX_SHIFT) as u16;
if last_index < current_index {
return Err(Self::Error::InvalidPartitionInfoGetRegsResponse);
}
let info_tag = (args[0] >> Self::INFO_TAG_SHIFT) as u16;
// Convert registers into byte array
let descriptor_regs: [u64; SuccessArgsPartitionInfoGetRegs::DESCRIPTOR_REG_COUNT] =
args[1..].try_into().unwrap();
let descriptor_data = transmute!(descriptor_regs);
Ok(Self {
last_index,
current_index,
info_tag,
descriptor_data,
})
}
}
/// Iterator of partition information descriptors.
pub struct PartitionInfoIterator<'a> {
version: Version,
buf: &'a [u8],
offset: usize,
count: usize,
}
impl<'a> PartitionInfoIterator<'a> {
/// Create an iterator of partition information descriptors from a buffer.
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);
};
if buf.len() < total_size {
return Err(Error::InvalidBufferSize);
}
Ok(Self {
version,
buf,
offset: 0,
count,
})
}
}
impl Iterator for PartitionInfoIterator<'_> {
type Item = Result<PartitionInfo, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.count > 0 {
let offset = self.offset;
self.offset += PartitionInfo::DESC_SIZE;
self.count -= 1;
let Ok(desc_raw) = partition_info_descriptor::ref_from_bytes(
&self.buf[offset..offset + PartitionInfo::DESC_SIZE],
) else {
return Some(Err(Error::MalformedDescriptor));
};
let partition_id = desc_raw.partition_id;
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,
props,
};
return Some(Ok(desc));
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::uuid;
// TODO: add tests with a known correct partition info blob
#[test]
fn part_info() {
let desc1 = PartitionInfo {
uuid: uuid!("12345678-1234-1234-1234-123456789abc"),
partition_id: 0x8001,
partition_id_type: PartitionIdType::PeEndpoint {
execution_ctx_count: 1,
},
props: PartitionProperties {
support_direct_req_rec: true,
support_direct_req_send: true,
support_indirect_msg: false,
support_notif_rec: false,
subscribe_vm_created: true,
subscribe_vm_destroyed: true,
is_aarch64: true,
support_direct_req2_rec: Some(true),
support_direct_req2_send: Some(true),
},
};
let desc2 = PartitionInfo {
uuid: uuid!("abcdef00-abcd-dcba-1234-abcdef012345"),
partition_id: 0x8002,
partition_id_type: PartitionIdType::SepidIndep,
props: PartitionProperties {
support_direct_req_rec: false,
support_direct_req_send: false,
support_indirect_msg: false,
support_notif_rec: false,
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(Version(1, 2), &[desc1, desc2], &mut buf, true);
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();
assert_eq!(desc1, desc1_check);
assert_eq!(desc2, desc2_check);
}
}