use bitmatch::bitmatch;
use bitvec::prelude::*;
use std::convert::TryInto;
#[cfg(feature = "serde")]
use serde_crate::{Deserialize, Serialize};
pub mod cortex_m {
pub use cortex_m::peripheral::scb::{Exception, VectActive};
#[cfg(feature = "serde")]
pub mod serde {
use super::{Exception, VectActive};
use serde_crate::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(crate = "serde_crate", remote = "Exception")]
pub enum ExceptionDef {
NonMaskableInt,
HardFault,
MemoryManagement,
BusFault,
UsageFault,
SecureFault,
SVCall,
DebugMonitor,
PendSV,
SysTick,
}
#[derive(Serialize, Deserialize)]
#[serde(crate = "serde_crate", remote = "VectActive")]
pub enum VectActiveDef {
ThreadMode,
Exception(#[serde(with = "ExceptionDef")] Exception),
Interrupt { irqn: u8 },
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub enum TracePacket {
Sync,
Overflow,
LocalTimestamp1 {
ts: u64,
data_relation: TimestampDataRelation,
},
LocalTimestamp2 {
ts: u8,
},
GlobalTimestamp1 {
ts: u64,
wrap: bool,
clkch: bool,
},
GlobalTimestamp2 {
ts: u64,
},
Extension {
page: u8,
},
Instrumentation {
port: u8,
payload: Vec<u8>,
},
EventCounterWrap {
cyc: bool,
fold: bool,
lsu: bool,
sleep: bool,
exc: bool,
cpi: bool,
},
ExceptionTrace {
#[cfg_attr(feature = "serde", serde(with = "cortex_m::serde::VectActiveDef"))]
exception: cortex_m::VectActive,
action: ExceptionAction,
},
PCSample {
pc: Option<u32>,
},
DataTracePC {
comparator: u8,
pc: u32,
},
DataTraceAddress {
comparator: u8,
data: Vec<u8>,
},
DataTraceValue {
comparator: u8,
access_type: MemoryAccessType,
value: Vec<u8>,
},
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub enum ExceptionAction {
Entered,
Exited,
Returned,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub enum MemoryAccessType {
Read,
Write,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub enum TimestampDataRelation {
Sync,
UnknownDelay,
AssocEventDelay,
UnknownAssocEventDelay,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub enum MalformedPacket {
#[error("Header is invalid and cannot be decoded: {}", format!("{:#b}", .0))]
InvalidHeader(u8),
#[error("Hardware source packet type discriminator ID ({disc_id}) or payload length ({}) is invalid", .payload.len())]
InvalidHardwarePacket {
disc_id: u8,
payload: Vec<u8>,
},
#[error("Hardware source packet discriminator ID is invalid: {disc_id}")]
InvalidHardwareDisc {
disc_id: u8,
size: usize,
},
#[error("IRQ number {exception} and/or action {function} is invalid")]
InvalidExceptionTrace {
exception: u16,
function: u8,
},
#[error("Payload length of PC sample is invalid: {}", .payload.len())]
InvalidPCSampleSize {
payload: Vec<u8>,
},
#[error("GlobalTimestamp2 packet does not contain a 48-bit or 64-bit timestamp")]
InvalidGTS2Size {
payload: Vec<u8>,
},
#[error(
"The number of zeroes in the Synchronization packet is less than expected: {0} < {}",
SYNC_MIN_ZEROS
)]
InvalidSync(usize),
#[error(
"A source packet (from software or hardware) contains an invalid expected payload size"
)]
InvalidSourcePayload {
header: u8,
size: u8,
},
}
const SYNC_MIN_ZEROS: usize = 47;
enum PacketStub {
Sync(usize),
Instrumentation { port: u8, expected_size: usize },
HardwareSource { disc_id: u8, expected_size: usize },
LocalTimestamp {
data_relation: TimestampDataRelation,
},
GlobalTimestamp1,
GlobalTimestamp2,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub struct Timestamp {
pub base: Option<usize>,
pub delta: Option<usize>,
pub data_relation: Option<TimestampDataRelation>,
pub diverged: bool,
}
impl Default for Timestamp {
fn default() -> Self {
Timestamp {
base: None,
delta: None,
data_relation: None,
diverged: false,
}
}
}
struct TimestampedContext {
pub packets: Vec<TracePacket>,
pub malformed_packets: Vec<MalformedPacket>,
pub gts1: Option<usize>,
pub gts2: Option<usize>,
pub ts: Timestamp,
pub packets_consumed: usize,
}
impl Default for TimestampedContext {
fn default() -> Self {
TimestampedContext {
packets: vec![],
malformed_packets: vec![],
gts1: None,
gts2: None,
ts: Timestamp::default(),
packets_consumed: 0,
}
}
}
pub struct DecoderOptions {
pub only_gts: bool,
}
impl Default for DecoderOptions {
fn default() -> Self {
Self { only_gts: false }
}
}
pub struct Decoder {
options: DecoderOptions,
incoming: BitVec,
sync: Option<usize>,
ts_ctx: TimestampedContext,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub struct TimestampedTracePackets {
pub timestamp: Timestamp,
pub packets: Vec<TracePacket>,
pub malformed_packets: Vec<MalformedPacket>,
pub packets_consumed: usize,
}
enum HeaderVariant {
Packet(TracePacket),
Stub(PacketStub),
}
impl Decoder {
pub fn new(options: DecoderOptions) -> Self {
Decoder {
options,
incoming: BitVec::new(),
sync: None,
ts_ctx: TimestampedContext::default(),
}
}
pub fn push(&mut self, data: &[u8]) {
let mut bv = BitVec::<LocalBits, _>::from_vec(data.to_vec());
bv.reverse();
bv.append(&mut self.incoming);
self.incoming.append(&mut bv);
}
pub fn pull(&mut self) -> Result<Option<TracePacket>, MalformedPacket> {
if self.sync.is_some() {
return self.handle_sync();
}
assert!(self.sync.is_none());
if self.incoming.len() < 8 {
return Ok(None);
}
self.ts_ctx.packets_consumed = self.ts_ctx.packets_consumed + 1;
match Self::decode_header(self.pull_byte())? {
HeaderVariant::Packet(p) => Ok(Some(p)),
HeaderVariant::Stub(s) => self.process_stub(&s),
}
}
pub fn pull_with_timestamp(&mut self) -> Option<TimestampedTracePackets> {
fn assoc_packets_with_lts(
packets: Vec<TracePacket>,
malformed_packets: Vec<MalformedPacket>,
ts: &mut Timestamp,
lts: usize,
data_relation: TimestampDataRelation,
packets_consumed: &mut usize,
) -> TimestampedTracePackets {
if let Some(ref mut delta) = ts.delta {
*delta += lts as usize;
} else {
ts.delta = Some(lts);
}
ts.data_relation = Some(data_relation);
let ttp = TimestampedTracePackets {
timestamp: ts.clone(),
packets,
malformed_packets,
packets_consumed: *packets_consumed,
};
*packets_consumed = 0;
ttp
}
loop {
match self.pull() {
Ok(None) => return None,
Ok(Some(TracePacket::LocalTimestamp1 { ts, data_relation }))
if !self.options.only_gts =>
{
return Some(assoc_packets_with_lts(
self.ts_ctx.packets.drain(..).collect(),
self.ts_ctx.malformed_packets.drain(..).collect(),
&mut self.ts_ctx.ts,
ts as usize,
data_relation,
&mut self.ts_ctx.packets_consumed,
));
}
Ok(Some(TracePacket::LocalTimestamp2 { ts })) if !self.options.only_gts => {
return Some(assoc_packets_with_lts(
self.ts_ctx.packets.drain(..).collect(),
self.ts_ctx.malformed_packets.drain(..).collect(),
&mut self.ts_ctx.ts,
ts as usize,
TimestampDataRelation::Sync,
&mut self.ts_ctx.packets_consumed,
));
}
Ok(Some(TracePacket::GlobalTimestamp1 { ts, wrap, clkch })) => {
self.ts_ctx.gts1 = Some(ts as usize);
if wrap {
self.ts_ctx.gts2 = None;
}
if clkch {
self.ts_ctx.gts1 = None;
self.ts_ctx.gts2 = None;
}
}
Ok(Some(TracePacket::GlobalTimestamp2 { ts })) => {
self.ts_ctx.gts2 = Some(ts as usize)
}
Ok(Some(TracePacket::Overflow)) => {
self.ts_ctx.ts.diverged = true;
self.ts_ctx.packets.push(TracePacket::Overflow);
}
Ok(Some(packet)) if !self.options.only_gts => self.ts_ctx.packets.push(packet),
Err(malformed) => self.ts_ctx.malformed_packets.push(malformed),
Ok(Some(packet)) if self.options.only_gts => {
return Some(TimestampedTracePackets {
timestamp: self.ts_ctx.ts.clone(),
packets: vec![packet],
malformed_packets: vec![],
packets_consumed: 1,
});
}
_ => unreachable!(),
}
if let (Some(lower), Some(upper)) = (self.ts_ctx.gts1, self.ts_ctx.gts2) {
const GTS2_TS_SHIFT: usize = 26; self.ts_ctx.ts = Timestamp::default();
self.ts_ctx.ts.base = Some((upper << GTS2_TS_SHIFT) | lower);
self.ts_ctx.gts1 = None;
self.ts_ctx.gts2 = None;
}
}
}
fn handle_sync(&mut self) -> Result<Option<TracePacket>, MalformedPacket> {
if let Some(mut count) = self.sync {
while let Some(bit) = self.incoming.pop() {
if !bit && count < SYNC_MIN_ZEROS {
count += 1;
continue;
} else if bit && count >= SYNC_MIN_ZEROS {
self.sync = None;
return Ok(Some(TracePacket::Sync));
} else {
self.sync = None;
return Err(MalformedPacket::InvalidSync(count));
}
}
}
Ok(None)
}
fn pull_byte(&mut self) -> u8 {
let mut b: u8 = 0;
for i in 0..8 {
b |= (self.incoming.pop().unwrap() as u8) << i;
}
b
}
fn pull_bytes(&mut self, cnt: usize) -> Option<Vec<u8>> {
if self.incoming.len() < cnt * 8 {
return None;
}
let mut payload = vec![];
for _ in 0..cnt {
payload.push(self.pull_byte());
}
Some(payload)
}
fn pull_payload(&mut self) -> Option<Vec<u8>> {
let mut iter = self.incoming.rchunks(8);
let mut cnt = 0;
loop {
cnt = cnt + 1;
match iter.next() {
None => return None,
Some(b) if b.len() < 8 => return None,
Some(b) => match b.first_zero() {
Some(0) => break,
_ => continue,
},
}
}
Some(self.pull_bytes(cnt).unwrap())
}
fn process_stub(&mut self, stub: &PacketStub) -> Result<Option<TracePacket>, MalformedPacket> {
match stub {
PacketStub::Sync(count) => {
self.sync = Some(*count);
self.handle_sync()
}
PacketStub::HardwareSource {
disc_id,
expected_size,
} => {
if let Some(payload) = self.pull_bytes(*expected_size) {
Self::handle_hardware_source(*disc_id, payload).map(|p| Some(p))
} else {
Ok(None)
}
}
PacketStub::LocalTimestamp { data_relation } => {
if let Some(payload) = self.pull_payload() {
Ok(Some(TracePacket::LocalTimestamp1 {
data_relation: data_relation.clone(),
ts: Decoder::extract_timestamp(payload, 27),
}))
} else {
Ok(None)
}
}
PacketStub::GlobalTimestamp1 => {
if let Some(payload) = self.pull_payload() {
Ok(Some(TracePacket::GlobalTimestamp1 {
ts: Decoder::extract_timestamp(payload.clone(), 25),
clkch: (payload.last().unwrap() & (1 << 5)) >> 5 == 1,
wrap: (payload.last().unwrap() & (1 << 6)) >> 6 == 1,
}))
} else {
Ok(None)
}
}
PacketStub::GlobalTimestamp2 => {
if let Some(payload) = self.pull_payload() {
Ok(Some(TracePacket::GlobalTimestamp2 {
ts: Decoder::extract_timestamp(
payload.to_vec(),
match payload.len() {
4 => 47 - 26, 6 => 63 - 26, _ => {
return Err(MalformedPacket::InvalidGTS2Size {
payload: payload.to_vec(),
})
}
},
),
}))
} else {
Ok(None)
}
}
PacketStub::Instrumentation {
port,
expected_size,
} => {
if let Some(payload) = self.pull_bytes(*expected_size) {
Ok(Some(TracePacket::Instrumentation {
port: *port,
payload: payload.to_vec(),
}))
} else {
Ok(None)
}
}
}
}
fn extract_timestamp(payload: Vec<u8>, max_len: u64) -> u64 {
let (rtail, head) = payload.split_at(payload.len() - 1);
let mut ts: u64 = 0;
for (i, b) in rtail.iter().enumerate() {
ts |= ((b & !(1 << 7)) as u64) << (7 * i);
}
let shift = 7 - (max_len % 7);
let mask: u8 = 0xFFu8.wrapping_shl(shift.try_into().unwrap()) >> shift;
ts | (((head[0] & mask) as u64) << (7 * rtail.len()))
}
#[bitmatch]
fn handle_hardware_source(
disc_id: u8,
payload: Vec<u8>,
) -> Result<TracePacket, MalformedPacket> {
match disc_id {
0 => {
if payload.len() != 1 {
return Err(MalformedPacket::InvalidHardwarePacket { disc_id, payload });
}
let b = payload[0];
Ok(TracePacket::EventCounterWrap {
cyc: b & (1 << 5) != 0,
fold: b & (1 << 4) != 0,
lsu: b & (1 << 3) != 0,
sleep: b & (1 << 2) != 0,
exc: b & (1 << 1) != 0,
cpi: b & (1 << 0) != 0,
})
}
1 => {
if payload.len() != 2 {
return Err(MalformedPacket::InvalidHardwarePacket { disc_id, payload });
}
let function = (payload[1] >> 4) & 0b11;
let exception_number = ((payload[1] as u16 & 1) << 8) | payload[0] as u16;
let exception_number: u8 = if let Ok(nr) = exception_number.try_into() {
nr
} else {
return Err(MalformedPacket::InvalidExceptionTrace {
exception: exception_number,
function,
});
};
return Ok(TracePacket::ExceptionTrace {
exception: if let Some(exception) = cortex_m::VectActive::from(exception_number)
{
exception
} else {
return Err(MalformedPacket::InvalidExceptionTrace {
exception: exception_number.into(),
function,
});
},
action: match function {
0b01 => ExceptionAction::Entered,
0b10 => ExceptionAction::Exited,
0b11 => ExceptionAction::Returned,
_ => {
return Err(MalformedPacket::InvalidExceptionTrace {
exception: exception_number.into(),
function,
})
}
},
});
}
2 => {
match payload.len() {
1 if payload[0] == 0 => Ok(TracePacket::PCSample { pc: None }),
4 => Ok(TracePacket::PCSample {
pc: Some(u32::from_le_bytes(payload.try_into().unwrap())),
}),
_ => Err(MalformedPacket::InvalidPCSampleSize { payload }),
}
}
8..=23 => {
#[bitmatch]
let "???t_tccd" = disc_id; let comparator = c;
match (t, d, payload.len()) {
(0b01, 0, 4) => {
Ok(TracePacket::DataTracePC {
comparator,
pc: u32::from_le_bytes(payload.try_into().unwrap()),
})
}
(0b01, 1, 2) => {
Ok(TracePacket::DataTraceAddress {
comparator,
data: payload,
})
}
(0b10, d, _) => {
Ok(TracePacket::DataTraceValue {
comparator,
access_type: if d == 0 {
MemoryAccessType::Read
} else {
MemoryAccessType::Write
},
value: payload,
})
}
_ => Err(MalformedPacket::InvalidHardwarePacket { disc_id, payload }),
}
}
_ => unreachable!(), }
}
#[bitmatch]
fn decode_header(header: u8) -> Result<HeaderVariant, MalformedPacket> {
fn translate_ss(ss: u8) -> Option<usize> {
Some(
match ss {
0b01 => 2,
0b10 => 3,
0b11 => 5,
_ => return None,
} - 1, )
}
let stub = |s| Ok(HeaderVariant::Stub(s));
let packet = |p| Ok(HeaderVariant::Packet(p));
#[bitmatch]
match header {
"0000_0000" => stub(PacketStub::Sync(8)),
"0111_0000" => packet(TracePacket::Overflow),
"11rr_0000" => {
let tc = r;
stub(PacketStub::LocalTimestamp {
data_relation: match tc {
0b00 => TimestampDataRelation::Sync,
0b01 => TimestampDataRelation::UnknownDelay,
0b10 => TimestampDataRelation::AssocEventDelay,
0b11 => TimestampDataRelation::UnknownAssocEventDelay,
_ => unreachable!(),
},
})
}
"0ttt_0000" => {
packet(TracePacket::LocalTimestamp2 { ts: t })
}
"1001_0100" => {
stub(PacketStub::GlobalTimestamp1)
}
"1011_0100" => {
stub(PacketStub::GlobalTimestamp2)
}
"0ppp_1000" => {
packet(TracePacket::Extension { page: p })
}
"aaaa_a0ss" => {
stub(PacketStub::Instrumentation {
port: a,
expected_size: if let Some(s) = translate_ss(s) {
s
} else {
return Err(MalformedPacket::InvalidSourcePayload { header, size: s });
},
})
}
"aaaa_a1ss" => {
let disc_id = a;
if !(0..=2).contains(&disc_id) && !(8..=23).contains(&disc_id) {
return Err(MalformedPacket::InvalidHardwareDisc {
disc_id,
size: s.into(),
});
}
stub(PacketStub::HardwareSource {
disc_id,
expected_size: if let Some(s) = translate_ss(s) {
s
} else {
return Err(MalformedPacket::InvalidSourcePayload { header, size: s });
},
})
}
"hhhh_hhhh" => Err(MalformedPacket::InvalidHeader(h)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pull_bytes() {
let mut decoder = Decoder::new(DecoderOptions::default());
let payload = vec![0b1000_0000, 0b1010_0000, 0b1000_0100, 0b0110_0000];
decoder.push(&payload);
assert_eq!(decoder.pull_bytes(3).unwrap().len(), 3);
}
#[test]
fn pull_payload() {
let mut decoder = Decoder::new(DecoderOptions::default());
let payload = vec![0b1000_0000, 0b1010_0000, 0b1000_0100, 0b0110_0000];
#[rustfmt::skip]
decoder.push(&payload);
assert_eq!(decoder.pull_payload(), Some(payload));
}
#[test]
fn extract_timestamp() {
#[rustfmt::skip]
let ts: Vec<u8> = [
0b1000_0000,
0b1000_0000,
0b1000_0000,
0b0000_0000,
].to_vec();
assert_eq!(Decoder::extract_timestamp(ts, 25), 0);
#[rustfmt::skip]
let ts: Vec<u8> = [
0b1000_0001,
0b1000_0111,
0b1001_1111,
0b0111_1111
].to_vec();
assert_eq!(
Decoder::extract_timestamp(ts, 27),
0b1111111_0011111_0000111_0000001,
);
#[rustfmt::skip]
let ts: Vec<u8> = [
0b1000_0001,
0b1000_0111,
0b1001_1111,
0b1111_1111
].to_vec();
assert_eq!(
Decoder::extract_timestamp(ts, 25),
0b11111_0011111_0000111_0000001,
);
}
}