Re-implementation of rustbootd's ecat_el6695_* examples as a single binary with fixes found in review and on hardware: - DC-follow PLL hardened against period-2 hunting: slew-limited anchor (+/-50us/cycle), bistable-trap snap re-anchor, re-prime on stale deadline - drift-free absolute-grid ticker mode; probe mode for timestamp forensics - bounded-memory online stats (histograms), graceful SIGINT/SIGTERM shutdown with full report, error-streak abort - timestamp plausibility filter comparing against the previous raw sample (avoids the deadlock after a startup outlier) - XFC scope waveform options: --el2202 (with --el2202-dual), --el2262, --el1252 latch timestamp readback with per-channel edge statistics - register access unified in regs.rs: named bit constants everywhere, read-modify-write for enable/activation bytes - vendored patched ethercrab 0.7.1 (sdo_write_complete, send_raw_coe) Verified on J1900 (PREEMPT_RT 6.6.135): 600k cycles/600s exact 1 kHz, tx/rx zero errors, phase_err p50=164us std=5us; EL2202<->EL1252 loopback edge interval mean 2000.24us std=24.65us.
210 lines
6.0 KiB
Rust
210 lines
6.0 KiB
Rust
//! Ethernet frame, copied from SmolTCP,
|
|
//! [here](https://github.com/smoltcp-rs/smoltcp/blob/53caf70f640d5ccb3cd1492e1cb178bc7dfa3cdd/src/wire/ethernet.rs)
|
|
//! at time of writing.
|
|
//!
|
|
//! Then drastically modified/stripped down to suit EtherCrab's needs.
|
|
|
|
use core::fmt;
|
|
|
|
use crate::error::{Error, PduError};
|
|
|
|
/// A six-octet Ethernet II address.
|
|
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
|
|
pub struct EthernetAddress(pub [u8; 6]);
|
|
|
|
impl EthernetAddress {
|
|
/// The broadcast address.
|
|
pub const BROADCAST: EthernetAddress = EthernetAddress([0xff; 6]);
|
|
|
|
/// Construct an Ethernet address from a sequence of octets, in big-endian.
|
|
///
|
|
/// # Panics
|
|
/// The function panics if `data` is not six octets long.
|
|
pub fn from_bytes(data: &[u8]) -> EthernetAddress {
|
|
let mut bytes = [0; 6];
|
|
bytes.copy_from_slice(data);
|
|
EthernetAddress(bytes)
|
|
}
|
|
|
|
/// Return an Ethernet address as a sequence of octets, in big-endian.
|
|
pub const fn as_bytes(&self) -> &[u8] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for EthernetAddress {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let bytes = self.0;
|
|
write!(
|
|
f,
|
|
"{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
|
|
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "defmt")]
|
|
impl defmt::Format for EthernetAddress {
|
|
fn format(&self, fmt: defmt::Formatter) {
|
|
let bytes = self.0;
|
|
defmt::write!(
|
|
fmt,
|
|
"{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}",
|
|
bytes[0],
|
|
bytes[1],
|
|
bytes[2],
|
|
bytes[3],
|
|
bytes[4],
|
|
bytes[5]
|
|
)
|
|
}
|
|
}
|
|
|
|
/// A read/write wrapper around an Ethernet II frame buffer.
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct EthernetFrame<T: AsRef<[u8]>> {
|
|
buffer: T,
|
|
}
|
|
|
|
mod field {
|
|
use core::ops::{Range, RangeFrom};
|
|
|
|
pub const DESTINATION: Range<usize> = 0..6;
|
|
pub const SOURCE: Range<usize> = 6..12;
|
|
pub const ETHERTYPE: Range<usize> = 12..14;
|
|
pub const PAYLOAD: RangeFrom<usize> = 14..;
|
|
}
|
|
|
|
/// The Ethernet header length
|
|
pub const ETHERNET_HEADER_LEN: usize = field::PAYLOAD.start;
|
|
|
|
impl<T: AsRef<[u8]>> EthernetFrame<T> {
|
|
/// Imbue a raw octet buffer with Ethernet frame structure.
|
|
pub const fn new_unchecked(buffer: T) -> EthernetFrame<T> {
|
|
EthernetFrame { buffer }
|
|
}
|
|
|
|
/// Shorthand for a combination of [new_unchecked] and [check_len].
|
|
///
|
|
/// [new_unchecked]: #method.new_unchecked
|
|
/// [check_len]: #method.check_len
|
|
pub fn new_checked(buffer: T) -> Result<EthernetFrame<T>, Error> {
|
|
let packet = Self::new_unchecked(buffer);
|
|
packet.check_len()?;
|
|
Ok(packet)
|
|
}
|
|
|
|
/// Ensure that no accessor method will panic if called.
|
|
/// Returns `Err(Error)` if the buffer is too short.
|
|
pub fn check_len(&self) -> Result<(), Error> {
|
|
let len = self.buffer.as_ref().len();
|
|
if len < ETHERNET_HEADER_LEN {
|
|
Err(Error::Pdu(PduError::Ethernet))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Consumes the frame, returning the underlying buffer.
|
|
pub fn into_inner(self) -> T {
|
|
self.buffer
|
|
}
|
|
|
|
/// Return the length of a frame header.
|
|
pub const fn header_len() -> usize {
|
|
ETHERNET_HEADER_LEN
|
|
}
|
|
|
|
/// Return the length of a buffer required to hold a packet with the payload
|
|
/// of a given length.
|
|
pub const fn buffer_len(payload_len: usize) -> usize {
|
|
ETHERNET_HEADER_LEN + payload_len
|
|
}
|
|
|
|
/// Return the destination address field.
|
|
#[inline]
|
|
pub fn dst_addr(&self) -> EthernetAddress {
|
|
let data = self.buffer.as_ref();
|
|
EthernetAddress::from_bytes(&data[field::DESTINATION])
|
|
}
|
|
|
|
/// Return the source address field.
|
|
#[inline]
|
|
pub fn src_addr(&self) -> EthernetAddress {
|
|
let data = self.buffer.as_ref();
|
|
EthernetAddress::from_bytes(&data[field::SOURCE])
|
|
}
|
|
|
|
/// Return the EtherType field, without checking for 802.1Q.
|
|
#[inline]
|
|
pub fn ethertype(&self) -> u16 {
|
|
let data = self.buffer.as_ref();
|
|
|
|
// Ethernet is big-endian
|
|
data.get(field::ETHERTYPE)
|
|
.map(|res| u16::from_be_bytes(res.try_into().unwrap()))
|
|
// EtherCrab only really cares whether the ethertype is 0x88a4, so defaulting to zero on
|
|
// unparseable ethertypes is fine here (imo, lol)
|
|
.unwrap_or(0)
|
|
}
|
|
}
|
|
|
|
impl<'a, T: AsRef<[u8]> + ?Sized> EthernetFrame<&'a T> {
|
|
/// Return a pointer to the payload, without checking for 802.1Q.
|
|
#[inline]
|
|
pub fn payload(&self) -> &'a [u8] {
|
|
let data = self.buffer.as_ref();
|
|
&data[field::PAYLOAD]
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<[u8]> + AsMut<[u8]>> EthernetFrame<T> {
|
|
/// Set the destination address field.
|
|
#[inline]
|
|
pub fn set_dst_addr(&mut self, value: EthernetAddress) {
|
|
let data = self.buffer.as_mut();
|
|
data[field::DESTINATION].copy_from_slice(value.as_bytes())
|
|
}
|
|
|
|
/// Set the source address field.
|
|
#[inline]
|
|
pub fn set_src_addr(&mut self, value: EthernetAddress) {
|
|
let data = self.buffer.as_mut();
|
|
data[field::SOURCE].copy_from_slice(value.as_bytes())
|
|
}
|
|
|
|
/// Set the EtherType field.
|
|
#[inline]
|
|
pub fn set_ethertype(&mut self, value: u16) {
|
|
let data = self.buffer.as_mut();
|
|
|
|
data[field::ETHERTYPE].copy_from_slice(&value.to_be_bytes());
|
|
}
|
|
|
|
/// Return a mutable pointer to the payload.
|
|
#[inline]
|
|
pub fn payload_mut(&mut self) -> &mut [u8] {
|
|
let data = self.buffer.as_mut();
|
|
&mut data[field::PAYLOAD]
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<[u8]>> AsRef<[u8]> for EthernetFrame<T> {
|
|
fn as_ref(&self) -> &[u8] {
|
|
self.buffer.as_ref()
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<[u8]>> fmt::Display for EthernetFrame<T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"EthernetII src={} dst={} type={}",
|
|
self.src_addr(),
|
|
self.dst_addr(),
|
|
self.ethertype()
|
|
)
|
|
}
|
|
}
|