#![warn(missing_docs)]
#![deny(unsafe_code)]
use std::fmt::{self, Display};
use bitfield::bitfield;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TlpMode {
NonFlit,
Flit,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TlpError {
InvalidFormat,
InvalidType,
UnsupportedCombination,
InvalidLength,
NotImplemented,
MissingMandatoryOhc,
}
impl fmt::Display for TlpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TlpError::InvalidFormat => write!(f, "invalid TLP format field"),
TlpError::InvalidType => write!(f, "invalid TLP type field"),
TlpError::UnsupportedCombination => write!(f, "unsupported format/type combination"),
TlpError::InvalidLength => write!(f, "byte slice too short for expected TLP fields"),
TlpError::NotImplemented => write!(f, "feature not yet implemented"),
TlpError::MissingMandatoryOhc => {
write!(f, "mandatory OHC word missing for this TLP type")
}
}
}
}
impl std::error::Error for TlpError {}
#[repr(u8)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TlpFmt {
NoDataHeader3DW = 0b000,
NoDataHeader4DW = 0b001,
WithDataHeader3DW = 0b010,
WithDataHeader4DW = 0b011,
TlpPrefix = 0b100,
}
impl Display for TlpFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
TlpFmt::NoDataHeader3DW => "3DW no Data Header",
TlpFmt::NoDataHeader4DW => "4DW no Data Header",
TlpFmt::WithDataHeader3DW => "3DW with Data Header",
TlpFmt::WithDataHeader4DW => "4DW with Data Header",
TlpFmt::TlpPrefix => "Tlp Prefix",
};
write!(f, "{name}")
}
}
impl TryFrom<u32> for TlpFmt {
type Error = TlpError;
fn try_from(v: u32) -> Result<Self, Self::Error> {
match v {
0b000 => Ok(TlpFmt::NoDataHeader3DW),
0b001 => Ok(TlpFmt::NoDataHeader4DW),
0b010 => Ok(TlpFmt::WithDataHeader3DW),
0b011 => Ok(TlpFmt::WithDataHeader4DW),
0b100 => Ok(TlpFmt::TlpPrefix),
_ => Err(TlpError::InvalidFormat),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum AtomicOp {
FetchAdd,
Swap,
CompareSwap,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum AtomicWidth {
W32,
W64,
}
#[derive(PartialEq)]
pub(crate) enum TlpFormatEncodingType {
MemoryRequest = 0b00000,
MemoryLockRequest = 0b00001,
IORequest = 0b00010,
ConfigType0Request = 0b00100,
ConfigType1Request = 0b00101,
Completion = 0b01010,
CompletionLocked = 0b01011,
FetchAtomicOpRequest = 0b01100,
UnconSwapAtomicOpRequest = 0b01101,
CompSwapAtomicOpRequest = 0b01110,
DeferrableMemoryWriteRequest = 0b11011,
MessageRequest = 0b10000,
}
impl TryFrom<u32> for TlpFormatEncodingType {
type Error = TlpError;
fn try_from(v: u32) -> Result<Self, Self::Error> {
match v {
0b00000 => Ok(TlpFormatEncodingType::MemoryRequest),
0b00001 => Ok(TlpFormatEncodingType::MemoryLockRequest),
0b00010 => Ok(TlpFormatEncodingType::IORequest),
0b00100 => Ok(TlpFormatEncodingType::ConfigType0Request),
0b00101 => Ok(TlpFormatEncodingType::ConfigType1Request),
0b01010 => Ok(TlpFormatEncodingType::Completion),
0b01011 => Ok(TlpFormatEncodingType::CompletionLocked),
0b01100 => Ok(TlpFormatEncodingType::FetchAtomicOpRequest),
0b01101 => Ok(TlpFormatEncodingType::UnconSwapAtomicOpRequest),
0b01110 => Ok(TlpFormatEncodingType::CompSwapAtomicOpRequest),
0b11011 => Ok(TlpFormatEncodingType::DeferrableMemoryWriteRequest),
0b10000..=0b10101 => Ok(TlpFormatEncodingType::MessageRequest),
_ => Err(TlpError::InvalidType),
}
}
}
#[derive(PartialEq, Debug)]
pub enum TlpType {
MemReadReq,
MemReadLockReq,
MemWriteReq,
IOReadReq,
IOWriteReq,
ConfType0ReadReq,
ConfType0WriteReq,
ConfType1ReadReq,
ConfType1WriteReq,
MsgReq,
MsgReqData,
Cpl,
CplData,
CplLocked,
CplDataLocked,
FetchAddAtomicOpReq,
SwapAtomicOpReq,
CompareSwapAtomicOpReq,
DeferrableMemWriteReq,
LocalTlpPrefix,
EndToEndTlpPrefix,
}
impl Display for TlpType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
TlpType::MemReadReq => "Memory Read Request",
TlpType::MemReadLockReq => "Locked Memory Read Request",
TlpType::MemWriteReq => "Memory Write Request",
TlpType::IOReadReq => "IO Read Request",
TlpType::IOWriteReq => "IO Write Request",
TlpType::ConfType0ReadReq => "Type 0 Config Read Request",
TlpType::ConfType0WriteReq => "Type 0 Config Write Request",
TlpType::ConfType1ReadReq => "Type 1 Config Read Request",
TlpType::ConfType1WriteReq => "Type 1 Config Write Request",
TlpType::MsgReq => "Message Request",
TlpType::MsgReqData => "Message with Data Request",
TlpType::Cpl => "Completion",
TlpType::CplData => "Completion with Data",
TlpType::CplLocked => "Locked Completion",
TlpType::CplDataLocked => "Locked Completion with Data",
TlpType::FetchAddAtomicOpReq => "Fetch Add Atomic Op Request",
TlpType::SwapAtomicOpReq => "Swap Atomic Op Request",
TlpType::CompareSwapAtomicOpReq => "Compare Swap Atomic Op Request",
TlpType::DeferrableMemWriteReq => "Deferrable Memory Write Request",
TlpType::LocalTlpPrefix => "Local Tlp Prefix",
TlpType::EndToEndTlpPrefix => "End To End Tlp Prefix",
};
write!(f, "{name}")
}
}
impl TlpType {
pub fn is_non_posted(&self) -> bool {
matches!(
self,
TlpType::MemReadReq
| TlpType::MemReadLockReq
| TlpType::IOReadReq
| TlpType::IOWriteReq
| TlpType::ConfType0ReadReq
| TlpType::ConfType0WriteReq
| TlpType::ConfType1ReadReq
| TlpType::ConfType1WriteReq
| TlpType::FetchAddAtomicOpReq
| TlpType::SwapAtomicOpReq
| TlpType::CompareSwapAtomicOpReq
| TlpType::DeferrableMemWriteReq
)
}
pub fn is_posted(&self) -> bool {
!self.is_non_posted()
}
}
bitfield! {
struct TlpHeader(MSB0 [u8]);
u32;
get_format, _: 2, 0;
get_type, _: 7, 3;
get_t9, _: 8, 8;
get_tc, _: 11, 9;
get_t8, _: 12, 12;
get_attr_b2, _: 13, 13;
get_ln, _: 14, 14;
get_th, _: 15, 15;
get_td, _: 16, 16;
get_ep, _: 17, 17;
get_attr, _: 19, 18;
get_at, _: 21, 20;
get_length, _: 31, 22;
}
impl<T: AsRef<[u8]>> TlpHeader<T> {
fn get_tlp_type(&self) -> Result<TlpType, TlpError> {
let tlp_type = self.get_type();
let tlp_fmt = self.get_format();
if let Ok(TlpFmt::TlpPrefix) = TlpFmt::try_from(tlp_fmt) {
return if tlp_type & 0b10000 != 0 {
Ok(TlpType::EndToEndTlpPrefix)
} else {
Ok(TlpType::LocalTlpPrefix)
};
}
match TlpFormatEncodingType::try_from(tlp_type) {
Ok(TlpFormatEncodingType::MemoryRequest) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::MemReadReq),
Ok(TlpFmt::NoDataHeader4DW) => Ok(TlpType::MemReadReq),
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::MemWriteReq),
Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::MemWriteReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::MemoryLockRequest) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::MemReadLockReq),
Ok(TlpFmt::NoDataHeader4DW) => Ok(TlpType::MemReadLockReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::IORequest) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::IOReadReq),
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::IOWriteReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::ConfigType0Request) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::ConfType0ReadReq),
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::ConfType0WriteReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::ConfigType1Request) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::ConfType1ReadReq),
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::ConfType1WriteReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::Completion) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::Cpl),
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::CplData),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::CompletionLocked) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) => Ok(TlpType::CplLocked),
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::CplDataLocked),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::FetchAtomicOpRequest) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::FetchAddAtomicOpReq),
Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::FetchAddAtomicOpReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::UnconSwapAtomicOpRequest) => {
match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::SwapAtomicOpReq),
Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::SwapAtomicOpReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
}
}
Ok(TlpFormatEncodingType::CompSwapAtomicOpRequest) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::CompareSwapAtomicOpReq),
Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::CompareSwapAtomicOpReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Ok(TlpFormatEncodingType::DeferrableMemoryWriteRequest) => {
match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::WithDataHeader3DW) => Ok(TlpType::DeferrableMemWriteReq),
Ok(TlpFmt::WithDataHeader4DW) => Ok(TlpType::DeferrableMemWriteReq),
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
}
}
Ok(TlpFormatEncodingType::MessageRequest) => match TlpFmt::try_from(tlp_fmt) {
Ok(TlpFmt::NoDataHeader3DW) | Ok(TlpFmt::NoDataHeader4DW) => Ok(TlpType::MsgReq),
Ok(TlpFmt::WithDataHeader3DW) | Ok(TlpFmt::WithDataHeader4DW) => {
Ok(TlpType::MsgReqData)
}
Ok(_) => Err(TlpError::UnsupportedCombination),
Err(e) => Err(e),
},
Err(e) => Err(e),
}
}
}
pub trait MemRequest {
fn address(&self) -> u64;
fn req_id(&self) -> u16;
fn tag(&self) -> u8;
fn ldwbe(&self) -> u8;
fn fdwbe(&self) -> u8;
}
bitfield! {
#[allow(missing_docs)]
pub struct MemRequest3DW(MSB0 [u8]);
u32;
pub get_requester_id, _: 15, 0;
pub get_tag, _: 23, 16;
pub get_last_dw_be, _: 27, 24;
pub get_first_dw_be, _: 31, 28;
pub get_address32, _: 63, 32;
}
bitfield! {
#[allow(missing_docs)]
pub struct MemRequest4DW(MSB0 [u8]);
u64;
pub get_requester_id, _: 15, 0;
pub get_tag, _: 23, 16;
pub get_last_dw_be, _: 27, 24;
pub get_first_dw_be, _: 31, 28;
pub get_address64, _: 95, 32;
}
impl<T: AsRef<[u8]>> MemRequest for MemRequest3DW<T> {
fn address(&self) -> u64 {
self.get_address32().into()
}
fn req_id(&self) -> u16 {
self.get_requester_id() as u16
}
fn tag(&self) -> u8 {
self.get_tag() as u8
}
fn ldwbe(&self) -> u8 {
self.get_last_dw_be() as u8
}
fn fdwbe(&self) -> u8 {
self.get_first_dw_be() as u8
}
}
impl<T: AsRef<[u8]>> MemRequest for MemRequest4DW<T> {
fn address(&self) -> u64 {
self.get_address64()
}
fn req_id(&self) -> u16 {
self.get_requester_id() as u16
}
fn tag(&self) -> u8 {
self.get_tag() as u8
}
fn ldwbe(&self) -> u8 {
self.get_last_dw_be() as u8
}
fn fdwbe(&self) -> u8 {
self.get_first_dw_be() as u8
}
}
pub fn new_mem_req(
bytes: impl Into<Vec<u8>>,
format: &TlpFmt,
) -> Result<Box<dyn MemRequest>, TlpError> {
let bytes = bytes.into();
match format {
TlpFmt::NoDataHeader3DW | TlpFmt::WithDataHeader3DW => {
if bytes.len() < 8 {
return Err(TlpError::InvalidLength);
}
Ok(Box::new(MemRequest3DW(bytes)))
}
TlpFmt::NoDataHeader4DW | TlpFmt::WithDataHeader4DW => {
if bytes.len() < 12 {
return Err(TlpError::InvalidLength);
}
Ok(Box::new(MemRequest4DW(bytes)))
}
TlpFmt::TlpPrefix => Err(TlpError::UnsupportedCombination),
}
}
pub trait ConfigurationRequest {
fn req_id(&self) -> u16;
fn tag(&self) -> u8;
fn bus_nr(&self) -> u8;
fn dev_nr(&self) -> u8;
fn func_nr(&self) -> u8;
fn ext_reg_nr(&self) -> u8;
fn reg_nr(&self) -> u8;
}
pub fn new_conf_req(bytes: impl Into<Vec<u8>>) -> Result<Box<dyn ConfigurationRequest>, TlpError> {
let bytes = bytes.into();
if bytes.len() < 8 {
return Err(TlpError::InvalidLength);
}
Ok(Box::new(ConfigRequest(bytes)))
}
bitfield! {
#[allow(missing_docs)]
pub struct ConfigRequest(MSB0 [u8]);
u32;
pub get_requester_id, _: 15, 0;
pub get_tag, _: 23, 16;
pub get_last_dw_be, _: 27, 24;
pub get_first_dw_be, _: 31, 28;
pub get_bus_nr, _: 39, 32;
pub get_dev_nr, _: 44, 40;
pub get_func_nr, _: 47, 45;
pub rsvd, _: 51, 48;
pub get_ext_reg_nr, _: 55, 52;
pub get_register_nr, _: 61, 56;
r, _: 63, 62;
}
impl<T: AsRef<[u8]>> ConfigurationRequest for ConfigRequest<T> {
fn req_id(&self) -> u16 {
self.get_requester_id() as u16
}
fn tag(&self) -> u8 {
self.get_tag() as u8
}
fn bus_nr(&self) -> u8 {
self.get_bus_nr() as u8
}
fn dev_nr(&self) -> u8 {
self.get_dev_nr() as u8
}
fn func_nr(&self) -> u8 {
self.get_func_nr() as u8
}
fn ext_reg_nr(&self) -> u8 {
self.get_ext_reg_nr() as u8
}
fn reg_nr(&self) -> u8 {
self.get_register_nr() as u8
}
}
pub trait CompletionRequest {
fn cmpl_id(&self) -> u16;
fn cmpl_stat(&self) -> u8;
fn bcm(&self) -> u8;
fn byte_cnt(&self) -> u16;
fn req_id(&self) -> u16;
fn tag(&self) -> u8;
fn laddr(&self) -> u8;
}
bitfield! {
#[allow(missing_docs)]
pub struct CompletionReqDW23(MSB0 [u8]);
u16;
pub get_completer_id, _: 15, 0;
pub get_cmpl_stat, _: 18, 16;
pub get_bcm, _: 19, 19;
pub get_byte_cnt, _: 31, 20;
pub get_req_id, _: 47, 32;
pub get_tag, _: 55, 48;
r, _: 56, 56;
pub get_laddr, _: 63, 57;
}
impl<T: AsRef<[u8]>> CompletionRequest for CompletionReqDW23<T> {
fn cmpl_id(&self) -> u16 {
self.get_completer_id()
}
fn cmpl_stat(&self) -> u8 {
self.get_cmpl_stat() as u8
}
fn bcm(&self) -> u8 {
self.get_bcm() as u8
}
fn byte_cnt(&self) -> u16 {
self.get_byte_cnt()
}
fn req_id(&self) -> u16 {
self.get_req_id()
}
fn tag(&self) -> u8 {
self.get_tag() as u8
}
fn laddr(&self) -> u8 {
self.get_laddr() as u8
}
}
pub fn new_cmpl_req(bytes: impl Into<Vec<u8>>) -> Result<Box<dyn CompletionRequest>, TlpError> {
let bytes = bytes.into();
if bytes.len() < 8 {
return Err(TlpError::InvalidLength);
}
Ok(Box::new(CompletionReqDW23(bytes)))
}
pub trait MessageRequest {
fn req_id(&self) -> u16;
fn tag(&self) -> u8;
fn msg_code(&self) -> u8;
fn dw3(&self) -> u32;
fn dw4(&self) -> u32;
}
bitfield! {
#[allow(missing_docs)]
pub struct MessageReqDW24(MSB0 [u8]);
u32;
pub get_requester_id, _: 15, 0;
pub get_tag, _: 23, 16;
pub get_msg_code, _: 31, 24;
pub get_dw3, _: 63, 32;
pub get_dw4, _: 95, 64;
}
impl<T: AsRef<[u8]>> MessageRequest for MessageReqDW24<T> {
fn req_id(&self) -> u16 {
self.get_requester_id() as u16
}
fn tag(&self) -> u8 {
self.get_tag() as u8
}
fn msg_code(&self) -> u8 {
self.get_msg_code() as u8
}
fn dw3(&self) -> u32 {
self.get_dw3()
}
fn dw4(&self) -> u32 {
self.get_dw4()
}
}
pub fn new_msg_req(bytes: impl Into<Vec<u8>>) -> Result<Box<dyn MessageRequest>, TlpError> {
let bytes = bytes.into();
if bytes.len() < 12 {
return Err(TlpError::InvalidLength);
}
Ok(Box::new(MessageReqDW24(bytes)))
}
pub trait AtomicRequest: std::fmt::Debug {
fn op(&self) -> AtomicOp;
fn width(&self) -> AtomicWidth;
fn req_id(&self) -> u16;
fn tag(&self) -> u8;
fn address(&self) -> u64;
fn operand0(&self) -> u64;
fn operand1(&self) -> Option<u64>;
}
#[derive(Debug)]
struct AtomicReq {
op: AtomicOp,
width: AtomicWidth,
req_id: u16,
tag: u8,
address: u64,
operand0: u64,
operand1: Option<u64>,
}
impl AtomicRequest for AtomicReq {
fn op(&self) -> AtomicOp {
self.op
}
fn width(&self) -> AtomicWidth {
self.width
}
fn req_id(&self) -> u16 {
self.req_id
}
fn tag(&self) -> u8 {
self.tag
}
fn address(&self) -> u64 {
self.address
}
fn operand0(&self) -> u64 {
self.operand0
}
fn operand1(&self) -> Option<u64> {
self.operand1
}
}
fn read_operand_be(b: &[u8], off: usize, width: AtomicWidth) -> u64 {
match width {
AtomicWidth::W32 => u32::from_be_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]]) as u64,
AtomicWidth::W64 => u64::from_be_bytes([
b[off],
b[off + 1],
b[off + 2],
b[off + 3],
b[off + 4],
b[off + 5],
b[off + 6],
b[off + 7],
]),
}
}
pub fn new_atomic_req(pkt: &TlpPacket) -> Result<Box<dyn AtomicRequest>, TlpError> {
let tlp_type = pkt.tlp_type()?;
let format = pkt.tlp_format()?;
let bytes = pkt.data();
let op = match tlp_type {
TlpType::FetchAddAtomicOpReq => AtomicOp::FetchAdd,
TlpType::SwapAtomicOpReq => AtomicOp::Swap,
TlpType::CompareSwapAtomicOpReq => AtomicOp::CompareSwap,
_ => return Err(TlpError::UnsupportedCombination),
};
let (width, hdr_len) = match format {
TlpFmt::WithDataHeader3DW => (AtomicWidth::W32, 8usize),
TlpFmt::WithDataHeader4DW => (AtomicWidth::W64, 12usize),
_ => return Err(TlpError::UnsupportedCombination),
};
let op_size = match width {
AtomicWidth::W32 => 4usize,
AtomicWidth::W64 => 8usize,
};
let num_ops = if matches!(op, AtomicOp::CompareSwap) {
2
} else {
1
};
let needed = hdr_len + op_size * num_ops;
if bytes.len() != needed {
return Err(TlpError::InvalidLength);
}
let req_id = u16::from_be_bytes([bytes[0], bytes[1]]);
let tag = bytes[2];
let address = match width {
AtomicWidth::W32 => u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as u64,
AtomicWidth::W64 => u64::from_be_bytes([
bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11],
]),
};
let operand0 = read_operand_be(bytes, hdr_len, width);
let operand1 = if matches!(op, AtomicOp::CompareSwap) {
Some(read_operand_be(bytes, hdr_len + op_size, width))
} else {
None
};
Ok(Box::new(AtomicReq {
op,
width,
req_id,
tag,
address,
operand0,
operand1,
}))
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FlitTlpType {
Nop,
MemRead32,
UioMemRead,
MsgToRc,
MemWrite32,
IoWrite,
CfgWrite0,
FetchAdd32,
CompareSwap32,
DeferrableMemWrite32,
UioMemWrite,
MsgDToRc,
LocalTlpPrefix,
}
impl FlitTlpType {
pub fn base_header_dw(&self) -> u8 {
match self {
FlitTlpType::Nop | FlitTlpType::LocalTlpPrefix => 1,
FlitTlpType::UioMemRead | FlitTlpType::UioMemWrite => 4,
_ => 3,
}
}
pub fn is_read_request(&self) -> bool {
matches!(self, FlitTlpType::MemRead32 | FlitTlpType::UioMemRead)
}
pub fn has_data_payload(&self) -> bool {
matches!(
self,
FlitTlpType::MemWrite32
| FlitTlpType::UioMemWrite
| FlitTlpType::IoWrite
| FlitTlpType::CfgWrite0
| FlitTlpType::FetchAdd32
| FlitTlpType::CompareSwap32
| FlitTlpType::DeferrableMemWrite32
| FlitTlpType::MsgDToRc
)
}
}
impl Display for FlitTlpType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
FlitTlpType::Nop => "NOP",
FlitTlpType::MemRead32 => "Memory Read (32-bit)",
FlitTlpType::UioMemRead => "UIO Memory Read (64-bit)",
FlitTlpType::MsgToRc => "Message routed to RC",
FlitTlpType::MemWrite32 => "Memory Write (32-bit)",
FlitTlpType::IoWrite => "I/O Write",
FlitTlpType::CfgWrite0 => "Config Type 0 Write",
FlitTlpType::FetchAdd32 => "FetchAdd AtomicOp (32-bit)",
FlitTlpType::CompareSwap32 => "CompareSwap AtomicOp (32-bit)",
FlitTlpType::DeferrableMemWrite32 => "Deferrable Memory Write (32-bit)",
FlitTlpType::UioMemWrite => "UIO Memory Write (64-bit)",
FlitTlpType::MsgDToRc => "Message with Data routed to RC",
FlitTlpType::LocalTlpPrefix => "Local TLP Prefix",
};
write!(f, "{name}")
}
}
impl TryFrom<u8> for FlitTlpType {
type Error = TlpError;
fn try_from(v: u8) -> Result<Self, Self::Error> {
match v {
0x00 => Ok(FlitTlpType::Nop),
0x03 => Ok(FlitTlpType::MemRead32),
0x22 => Ok(FlitTlpType::UioMemRead),
0x30 => Ok(FlitTlpType::MsgToRc),
0x40 => Ok(FlitTlpType::MemWrite32),
0x42 => Ok(FlitTlpType::IoWrite),
0x44 => Ok(FlitTlpType::CfgWrite0),
0x4C => Ok(FlitTlpType::FetchAdd32),
0x4E => Ok(FlitTlpType::CompareSwap32),
0x5B => Ok(FlitTlpType::DeferrableMemWrite32),
0x61 => Ok(FlitTlpType::UioMemWrite),
0x70 => Ok(FlitTlpType::MsgDToRc),
0x8D => Ok(FlitTlpType::LocalTlpPrefix),
_ => Err(TlpError::InvalidType),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FlitDW0 {
pub tlp_type: FlitTlpType,
pub tc: u8,
pub ohc: u8,
pub ts: u8,
pub attr: u8,
pub length: u16,
}
impl FlitDW0 {
pub fn from_dw0(b: &[u8]) -> Result<Self, TlpError> {
if b.len() < 4 {
return Err(TlpError::InvalidLength);
}
let tlp_type = FlitTlpType::try_from(b[0])?;
let tc = (b[1] >> 5) & 0x07;
let ohc = b[1] & 0x1F;
let ts = (b[2] >> 5) & 0x07;
let attr = (b[2] >> 2) & 0x07;
let length = (((b[2] & 0x03) as u16) << 8) | (b[3] as u16);
Ok(FlitDW0 {
tlp_type,
tc,
ohc,
ts,
attr,
length,
})
}
pub fn ohc_count(&self) -> u8 {
self.ohc.count_ones() as u8
}
pub fn total_bytes(&self) -> usize {
let header_bytes =
(self.tlp_type.base_header_dw() as usize + self.ohc_count() as usize) * 4;
let payload_bytes = if !self.tlp_type.has_data_payload() {
0
} else {
let dw_count = if self.length == 0 {
1024
} else {
self.length as usize
};
dw_count * 4
};
header_bytes + payload_bytes
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FlitOhcA {
pub pasid: u32,
pub fdwbe: u8,
pub ldwbe: u8,
}
impl FlitOhcA {
pub fn from_bytes(b: &[u8]) -> Result<Self, TlpError> {
if b.len() < 4 {
return Err(TlpError::InvalidLength);
}
let pasid = ((b[0] as u32 & 0x0F) << 16) | ((b[1] as u32) << 8) | (b[2] as u32);
let fdwbe = b[3] & 0x0F;
let ldwbe = (b[3] >> 4) & 0x0F;
Ok(FlitOhcA {
pasid,
fdwbe,
ldwbe,
})
}
}
impl FlitDW0 {
pub fn validate_mandatory_ohc(&self) -> Result<(), TlpError> {
match self.tlp_type {
FlitTlpType::IoWrite | FlitTlpType::CfgWrite0 => {
if self.ohc & 0x01 == 0 {
return Err(TlpError::MissingMandatoryOhc);
}
Ok(())
}
_ => Ok(()),
}
}
}
pub struct FlitStreamWalker<'a> {
data: &'a [u8],
pos: usize,
errored: bool,
}
impl<'a> FlitStreamWalker<'a> {
pub fn new(data: &'a [u8]) -> Self {
FlitStreamWalker {
data,
pos: 0,
errored: false,
}
}
}
impl Iterator for FlitStreamWalker<'_> {
type Item = Result<(usize, FlitTlpType, usize), TlpError>;
fn next(&mut self) -> Option<Self::Item> {
if self.errored || self.pos >= self.data.len() {
return None;
}
let offset = self.pos;
let dw0 = match FlitDW0::from_dw0(&self.data[self.pos..]) {
Ok(d) => d,
Err(e) => {
self.errored = true;
return Some(Err(e));
}
};
let total = dw0.total_bytes();
if self.pos + total > self.data.len() {
self.errored = true;
return Some(Err(TlpError::InvalidLength));
}
self.pos += total;
Some(Ok((offset, dw0.tlp_type, total)))
}
}
pub struct TlpPacketHeader {
header: TlpHeader<Vec<u8>>,
}
impl fmt::Debug for TlpPacketHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TlpPacketHeader")
.field("format", &self.get_format())
.field("type", &self.get_type())
.field("tc", &self.get_tc())
.field("t9", &self.get_t9())
.field("t8", &self.get_t8())
.field("attr_b2", &self.get_attr_b2())
.field("ln", &self.get_ln())
.field("th", &self.get_th())
.field("td", &self.get_td())
.field("ep", &self.get_ep())
.field("attr", &self.get_attr())
.field("at", &self.get_at())
.field("length", &self.get_length())
.finish()
}
}
impl TlpPacketHeader {
pub fn new(bytes: Vec<u8>, mode: TlpMode) -> Result<TlpPacketHeader, TlpError> {
match mode {
TlpMode::NonFlit => Self::new_non_flit(bytes),
TlpMode::Flit => Err(TlpError::NotImplemented),
}
}
fn new_non_flit(bytes: Vec<u8>) -> Result<TlpPacketHeader, TlpError> {
if bytes.len() < 4 {
return Err(TlpError::InvalidLength);
}
let mut dw0 = vec![0; 4];
dw0[..4].clone_from_slice(&bytes[0..4]);
Ok(TlpPacketHeader {
header: TlpHeader(dw0),
})
}
pub fn tlp_type(&self) -> Result<TlpType, TlpError> {
self.header.get_tlp_type()
}
#[deprecated(since = "0.5.0", note = "use tlp_type() instead")]
pub fn get_tlp_type(&self) -> Result<TlpType, TlpError> {
self.tlp_type()
}
pub fn get_tc(&self) -> u32 {
self.header.get_tc()
}
pub(crate) fn get_format(&self) -> u32 {
self.header.get_format()
}
pub(crate) fn get_type(&self) -> u32 {
self.header.get_type()
}
pub(crate) fn get_t9(&self) -> u32 {
self.header.get_t9()
}
pub(crate) fn get_t8(&self) -> u32 {
self.header.get_t8()
}
pub(crate) fn get_attr_b2(&self) -> u32 {
self.header.get_attr_b2()
}
pub(crate) fn get_ln(&self) -> u32 {
self.header.get_ln()
}
pub(crate) fn get_th(&self) -> u32 {
self.header.get_th()
}
pub(crate) fn get_td(&self) -> u32 {
self.header.get_td()
}
pub(crate) fn get_ep(&self) -> u32 {
self.header.get_ep()
}
pub(crate) fn get_attr(&self) -> u32 {
self.header.get_attr()
}
pub(crate) fn get_at(&self) -> u32 {
self.header.get_at()
}
pub(crate) fn get_length(&self) -> u32 {
self.header.get_length()
}
}
pub struct TlpPacket {
header: TlpPacketHeader,
flit_dw0: Option<FlitDW0>,
data: Vec<u8>,
}
impl fmt::Debug for TlpPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TlpPacket")
.field("header", &self.header)
.field("flit_dw0", &self.flit_dw0)
.field("data_len", &self.data.len())
.finish()
}
}
impl TlpPacket {
pub fn new(bytes: Vec<u8>, mode: TlpMode) -> Result<TlpPacket, TlpError> {
match mode {
TlpMode::NonFlit => Self::new_non_flit(bytes),
TlpMode::Flit => Self::new_flit(bytes),
}
}
fn new_non_flit(mut bytes: Vec<u8>) -> Result<TlpPacket, TlpError> {
if bytes.len() < 4 {
return Err(TlpError::InvalidLength);
}
let mut header = vec![0; 4];
header.clone_from_slice(&bytes[0..4]);
let data = bytes.drain(4..).collect();
Ok(TlpPacket {
header: TlpPacketHeader::new_non_flit(header)?,
flit_dw0: None,
data,
})
}
fn new_flit(bytes: Vec<u8>) -> Result<TlpPacket, TlpError> {
if bytes.len() < 4 {
return Err(TlpError::InvalidLength);
}
let dw0 = FlitDW0::from_dw0(&bytes)?;
dw0.validate_mandatory_ohc()?;
let hdr_bytes = (dw0.tlp_type.base_header_dw() as usize + dw0.ohc_count() as usize) * 4;
if bytes.len() < hdr_bytes {
return Err(TlpError::InvalidLength);
}
let payload = bytes[hdr_bytes..].to_vec();
let dummy = TlpPacketHeader::new_non_flit(vec![0u8; 4])?;
Ok(TlpPacket {
header: dummy,
flit_dw0: Some(dw0),
data: payload,
})
}
pub fn header(&self) -> &TlpPacketHeader {
&self.header
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn tlp_type(&self) -> Result<TlpType, TlpError> {
if self.flit_dw0.is_some() {
return Err(TlpError::NotImplemented);
}
self.header.tlp_type()
}
pub fn tlp_format(&self) -> Result<TlpFmt, TlpError> {
if self.flit_dw0.is_some() {
return Err(TlpError::NotImplemented);
}
TlpFmt::try_from(self.header.get_format())
}
pub fn flit_type(&self) -> Option<FlitTlpType> {
self.flit_dw0.map(|d| d.tlp_type)
}
pub fn mode(&self) -> TlpMode {
if self.flit_dw0.is_some() {
TlpMode::Flit
} else {
TlpMode::NonFlit
}
}
#[deprecated(since = "0.5.0", note = "use header() instead")]
pub fn get_header(&self) -> &TlpPacketHeader {
self.header()
}
#[deprecated(since = "0.5.0", note = "use data() which returns &[u8] instead")]
pub fn get_data(&self) -> Vec<u8> {
self.data.clone()
}
#[deprecated(since = "0.5.0", note = "use tlp_type() instead")]
pub fn get_tlp_type(&self) -> Result<TlpType, TlpError> {
self.tlp_type()
}
#[deprecated(since = "0.5.0", note = "use tlp_format() instead")]
pub fn get_tlp_format(&self) -> Result<TlpFmt, TlpError> {
self.tlp_format()
}
#[deprecated(since = "0.5.0", note = "use flit_type() instead")]
pub fn get_flit_type(&self) -> Option<FlitTlpType> {
self.flit_type()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tlp_header_type() {
let memread = TlpHeader([0x0, 0x0, 0x0, 0x0]);
assert_eq!(memread.get_tlp_type().unwrap(), TlpType::MemReadReq);
let memread32 = TlpHeader([0x00, 0x00, 0x20, 0x01]);
assert_eq!(memread32.get_tlp_type().unwrap(), TlpType::MemReadReq);
let memwrite32 = TlpHeader([0x40, 0x00, 0x00, 0x01]);
assert_eq!(memwrite32.get_tlp_type().unwrap(), TlpType::MemWriteReq);
let cpl_no_data = TlpHeader([0x0a, 0x00, 0x10, 0x00]);
assert_eq!(cpl_no_data.get_tlp_type().unwrap(), TlpType::Cpl);
let cpl_with_data = TlpHeader([0x4a, 0x00, 0x20, 0x40]);
assert_eq!(cpl_with_data.get_tlp_type().unwrap(), TlpType::CplData);
let memread_4dw = TlpHeader([0x20, 0x00, 0x20, 0x40]);
assert_eq!(memread_4dw.get_tlp_type().unwrap(), TlpType::MemReadReq);
let conf_t0_read = TlpHeader([0x04, 0x00, 0x00, 0x01]);
assert_eq!(
conf_t0_read.get_tlp_type().unwrap(),
TlpType::ConfType0ReadReq
);
let conf_t0_write = TlpHeader([0x44, 0x00, 0x00, 0x01]);
assert_eq!(
conf_t0_write.get_tlp_type().unwrap(),
TlpType::ConfType0WriteReq
);
let conf_t1_read = TlpHeader([0x05, 0x88, 0x80, 0x01]);
assert_eq!(
conf_t1_read.get_tlp_type().unwrap(),
TlpType::ConfType1ReadReq
);
let conf_t1_write = TlpHeader([0x45, 0x88, 0x80, 0x01]);
assert_eq!(
conf_t1_write.get_tlp_type().unwrap(),
TlpType::ConfType1WriteReq
);
let memwrite64 = TlpHeader([0x60, 0x00, 0x90, 0x01]);
assert_eq!(memwrite64.get_tlp_type().unwrap(), TlpType::MemWriteReq);
}
#[test]
fn tlp_header_works_all_zeros() {
let bits_locations = TlpHeader([0x0, 0x0, 0x0, 0x0]);
assert_eq!(bits_locations.get_format(), 0);
assert_eq!(bits_locations.get_type(), 0);
assert_eq!(bits_locations.get_t9(), 0);
assert_eq!(bits_locations.get_tc(), 0);
assert_eq!(bits_locations.get_t8(), 0);
assert_eq!(bits_locations.get_attr_b2(), 0);
assert_eq!(bits_locations.get_ln(), 0);
assert_eq!(bits_locations.get_th(), 0);
assert_eq!(bits_locations.get_td(), 0);
assert_eq!(bits_locations.get_ep(), 0);
assert_eq!(bits_locations.get_attr(), 0);
assert_eq!(bits_locations.get_at(), 0);
assert_eq!(bits_locations.get_length(), 0);
}
#[test]
fn tlp_header_works_all_ones() {
let bits_locations = TlpHeader([0xff, 0xff, 0xff, 0xff]);
assert_eq!(bits_locations.get_format(), 0x7);
assert_eq!(bits_locations.get_type(), 0x1f);
assert_eq!(bits_locations.get_t9(), 0x1);
assert_eq!(bits_locations.get_tc(), 0x7);
assert_eq!(bits_locations.get_t8(), 0x1);
assert_eq!(bits_locations.get_attr_b2(), 0x1);
assert_eq!(bits_locations.get_ln(), 0x1);
assert_eq!(bits_locations.get_th(), 0x1);
assert_eq!(bits_locations.get_td(), 0x1);
assert_eq!(bits_locations.get_ep(), 0x1);
assert_eq!(bits_locations.get_attr(), 0x3);
assert_eq!(bits_locations.get_at(), 0x3);
assert_eq!(bits_locations.get_length(), 0x3ff);
}
#[test]
fn test_invalid_format_error() {
let invalid_fmt_101 = TlpHeader([0xa0, 0x00, 0x00, 0x01]);
assert_eq!(
invalid_fmt_101.get_tlp_type().unwrap_err(),
TlpError::InvalidFormat
);
let invalid_fmt_110 = TlpHeader([0xc0, 0x00, 0x00, 0x01]);
assert_eq!(
invalid_fmt_110.get_tlp_type().unwrap_err(),
TlpError::InvalidFormat
);
let invalid_fmt_111 = TlpHeader([0xe0, 0x00, 0x00, 0x01]);
assert_eq!(
invalid_fmt_111.get_tlp_type().unwrap_err(),
TlpError::InvalidFormat
);
}
#[test]
fn test_invalid_type_error() {
let invalid_type = TlpHeader([0x0f, 0x00, 0x00, 0x01]); let result = invalid_type.get_tlp_type();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), TlpError::InvalidType);
}
#[test]
fn test_unsupported_combination_error() {
let invalid_combo = TlpHeader([0x22, 0x00, 0x00, 0x01]); let result = invalid_combo.get_tlp_type();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), TlpError::UnsupportedCombination);
}
#[test]
fn mem_req_rejects_tlp_prefix() {
let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let result = new_mem_req(bytes, &TlpFmt::TlpPrefix);
assert!(matches!(result, Err(TlpError::UnsupportedCombination)));
}
#[test]
fn packet_new_rejects_empty_input() {
assert!(matches!(
TlpPacket::new(vec![], TlpMode::NonFlit),
Err(TlpError::InvalidLength)
));
}
#[test]
fn packet_new_rejects_3_bytes() {
assert!(matches!(
TlpPacket::new(vec![0x00, 0x00, 0x00], TlpMode::NonFlit),
Err(TlpError::InvalidLength)
));
}
#[test]
fn packet_new_accepts_4_bytes() {
assert!(TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::NonFlit).is_ok());
}
#[test]
fn packet_header_new_rejects_short_input() {
assert!(matches!(
TlpPacketHeader::new(vec![0x00, 0x00], TlpMode::NonFlit),
Err(TlpError::InvalidLength)
));
}
#[test]
fn packet_new_flit_succeeds_for_valid_nop() {
let bytes = vec![0x00, 0x00, 0x00, 0x00];
let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
assert_eq!(pkt.flit_type(), Some(FlitTlpType::Nop));
assert!(pkt.data().is_empty());
}
#[test]
fn packet_new_flit_mrd32_has_no_payload() {
let bytes = vec![
0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
assert_eq!(pkt.flit_type(), Some(FlitTlpType::MemRead32));
assert!(pkt.data().is_empty()); }
#[test]
fn packet_new_flit_mwr32_has_payload() {
let bytes = vec![
0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, ];
let pkt = TlpPacket::new(bytes, TlpMode::Flit).unwrap();
assert_eq!(pkt.flit_type(), Some(FlitTlpType::MemWrite32));
assert_eq!(pkt.data(), [0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn packet_new_flit_nonflit_returns_none_for_flit_type() {
let pkt = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::NonFlit).unwrap();
assert_eq!(pkt.flit_type(), None);
}
#[test]
fn packet_header_new_flit_returns_not_implemented() {
let bytes = vec![0x00, 0x00, 0x00, 0x00];
assert_eq!(
TlpPacketHeader::new(bytes, TlpMode::Flit).err().unwrap(),
TlpError::NotImplemented
);
}
#[test]
fn tlp_mode_debug_and_partialeq() {
assert_eq!(TlpMode::NonFlit, TlpMode::NonFlit);
assert_ne!(TlpMode::NonFlit, TlpMode::Flit);
let s = format!("{:?}", TlpMode::NonFlit);
assert!(s.contains("NonFlit"));
let s2 = format!("{:?}", TlpMode::Flit);
assert!(s2.contains("Flit"));
}
#[test]
#[allow(clippy::clone_on_copy)]
fn tlp_mode_copy_and_clone() {
let m = TlpMode::NonFlit;
let m2 = m; let m3 = m.clone(); assert_eq!(m2, TlpMode::NonFlit);
assert_eq!(m3, TlpMode::NonFlit);
}
#[test]
fn not_implemented_error_is_distinct() {
let e = TlpError::NotImplemented;
assert_ne!(e, TlpError::InvalidFormat);
assert_ne!(e, TlpError::InvalidType);
assert_ne!(e, TlpError::UnsupportedCombination);
assert_ne!(e, TlpError::InvalidLength);
assert_eq!(e, TlpError::NotImplemented);
let s = format!("{:?}", e);
assert!(s.contains("NotImplemented"));
}
fn dw0(fmt: u8, typ: u8) -> TlpHeader<[u8; 4]> {
TlpHeader([(fmt << 5) | (typ & 0x1f), 0x00, 0x00, 0x00])
}
fn mk_tlp(fmt: u8, typ: u8, rest: &[u8]) -> Vec<u8> {
let mut v = Vec::with_capacity(4 + rest.len());
v.push((fmt << 5) | (typ & 0x1f));
v.push(0x00); v.push(0x00); v.push(0x00); v.extend_from_slice(rest);
v
}
#[test]
fn header_decode_supported_pairs() {
const FMT_3DW_NO_DATA: u8 = 0b000;
const FMT_4DW_NO_DATA: u8 = 0b001;
const FMT_3DW_WITH_DATA: u8 = 0b010;
const FMT_4DW_WITH_DATA: u8 = 0b011;
const TY_MEM: u8 = 0b00000;
const TY_MEM_LK: u8 = 0b00001;
const TY_IO: u8 = 0b00010;
const TY_CFG0: u8 = 0b00100;
const TY_CFG1: u8 = 0b00101;
const TY_CPL: u8 = 0b01010;
const TY_CPL_LK: u8 = 0b01011;
const TY_ATOM_FETCH: u8 = 0b01100;
const TY_ATOM_SWAP: u8 = 0b01101;
const TY_ATOM_CAS: u8 = 0b01110;
const TY_DMWR: u8 = 0b11011;
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_MEM).get_tlp_type().unwrap(),
TlpType::MemReadReq
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_MEM).get_tlp_type().unwrap(),
TlpType::MemReadReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_MEM).get_tlp_type().unwrap(),
TlpType::MemWriteReq
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_MEM).get_tlp_type().unwrap(),
TlpType::MemWriteReq
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_MEM_LK).get_tlp_type().unwrap(),
TlpType::MemReadLockReq
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_MEM_LK).get_tlp_type().unwrap(),
TlpType::MemReadLockReq
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_IO).get_tlp_type().unwrap(),
TlpType::IOReadReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_IO).get_tlp_type().unwrap(),
TlpType::IOWriteReq
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_CFG0).get_tlp_type().unwrap(),
TlpType::ConfType0ReadReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_CFG0).get_tlp_type().unwrap(),
TlpType::ConfType0WriteReq
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_CFG1).get_tlp_type().unwrap(),
TlpType::ConfType1ReadReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_CFG1).get_tlp_type().unwrap(),
TlpType::ConfType1WriteReq
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_CPL).get_tlp_type().unwrap(),
TlpType::Cpl
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_CPL).get_tlp_type().unwrap(),
TlpType::CplData
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_CPL_LK).get_tlp_type().unwrap(),
TlpType::CplLocked
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_CPL_LK).get_tlp_type().unwrap(),
TlpType::CplDataLocked
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_ATOM_FETCH)
.get_tlp_type()
.unwrap(),
TlpType::FetchAddAtomicOpReq
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_ATOM_FETCH)
.get_tlp_type()
.unwrap(),
TlpType::FetchAddAtomicOpReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_ATOM_SWAP).get_tlp_type().unwrap(),
TlpType::SwapAtomicOpReq
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_ATOM_SWAP).get_tlp_type().unwrap(),
TlpType::SwapAtomicOpReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_ATOM_CAS).get_tlp_type().unwrap(),
TlpType::CompareSwapAtomicOpReq
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_ATOM_CAS).get_tlp_type().unwrap(),
TlpType::CompareSwapAtomicOpReq
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_DMWR).get_tlp_type().unwrap(),
TlpType::DeferrableMemWriteReq
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_DMWR).get_tlp_type().unwrap(),
TlpType::DeferrableMemWriteReq
);
for routing in 0b10000u8..=0b10101u8 {
assert_eq!(
dw0(FMT_3DW_NO_DATA, routing).get_tlp_type().unwrap(),
TlpType::MsgReq,
"Fmt=000 Type={:#07b} should be MsgReq",
routing
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, routing).get_tlp_type().unwrap(),
TlpType::MsgReq,
"Fmt=001 Type={:#07b} should be MsgReq",
routing
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, routing).get_tlp_type().unwrap(),
TlpType::MsgReqData,
"Fmt=010 Type={:#07b} should be MsgReqData",
routing
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, routing).get_tlp_type().unwrap(),
TlpType::MsgReqData,
"Fmt=011 Type={:#07b} should be MsgReqData",
routing
);
}
}
#[test]
fn header_decode_rejects_unsupported_combinations() {
const FMT_3DW_NO_DATA: u8 = 0b000;
const FMT_4DW_NO_DATA: u8 = 0b001;
const FMT_3DW_WITH_DATA: u8 = 0b010;
const FMT_4DW_WITH_DATA: u8 = 0b011;
const TY_MEM_LK: u8 = 0b00001;
const TY_IO: u8 = 0b00010;
const TY_CFG0: u8 = 0b00100;
const TY_CFG1: u8 = 0b00101;
const TY_CPL: u8 = 0b01010;
const TY_CPL_LK: u8 = 0b01011;
const TY_ATOM_FETCH: u8 = 0b01100;
const TY_ATOM_SWAP: u8 = 0b01101;
const TY_ATOM_CAS: u8 = 0b01110;
const TY_DMWR: u8 = 0b11011;
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_IO).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_IO).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_CFG0).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_CFG0).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_CFG1).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_CFG1).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_CPL).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_CPL).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_CPL_LK).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_CPL_LK)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_ATOM_FETCH)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_ATOM_FETCH)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_ATOM_SWAP)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_ATOM_SWAP)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_ATOM_CAS)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_ATOM_CAS)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_3DW_WITH_DATA, TY_MEM_LK)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_WITH_DATA, TY_MEM_LK)
.get_tlp_type()
.unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_3DW_NO_DATA, TY_DMWR).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
assert_eq!(
dw0(FMT_4DW_NO_DATA, TY_DMWR).get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
}
#[test]
fn tlp_header_dmwr32_decode() {
let dmwr32 = TlpHeader([0x5B, 0x00, 0x00, 0x00]);
assert_eq!(
dmwr32.get_tlp_type().unwrap(),
TlpType::DeferrableMemWriteReq
);
}
#[test]
fn tlp_header_dmwr64_decode() {
let dmwr64 = TlpHeader([0x7B, 0x00, 0x00, 0x00]);
assert_eq!(
dmwr64.get_tlp_type().unwrap(),
TlpType::DeferrableMemWriteReq
);
}
#[test]
fn tlp_header_dmwr_rejects_nodata_formats() {
let dmwr_bad_3dw_nodata = TlpHeader([0x1B, 0x00, 0x00, 0x00]);
assert_eq!(
dmwr_bad_3dw_nodata.get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
let dmwr_bad_4dw_nodata = TlpHeader([0x3B, 0x00, 0x00, 0x00]);
assert_eq!(
dmwr_bad_4dw_nodata.get_tlp_type().unwrap_err(),
TlpError::UnsupportedCombination
);
}
#[test]
fn dmwr_full_packet_3dw_fields() {
let payload = [
0xAB, 0xCD, 0x42, 0x0F, 0xDE, 0xAD, 0x00, 0x00, ];
let pkt = TlpPacket::new(mk_tlp(0b010, 0b11011, &payload), TlpMode::NonFlit).unwrap();
assert_eq!(pkt.tlp_type().unwrap(), TlpType::DeferrableMemWriteReq);
assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader3DW);
let mr = new_mem_req(pkt.data().to_vec(), &pkt.tlp_format().unwrap()).unwrap();
assert_eq!(mr.req_id(), 0xABCD);
assert_eq!(mr.tag(), 0x42);
assert_eq!(mr.address(), 0xDEAD_0000);
}
#[test]
fn dmwr_full_packet_4dw_fields() {
let payload = [
0xBE, 0xEF, 0xA5, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, ];
let pkt = TlpPacket::new(mk_tlp(0b011, 0b11011, &payload), TlpMode::NonFlit).unwrap();
assert_eq!(pkt.tlp_type().unwrap(), TlpType::DeferrableMemWriteReq);
assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader4DW);
let mr = new_mem_req(pkt.data().to_vec(), &pkt.tlp_format().unwrap()).unwrap();
assert_eq!(mr.req_id(), 0xBEEF);
assert_eq!(mr.tag(), 0xA5);
assert_eq!(mr.address(), 0x1122_3344_5566_7788);
}
#[test]
fn is_non_posted_returns_true_for_non_posted_types() {
assert!(TlpType::MemReadReq.is_non_posted());
assert!(TlpType::MemReadLockReq.is_non_posted());
assert!(TlpType::IOReadReq.is_non_posted());
assert!(TlpType::IOWriteReq.is_non_posted());
assert!(TlpType::ConfType0ReadReq.is_non_posted());
assert!(TlpType::ConfType0WriteReq.is_non_posted());
assert!(TlpType::ConfType1ReadReq.is_non_posted());
assert!(TlpType::ConfType1WriteReq.is_non_posted());
assert!(TlpType::FetchAddAtomicOpReq.is_non_posted());
assert!(TlpType::SwapAtomicOpReq.is_non_posted());
assert!(TlpType::CompareSwapAtomicOpReq.is_non_posted());
assert!(TlpType::DeferrableMemWriteReq.is_non_posted());
}
#[test]
fn is_non_posted_returns_false_for_posted_types() {
assert!(!TlpType::MemWriteReq.is_non_posted());
assert!(!TlpType::MsgReq.is_non_posted());
assert!(!TlpType::MsgReqData.is_non_posted());
}
#[test]
fn is_non_posted_returns_false_for_completions() {
assert!(!TlpType::Cpl.is_non_posted());
assert!(!TlpType::CplData.is_non_posted());
assert!(!TlpType::CplLocked.is_non_posted());
assert!(!TlpType::CplDataLocked.is_non_posted());
}
#[test]
fn is_non_posted_exhaustive_all_21_variants() {
assert!(
TlpType::MemReadReq.is_non_posted(),
"MemReadReq must be non-posted"
);
assert!(
TlpType::MemReadLockReq.is_non_posted(),
"MemReadLockReq must be non-posted"
);
assert!(
TlpType::IOReadReq.is_non_posted(),
"IOReadReq must be non-posted"
);
assert!(
TlpType::IOWriteReq.is_non_posted(),
"IOWriteReq must be non-posted"
);
assert!(
TlpType::ConfType0ReadReq.is_non_posted(),
"ConfType0ReadReq must be non-posted"
);
assert!(
TlpType::ConfType0WriteReq.is_non_posted(),
"ConfType0WriteReq must be non-posted"
);
assert!(
TlpType::ConfType1ReadReq.is_non_posted(),
"ConfType1ReadReq must be non-posted"
);
assert!(
TlpType::ConfType1WriteReq.is_non_posted(),
"ConfType1WriteReq must be non-posted"
);
assert!(
TlpType::FetchAddAtomicOpReq.is_non_posted(),
"FetchAddAtomicOpReq must be non-posted"
);
assert!(
TlpType::SwapAtomicOpReq.is_non_posted(),
"SwapAtomicOpReq must be non-posted"
);
assert!(
TlpType::CompareSwapAtomicOpReq.is_non_posted(),
"CompareSwapAtomicOpReq must be non-posted"
);
assert!(
TlpType::DeferrableMemWriteReq.is_non_posted(),
"DeferrableMemWriteReq must be non-posted"
);
assert!(
!TlpType::MemWriteReq.is_non_posted(),
"MemWriteReq is posted"
);
assert!(!TlpType::MsgReq.is_non_posted(), "MsgReq is posted");
assert!(!TlpType::MsgReqData.is_non_posted(), "MsgReqData is posted");
assert!(
!TlpType::Cpl.is_non_posted(),
"Cpl is a response, not a request"
);
assert!(
!TlpType::CplData.is_non_posted(),
"CplData is a response, not a request"
);
assert!(
!TlpType::CplLocked.is_non_posted(),
"CplLocked is a response, not a request"
);
assert!(
!TlpType::CplDataLocked.is_non_posted(),
"CplDataLocked is a response, not a request"
);
assert!(
!TlpType::LocalTlpPrefix.is_non_posted(),
"LocalTlpPrefix is not a transaction"
);
assert!(
!TlpType::EndToEndTlpPrefix.is_non_posted(),
"EndToEndTlpPrefix is not a transaction"
);
}
#[test]
fn atomic_fetchadd_3dw_type_and_fields() {
const FMT_3DW_WITH_DATA: u8 = 0b010;
const TY_ATOM_FETCH: u8 = 0b01100;
let payload = [
0x12, 0x34, 0x56, 0x00, 0x89, 0xAB, 0xCD, 0xEF, ];
let pkt = TlpPacket::new(
mk_tlp(FMT_3DW_WITH_DATA, TY_ATOM_FETCH, &payload),
TlpMode::NonFlit,
)
.unwrap();
assert_eq!(pkt.tlp_type().unwrap(), TlpType::FetchAddAtomicOpReq);
assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader3DW);
let fmt = pkt.tlp_format().unwrap();
let mr = new_mem_req(pkt.data().to_vec(), &fmt).unwrap();
assert_eq!(mr.req_id(), 0x1234);
assert_eq!(mr.tag(), 0x56);
assert_eq!(mr.address(), 0x89AB_CDEF);
}
#[test]
fn atomic_cas_4dw_type_and_fields() {
const FMT_4DW_WITH_DATA: u8 = 0b011;
const TY_ATOM_CAS: u8 = 0b01110;
let payload = [
0xBE, 0xEF, 0xA5, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, ];
let pkt = TlpPacket::new(
mk_tlp(FMT_4DW_WITH_DATA, TY_ATOM_CAS, &payload),
TlpMode::NonFlit,
)
.unwrap();
assert_eq!(pkt.tlp_type().unwrap(), TlpType::CompareSwapAtomicOpReq);
assert_eq!(pkt.tlp_format().unwrap(), TlpFmt::WithDataHeader4DW);
let fmt = pkt.tlp_format().unwrap();
let mr = new_mem_req(pkt.data().to_vec(), &fmt).unwrap();
assert_eq!(mr.req_id(), 0xBEEF);
assert_eq!(mr.tag(), 0xA5);
assert_eq!(mr.address(), 0x1122_3344_5566_7788);
}
#[test]
fn fetchadd_3dw_operand() {
let payload = [
0xDE, 0xAD, 0x42, 0x00, 0xC0, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0A, ];
let pkt = TlpPacket::new(mk_tlp(0b010, 0b01100, &payload), TlpMode::NonFlit).unwrap();
let ar = new_atomic_req(&pkt).unwrap();
assert_eq!(ar.op(), AtomicOp::FetchAdd);
assert_eq!(ar.width(), AtomicWidth::W32);
assert_eq!(ar.req_id(), 0xDEAD);
assert_eq!(ar.tag(), 0x42);
assert_eq!(ar.address(), 0xC001_0004);
assert_eq!(ar.operand0(), 0x0A);
assert!(ar.operand1().is_none());
}
#[test]
fn fetchadd_4dw_operand() {
let payload = [
0x00, 0x42, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ];
let pkt = TlpPacket::new(mk_tlp(0b011, 0b01100, &payload), TlpMode::NonFlit).unwrap();
let ar = new_atomic_req(&pkt).unwrap();
assert_eq!(ar.op(), AtomicOp::FetchAdd);
assert_eq!(ar.width(), AtomicWidth::W64);
assert_eq!(ar.req_id(), 0x0042);
assert_eq!(ar.tag(), 0xBB);
assert_eq!(ar.address(), 0x0000_0001_0000_0000);
assert_eq!(ar.operand0(), 0xFFFF_FFFF_FFFF_FFFF);
assert!(ar.operand1().is_none());
}
#[test]
fn swap_3dw_operand() {
let payload = [
0x11, 0x11, 0x05, 0x00, 0xF0, 0x00, 0x00, 0x08, 0xAB, 0xCD, 0xEF, 0x01, ];
let pkt = TlpPacket::new(mk_tlp(0b010, 0b01101, &payload), TlpMode::NonFlit).unwrap();
let ar = new_atomic_req(&pkt).unwrap();
assert_eq!(ar.op(), AtomicOp::Swap);
assert_eq!(ar.width(), AtomicWidth::W32);
assert_eq!(ar.req_id(), 0x1111);
assert_eq!(ar.tag(), 0x05);
assert_eq!(ar.address(), 0xF000_0008);
assert_eq!(ar.operand0(), 0xABCD_EF01);
assert!(ar.operand1().is_none());
}
#[test]
fn cas_3dw_two_operands() {
let payload = [
0xAB, 0xCD, 0x07, 0x00, 0x00, 0x00, 0x40, 0x00, 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, ];
let pkt = TlpPacket::new(mk_tlp(0b010, 0b01110, &payload), TlpMode::NonFlit).unwrap();
let ar = new_atomic_req(&pkt).unwrap();
assert_eq!(ar.op(), AtomicOp::CompareSwap);
assert_eq!(ar.width(), AtomicWidth::W32);
assert_eq!(ar.req_id(), 0xABCD);
assert_eq!(ar.tag(), 0x07);
assert_eq!(ar.address(), 0x0000_4000);
assert_eq!(ar.operand0(), 0xCAFE_BABE);
assert_eq!(ar.operand1(), Some(0xDEAD_BEEF));
}
#[test]
fn cas_4dw_two_operands() {
let payload = [
0x12, 0x34, 0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, ];
let pkt = TlpPacket::new(mk_tlp(0b011, 0b01110, &payload), TlpMode::NonFlit).unwrap();
let ar = new_atomic_req(&pkt).unwrap();
assert_eq!(ar.op(), AtomicOp::CompareSwap);
assert_eq!(ar.width(), AtomicWidth::W64);
assert_eq!(ar.req_id(), 0x1234);
assert_eq!(ar.tag(), 0xAA);
assert_eq!(ar.address(), 0xFFFF_FFFF_0000_0000);
assert_eq!(ar.operand0(), 0x0101_0101_0202_0202);
assert_eq!(ar.operand1(), Some(0x0303_0303_0404_0404));
}
#[test]
fn atomic_req_rejects_wrong_tlp_type() {
let pkt = TlpPacket::new(mk_tlp(0b000, 0b00000, &[0u8; 16]), TlpMode::NonFlit).unwrap();
assert_eq!(
new_atomic_req(&pkt).err().unwrap(),
TlpError::UnsupportedCombination
);
}
#[test]
fn atomic_req_rejects_wrong_format() {
let pkt = TlpPacket::new(mk_tlp(0b000, 0b01100, &[0u8; 16]), TlpMode::NonFlit).unwrap();
assert_eq!(
new_atomic_req(&pkt).err().unwrap(),
TlpError::UnsupportedCombination
);
}
#[test]
fn atomic_req_rejects_short_payload() {
let pkt = TlpPacket::new(mk_tlp(0b010, 0b01100, &[0u8; 3]), TlpMode::NonFlit).unwrap();
assert_eq!(new_atomic_req(&pkt).err().unwrap(), TlpError::InvalidLength);
let pkt = TlpPacket::new(mk_tlp(0b010, 0b01100, &[0u8; 8]), TlpMode::NonFlit).unwrap();
assert_eq!(new_atomic_req(&pkt).err().unwrap(), TlpError::InvalidLength);
let pkt = TlpPacket::new(mk_tlp(0b011, 0b01110, &[0u8; 20]), TlpMode::NonFlit).unwrap();
assert_eq!(new_atomic_req(&pkt).err().unwrap(), TlpError::InvalidLength);
}
fn mk_pkt(fmt: u8, typ: u8, data: &[u8]) -> TlpPacket {
TlpPacket::new(mk_tlp(fmt, typ, data), TlpMode::NonFlit).unwrap()
}
#[test]
fn atomic_fetchadd_3dw_32_parses_operands() {
let data = [
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x07, ];
let pkt = mk_pkt(0b010, 0b01100, &data);
let a = new_atomic_req(&pkt).unwrap();
assert_eq!(a.op(), AtomicOp::FetchAdd);
assert_eq!(a.width(), AtomicWidth::W32);
assert_eq!(a.req_id(), 0x0100);
assert_eq!(a.tag(), 0x01);
assert_eq!(a.address(), 0x0000_1000);
assert_eq!(a.operand0(), 7);
assert!(a.operand1().is_none());
}
#[test]
fn atomic_swap_4dw_64_parses_operands() {
let data = [
0xBE, 0xEF, 0xA5, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, ];
let pkt = mk_pkt(0b011, 0b01101, &data);
let a = new_atomic_req(&pkt).unwrap();
assert_eq!(a.op(), AtomicOp::Swap);
assert_eq!(a.width(), AtomicWidth::W64);
assert_eq!(a.req_id(), 0xBEEF);
assert_eq!(a.tag(), 0xA5);
assert_eq!(a.address(), 0x0000_0001_0000_0000);
assert_eq!(a.operand0(), 0xDEAD_BEEF_CAFE_BABE);
assert!(a.operand1().is_none());
}
#[test]
fn atomic_cas_3dw_32_parses_operands() {
let data = [
0xAB, 0xCD, 0x07, 0x00, 0x00, 0x00, 0x40, 0x00, 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, ];
let pkt = mk_pkt(0b010, 0b01110, &data);
let a = new_atomic_req(&pkt).unwrap();
assert_eq!(a.op(), AtomicOp::CompareSwap);
assert_eq!(a.width(), AtomicWidth::W32);
assert_eq!(a.req_id(), 0xABCD);
assert_eq!(a.tag(), 0x07);
assert_eq!(a.address(), 0x0000_4000);
assert_eq!(a.operand0(), 0xCAFE_BABE);
assert_eq!(a.operand1(), Some(0xDEAD_BEEF));
}
#[test]
fn completion_laddr_full_7_bits() {
let bytes = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, ];
let cmpl = new_cmpl_req(bytes).unwrap();
assert_eq!(cmpl.laddr(), 0x7F);
}
#[test]
fn completion_laddr_bit6_set() {
let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40];
let cmpl = new_cmpl_req(bytes).unwrap();
assert_eq!(cmpl.laddr(), 0x40);
}
#[test]
fn completion_laddr_with_reserved_bit_set() {
let bytes = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD5];
let cmpl = new_cmpl_req(bytes).unwrap();
assert_eq!(cmpl.laddr(), 0x55);
}
#[test]
fn completion_full_fields_with_laddr() {
let bytes = vec![
0x20, 0x01, 0x00, 0xFC, 0x12, 0x34, 0xAB, 0x64, ];
let cmpl = new_cmpl_req(bytes).unwrap();
assert_eq!(cmpl.cmpl_id(), 0x2001);
assert_eq!(cmpl.byte_cnt(), 0x0FC);
assert_eq!(cmpl.req_id(), 0x1234);
assert_eq!(cmpl.tag(), 0xAB);
assert_eq!(cmpl.laddr(), 0x64);
}
#[test]
fn atomic_fetchadd_rejects_invalid_operand_length() {
let bad = [
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let pkt = mk_pkt(0b010, 0b01100, &bad);
assert_eq!(new_atomic_req(&pkt).unwrap_err(), TlpError::InvalidLength);
}
#[test]
fn message_dw3_preserves_upper_16_bits() {
let bytes = vec![
0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, ];
let msg = new_msg_req(bytes).unwrap();
assert_eq!(msg.dw3(), 0xDEAD_BEEF);
}
#[test]
fn message_dw4_preserves_upper_16_bits() {
let bytes = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCA, 0xFE, 0xBA, 0xBE, ];
let msg = new_msg_req(bytes).unwrap();
assert_eq!(msg.dw4(), 0xCAFE_BABE);
}
#[test]
fn message_dw3_dw4_all_bits_set() {
let bytes = vec![
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
];
let msg = new_msg_req(bytes).unwrap();
assert_eq!(msg.dw3(), 0xFFFF_FFFF);
assert_eq!(msg.dw4(), 0xFFFF_FFFF);
}
#[test]
fn message_request_full_fields() {
let bytes = vec![
0xAB, 0xCD, 0x42, 0x7F, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
];
let msg = new_msg_req(bytes).unwrap();
assert_eq!(msg.req_id(), 0xABCD);
assert_eq!(msg.tag(), 0x42);
assert_eq!(msg.msg_code(), 0x7F);
assert_eq!(msg.dw3(), 0x1234_5678);
assert_eq!(msg.dw4(), 0x9ABC_DEF0);
}
#[test]
fn tlp_packet_header_debug() {
let hdr = TlpPacketHeader::new(vec![0x00, 0x00, 0x20, 0x01], TlpMode::NonFlit).unwrap();
let s = format!("{:?}", hdr);
assert!(s.contains("TlpPacketHeader"));
assert!(s.contains("format"));
assert!(s.contains("length"));
}
#[test]
fn tlp_packet_debug() {
let pkt =
TlpPacket::new(vec![0x40, 0x00, 0x00, 0x01, 0xDE, 0xAD], TlpMode::NonFlit).unwrap();
let s = format!("{:?}", pkt);
assert!(s.contains("TlpPacket"));
assert!(s.contains("data_len"));
}
#[test]
fn packet_mode_returns_correct_mode() {
let non_flit = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::NonFlit).unwrap();
assert_eq!(non_flit.mode(), TlpMode::NonFlit);
let flit = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::Flit).unwrap();
assert_eq!(flit.mode(), TlpMode::Flit);
}
#[test]
fn tlp_packet_debug_flit() {
let pkt = TlpPacket::new(vec![0x00, 0x00, 0x00, 0x00], TlpMode::Flit).unwrap();
let s = format!("{:?}", pkt);
assert!(s.contains("TlpPacket"));
assert!(s.contains("flit_dw0"));
}
}