Skip to content

Commit

Permalink
hw-model: High-level API for making mailbox requests. (#1070)
Browse files Browse the repository at this point in the history
  • Loading branch information
korran authored Nov 15, 2023
1 parent 2d2cd51 commit 8738d92
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 57 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions api/src/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ impl From<CommandId> for u32 {
}
}

/// A trait implemented by request types. Describes the associated command ID
/// and response type.
pub trait Request: AsBytes + FromBytes {
const ID: CommandId;
type Resp: FromBytes;
}

// Contains all the possible mailbox response structs
#[cfg_attr(test, derive(PartialEq, Debug, Eq))]
#[allow(clippy::large_enum_variant)]
Expand Down Expand Up @@ -292,6 +299,16 @@ pub struct GetIdevInfoResp {
pub idev_pub_y: [u8; 48],
}

#[repr(C)]
#[derive(Default, Debug, AsBytes, FromBytes, PartialEq, Eq)]
pub struct TestOnlyGetLdevCertReq {
header: MailboxReqHeader,
}
impl Request for TestOnlyGetLdevCertReq {
const ID: CommandId = CommandId::TEST_ONLY_GET_LDEV_CERT;
type Resp = GetLdevCertResp;
}

// GET_LDEV_CERT
// No command-specific input args
#[repr(C)]
Expand All @@ -303,6 +320,10 @@ pub struct GetLdevCertResp {
}
impl GetLdevCertResp {
pub const DATA_MAX_SIZE: usize = 1024;

pub fn data(&self) -> Option<&[u8]> {
self.data.get(..self.data_size as usize)
}
}

// ECDSA384_SIGNATURE_VERIFY
Expand All @@ -315,6 +336,10 @@ pub struct EcdsaVerifyReq {
pub signature_r: [u8; 48],
pub signature_s: [u8; 48],
}
impl Request for EcdsaVerifyReq {
const ID: CommandId = CommandId::ECDSA384_VERIFY;
type Resp = MailboxRespHeader;
}
// No command-specific output args

// TEST_ONLY_HMAC384_SIGNATURE_VERIFY
Expand All @@ -327,6 +352,10 @@ pub struct HmacVerifyReq {
pub len: u32,
pub msg: [u8; 256],
}
impl Request for HmacVerifyReq {
const ID: CommandId = CommandId::TEST_ONLY_HMAC384_VERIFY;
type Resp = MailboxRespHeader;
}
// No command-specific output args

// STASH_MEASUREMENT
Expand All @@ -350,6 +379,10 @@ impl Default for StashMeasurementReq {
}
}
}
impl Request for StashMeasurementReq {
const ID: CommandId = CommandId::STASH_MEASUREMENT;
type Resp = StashMeasurementResp;
}

#[repr(C)]
#[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)]
Expand Down Expand Up @@ -384,6 +417,10 @@ impl Default for InvokeDpeReq {
}
}
}
impl Request for InvokeDpeReq {
const ID: CommandId = CommandId::INVOKE_DPE;
type Resp = InvokeDpeResp;
}

#[repr(C)]
#[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)]
Expand Down Expand Up @@ -417,6 +454,16 @@ impl Default for InvokeDpeResp {
}
}

#[repr(C)]
#[derive(Debug, Default, AsBytes, FromBytes, PartialEq, Eq)]
pub struct TestOnlyGetFmcAliasCertReq {
header: MailboxReqHeader,
}
impl Request for TestOnlyGetFmcAliasCertReq {
const ID: CommandId = CommandId::TEST_ONLY_GET_FMC_ALIAS_CERT;
type Resp = GetLdevCertResp;
}

// TEST_ONLY_GET_FMC_ALIAS_CERT
// No command-specific input args
#[repr(C)]
Expand All @@ -428,6 +475,10 @@ pub struct TestGetFmcAliasCertResp {
}
impl TestGetFmcAliasCertResp {
pub const DATA_MAX_SIZE: usize = 1024;

pub fn data(&self) -> Option<&[u8]> {
self.data.get(..self.data_size as usize)
}
}

// FIPS_SELF_TEST
Expand Down
228 changes: 227 additions & 1 deletion hw-model/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Licensed under the Apache-2.0 license

use std::mem;
use std::path::PathBuf;
use std::str::FromStr;
use std::{
Expand All @@ -8,6 +9,8 @@ use std::{
io::{stdout, ErrorKind, Write},
};

use api::calc_checksum;
use api::mailbox::{MailboxReqHeader, MailboxRespHeader};
use caliptra_api as api;
use caliptra_emu_bus::Bus;
use caliptra_hw_model_types::{
Expand Down Expand Up @@ -237,6 +240,12 @@ pub enum ModelError {
UnableToLockSha512Acc,
UploadMeasurementResponseError,
UnableToReadMailbox,
MailboxNoResponseData,
MailboxReqTypeTooSmall,
MailboxRespTypeTooSmall,
MailboxUnexpectedResponseLen { expected: u32, actual: u32 },
MailboxRespInvalidChecksum { expected: i32, actual: i32 },
MailboxRespInvalidFipsStatus(u32),
}
impl Error for ModelError {}
impl Display for ModelError {
Expand Down Expand Up @@ -268,6 +277,33 @@ impl Display for ModelError {
write!(f, "Error in response after uploading measurement")
}
ModelError::UnableToReadMailbox => write!(f, "Unable to read mailbox regs"),
ModelError::MailboxNoResponseData => {
write!(f, "Expected response data but none was found")
}
ModelError::MailboxReqTypeTooSmall => {
write!(f, "Mailbox request type too small to contain header")
}
ModelError::MailboxRespTypeTooSmall => {
write!(f, "Mailbox response type too small to contain header")
}
ModelError::MailboxUnexpectedResponseLen { expected, actual } => {
write!(
f,
"Expected mailbox response lenth of {expected}, was {actual}"
)
}
ModelError::MailboxRespInvalidChecksum { expected, actual } => {
write!(
f,
"Mailbox response had invalid checksum: expected {expected}, was {actual}"
)
}
ModelError::MailboxRespInvalidFipsStatus(status) => {
write!(
f,
"Mailbox response had non-success FIPS status: 0x{status:x}"
)
}
}
}
}
Expand Down Expand Up @@ -732,6 +768,56 @@ pub trait HwModel {

fn set_apb_pauser(&mut self, pauser: u32);

/// Executes a typed request and (if success), returns the typed response.
/// The checksum field of the request is calculated, and the checksum of the
/// response is validated.
fn mailbox_execute_req<R: api::mailbox::Request>(
&mut self,
mut req: R,
) -> std::result::Result<R::Resp, ModelError> {
if mem::size_of::<R>() < mem::size_of::<MailboxReqHeader>() {
return Err(ModelError::MailboxReqTypeTooSmall);
}
if mem::size_of::<R::Resp>() < mem::size_of::<MailboxRespHeader>() {
return Err(ModelError::MailboxRespTypeTooSmall);
}
let (header_bytes, payload_bytes) = req
.as_bytes_mut()
.split_at_mut(mem::size_of::<MailboxReqHeader>());

let mut header = MailboxReqHeader::read_from(header_bytes as &[u8]).unwrap();
header.chksum = api::calc_checksum(R::ID.into(), payload_bytes);
header_bytes.copy_from_slice(header.as_bytes());

let Some(response_bytes) = self.mailbox_execute(R::ID.into(), req.as_bytes())? else {
return Err(ModelError::MailboxNoResponseData);
};
let response = match R::Resp::read_from(response_bytes.as_slice()) {
Some(response) => response,
None => {
return Err(ModelError::MailboxUnexpectedResponseLen {
expected: mem::size_of::<R::Resp>() as u32,
actual: response_bytes.len() as u32,
})
}
};
let response_header =
MailboxRespHeader::read_from_prefix(response_bytes.as_slice()).unwrap();
let actual_checksum = calc_checksum(0, &response_bytes[4..]);
if actual_checksum != response_header.chksum {
return Err(ModelError::MailboxRespInvalidChecksum {
expected: response_header.chksum,
actual: actual_checksum,
});
}
if response_header.fips_status != MailboxRespHeader::FIPS_STATUS_APPROVED {
return Err(ModelError::MailboxRespInvalidFipsStatus(
response_header.fips_status,
));
}
Ok(response)
}

/// Executes `cmd` with request data `buf`. Returns `Ok(Some(_))` if
/// the uC responded with data, `Ok(None)` if the uC indicated success
/// without data, Err(ModelError::MailboxCmdFailed) if the microcontroller
Expand Down Expand Up @@ -962,11 +1048,15 @@ pub trait HwModel {

#[cfg(test)]
mod tests {
use crate::{mmio::Rv32GenMmio, BootParams, HwModel, InitParams, ModelError, ShaAccMode};
use crate::{
mmio::Rv32GenMmio, BootParams, DefaultHwModel, HwModel, InitParams, ModelError, ShaAccMode,
};
use caliptra_api::mailbox::{self, CommandId, MailboxReqHeader, MailboxRespHeader};
use caliptra_builder::firmware;
use caliptra_emu_bus::Bus;
use caliptra_emu_types::RvSize;
use caliptra_registers::{mbox::enums::MboxStatusE, soc_ifc};
use zerocopy::{AsBytes, FromBytes};

use crate as caliptra_hw_model;

Expand Down Expand Up @@ -1377,4 +1467,140 @@ mod tests {
);
}
}

#[test]
pub fn test_mailbox_execute_req() {
const NO_DATA_CMD: u32 = 0x2000_0000;
const SET_RESPONSE_CMD: u32 = 0x3000_0000;
const GET_RESPONSE_CMD: u32 = 0x3000_0001;

#[repr(C)]
#[derive(AsBytes, FromBytes, Default)]
struct TestReq {
hdr: MailboxReqHeader,
data: [u8; 4],
}
impl mailbox::Request for TestReq {
const ID: CommandId = CommandId(GET_RESPONSE_CMD);
type Resp = TestResp;
}
#[repr(C)]
#[derive(AsBytes, Debug, FromBytes, PartialEq, Eq)]
struct TestResp {
hdr: MailboxRespHeader,
data: [u8; 4],
}

#[repr(C)]
#[derive(AsBytes, FromBytes, Default)]
struct TestReqNoData {
hdr: MailboxReqHeader,
data: [u8; 4],
}
impl mailbox::Request for TestReqNoData {
const ID: CommandId = CommandId(NO_DATA_CMD);
type Resp = TestResp;
}

fn set_response(model: &mut DefaultHwModel, data: &[u8]) {
model.mailbox_execute(SET_RESPONSE_CMD, data).unwrap();
}

let rom =
caliptra_builder::build_firmware_rom(&firmware::hw_model_tests::MAILBOX_RESPONDER)
.unwrap();
let mut model = caliptra_hw_model::new(BootParams {
init_params: InitParams {
rom: &rom,
..Default::default()
},
..Default::default()
})
.unwrap();

// Success
set_response(
&mut model,
&[
0x2d, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, b'H', b'I', b'!', b'!',
],
);
let resp = model
.mailbox_execute_req(TestReq {
data: *b"Hi!!",
..Default::default()
})
.unwrap();
model
.step_until_output_and_take("|dcfeffff48692121|")
.unwrap();
assert_eq!(
resp,
TestResp {
hdr: MailboxRespHeader {
chksum: -211,
fips_status: 0
},
data: *b"HI!!",
},
);

// Set wrong length in response
set_response(
&mut model,
&[
0x2d, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, b'H', b'I', b'!',
],
);
let resp = model.mailbox_execute_req(TestReq {
data: *b"Hi!!",
..Default::default()
});
assert_eq!(
resp,
Err(ModelError::MailboxUnexpectedResponseLen {
expected: 12,
actual: 11
})
);

// Set bad checksum in response
set_response(
&mut model,
&[
0x2e, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, b'H', b'I', b'!', b'!',
],
);
let resp = model.mailbox_execute_req(TestReq {
data: *b"Hi!!",
..Default::default()
});
assert_eq!(
resp,
Err(ModelError::MailboxRespInvalidChecksum {
expected: -210,
actual: -211
})
);

// Set bad FIPS status in response
set_response(
&mut model,
&[
0x0c, 0xff, 0xff, 0xff, 0x01, 0x20, 0x00, 0x00, b'H', b'I', b'!', b'!',
],
);
let resp = model.mailbox_execute_req(TestReq {
data: *b"Hi!!",
..Default::default()
});
assert_eq!(resp, Err(ModelError::MailboxRespInvalidFipsStatus(0x2001)));

// Set no data in response
let resp = model.mailbox_execute_req(TestReqNoData {
data: *b"Hi!!",
..Default::default()
});
assert_eq!(resp, Err(ModelError::MailboxNoResponseData));
}
}
Loading

0 comments on commit 8738d92

Please sign in to comment.