pub mod ec {
pub const UNKNOWN: u8 = 0x00;
pub const WFX: u8 = 0x01;
pub const MCR_MRC_CP15: u8 = 0x03;
pub const MCRR_MRRC_CP15: u8 = 0x04;
pub const MCR_MRC_CP14: u8 = 0x05;
pub const LDC_STC: u8 = 0x06;
pub const FP_ASIMD: u8 = 0x07;
pub const BTI: u8 = 0x0D;
pub const ILLEGAL_STATE: u8 = 0x0E;
pub const SVC: u8 = 0x15;
pub const HVC: u8 = 0x16;
pub const SMC: u8 = 0x17;
pub const SYSTEM_REGISTER: u8 = 0x18;
pub const PAUTH: u8 = 0x1C;
pub const INST_ABORT_LOWER_EL: u8 = 0x20;
pub const INST_ABORT_SAME_EL: u8 = 0x21;
pub const PC_ALIGNMENT: u8 = 0x22;
pub const DATA_ABORT_LOWER_EL: u8 = 0x24;
pub const DATA_ABORT_SAME_EL: u8 = 0x25;
pub const SP_ALIGNMENT: u8 = 0x26;
pub const FPE_AARCH64: u8 = 0x2C;
pub const SERROR: u8 = 0x2F;
pub const BREAKPOINT_LOWER_EL: u8 = 0x30;
pub const BREAKPOINT_SAME_EL: u8 = 0x31;
pub const SOFTWARE_STEP_LOWER_EL: u8 = 0x32;
pub const SOFTWARE_STEP_SAME_EL: u8 = 0x33;
pub const WATCHPOINT_LOWER_EL: u8 = 0x34;
pub const WATCHPOINT_SAME_EL: u8 = 0x35;
pub const BKPT_AARCH32: u8 = 0x38;
pub const BRK_AARCH64: u8 = 0x3C;
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum EsrDecoded {
DataAbort {
is_write: bool,
sas: u8,
srt: u8,
sf: bool,
},
Hvc {
imm16: u16,
},
Smc {
imm16: u16,
},
SystemRegister {
read: bool,
op0: u8,
op1: u8,
crn: u8,
crm: u8,
op2: u8,
xt: u8,
},
Wfi,
Wfe,
Brk {
imm16: u16,
},
Other {
ec: u8,
raw: u64,
},
}
#[must_use]
pub fn decode(esr: u64) -> EsrDecoded {
let ec = ((esr >> 26) & 0x3F) as u8;
let iss = esr & 0x01FF_FFFF; match ec {
ec::DATA_ABORT_LOWER_EL | ec::DATA_ABORT_SAME_EL => decode_data_abort(iss),
ec::HVC => EsrDecoded::Hvc {
imm16: (iss & 0xFFFF) as u16,
},
ec::SMC => EsrDecoded::Smc {
imm16: (iss & 0xFFFF) as u16,
},
ec::SYSTEM_REGISTER => decode_system_register(iss),
ec::WFX => decode_wfx(iss),
ec::BRK_AARCH64 => EsrDecoded::Brk {
imm16: (iss & 0xFFFF) as u16,
},
_ => EsrDecoded::Other { ec, raw: esr },
}
}
fn decode_data_abort(iss: u64) -> EsrDecoded {
let is_write = (iss & (1 << 6)) != 0;
let sas = ((iss >> 22) & 0x3) as u8;
let srt = ((iss >> 16) & 0x1F) as u8;
let sf = (iss & (1 << 15)) != 0;
EsrDecoded::DataAbort {
is_write,
sas,
srt,
sf,
}
}
fn decode_system_register(iss: u64) -> EsrDecoded {
let op0 = ((iss >> 20) & 0x3) as u8;
let op2 = ((iss >> 17) & 0x7) as u8;
let op1 = ((iss >> 14) & 0x7) as u8;
let crn = ((iss >> 10) & 0xF) as u8;
let xt = ((iss >> 5) & 0x1F) as u8;
let crm = ((iss >> 1) & 0xF) as u8;
let read = (iss & 0x1) == 1;
EsrDecoded::SystemRegister {
read,
op0,
op1,
crn,
crm,
op2,
xt,
}
}
fn decode_wfx(iss: u64) -> EsrDecoded {
if (iss & 0x1) == 1 {
EsrDecoded::Wfe
} else {
EsrDecoded::Wfi
}
}
#[cfg(test)]
mod tests {
use super::*;
const fn esr(ec: u8, iss: u64) -> u64 {
((ec as u64) << 26) | (iss & 0x01FF_FFFF)
}
#[test]
fn decoder_is_total_for_every_ec() {
for ec_value in 0u8..=0x3F {
let _ = decode(esr(ec_value, 0));
let _ = decode(esr(ec_value, 0x01FF_FFFF));
}
}
#[test]
fn unknown_ec_returns_other_with_raw_preserved() {
let raw = esr(0x10, 0xDEAD_BEEF);
match decode(raw) {
EsrDecoded::Other { ec, raw: got } => {
assert_eq!(ec, 0x10);
assert_eq!(got, raw);
}
other => panic!("expected Other, got {other:?}"),
}
}
#[test]
fn data_abort_decodes_write_register_and_size() {
let iss = (1u64 << 6) | (1u64 << 15) | (5u64 << 16) | (2u64 << 22);
let decoded = decode(esr(ec::DATA_ABORT_LOWER_EL, iss));
assert_eq!(
decoded,
EsrDecoded::DataAbort {
is_write: true,
sas: 2,
srt: 5,
sf: true,
}
);
}
#[test]
fn data_abort_distinguishes_load_vs_store() {
let iss = 0;
let decoded = decode(esr(ec::DATA_ABORT_LOWER_EL, iss));
let EsrDecoded::DataAbort { is_write, .. } = decoded else {
panic!("expected DataAbort, got {decoded:?}");
};
assert!(!is_write);
}
#[test]
fn hvc_extracts_imm16() {
let imm = 0xBEEF_u16;
let decoded = decode(esr(ec::HVC, u64::from(imm)));
assert_eq!(decoded, EsrDecoded::Hvc { imm16: imm });
}
#[test]
fn smc_extracts_imm16() {
let imm = 0x0042_u16;
let decoded = decode(esr(ec::SMC, u64::from(imm)));
assert_eq!(decoded, EsrDecoded::Smc { imm16: imm });
}
#[test]
fn brk_extracts_imm16() {
let decoded = decode(esr(ec::BRK_AARCH64, 0xF000));
assert_eq!(decoded, EsrDecoded::Brk { imm16: 0xF000 });
}
#[test]
fn wfi_vs_wfe_distinguished_by_iss_bit_0() {
let wfi = decode(esr(ec::WFX, 0));
let wfe = decode(esr(ec::WFX, 1));
assert_eq!(wfi, EsrDecoded::Wfi);
assert_eq!(wfe, EsrDecoded::Wfe);
}
#[test]
fn system_register_decodes_full_op_tuple() {
#[allow(clippy::identity_op)]
let iss = (3u64 << 20) | (4u64 << 17) | (0u64 << 14) | (1u64 << 10) | (5u64 << 5) | (2u64 << 1) | 1u64; let decoded = decode(esr(ec::SYSTEM_REGISTER, iss));
assert_eq!(
decoded,
EsrDecoded::SystemRegister {
read: true,
op0: 3,
op1: 0,
crn: 1,
crm: 2,
op2: 4,
xt: 5,
}
);
}
#[test]
fn property_test_random_inputs_never_panic() {
let mut esr_value: u64 = 0;
for _ in 0..1000 {
let _ = decode(esr_value);
esr_value = esr_value.wrapping_add(0x0123_4567_89AB_CDEF);
}
}
}