diff --git a/Cargo.toml b/Cargo.toml index 644c8f4..b9d1f6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ ieee802_3_miim = "0.7" cortex-m = "0.7" log = { version = "0.4", optional = true } defmt = { version = "0.3", optional = true } +bitflags = "1.3.2" [dependencies.smoltcp] version = "0.8" @@ -36,7 +37,7 @@ features = ["medium-ethernet", "proto-ipv4"] optional = true [features] -default = [ "defmt" ] +default = ["defmt"] device-selected = [] fence = [] @@ -64,8 +65,8 @@ stm32f779 = ["stm32f7xx-hal/stm32f779", "device-selected", "fence"] smoltcp-phy = ["smoltcp"] # Example features -example-nucleo-pins = [ ] -rtic-echo-example = [ "defmt", "smoltcp-phy", "smoltcp/defmt", "smoltcp/medium-ethernet", "smoltcp/socket-tcp" ] +example-nucleo-pins = [] +rtic-echo-example = ["defmt", "smoltcp-phy", "smoltcp/defmt", "smoltcp/medium-ethernet", "smoltcp/socket-tcp"] [dev-dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } diff --git a/examples/arp.rs b/examples/arp.rs index 1aa18fd..2c5c363 100644 --- a/examples/arp.rs +++ b/examples/arp.rs @@ -16,7 +16,11 @@ use cortex_m_rt::{entry, exception}; use cortex_m::interrupt::Mutex; use stm32_eth::{ - mac::{phy::BarePhy, Phy}, + mac::{ + frame_filter::{Filter, FilterConfig, Mac}, + phy::BarePhy, + Phy, + }, stm32::{interrupt, CorePeripherals, Peripherals, SYST}, }; @@ -57,6 +61,15 @@ fn main() -> ! { .unwrap(); eth_dma.enable_interrupt(); + let some_mac = if eth_dma.tx_is_running() { 1 } else { 0 }; + + let filter_config = FilterConfig::filter_destinations( + Mac::new([some_mac, some_mac, some_mac, some_mac, some_mac, some_mac]), + &[], + ); + + eth_mac.configure_frame_filter(&Filter::Filter(filter_config)); + let mut last_link_up = false; let mut bare_phy = BarePhy::new(eth_mac.with_mii(mdio, mdc), PHY_ADDR, Default::default()); @@ -111,6 +124,15 @@ fn main() -> ! { } Err(TxError::WouldBlock) => defmt::info!("ARP failed"), } + + // while let Ok(message) = eth_dma.recv_next() { + // let mut chunks = message.chunks(6); + + // let dst_mac = Mac::try_from(chunks.next().unwrap()).ok().unwrap(); + // let src_mac = Mac::try_from(chunks.next().unwrap()).ok().unwrap(); + + // defmt::warn!("DA: {}, SA: {}", dst_mac, src_mac); + // } } else { defmt::info!("Down"); } diff --git a/src/mac/frame_filter/control.rs b/src/mac/frame_filter/control.rs new file mode 100644 index 0000000..432a7ef --- /dev/null +++ b/src/mac/frame_filter/control.rs @@ -0,0 +1,31 @@ +/// The type of frame filtering that the MAC should perform +/// on received control frames, +#[derive(Debug, Clone)] + +pub enum ControlFrameFilter { + /// Prevent all control frames from reaching + /// the application. + BlockAll, + /// Allow all control frames to reach the application, + /// except for PAUSE control frames. + NoPause, + /// Allow all control frames to reach the application. + AllowAll, + /// Block control frames that do not pass the + /// configured address filter, but allow those that do. + AddressFilter, +} + +impl ControlFrameFilter { + /// Create a new [`ControlFrameFilter`] that filters out + /// all control frames. + pub const fn new() -> Self { + Self::BlockAll + } +} + +impl Default for ControlFrameFilter { + fn default() -> Self { + Self::new() + } +} diff --git a/src/mac/frame_filter/destination.rs b/src/mac/frame_filter/destination.rs new file mode 100644 index 0000000..456d1b6 --- /dev/null +++ b/src/mac/frame_filter/destination.rs @@ -0,0 +1,59 @@ +/// The type of destination address filtering that +/// the MAC should apply to incoming frames. +#[derive(Debug, Clone)] +pub struct DaFilter { + /// Filtering to be performed based on perfect address matches. + pub perfect_filtering: PerfectDaFilterMode, + /// Enable or disable hash table filtering for destination + /// addresses. + pub hash_table_filtering: bool, +} + +impl DaFilter { + /// Create a new [`DaFilter`] that does + /// not filter any frames. + pub const fn new() -> Self { + Self { + perfect_filtering: PerfectDaFilterMode::new(), + hash_table_filtering: false, + } + } +} + +impl Default for DaFilter { + fn default() -> Self { + Self::new() + } +} + +/// The type of destination address filtering that +/// the MAC should apply to incoming frames. +#[derive(Debug, Clone)] + +pub enum PerfectDaFilterMode { + /// Filter frames by their Destination Address, based on + /// the addresses configured with [`AddressFilterType::Destination`]. + /// + /// [`AddressFilterType::Destination`]: `super::AddressFilterType::Destination` + Normal, + /// Filter frames by their Destination Address, based on + /// the inverse of the addresses configured with + /// [`AddressFilterType::Destination`]. + /// + /// [`AddressFilterType::Destination`]: `super::AddressFilterType::Destination` + Inverse, +} + +impl PerfectDaFilterMode { + /// Create a new [`PerfectDaFilterMode`] that filters + /// out all frames. + pub const fn new() -> Self { + Self::Normal + } +} + +impl Default for PerfectDaFilterMode { + fn default() -> Self { + Self::new() + } +} diff --git a/src/mac/frame_filter/hash_table.rs b/src/mac/frame_filter/hash_table.rs new file mode 100644 index 0000000..e554736 --- /dev/null +++ b/src/mac/frame_filter/hash_table.rs @@ -0,0 +1,25 @@ +#[derive(Debug, Clone)] +/// Configuration of the hash table used by the MAC for +/// destination address filtering. +pub struct HashTableValue { + /// The low register of the hash table. + pub low: u32, + /// The high register of the hash table. + pub high: u32, +} + +impl HashTableValue { + /// Create a new hash table value that filters + /// out all frames. + pub const fn new() -> Self { + Self { low: 0, high: 0 } + } +} + +/// By default, the hash table is configured so that +/// it filters out all frames. +impl Default for HashTableValue { + fn default() -> Self { + Self::new() + } +} diff --git a/src/mac/frame_filter/mod.rs b/src/mac/frame_filter/mod.rs new file mode 100644 index 0000000..41af9bd --- /dev/null +++ b/src/mac/frame_filter/mod.rs @@ -0,0 +1,381 @@ +//! Hardware filtering of Ethernet frames + +use crate::hal::pac::ETHERNET_MAC; + +mod destination; +pub use destination::*; + +mod source; +pub use source::*; + +mod multicast; +pub use multicast::*; + +mod hash_table; +pub use hash_table::*; + +mod control; +pub use control::*; + +/// The frame filtering that the MAC should apply to +/// received Ethernet frames. +#[derive(Debug, Clone)] +pub enum Filter { + /// No frame filtering on any frames. + Promiscuous, + /// Perform frame filtering based on the provided + /// configuration. + Filter(FilterConfig), +} + +impl Default for Filter { + fn default() -> Self { + Self::Promiscuous + } +} + +impl Filter { + pub(crate) fn configure(&self, eth_mac: ÐERNET_MAC) { + match self { + Filter::Promiscuous => eth_mac.macffr.write(|w| w.pm().set_bit()), + Filter::Filter(config) => config.configure(eth_mac), + } + } +} + +/// The type of frame filtering that the MAC +/// should perform. +/// +/// The total amount of [`MacAddressFilter`]s in this configuration object may +/// be at most 3. +#[derive(Debug, Clone)] +pub struct FilterConfig { + /// The MAC address of this station. This address is always + /// used for Destination Address filtering. + pub base_address: Mac, + + /// Extra address filters to be used. + pub address_filters: [Option; 3], + + /// Frame filtering applied to frames based on + /// their destination address. + pub destination_address_filter: DaFilter, + + /// Frame filtering applied to frames based on + /// their source address. + pub source_address_filter: SaFilter, + + /// Frame filtering applied to frames based on + /// whether they have a multicast address or not. + pub multicast_address_filter: MulticastAddressFilter, + + /// Control frame filtering mode, + pub control_filter: ControlFrameFilter, + + /// Hash table configuration. + pub hash_table_value: HashTableValue, + + /// Enable or disable broadcast frame filtering. + /// + /// If set to `true`, broadcast frames will be filtered out. + pub filter_broadcast: bool, + + /// Enable or disable receive all mode. + /// + /// If receive all mode is disabled, address filtering + /// is applied to incoming frames (and corresponding bits + /// in the descriptors are configured), but the MAC does not + /// drop any frames. + pub receive_all: bool, +} + +impl FilterConfig { + /// Create a new basic [`FilterConfig`] that: + /// * Does not filter out frames destined for `station_addr` or an address + /// contained in `extra_address`. + /// * Does not filter out multicast frames. + /// * Does not filter out broadcast frames. + /// * Filters out all control frames. + pub const fn filter_destinations(station_addr: Mac, extra_addresses: &[Mac]) -> Self { + assert!(extra_addresses.len() <= 3); + + let mut address_filters = [None, None, None]; + + let mut i = 0; + loop { + address_filters[i] = Some(MacAddressFilter::new( + AddressFilterType::Destination, + extra_addresses[i], + MacAddressFilterMask::empty(), + )); + + i += 1; + if i == 3 || i == extra_addresses.len() { + break; + } + } + + FilterConfig { + base_address: station_addr, + address_filters, + destination_address_filter: DaFilter { + perfect_filtering: PerfectDaFilterMode::Normal, + hash_table_filtering: false, + }, + source_address_filter: SaFilter::Ignore, + multicast_address_filter: MulticastAddressFilter::PassAll, + control_filter: ControlFrameFilter::BlockAll, + hash_table_value: HashTableValue::new(), + filter_broadcast: false, + receive_all: false, + } + } + + fn configure(&self, eth_mac: ÐERNET_MAC) { + let FilterConfig { + base_address, + address_filters, + destination_address_filter, + source_address_filter, + multicast_address_filter, + control_filter, + hash_table_value: hash_table_filtering, + filter_broadcast, + receive_all, + } = self; + + eth_mac + .maca0hr + .write(|w| w.maca0h().bits(base_address.high())); + eth_mac + .maca0lr + .write(|w| w.maca0l().bits(base_address.low())); + + let daif = match &destination_address_filter.perfect_filtering { + PerfectDaFilterMode::Normal => false, + PerfectDaFilterMode::Inverse => true, + }; + let hu = destination_address_filter.hash_table_filtering; + + let (saf, saif) = match &source_address_filter { + SaFilter::Ignore => (false, false), + SaFilter::Normal => (true, false), + SaFilter::Inverse => (true, true), + }; + + let (pam, hm) = match &multicast_address_filter { + MulticastAddressFilter::PassAll => (true, false), + MulticastAddressFilter::DestinationAddressHash => (false, true), + MulticastAddressFilter::DestinationAddress => (false, false), + }; + + let pcf = match &control_filter { + ControlFrameFilter::BlockAll => 0b00, + ControlFrameFilter::NoPause => 0b01, + ControlFrameFilter::AllowAll => 0b10, + ControlFrameFilter::AddressFilter => 0b11, + }; + + macro_rules! next_addr_reg { + ($idx:literal, $regh:ident, $regl:ident, $ah:ident, $al:ident) => { + if let Some(filter) = &address_filters[$idx] { + let sa = match filter.ty { + AddressFilterType::Destination => false, + AddressFilterType::Source => true, + }; + + eth_mac.$regh.write(|w| { + w.ae() + .set_bit() + .sa() + .bit(sa) + .mbc() + .bits(filter.mask.bits()) + .$ah() + .bits(filter.address.high()) + }); + + // This operation is only unsafe for register maca2lr STM32F107 + // + // NOTE(safety): this operation is only unsafe for a single one + // of the lower-address-part registers, so this should be fine. + #[allow(unused_unsafe)] + eth_mac + .$regl + .write(|w| unsafe { w.$al().bits(filter.address.low()) }); + } else { + eth_mac.$regh.write(|w| w.ae().clear_bit()); + } + }; + } + + next_addr_reg!(0, maca1hr, maca1lr, maca1h, maca1l); + next_addr_reg!(1, maca2hr, maca2lr, maca2h, maca2l); + next_addr_reg!(2, maca3hr, maca3lr, maca3h, maca3l); + + eth_mac.macffr.write(|w| { + w.hpf() + .clear_bit() + .pm() + .clear_bit() + .ra() + .bit(*receive_all) + .saf() + .bit(saf) + .saif() + .bit(saif) + .pcf() + .bits(pcf) + .bfd() + .bit(*filter_broadcast) + .pam() + .bit(pam) + .daif() + .bit(daif) + .hm() + .bit(hm) + .hu() + .bit(hu) + }); + + eth_mac + .machthr + .write(|w| w.hth().bits(hash_table_filtering.high)); + + eth_mac + .machtlr + .write(|w| w.htl().bits(hash_table_filtering.low)); + } +} + +/// A big-endian MAC address. +#[derive(Debug, Clone, Copy)] +pub struct Mac([u8; 6]); + +impl Mac { + /// Create a new MAC address with the given big-endian value. + pub fn new(address: [u8; 6]) -> Self { + Self(address) + } + + /// Get the raw bytes of this MAC address. + pub fn raw(&self) -> &[u8; 6] { + &self.0 + } + /// Returns `true` if this MAC is locally administred, i.e. it has the I/G bit set. + pub fn is_multicast(&self) -> bool { + (self.0[0] & 0b1) == 0b1 + } + + /// Returns `true` if this MAC is locally administred, i.e. it has the U/L bit set. + pub fn is_locally_administred(&self) -> bool { + (self.0[0] & 0b10) == 0b10 + } + + /// Returns `true` if this MAC is the broadcast address. + pub fn is_broadcast(&self) -> bool { + self.0.iter().all(|v| v == &0xFF) + } + + /// Return bytes in a form that can be put into the high portion + /// of a MAC address register by converting them to little-endian + fn high(&self) -> u16 { + let high_bytes = [self.0[5], self.0[4]]; + u16::from_ne_bytes(high_bytes) + } + + /// Return bytes in a form that can be put into the low portion + /// of a MAC address register by converting them to little-endian. + fn low(&self) -> u32 { + let low_bytes = [self.0[3], self.0[2], self.0[1], self.0[0]]; + u32::from_ne_bytes(low_bytes) + } +} + +impl From<[u8; 6]> for Mac { + fn from(value: [u8; 6]) -> Self { + Self::new(value) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Mac { + fn format(&self, fmt: defmt::Formatter) { + let addr = self.0; + defmt::write!( + fmt, + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5] + ) + } +} + +#[cfg(feature = "smoltcp-phy")] +impl From for Mac { + fn from(value: smoltcp::wire::EthernetAddress) -> Self { + Self::new(value.0) + } +} + +#[cfg(feature = "smoltcp-phy")] +impl From for smoltcp::wire::EthernetAddress { + fn from(value: Mac) -> Self { + smoltcp::wire::EthernetAddress::from_bytes(&value.0) + } +} + +#[derive(Debug, Clone, Copy)] +/// The type of an address filter. +pub enum AddressFilterType { + /// Filter based on Source Address. + Source, + /// Filter based on Destination address. + Destination, +} + +/// A MAC address filter +#[derive(Debug, Clone, Copy)] + +pub struct MacAddressFilter { + /// The address on which this filter + /// should operate. + pub ty: AddressFilterType, + /// The address that this filter should use. + pub address: Mac, + /// The byte mask that should be used to mask + /// out specific parts of the to-be-compared address + /// for comparison. + pub mask: MacAddressFilterMask, +} + +impl MacAddressFilter { + /// Create a new MAC address filter. + pub const fn new(ty: AddressFilterType, address: Mac, mask: MacAddressFilterMask) -> Self { + Self { ty, address, mask } + } +} + +bitflags::bitflags! { + /// A mask to be applied when comparing a [`MacAddressFilter`] + /// to an incoming address. + pub struct MacAddressFilterMask: u8 { + /// Ignore byte 5 (the most significant byte) + /// during address comparison. + const BYTE5 = 1 << 5; + /// Ignore byte 4 during address comparison. + const BYTE4 = 1 << 4; + /// Ignore byte 3 during address comparison. + const BYTE3 = 1 << 3; + /// Ignore byte 2 during address comparison. + const BYTE2 = 1 << 2; + /// Ignore byte 1 during address comparison. + const BYTE1 = 1 << 1; + /// Ignore byte 0 during address comparison. + const BYTE0 = 1 << 0; + } +} diff --git a/src/mac/frame_filter/multicast.rs b/src/mac/frame_filter/multicast.rs new file mode 100644 index 0000000..4581d80 --- /dev/null +++ b/src/mac/frame_filter/multicast.rs @@ -0,0 +1,29 @@ +/// Multicast address filtering +#[derive(Debug, Clone)] +pub enum MulticastAddressFilter { + /// All received multicast frames are passed to the + /// application. + PassAll, + /// Only multicast frames whose destination address + /// passes the hash table check are passed to the + /// application. + DestinationAddressHash, + /// Only multicast frames whose destination address + /// is equal to one of the provided destination addresses + /// are passed to the application. + DestinationAddress, +} + +impl MulticastAddressFilter { + /// Create a new MulticastAddressFiltering that does not + /// filter any multicast frames. + pub const fn new() -> Self { + Self::PassAll + } +} + +impl Default for MulticastAddressFilter { + fn default() -> Self { + Self::new() + } +} diff --git a/src/mac/frame_filter/source.rs b/src/mac/frame_filter/source.rs new file mode 100644 index 0000000..983fbec --- /dev/null +++ b/src/mac/frame_filter/source.rs @@ -0,0 +1,32 @@ +/// The type of source address frame filtering that +/// the MAC should perform to received frames. +#[derive(Debug, Clone)] + +pub enum SaFilter { + /// Source address filtering never fails. + Ignore, + /// Filter frames by their Source Address, based on + /// the addresses configured with [`AddressFilterType::Source`]. + /// + /// [`AddressFilterType::Source`]: `super::AddressFilterType::Destination` + Normal, + /// Filter frames by their Source Address, based on + /// the addresses configured with [`AddressFilterType::Source`]. + /// + /// [`AddressFilterType::Source`]: `super::AddressFilterType::Destination` + Inverse, +} + +impl SaFilter { + /// Create a new [`SaFilter`] that + /// does not filter any frames. + pub const fn new() -> Self { + Self::Inverse + } +} + +impl Default for SaFilter { + fn default() -> Self { + Self::new() + } +} diff --git a/src/mac/mod.rs b/src/mac/mod.rs index 7f0c706..7440dbd 100644 --- a/src/mac/mod.rs +++ b/src/mac/mod.rs @@ -11,6 +11,8 @@ use crate::{ mod miim; pub use miim::*; +pub mod frame_filter; + /// Speeds at which this MAC can be configured #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -37,7 +39,7 @@ mod consts { /* For HCLK over 150 MHz */ pub const ETH_MACMIIAR_CR_HCLK_DIV_102: u8 = 4; } -use self::consts::*; +use self::{consts::*, frame_filter::Filter}; /// HCLK must be at least 25MHz to use the ethernet peripheral. /// This (empty) struct is returned to indicate that it is not set @@ -115,16 +117,6 @@ impl EthernetMAC { .set_bit() }); - // Frame filter register - eth_mac.macffr.modify(|_, w| { - // Receive All - w.ra() - .set_bit() - // Promiscuous mode - .pm() - .set_bit() - }); - // Flow Control Register eth_mac.macfcr.modify(|_, w| { // Pause time @@ -147,6 +139,8 @@ impl EthernetMAC { .mmctimr .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 21)) }); + Filter::Promiscuous.configure(ð_mac); + let mut me = Self { eth_mac }; me.set_speed(initial_speed); @@ -210,6 +204,12 @@ impl EthernetMAC { (true, true) => Speed::FullDuplexBase100Tx, } } + + /// Configure the frame filtering performed by this MAC. + #[inline(always)] + pub fn configure_frame_filter(&self, filter_config: &Filter) { + filter_config.configure(&self.eth_mac); + } } /// Ethernet media access control (MAC) with owned MII diff --git a/src/rx.rs b/src/rx.rs index cc08b73..0a5882c 100644 --- a/src/rx.rs +++ b/src/rx.rs @@ -34,6 +34,8 @@ const RXDESC_0_ES: u32 = 1 << 15; const RXDESC_0_FL_MASK: u32 = 0x3FFF; const RXDESC_0_FL_SHIFT: usize = 16; +const RXDESC_0_SAF: u32 = 1 << 13; + const RXDESC_1_RBS_SHIFT: usize = 0; const RXDESC_1_RBS_MASK: u32 = 0x0fff << RXDESC_1_RBS_SHIFT; /// Second address chained @@ -123,6 +125,10 @@ impl RxDescriptor { fn get_frame_len(&self) -> usize { ((self.desc.read(0) >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize } + + fn failed_saf(&self) -> bool { + (self.desc.read(0) & RXDESC_0_SAF) == RXDESC_0_SAF + } } /// An RX DMA Ring Descriptor entry @@ -203,6 +209,11 @@ impl<'a> RxPacket<'a> { pub fn free(self) { drop(self) } + + // Whether or not the packet failed the source address filter. + pub fn failed_source_address_filter(&self) -> bool { + self.entry.desc().failed_saf() + } } /// Rx DMA state diff --git a/src/tx.rs b/src/tx.rs index d2214ed..f2fd478 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -222,7 +222,6 @@ impl<'a> TxRing<'a> { // Register TxDescriptor eth_dma .dmatdlar - // Note: unsafe block required for `stm32f107`. .write(|w| unsafe { w.stl().bits(ring_ptr as u32) }); // "Preceding reads and writes cannot be moved past subsequent writes."