From 669d6f8a2eb5a5a2ff5e34825f04e9a87c8c2823 Mon Sep 17 00:00:00 2001 From: Charles-Henri Mousset Date: Sun, 9 Mar 2025 09:38:18 +0100 Subject: [PATCH 1/4] [enh] added working Ethernet and demo for 32V208 --- Cargo.toml | 12 +- examples/ch32v208/Cargo.toml | 30 +- examples/ch32v208/src/bin/eth.rs | 174 +++++++++++ src/eth/_v1/mod.rs | 491 +++++++++++++++++++++++++++++++ src/eth/_v1/rx_desc.rs | 232 +++++++++++++++ src/eth/_v1/tx_desc.rs | 171 +++++++++++ src/eth/generic_smi.rs | 78 +++++ src/eth/mod.rs | 52 ++++ src/eth/v1/mod.rs | 0 src/eth/v10m/mod.rs | 322 ++++++++++++++++++++ src/lib.rs | 3 + 11 files changed, 1555 insertions(+), 10 deletions(-) create mode 100644 examples/ch32v208/src/bin/eth.rs create mode 100644 src/eth/_v1/mod.rs create mode 100644 src/eth/_v1/rx_desc.rs create mode 100644 src/eth/_v1/tx_desc.rs create mode 100644 src/eth/generic_smi.rs create mode 100644 src/eth/mod.rs create mode 100644 src/eth/v1/mod.rs create mode 100644 src/eth/v10m/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 78da824..20fb711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0" [dependencies] ch32-metapac = { features = [ "rt", -], git = "https://github.com/ch32-rs/ch32-metapac", rev = "b1cbc7a98e43af3fd3170821654784e2c01cb26b" } +], git = "https://github.com/chmousset/rs-ch32-metapac", rev = "3c2fe80fb582c4a7feba3e8600234edfadc152e2" } qingke = { version = "0.5.0", features = ["critical-section-impl"] } qingke-rt = { version = "0.5.0", optional = true } @@ -45,12 +45,13 @@ futures = { version = "0.3.31", default-features = false, features = [ rand_core = "0.6.3" sdio-host = "0.5.0" embassy-hal-internal = "0.2.0" +embassy-net-driver-channel = "0.3.0" +embassy-net-driver = "0.2.0" [build-dependencies] ch32-metapac = { features = [ "metadata", -], git = "https://github.com/ch32-rs/ch32-metapac", rev = "b1cbc7a98e43af3fd3170821654784e2c01cb26b" } - +], git = "https://github.com/chmousset/rs-ch32-metapac", rev = "3c2fe80fb582c4a7feba3e8600234edfadc152e2" } proc-macro2 = "1.0" quote = "1.0" @@ -58,10 +59,7 @@ quote = "1.0" default = ["embassy", "rt"] rt = ["dep:qingke-rt"] highcode = ["qingke-rt/highcode"] -embassy = [ - "dep:embassy-time-driver", - "dep:embassy-time", -] +embassy = ["dep:embassy-time-driver", "dep:embassy-time"] defmt = ["dep:defmt"] memory-x = ["ch32-metapac/memory-x"] diff --git a/examples/ch32v208/Cargo.toml b/examples/ch32v208/Cargo.toml index 558944b..428fdcd 100644 --- a/examples/ch32v208/Cargo.toml +++ b/examples/ch32v208/Cargo.toml @@ -9,19 +9,43 @@ ch32-hal = { path = "../../", features = [ "memory-x", "embassy", "rt", + "time-driver-tim1", + "defmt", + # "time-driver-any", ], default-features = false } embassy-executor = { version = "0.6.0", features = [ "integrated-timers", - "arch-riscv32", + "arch-spin", "executor-thread", + # "task-arena-size-8192", ] } -embassy-time = { version = "0.3.2" } + +critical-section = "1.2.0" # This is okay because we should automatically use whatever ch32-hal uses qingke-rt = "*" qingke = "*" -panic-halt = "1.0" +# panic-halt = "1.0" + +embassy-net = { version = "0.4.0", features = [ + "proto-ipv4", + "udp", + "medium-ethernet", + "dhcpv4", + "log", +] } +static_cell = "2.1.0" +portable-atomic = { version = "*", features = ["critical-section"] } +embassy-futures = "0.1.1" +embassy-time = "0.3" +heapless = "0.8.0" +smoltcp = { version = "*", default-features = false, features = [ + "proto-ipv4", + "medium-ethernet", + "log", +] } +defmt = "0.3.10" [profile.release] strip = false # symbols are not flashed to the microcontroller, so don't strip them. diff --git a/examples/ch32v208/src/bin/eth.rs b/examples/ch32v208/src/bin/eth.rs new file mode 100644 index 0000000..f9efd3c --- /dev/null +++ b/examples/ch32v208/src/bin/eth.rs @@ -0,0 +1,174 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +use ch32_hal as hal; +// use panic_halt as _; +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{self, IpAddress, Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4}; +use embassy_time::{Duration, Timer}; +use hal::delay::Delay; +use hal::eth::generic_smi::GenericSMI; +use hal::eth::{Device, EthernetStationManagement, Runner, State}; +use hal::rcc::*; +use hal::time::Hertz; +use hal::{eth, interrupt, println}; +use heapless::Vec; +use static_cell::StaticCell; + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, GenericSMI>) -> ! { + println!("ethernet_task()"); + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + println!("net_task()"); + stack.run().await +} + +#[embassy_executor::task] +async fn tick() { + loop { + Timer::after(Duration::from_millis(1000)).await; + println!("tick"); + } +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = println!("\n\n\n{}", info); + + loop {} +} + +#[embassy_executor::main(entry = "ch32_hal::entry")] +async fn main(spawner: Spawner) -> ! { + // configure core speed at 120MHz + let _p = ch32_hal::init(ch32_hal::Config { + rcc: ch32_hal::rcc::Config { + hse: Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }), + sys: Sysclk::PLL, + pll_src: PllSource::HSE, + pll: Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL15, + }), + pllx: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + ls: LsConfig::default(), + hspll_src: HsPllSource::HSI, + hspll: None, + }, + dma_interrupt_priority: interrupt::Priority::P0, + }); + let core_freq: u32 = 60_000_000; + + ch32_hal::debug::SDIPrint::enable(); + Timer::after(Duration::from_millis(1000)).await; // let some time to the debug interface to start + println!("Hello CH32!"); + let ret = spawner.spawn(tick()).unwrap(); + println!("tick task: {ret:?}"); + + info!("plop"); + + // loop { + // Timer::after(Duration::from_millis(1000)).await; + // println!("tick2"); + // } + + // Ethernet setup + let mac_addr = eth::get_mac(); + println!("mac_addr: {mac_addr:?}"); + static STATE: StaticCell> = StaticCell::new(); + println!("STATE: done"); + let state = STATE.init_with(|| State::<2, 2>::new()); + println!("state: done"); + let phy = GenericSMI::new(0); + println!("phy: done"); + let (device, runner) = hal::eth::new(mac_addr, state, core_freq, phy).await.unwrap(); + println!("device: done"); + // println!("runner: {runner:?}"); + let station_management: EthernetStationManagement = EthernetStationManagement::new(); + println!("station_management: done"); + let ret = spawner.spawn(ethernet_task(runner)); + println!("eth task: {ret:?}"); + + // Generate random seed + let seed = 0xdeadbeef_abadbabe; + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::<2>::new()), + seed, + )); + + // Launch network task + let ret = spawner.spawn(net_task(&stack)); + println!("net task: {ret:?}"); + + // loop { + // Timer::after(Duration::from_millis(1000)).await; + // // println!("tick"); + // } + + println!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + println!("IP address: {:?}", local_addr); + // stack.set_config_v4(embassy_net::ConfigV4::Static(StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address { 0: [192, 168, 1, 10] }, 24), + // /// Default gateway. + // gateway: None, + // /// DNS servers. + // dns_servers: Vec::new(), + // })); + + // FIXME! + // RX packets either aren't received by the stack, or smoltcp (silently) discards them for some + // reason. It could be worth checking what smoltcp expects as packet format, and also memory + // content + + // } + // Then we can use it! + let mut rx_buffer = [0; 300]; + let mut tx_buffer = [0; 300]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 300]; + loop { + let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + socket.bind(1234).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + println!("rxd from {}: {}", ep, s); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/src/eth/_v1/mod.rs b/src/eth/_v1/mod.rs new file mode 100644 index 0000000..2525c7b --- /dev/null +++ b/src/eth/_v1/mod.rs @@ -0,0 +1,491 @@ +// The v1c ethernet driver was ported to embassy from the awesome stm32-eth project (https://github.com/stm32-rs/stm32-eth). + +mod rx_desc; +mod tx_desc; + +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::sync::atomic::{fence, Ordering}; +use core::task::Context; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; +use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; + +pub(crate) use self::rx_desc::{RDes, RDesRing}; +pub(crate) use self::tx_desc::{TDes, TDesRing}; +use super::*; +#[cfg(eth_v1a)] +use crate::gpio::Pull; +use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; +use crate::interrupt::InterruptExt; +#[cfg(eth_v1a)] +use crate::pac::AFIO; +#[cfg(any(eth_v1b, eth_v1c))] +use crate::pac::SYSCFG; +use crate::pac::{ETH, RCC}; +use crate::rcc::SealedRccPeripheral; +use crate::{interrupt, Peripheral}; + +const TX_BUFFER_SIZE: usize = 1514; +const RX_BUFFER_SIZE: usize = 1536; + +#[repr(C, align(8))] +#[derive(Copy, Clone)] +pub(crate) struct Packet([u8; N]); + +/// Ethernet packet queue. +/// +/// This struct owns the memory used for reading and writing packets. +/// +/// `TX` is the number of packets in the transmit queue, `RX` in the receive +/// queue. A bigger queue allows the hardware to receive more packets while the +/// CPU is busy doing other things, which may increase performance (especially for RX) +/// at the cost of more RAM usage. +pub struct PacketQueue { + tx_desc: [TDes; TX], + rx_desc: [RDes; RX], + tx_buf: [Packet; TX], + rx_buf: [Packet; RX], +} + +impl PacketQueue { + /// Create a new packet queue. + pub const fn new() -> Self { + Self { + tx_desc: [const { TDes::new() }; TX], + rx_desc: [const { RDes::new() }; RX], + tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], + rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], + } + } + + /// Initialize a packet queue in-place. + /// + /// This can be helpful to avoid accidentally stack-allocating the packet queue in the stack. The + /// Rust compiler can sometimes be a bit dumb when working with large owned values: if you call `new()` + /// and then store the returned PacketQueue in its final place (like a `static`), the compiler might + /// place it temporarily on the stack then move it. Since this struct is quite big, it may result + /// in a stack overflow. + /// + /// With this function, you can create an uninitialized `static` with type `MaybeUninit>` + /// and initialize it in-place, guaranteeing no stack usage. + /// + /// After calling this function, calling `assume_init` on the MaybeUninit is guaranteed safe. + pub fn init(this: &mut MaybeUninit) { + unsafe { + this.as_mut_ptr().write_bytes(0u8, 1); + } + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { + type RxToken<'a> + = RxToken<'a, 'd> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, 'd> + where + Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + WAKER.register(cx.waker()); + if self.rx.available().is_some() && self.tx.available().is_some() { + Some((RxToken { rx: &mut self.rx }, TxToken { tx: &mut self.tx })) + } else { + None + } + } + + fn transmit(&mut self, cx: &mut Context) -> Option> { + WAKER.register(cx.waker()); + if self.tx.available().is_some() { + Some(TxToken { tx: &mut self.tx }) + } else { + None + } + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.max_burst_size = Some(self.tx.len()); + caps + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + if self.phy.poll_link(&mut self.station_management, cx) { + LinkState::Up + } else { + LinkState::Down + } + } + + fn hardware_address(&self) -> HardwareAddress { + HardwareAddress::Ethernet(self.mac_addr) + } +} + +/// `embassy-net` RX token. +pub struct RxToken<'a, 'd> { + rx: &'a mut RDesRing<'d>, +} + +impl<'a, 'd> embassy_net_driver::RxToken for RxToken<'a, 'd> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.available()); + let r = f(pkt); + self.rx.pop_packet(); + r + } +} + +/// `embassy-net` TX token. +pub struct TxToken<'a, 'd> { + tx: &'a mut TDesRing<'d>, +} + +impl<'a, 'd> embassy_net_driver::TxToken for TxToken<'a, 'd> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.available()); + let r = f(&mut pkt[..len]); + self.tx.transmit(len); + r + } +} + +/// Interrupt handler. +pub struct InterruptHandler {} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + WAKER.wake(); + + // TODO: Check and clear more flags + let dma = ETH.ethernet_dma(); + + dma.dmasr().modify(|w| { + w.set_ts(true); + w.set_rs(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmasr().read(); + dma.dmasr().read(); + } +} + +/// Ethernet driver. +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, + + pins: [PeripheralRef<'d, AnyPin>; 9], + pub(crate) phy: P, + pub(crate) station_management: EthernetStationManagement, + pub(crate) mac_addr: [u8; 6], +} + +#[cfg(eth_v1a)] +macro_rules! config_in_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + // TODO properly create a set_as_input function + $pin.set_as_af($pin.af_num(), AfType::input(Pull::None)); + )* + }) + } +} + +#[cfg(eth_v1a)] +macro_rules! config_af_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + )* + }) + }; +} + +#[cfg(any(eth_v1b, eth_v1c))] +macro_rules! config_pins { + ($($pin:ident),*) => { + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + )* + }) + }; +} + +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + /// safety: the returned instance is not leak-safe + pub fn new( + queue: &'d mut PacketQueue, + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + ref_clk: impl Peripheral

> + 'd, + mdio: impl Peripheral

> + 'd, + mdc: impl Peripheral

> + 'd, + crs: impl Peripheral

> + 'd, + rx_d0: impl Peripheral

> + 'd, + rx_d1: impl Peripheral

> + 'd, + tx_d0: impl Peripheral

> + 'd, + tx_d1: impl Peripheral

> + 'd, + tx_en: impl Peripheral

> + 'd, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + + // Enable the necessary Clocks + #[cfg(eth_v1a)] + critical_section::with(|_| { + RCC.apb2enr().modify(|w| w.set_afioen(true)); + + // Select RMII (Reduced Media Independent Interface) + // Must be done prior to enabling peripheral clock + AFIO.mapr().modify(|w| w.set_mii_rmii_sel(true)); + + RCC.ahbenr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + }); + + #[cfg(any(eth_v1b, eth_v1c))] + critical_section::with(|_| { + RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + + // RMII (Reduced Media Independent Interface) + SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); + }); + + #[cfg(eth_v1a)] + { + config_in_pins!(ref_clk, rx_d0, rx_d1); + config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); + } + + #[cfg(any(eth_v1b, eth_v1c))] + config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + + let dma = T::regs().ethernet_dma(); + let mac = T::regs().ethernet_mac(); + + // Reset and wait + dma.dmabmr().modify(|w| w.set_sr(true)); + while dma.dmabmr().read().sr() {} + + mac.maccr().modify(|w| { + w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times + w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping + w.set_fes(Fes::FES100); // fast ethernet speed + w.set_dm(Dm::FULL_DUPLEX); // full duplex + // TODO: Carrier sense ? ECRSFD + }); + + // Set the mac to pass all multicast packets + mac.macffr().modify(|w| { + w.set_pam(true); + }); + + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, + // so the LR write must happen after the HR write. + mac.maca0hr() + .modify(|w| w.set_maca0h(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); + mac.maca0lr().write(|w| { + w.set_maca0l( + u32::from(mac_addr[0]) + | (u32::from(mac_addr[1]) << 8) + | (u32::from(mac_addr[2]) << 16) + | (u32::from(mac_addr[3]) << 24), + ) + }); + + // pause time + mac.macfcr().modify(|w| w.set_pt(0x100)); + + // Transfer and Forward, Receive and Forward + dma.dmaomr().modify(|w| { + w.set_tsf(Tsf::STORE_FORWARD); + w.set_rsf(Rsf::STORE_FORWARD); + }); + + dma.dmabmr().modify(|w| { + w.set_pbl(Pbl::PBL32) // programmable burst length - 32 ? + }); + + // TODO MTU size setting not found for v1 ethernet, check if correct + + let hclk = ::frequency(); + let hclk_mhz = hclk.0 / 1_000_000; + + // Set the MDC clock frequency in the range 1MHz - 2.5MHz + let clock_range = match hclk_mhz { + 0..=24 => panic!("Invalid HCLK frequency - should be at least 25 MHz."), + 25..=34 => Cr::CR_20_35, // Divide by 16 + 35..=59 => Cr::CR_35_60, // Divide by 26 + 60..=99 => Cr::CR_60_100, // Divide by 42 + 100..=149 => Cr::CR_100_150, // Divide by 62 + 150..=216 => Cr::CR_150_168, // Divide by 102 + _ => { + panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") + } + }; + + let pins = [ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]; + + let mut this = Self { + _peri: peri, + pins, + phy: phy, + station_management: EthernetStationManagement { + peri: PhantomData, + clock_range: clock_range, + }, + mac_addr, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), + }; + + fence(Ordering::SeqCst); + + let mac = T::regs().ethernet_mac(); + let dma = T::regs().ethernet_dma(); + + mac.maccr().modify(|w| { + w.set_re(true); + w.set_te(true); + }); + dma.dmaomr().modify(|w| { + w.set_ftf(Ftf::FLUSH); // flush transmit fifo (queue) + w.set_st(St::STARTED); // start transmitting channel + w.set_sr(DmaomrSr::STARTED); // start receiving channel + }); + + this.rx.demand_poll(); + + // Enable interrupts + dma.dmaier().modify(|w| { + w.set_nise(true); + w.set_rie(true); + w.set_tie(true); + }); + + this.phy.phy_reset(&mut this.station_management); + this.phy.phy_init(&mut this.station_management); + + interrupt::ETH.unpend(); + unsafe { interrupt::ETH.enable() }; + + this + } +} + +/// Ethernet station management interface. +pub struct EthernetStationManagement { + peri: PhantomData, + clock_range: Cr, +} + +unsafe impl StationManagement for EthernetStationManagement { + fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16 { + let mac = T::regs().ethernet_mac(); + + mac.macmiiar().modify(|w| { + w.set_pa(phy_addr); + w.set_mr(reg); + w.set_mw(Mw::READ); // read operation + w.set_cr(self.clock_range); + w.set_mb(MbProgress::BUSY); // indicate that operation is in progress + }); + while mac.macmiiar().read().mb() == MbProgress::BUSY {} + mac.macmiidr().read().md() + } + + fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16) { + let mac = T::regs().ethernet_mac(); + + mac.macmiidr().write(|w| w.set_md(val)); + mac.macmiiar().modify(|w| { + w.set_pa(phy_addr); + w.set_mr(reg); + w.set_mw(Mw::WRITE); // write + w.set_cr(self.clock_range); + w.set_mb(MbProgress::BUSY); + }); + while mac.macmiiar().read().mb() == MbProgress::BUSY {} + } +} + +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { + fn drop(&mut self) { + let dma = T::regs().ethernet_dma(); + let mac = T::regs().ethernet_mac(); + + // Disable the TX DMA and wait for any previous transmissions to be completed + dma.dmaomr().modify(|w| w.set_st(St::STOPPED)); + + // Disable MAC transmitter and receiver + mac.maccr().modify(|w| { + w.set_re(false); + w.set_te(false); + }); + + dma.dmaomr().modify(|w| w.set_sr(DmaomrSr::STOPPED)); + + critical_section::with(|_| { + for pin in self.pins.iter_mut() { + pin.set_as_disconnected(); + } + }) + } +} + +pin_trait!(RXClkPin, Instance); +pin_trait!(TXClkPin, Instance); +pin_trait!(RefClkPin, Instance); +pin_trait!(MDIOPin, Instance); +pin_trait!(MDCPin, Instance); +pin_trait!(RXDVPin, Instance); +pin_trait!(CRSPin, Instance); +pin_trait!(RXD0Pin, Instance); +pin_trait!(RXD1Pin, Instance); +pin_trait!(RXD2Pin, Instance); +pin_trait!(RXD3Pin, Instance); +pin_trait!(TXD0Pin, Instance); +pin_trait!(TXD1Pin, Instance); +pin_trait!(TXD2Pin, Instance); +pin_trait!(TXD3Pin, Instance); +pin_trait!(TXEnPin, Instance); diff --git a/src/eth/_v1/rx_desc.rs b/src/eth/_v1/rx_desc.rs new file mode 100644 index 0000000..2a46c18 --- /dev/null +++ b/src/eth/_v1/rx_desc.rs @@ -0,0 +1,232 @@ +use core::sync::atomic::{compiler_fence, fence, Ordering}; + +use stm32_metapac::eth::vals::{Rpd, Rps}; +use vcell::VolatileCell; + +use crate::eth::RX_BUFFER_SIZE; +use crate::pac::ETH; + +mod rx_consts { + /// Owned by DMA engine + pub const RXDESC_0_OWN: u32 = 1 << 31; + /// First descriptor + pub const RXDESC_0_FS: u32 = 1 << 9; + /// Last descriptor + pub const RXDESC_0_LS: u32 = 1 << 8; + /// Error summary + pub const RXDESC_0_ES: u32 = 1 << 15; + /// Frame length + pub const RXDESC_0_FL_MASK: u32 = 0x3FFF; + pub const RXDESC_0_FL_SHIFT: usize = 16; + + pub const RXDESC_1_RBS_MASK: u32 = 0x0fff; + /// Second address chained + pub const RXDESC_1_RCH: u32 = 1 << 14; + /// End Of Ring + pub const RXDESC_1_RER: u32 = 1 << 15; +} + +use rx_consts::*; + +use super::Packet; + +/// Receive Descriptor representation +/// +/// * rdes0: OWN and Status +/// * rdes1: allocated buffer length +/// * rdes2: data buffer address +/// * rdes3: next descriptor address +#[repr(C)] +pub(crate) struct RDes { + rdes0: VolatileCell, + rdes1: VolatileCell, + rdes2: VolatileCell, + rdes3: VolatileCell, +} + +impl RDes { + pub const fn new() -> Self { + Self { + rdes0: VolatileCell::new(0), + rdes1: VolatileCell::new(0), + rdes2: VolatileCell::new(0), + rdes3: VolatileCell::new(0), + } + } + + /// Return true if this RDes is acceptable to us + #[inline(always)] + fn valid(&self) -> bool { + // Write-back descriptor is valid if: + // + // Contains first buffer of packet AND contains last buf of + // packet AND no errors + (self.rdes0.get() & (RXDESC_0_ES | RXDESC_0_FS | RXDESC_0_LS)) == (RXDESC_0_FS | RXDESC_0_LS) + } + + /// Return true if this RDes is not currently owned by the DMA + #[inline(always)] + fn available(&self) -> bool { + self.rdes0.get() & RXDESC_0_OWN == 0 // Owned by us + } + + /// Configures the reception buffer address and length and passed descriptor ownership to the DMA + #[inline(always)] + fn set_ready(&self, buf: *mut u8) { + self.rdes1 + .set(self.rdes1.get() | (RX_BUFFER_SIZE as u32) & RXDESC_1_RBS_MASK); + self.rdes2.set(buf as u32); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + compiler_fence(Ordering::Release); + + self.rdes0.set(self.rdes0.get() | RXDESC_0_OWN); + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + fence(Ordering::SeqCst); + } + + // points to next descriptor (RCH) + #[inline(always)] + fn set_buffer2(&self, buffer: *const u8) { + self.rdes3.set(buffer as u32); + } + + #[inline(always)] + fn set_end_of_ring(&self) { + self.rdes1.set(self.rdes1.get() | RXDESC_1_RER); + } + + #[inline(always)] + fn packet_len(&self) -> usize { + ((self.rdes0.get() >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize + } + + fn setup(&self, next: Option<&Self>, buf: *mut u8) { + // Defer this initialization to this function, so we can have `RingEntry` on bss. + self.rdes1.set(self.rdes1.get() | RXDESC_1_RCH); + + match next { + Some(next) => self.set_buffer2(next as *const _ as *const u8), + None => { + self.set_buffer2(0 as *const u8); + self.set_end_of_ring(); + } + } + + self.set_ready(buf); + } +} + +/// Running state of the `RxRing` +#[derive(PartialEq, Eq, Debug)] +pub enum RunningState { + Unknown, + Stopped, + Running, +} + +/// Rx ring of descriptors and packets +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, +} + +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); + + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr()); + } + + // Register rx descriptor start + ETH.ethernet_dma() + .dmardlar() + .write(|w| w.0 = descriptors.as_ptr() as u32); + // We already have fences in `set_owned`, which is called in `setup` + + Self { + descriptors, + buffers, + index: 0, + } + } + + pub(crate) fn demand_poll(&self) { + ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)); + } + + /// Get current `RunningState` + fn running_state(&self) -> RunningState { + match ETH.ethernet_dma().dmasr().read().rps() { + // Reset or Stop Receive Command issued + Rps::STOPPED => RunningState::Stopped, + // Fetching receive transfer descriptor + Rps::RUNNING_FETCHING => RunningState::Running, + // Waiting for receive packet + Rps::RUNNING_WAITING => RunningState::Running, + // Receive descriptor unavailable + Rps::SUSPENDED => RunningState::Stopped, + // Closing receive descriptor + Rps::_RESERVED_5 => RunningState::Running, + // Transferring the receive packet data from receive buffer to host memory + Rps::RUNNING_WRITING => RunningState::Running, + _ => RunningState::Unknown, + } + } + + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + if self.running_state() != RunningState::Running { + self.demand_poll(); + } + + // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using + // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the + // buffer (I think .-.) + fence(Ordering::SeqCst); + + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; + } + + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; + } + + break; + } + + let descriptor = &mut self.descriptors[self.index]; + let len = descriptor.packet_len(); + return Some(&mut self.buffers[self.index].0[..len]); + } + + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); + + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + self.demand_poll(); + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 + } + } +} diff --git a/src/eth/_v1/tx_desc.rs b/src/eth/_v1/tx_desc.rs new file mode 100644 index 0000000..1317d20 --- /dev/null +++ b/src/eth/_v1/tx_desc.rs @@ -0,0 +1,171 @@ +use core::sync::atomic::{compiler_fence, fence, Ordering}; + +use vcell::VolatileCell; + +use crate::eth::TX_BUFFER_SIZE; +use crate::pac::ETH; + +/// Transmit and Receive Descriptor fields +#[allow(dead_code)] +mod tx_consts { + pub const TXDESC_0_OWN: u32 = 1 << 31; + pub const TXDESC_0_IOC: u32 = 1 << 30; + // First segment of frame + pub const TXDESC_0_FS: u32 = 1 << 28; + // Last segment of frame + pub const TXDESC_0_LS: u32 = 1 << 29; + // Transmit end of ring + pub const TXDESC_0_TER: u32 = 1 << 21; + // Second address chained + pub const TXDESC_0_TCH: u32 = 1 << 20; + // Error status + pub const TXDESC_0_ES: u32 = 1 << 15; + + // Transmit buffer size + pub const TXDESC_1_TBS_SHIFT: usize = 0; + pub const TXDESC_1_TBS_MASK: u32 = 0x0fff << TXDESC_1_TBS_SHIFT; +} +use tx_consts::*; + +use super::Packet; + +/// Transmit Descriptor representation +/// +/// * tdes0: control +/// * tdes1: buffer lengths +/// * tdes2: data buffer address +/// * tdes3: next descriptor address +#[repr(C)] +pub(crate) struct TDes { + tdes0: VolatileCell, + tdes1: VolatileCell, + tdes2: VolatileCell, + tdes3: VolatileCell, +} + +impl TDes { + pub const fn new() -> Self { + Self { + tdes0: VolatileCell::new(0), + tdes1: VolatileCell::new(0), + tdes2: VolatileCell::new(0), + tdes3: VolatileCell::new(0), + } + } + + /// Return true if this TDes is not currently owned by the DMA + fn available(&self) -> bool { + (self.tdes0.get() & TXDESC_0_OWN) == 0 + } + + /// Pass ownership to the DMA engine + fn set_owned(&mut self) { + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + compiler_fence(Ordering::Release); + self.tdes0.set(self.tdes0.get() | TXDESC_0_OWN); + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + fence(Ordering::SeqCst); + } + + fn set_buffer1(&self, buffer: *const u8) { + self.tdes2.set(buffer as u32); + } + + fn set_buffer1_len(&self, len: usize) { + self.tdes1 + .set((self.tdes1.get() & !TXDESC_1_TBS_MASK) | ((len as u32) << TXDESC_1_TBS_SHIFT)); + } + + // points to next descriptor (RCH) + fn set_buffer2(&self, buffer: *const u8) { + self.tdes3.set(buffer as u32); + } + + fn set_end_of_ring(&self) { + self.tdes0.set(self.tdes0.get() | TXDESC_0_TER); + } + + // set up as a part fo the ring buffer - configures the tdes + fn setup(&self, next: Option<&Self>) { + // Defer this initialization to this function, so we can have `RingEntry` on bss. + self.tdes0.set(TXDESC_0_TCH | TXDESC_0_IOC | TXDESC_0_FS | TXDESC_0_LS); + match next { + Some(next) => self.set_buffer2(next as *const TDes as *const u8), + None => { + self.set_buffer2(0 as *const u8); + self.set_end_of_ring(); + } + } + } +} + +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, +} + +impl<'a> TDesRing<'a> { + /// Initialise this TDesRing. Assume TDesRing is corrupt + pub(crate) fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); + + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1)); + } + + // Register txdescriptor start + ETH.ethernet_dma() + .dmatdlar() + .write(|w| w.0 = descriptors.as_ptr() as u32); + + Self { + descriptors, + buffers, + index: 0, + } + } + + pub(crate) fn len(&self) -> usize { + self.descriptors.len() + } + + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let descriptor = &mut self.descriptors[self.index]; + if descriptor.available() { + Some(&mut self.buffers[self.index].0) + } else { + None + } + } + + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); + + descriptor.set_buffer1(self.buffers[self.index].0.as_ptr()); + descriptor.set_buffer1_len(len); + + descriptor.set_owned(); + + // Ensure changes to the descriptor are committed before DMA engine sees tail pointer store. + // This will generate an DMB instruction. + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + // Move the index to the next descriptor + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 + } + // Request the DMA engine to poll the latest tx descriptor + ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) + } +} diff --git a/src/eth/generic_smi.rs b/src/eth/generic_smi.rs new file mode 100644 index 0000000..36df2c6 --- /dev/null +++ b/src/eth/generic_smi.rs @@ -0,0 +1,78 @@ +//! Generic SMI Ethernet PHY + +use super::{StationManagement, PHY}; + +#[allow(dead_code)] +mod phy_consts { + pub const PHY_REG_BCR: u8 = 0x00; + pub const PHY_REG_BSR: u8 = 0x01; + pub const PHY_REG_ID1: u8 = 0x02; + pub const PHY_REG_ID2: u8 = 0x03; + pub const PHY_REG_ANTX: u8 = 0x04; + pub const PHY_REG_ANRX: u8 = 0x05; + pub const PHY_REG_ANEXP: u8 = 0x06; + pub const PHY_REG_ANNPTX: u8 = 0x07; + pub const PHY_REG_ANNPRX: u8 = 0x08; + pub const PHY_REG_CTL: u8 = 0x0D; // Ethernet PHY Register Control + pub const PHY_REG_ADDAR: u8 = 0x0E; // Ethernet PHY Address or Data + + pub const PHY_REG_WUCSR: u16 = 0x8010; + + pub const PHY_REG_BCR_COLTEST: u16 = 1 << 7; + pub const PHY_REG_BCR_FD: u16 = 1 << 8; + pub const PHY_REG_BCR_ANRST: u16 = 1 << 9; + pub const PHY_REG_BCR_ISOLATE: u16 = 1 << 10; + pub const PHY_REG_BCR_POWERDN: u16 = 1 << 11; + pub const PHY_REG_BCR_AN: u16 = 1 << 12; + pub const PHY_REG_BCR_100M: u16 = 1 << 13; + pub const PHY_REG_BCR_LOOPBACK: u16 = 1 << 14; + pub const PHY_REG_BCR_RESET: u16 = 1 << 15; + + pub const PHY_REG_BSR_JABBER: u16 = 1 << 1; + pub const PHY_REG_BSR_UP: u16 = 1 << 2; + pub const PHY_REG_BSR_FAULT: u16 = 1 << 4; + pub const PHY_REG_BSR_ANDONE: u16 = 1 << 5; +} +use self::phy_consts::*; + +/// Generic SMI Ethernet PHY implementation +pub struct GenericSMI { + phy_addr: u8, +} + +impl GenericSMI { + /// Construct the PHY. It assumes the address `phy_addr` in the SMI communication + pub fn new(phy_addr: u8) -> Self { + Self { phy_addr } + } +} + +unsafe impl PHY for GenericSMI { + fn phy_reset(&mut self, sm: &mut S) { + sm.smi_write(self.phy_addr, PHY_REG_BCR, PHY_REG_BCR_RESET); + while sm.smi_read(self.phy_addr, PHY_REG_BCR) & PHY_REG_BCR_RESET == PHY_REG_BCR_RESET {} + } + + fn link_up(&mut self, sm: &mut S) -> bool { + let bsr = sm.smi_read(self.phy_addr, PHY_REG_BSR); + + bsr & PHY_REG_BSR_UP != 0 + } +} + +/// Public functions for the PHY +impl GenericSMI { + /// Set the SMI polling interval. + #[cfg(feature = "time")] + pub fn set_poll_interval(&mut self, poll_interval: Duration) { + self.poll_interval = poll_interval + } + + // Writes a value to an extended PHY register in MMD address space + fn smi_write_ext(&mut self, sm: &mut S, reg_addr: u16, reg_data: u16) { + sm.smi_write(self.phy_addr, PHY_REG_CTL, 0x0003); // set address + sm.smi_write(self.phy_addr, PHY_REG_ADDAR, reg_addr); + sm.smi_write(self.phy_addr, PHY_REG_CTL, 0x4003); // set data + sm.smi_write(self.phy_addr, PHY_REG_ADDAR, reg_data); + } +} diff --git a/src/eth/mod.rs b/src/eth/mod.rs new file mode 100644 index 0000000..7d77643 --- /dev/null +++ b/src/eth/mod.rs @@ -0,0 +1,52 @@ +//! Ethernet (ETH) +#![macro_use] + +#[cfg_attr(eth_10m, path = "v10m/mod.rs")] +#[cfg_attr(eth_v1, path = "v1/mod.rs")] +mod _version; + +pub mod generic_smi; + +pub use self::_version::*; + +#[allow(unused)] +const MTU: usize = 1514; + +/// Station Management Interface (SMI) on an ethernet PHY +/// +/// # Safety +/// +/// The methods cannot move out of self +pub unsafe trait StationManagement { + /// Read a register over SMI. + fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16; + /// Write a register over SMI. + fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16); +} + +/// Traits for an Ethernet PHY +/// +/// # Safety +/// +/// The methods cannot move S +pub unsafe trait PHY { + /// Reset PHY and wait for it to come out of reset. + fn phy_reset(&mut self, sm: &mut S); + /// Read PHY registers to check if link is established + fn link_up(&mut self, sm: &mut S) -> bool; +} + +trait SealedInstance { + fn regs() -> crate::pac::eth::Eth; +} + +/// Ethernet instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + Send + 'static {} + +impl SealedInstance for crate::peripherals::ETH { + fn regs() -> crate::pac::eth::Eth { + crate::pac::ETH + } +} +impl Instance for crate::peripherals::ETH {} diff --git a/src/eth/v1/mod.rs b/src/eth/v1/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/eth/v10m/mod.rs b/src/eth/v10m/mod.rs new file mode 100644 index 0000000..abcb4df --- /dev/null +++ b/src/eth/v10m/mod.rs @@ -0,0 +1,322 @@ +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] + +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_futures::join::join; +use embassy_net_driver_channel::driver::LinkState; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use {ch32_metapac as pac, embassy_net_driver_channel as ch}; + +use crate::eth::{Instance, StationManagement, PHY}; +use crate::{interrupt, println}; + +// If you change this update the docs of State +const MTU: usize = 1514; + +/// Type alias for the embassy-net driver. +pub type Device<'d> = ch::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +/// +/// The two generic arguments `N_RX` and `N_TX` set the size of the receive and +/// send packet queue. With a the ethernet MTU of _1514_ this takes up `N_RX + +/// NTX * 1514` bytes. While setting these both to 1 is the minimum this might +/// hurt performance as a packet can not be received while processing another. +/// +/// # Warning +/// On devices with a small amount of ram (think ~64k) watch out with the size +/// of there parameters. They will quickly use too much RAM. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Error type when initializing a new Wiznet device +#[derive(Debug)] +pub enum InitError { + /// The core frequency isn't 60 or 120MHz + CoreFrequencyInvalid, +} + +// Interrupt interacts with Runner through 3 event channels +static TX_CH: Channel = Channel::new(); +static RX_CH: Channel = Channel::new(); +static LINK_CH: Channel = Channel::new(); + +#[interrupt] +unsafe fn ETH() { + let mac = pac::ETH; + + mac.eie().modify(|w| { + w.set_intie(false); + }); + fence(Ordering::SeqCst); + let flags = mac.eir().read(); + mac.eir().write_value(flags); + + // reception + if flags.rxif() { + // println!(">"); + let sender = RX_CH.sender(); + if let Err(_) = sender.try_send(pac::ETH.erxln().read().0) { + // there is no room in the buffer for this packet, consider it lost and restart + // reception immediately with same buffer. Otherwise, reception is started from Runner. + mac.econ1().write(|w| { + w.set_rx_en(true); + }); + } + } + + // transmission done + if flags.txif() { + // println!("<"); + let sender = TX_CH.sender(); + let _ = sender.try_send(()); + } + + // link changed + if flags.linkif() { + // println!("="); + let sender = LINK_CH.sender(); + let _ = sender.try_send(()); + } + fence(Ordering::SeqCst); + mac.eie().modify(|w| { + w.set_intie(true); + }); + // println!("#"); +} + +// lock to the SMI control registers +static SMI_LOCK: Mutex = Mutex::new(()); + +/// Background runner for the driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d, P: PHY> { + mac: pac::eth::Eth, + phy: P, + ch: ch::Runner<'d, MTU>, + station_management: EthernetStationManagement, +} + +/// You must call this in a background task for the driver to operate. +impl<'d, P: PHY> Runner<'d, P> { + /// Run the driver. + pub async fn run(mut self) -> ! { + println!("ETH: Runner run"); + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let tx_receiver = TX_CH.receiver(); + let rx_receiver = RX_CH.receiver(); + let link_receiver = LINK_CH.receiver(); + + // TX and RX paths use entirely seperate registers and channels, which means that they can + // fully work in parallel + loop { + join( + join( + async { + loop { + let buf = rx_chan.rx_buf().await; + // start receive at buf + self.mac.mamxfl().write(|w| w.set_mamxfl(buf.len() as u16)); + self.mac.erxst().write(|w| w.set_erxst(buf.as_ptr() as u16)); + self.mac.macon1().write(|w| w.set_marxen(true)); + self.mac.econ1().write(|w| { + w.set_rx_en(true); + }); + rx_receiver.receive().await; + let len = self.mac.erxln().read().0; + println!("RX: {len} B @ 0x{:X}", buf.as_ptr() as usize); + // println!("RX: {len} B @ 0x{:X} {:?}", buf.as_ptr() as usize, &buf[..len as usize]); + rx_chan.rx_done(len as usize); + } + }, + async { + loop { + let buf = tx_chan.tx_buf().await; + // start send buf + let address: u16 = buf.as_ptr() as u16; + let len: u16 = buf.len() as u16; + println!("TX: {len} B @0X{address:X}"); + self.mac.etxst().write(|w| w.set_etxst(address)); + self.mac.etxln().write(|w| w.set_etxln(len)); + self.mac.econ1().modify(|w| { + w.set_tx_rts(true); // start transmit + }); + tx_receiver.receive().await; + tx_chan.tx_done(); + // println!("TX done"); + } + }, + ), + async { + loop { + println!("Link event: processing"); + link_receiver.receive().await; + if self.phy.link_up(&mut self.station_management) { + state_chan.set_link_state(LinkState::Up); + } else { + state_chan.set_link_state(LinkState::Down); + } + // println!("Link event done"); + } + }, + ) + .await; + } + } +} + +/// Get the factory-programmed MAC +/// +/// Each CH32 comes with an Unique ID which can be used as MAC address. +/// This returns the MAC bytes, in the same order as they are transmitted on the wires. +pub fn get_mac() -> [u8; 6] { + const ADDRESS: *const u8 = (0x1FFFF7E8 + 5) as *const u8; + let mut mac = [0u8; 6]; + unsafe { + let mac_bytes: &[u8] = core::slice::from_raw_parts(ADDRESS, 6); + mac.copy_from_slice(mac_bytes); + mac.reverse(); + }; + mac[0] = mac[0] & 0xFE; // FIXME! smoltcp thinks the MAC is multicast otherwise... + mac +} + +/// Create a CH32 10M MAC+PHY driver for [`embassy-net`](https://crates.io/crates/embassy-net). +/// +/// This returns two structs: +/// - a `Device` that you must pass to the `embassy-net` stack. +/// - a `Runner`. You must call `.run()` on it in a background task. +pub async fn new<'a, const N_RX: usize, const N_TX: usize, P: PHY>( + mac_addr: [u8; 6], + state: &'a mut State, + core_freq: u32, + phy: P, +) -> Result<(Device<'a>, Runner<'a, P>), InitError> { + let mac = pac::ETH; + let extend = pac::EXTEND; + let rcc = pac::RCC; + let station_management = EthernetStationManagement::new(); + + if core_freq == 60_000_000 { + rcc.cfgr0().modify(|w| w.set_ethpre(pac::rcc::vals::Ethpre::DIV1)); + } else if core_freq == 120_000_000 { + // divide core clk by 2 + rcc.cfgr0().modify(|w| w.set_ethpre(pac::rcc::vals::Ethpre::DIV2)); + } else { + return Err(InitError::CoreFrequencyInvalid); + } + extend.ctr().modify(|w| w.set_eth_10m_en(true)); // enable MAC+PHY clock + + mac.econ1().write(|w| { + w.set_rx_rst(true); // reset MAC's RX path + w.set_rx_rst(true); // reset MAC's TX path + }); + Timer::after_micros(1).await; + mac.econ1().write(|w| { + // no reset + w.set_rx_en(true); // receive enable + }); + Timer::after_micros(1).await; + mac.eir().write(|w| w.0 = 0xff); // clear all interrupt flags + mac.econ2().write(|w| { + w.set_tx(false); // enable MAC transmitter + w.set_rx_must(0b110); // reserved value, must be written 0b110 + }); + mac.macon2().modify(|w| { + w.set_padcfg(0b001); // short packet padding (0-64B) + w.set_txcrcen(true); // CRC insertion (4B) + w.set_fuldpx(true); // full fuplex + }); + mac.erxfcon().write(|w| { + w.set_en(false); // disable receive filtering + w.set_bcen(true); // receive broadcast + w.set_hten(true); // receive packets with matching hash + w.set_mcen(true); // receive multicast + w.set_mpen(true); // receive magic packet + w.set_ucen(true); // receive packets with matching MAC + w.set_crcen(true); // receive packets with bad CRC + }); + + mac.eie().write(|w| { + w.set_intie(true); // global interrupt enable + w.set_rxie(true); // receive interrupt + w.set_linkie(true); // link change interrupt + w.set_txie(true); // transmit done interrupt + w.set_r_en50(true); // enable 50 Ohms termination resistor on RX + }); + + // Set MAC address filter + mac.maadr0().write(|w| w.set_maadr(mac_addr[5])); + mac.maadr1().write(|w| w.set_maadr(mac_addr[4])); + mac.maadr2().write(|w| w.set_maadr(mac_addr[3])); + mac.maadr3().write(|w| w.set_maadr(mac_addr[2])); + mac.maadr4().write(|w| w.set_maadr(mac_addr[1])); + mac.maadr5().write(|w| w.set_maadr(mac_addr[0])); + + fence(Ordering::SeqCst); + unsafe { + qingke::pfic::enable_interrupt(pac::Interrupt::ETH as u8); + }; + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); + + Ok(( + device, + Runner { + ch: runner, + mac, + phy, + station_management, + }, + )) +} + +/// Ethernet SMI driver. +pub struct EthernetStationManagement { + peri: PhantomData, +} + +impl EthernetStationManagement { + pub fn new() -> Self { + Self { peri: PhantomData {} } + } +} + +unsafe impl StationManagement for EthernetStationManagement { + fn smi_read(&mut self, _phy_addr: u8, reg: u8) -> u16 { + let mac = T::regs(); + let _ = SMI_LOCK.lock(); + + mac.miregadr().write(|w| { + w.set_miregadr(reg & 0x1F); + }); + mac.mird().read().rd() + } + + fn smi_write(&mut self, _phy_addr: u8, reg: u8, val: u16) { + let mac = T::regs(); + let _ = SMI_LOCK.lock(); + + mac.miwr().write(|w| { + w.set_write(true); + w.set_mirdl(reg & 0x1F); + w.set_wr(val); + }); + } +} diff --git a/src/lib.rs b/src/lib.rs index 997da34..23ad87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,9 @@ pub mod usbhs; #[cfg(usbpd)] pub mod usbpd; +#[cfg(eth)] +pub mod eth; + #[cfg(feature = "embassy")] pub mod embassy; From 6c38e85e783422ccf2bd088696761c387f49ac7f Mon Sep 17 00:00:00 2001 From: Charles-Henri Mousset Date: Sun, 9 Mar 2025 09:41:48 +0100 Subject: [PATCH 2/4] [clnp] --- examples/ch32v208/Cargo.toml | 8 +- examples/ch32v208/src/bin/eth.rs | 68 +---- src/eth/_v1/mod.rs | 491 ------------------------------- src/eth/_v1/rx_desc.rs | 232 --------------- src/eth/_v1/tx_desc.rs | 171 ----------- 5 files changed, 9 insertions(+), 961 deletions(-) delete mode 100644 src/eth/_v1/mod.rs delete mode 100644 src/eth/_v1/rx_desc.rs delete mode 100644 src/eth/_v1/tx_desc.rs diff --git a/examples/ch32v208/Cargo.toml b/examples/ch32v208/Cargo.toml index 428fdcd..caffa5b 100644 --- a/examples/ch32v208/Cargo.toml +++ b/examples/ch32v208/Cargo.toml @@ -9,15 +9,12 @@ ch32-hal = { path = "../../", features = [ "memory-x", "embassy", "rt", - "time-driver-tim1", - "defmt", - # "time-driver-any", + "time-driver-any", ], default-features = false } embassy-executor = { version = "0.6.0", features = [ "integrated-timers", "arch-spin", "executor-thread", - # "task-arena-size-8192", ] } critical-section = "1.2.0" @@ -26,7 +23,7 @@ critical-section = "1.2.0" qingke-rt = "*" qingke = "*" -# panic-halt = "1.0" +panic-halt = "1.0" embassy-net = { version = "0.4.0", features = [ "proto-ipv4", @@ -39,7 +36,6 @@ static_cell = "2.1.0" portable-atomic = { version = "*", features = ["critical-section"] } embassy-futures = "0.1.1" embassy-time = "0.3" -heapless = "0.8.0" smoltcp = { version = "*", default-features = false, features = [ "proto-ipv4", "medium-ethernet", diff --git a/examples/ch32v208/src/bin/eth.rs b/examples/ch32v208/src/bin/eth.rs index f9efd3c..3dcf0ab 100644 --- a/examples/ch32v208/src/bin/eth.rs +++ b/examples/ch32v208/src/bin/eth.rs @@ -3,22 +3,18 @@ #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] -use ch32_hal as hal; -// use panic_halt as _; -use defmt::info; use embassy_executor::Spawner; use embassy_futures::yield_now; use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embassy_net::{self, IpAddress, Ipv4Address, Ipv4Cidr, Stack, StackResources, StaticConfigV4}; +use embassy_net::{self, Stack, StackResources}; use embassy_time::{Duration, Timer}; -use hal::delay::Delay; use hal::eth::generic_smi::GenericSMI; use hal::eth::{Device, EthernetStationManagement, Runner, State}; use hal::rcc::*; use hal::time::Hertz; use hal::{eth, interrupt, println}; -use heapless::Vec; use static_cell::StaticCell; +use {ch32_hal as hal, panic_halt as _}; #[embassy_executor::task] async fn ethernet_task(runner: Runner<'static, GenericSMI>) -> ! { @@ -32,24 +28,9 @@ async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } -#[embassy_executor::task] -async fn tick() { - loop { - Timer::after(Duration::from_millis(1000)).await; - println!("tick"); - } -} - -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - let _ = println!("\n\n\n{}", info); - - loop {} -} - #[embassy_executor::main(entry = "ch32_hal::entry")] async fn main(spawner: Spawner) -> ! { - // configure core speed at 120MHz + // configure core speed at 60MHz let _p = ch32_hal::init(ch32_hal::Config { rcc: ch32_hal::rcc::Config { hse: Some(Hse { @@ -75,34 +56,18 @@ async fn main(spawner: Spawner) -> ! { let core_freq: u32 = 60_000_000; ch32_hal::debug::SDIPrint::enable(); - Timer::after(Duration::from_millis(1000)).await; // let some time to the debug interface to start + Timer::after(Duration::from_millis(100)).await; // let some time to the debug interface to start println!("Hello CH32!"); - let ret = spawner.spawn(tick()).unwrap(); - println!("tick task: {ret:?}"); - - info!("plop"); - - // loop { - // Timer::after(Duration::from_millis(1000)).await; - // println!("tick2"); - // } // Ethernet setup let mac_addr = eth::get_mac(); println!("mac_addr: {mac_addr:?}"); static STATE: StaticCell> = StaticCell::new(); - println!("STATE: done"); let state = STATE.init_with(|| State::<2, 2>::new()); - println!("state: done"); let phy = GenericSMI::new(0); - println!("phy: done"); let (device, runner) = hal::eth::new(mac_addr, state, core_freq, phy).await.unwrap(); - println!("device: done"); - // println!("runner: {runner:?}"); - let station_management: EthernetStationManagement = EthernetStationManagement::new(); - println!("station_management: done"); - let ret = spawner.spawn(ethernet_task(runner)); - println!("eth task: {ret:?}"); + let _station_management: EthernetStationManagement = EthernetStationManagement::new(); + spawner.spawn(ethernet_task(runner)).unwrap(); // Generate random seed let seed = 0xdeadbeef_abadbabe; @@ -118,32 +83,13 @@ async fn main(spawner: Spawner) -> ! { )); // Launch network task - let ret = spawner.spawn(net_task(&stack)); - println!("net task: {ret:?}"); - - // loop { - // Timer::after(Duration::from_millis(1000)).await; - // // println!("tick"); - // } + spawner.spawn(net_task(&stack)).unwrap(); println!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; let local_addr = cfg.address.address(); println!("IP address: {:?}", local_addr); - // stack.set_config_v4(embassy_net::ConfigV4::Static(StaticConfigV4 { - // address: Ipv4Cidr::new(Ipv4Address { 0: [192, 168, 1, 10] }, 24), - // /// Default gateway. - // gateway: None, - // /// DNS servers. - // dns_servers: Vec::new(), - // })); - - // FIXME! - // RX packets either aren't received by the stack, or smoltcp (silently) discards them for some - // reason. It could be worth checking what smoltcp expects as packet format, and also memory - // content - // } // Then we can use it! let mut rx_buffer = [0; 300]; let mut tx_buffer = [0; 300]; diff --git a/src/eth/_v1/mod.rs b/src/eth/_v1/mod.rs deleted file mode 100644 index 2525c7b..0000000 --- a/src/eth/_v1/mod.rs +++ /dev/null @@ -1,491 +0,0 @@ -// The v1c ethernet driver was ported to embassy from the awesome stm32-eth project (https://github.com/stm32-rs/stm32-eth). - -mod rx_desc; -mod tx_desc; - -use core::marker::PhantomData; -use core::mem::MaybeUninit; -use core::sync::atomic::{fence, Ordering}; -use core::task::Context; - -use embassy_hal_internal::{into_ref, PeripheralRef}; -use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; -use embassy_sync::waitqueue::AtomicWaker; -use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; - -pub(crate) use self::rx_desc::{RDes, RDesRing}; -pub(crate) use self::tx_desc::{TDes, TDesRing}; -use super::*; -#[cfg(eth_v1a)] -use crate::gpio::Pull; -use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; -use crate::interrupt::InterruptExt; -#[cfg(eth_v1a)] -use crate::pac::AFIO; -#[cfg(any(eth_v1b, eth_v1c))] -use crate::pac::SYSCFG; -use crate::pac::{ETH, RCC}; -use crate::rcc::SealedRccPeripheral; -use crate::{interrupt, Peripheral}; - -const TX_BUFFER_SIZE: usize = 1514; -const RX_BUFFER_SIZE: usize = 1536; - -#[repr(C, align(8))] -#[derive(Copy, Clone)] -pub(crate) struct Packet([u8; N]); - -/// Ethernet packet queue. -/// -/// This struct owns the memory used for reading and writing packets. -/// -/// `TX` is the number of packets in the transmit queue, `RX` in the receive -/// queue. A bigger queue allows the hardware to receive more packets while the -/// CPU is busy doing other things, which may increase performance (especially for RX) -/// at the cost of more RAM usage. -pub struct PacketQueue { - tx_desc: [TDes; TX], - rx_desc: [RDes; RX], - tx_buf: [Packet; TX], - rx_buf: [Packet; RX], -} - -impl PacketQueue { - /// Create a new packet queue. - pub const fn new() -> Self { - Self { - tx_desc: [const { TDes::new() }; TX], - rx_desc: [const { RDes::new() }; RX], - tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], - rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], - } - } - - /// Initialize a packet queue in-place. - /// - /// This can be helpful to avoid accidentally stack-allocating the packet queue in the stack. The - /// Rust compiler can sometimes be a bit dumb when working with large owned values: if you call `new()` - /// and then store the returned PacketQueue in its final place (like a `static`), the compiler might - /// place it temporarily on the stack then move it. Since this struct is quite big, it may result - /// in a stack overflow. - /// - /// With this function, you can create an uninitialized `static` with type `MaybeUninit>` - /// and initialize it in-place, guaranteeing no stack usage. - /// - /// After calling this function, calling `assume_init` on the MaybeUninit is guaranteed safe. - pub fn init(this: &mut MaybeUninit) { - unsafe { - this.as_mut_ptr().write_bytes(0u8, 1); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); - -impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { - type RxToken<'a> - = RxToken<'a, 'd> - where - Self: 'a; - type TxToken<'a> - = TxToken<'a, 'd> - where - Self: 'a; - - fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - WAKER.register(cx.waker()); - if self.rx.available().is_some() && self.tx.available().is_some() { - Some((RxToken { rx: &mut self.rx }, TxToken { tx: &mut self.tx })) - } else { - None - } - } - - fn transmit(&mut self, cx: &mut Context) -> Option> { - WAKER.register(cx.waker()); - if self.tx.available().is_some() { - Some(TxToken { tx: &mut self.tx }) - } else { - None - } - } - - fn capabilities(&self) -> Capabilities { - let mut caps = Capabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(self.tx.len()); - caps - } - - fn link_state(&mut self, cx: &mut Context) -> LinkState { - if self.phy.poll_link(&mut self.station_management, cx) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn hardware_address(&self) -> HardwareAddress { - HardwareAddress::Ethernet(self.mac_addr) - } -} - -/// `embassy-net` RX token. -pub struct RxToken<'a, 'd> { - rx: &'a mut RDesRing<'d>, -} - -impl<'a, 'd> embassy_net_driver::RxToken for RxToken<'a, 'd> { - fn consume(self, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - // NOTE(unwrap): we checked the queue wasn't full when creating the token. - let pkt = unwrap!(self.rx.available()); - let r = f(pkt); - self.rx.pop_packet(); - r - } -} - -/// `embassy-net` TX token. -pub struct TxToken<'a, 'd> { - tx: &'a mut TDesRing<'d>, -} - -impl<'a, 'd> embassy_net_driver::TxToken for TxToken<'a, 'd> { - fn consume(self, len: usize, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - // NOTE(unwrap): we checked the queue wasn't full when creating the token. - let pkt = unwrap!(self.tx.available()); - let r = f(&mut pkt[..len]); - self.tx.transmit(len); - r - } -} - -/// Interrupt handler. -pub struct InterruptHandler {} - -impl interrupt::typelevel::Handler for InterruptHandler { - unsafe fn on_interrupt() { - WAKER.wake(); - - // TODO: Check and clear more flags - let dma = ETH.ethernet_dma(); - - dma.dmasr().modify(|w| { - w.set_ts(true); - w.set_rs(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmasr().read(); - dma.dmasr().read(); - } -} - -/// Ethernet driver. -pub struct Ethernet<'d, T: Instance, P: PHY> { - _peri: PeripheralRef<'d, T>, - pub(crate) tx: TDesRing<'d>, - pub(crate) rx: RDesRing<'d>, - - pins: [PeripheralRef<'d, AnyPin>; 9], - pub(crate) phy: P, - pub(crate) station_management: EthernetStationManagement, - pub(crate) mac_addr: [u8; 6], -} - -#[cfg(eth_v1a)] -macro_rules! config_in_pins { - ($($pin:ident),*) => { - critical_section::with(|_| { - $( - // TODO properly create a set_as_input function - $pin.set_as_af($pin.af_num(), AfType::input(Pull::None)); - )* - }) - } -} - -#[cfg(eth_v1a)] -macro_rules! config_af_pins { - ($($pin:ident),*) => { - critical_section::with(|_| { - $( - $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - )* - }) - }; -} - -#[cfg(any(eth_v1b, eth_v1c))] -macro_rules! config_pins { - ($($pin:ident),*) => { - critical_section::with(|_| { - $( - $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - )* - }) - }; -} - -impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { - /// safety: the returned instance is not leak-safe - pub fn new( - queue: &'d mut PacketQueue, - peri: impl Peripheral

+ 'd, - _irq: impl interrupt::typelevel::Binding + 'd, - ref_clk: impl Peripheral

> + 'd, - mdio: impl Peripheral

> + 'd, - mdc: impl Peripheral

> + 'd, - crs: impl Peripheral

> + 'd, - rx_d0: impl Peripheral

> + 'd, - rx_d1: impl Peripheral

> + 'd, - tx_d0: impl Peripheral

> + 'd, - tx_d1: impl Peripheral

> + 'd, - tx_en: impl Peripheral

> + 'd, - phy: P, - mac_addr: [u8; 6], - ) -> Self { - into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - - // Enable the necessary Clocks - #[cfg(eth_v1a)] - critical_section::with(|_| { - RCC.apb2enr().modify(|w| w.set_afioen(true)); - - // Select RMII (Reduced Media Independent Interface) - // Must be done prior to enabling peripheral clock - AFIO.mapr().modify(|w| w.set_mii_rmii_sel(true)); - - RCC.ahbenr().modify(|w| { - w.set_ethen(true); - w.set_ethtxen(true); - w.set_ethrxen(true); - }); - }); - - #[cfg(any(eth_v1b, eth_v1c))] - critical_section::with(|_| { - RCC.ahb1enr().modify(|w| { - w.set_ethen(true); - w.set_ethtxen(true); - w.set_ethrxen(true); - }); - - // RMII (Reduced Media Independent Interface) - SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(true)); - }); - - #[cfg(eth_v1a)] - { - config_in_pins!(ref_clk, rx_d0, rx_d1); - config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_en); - } - - #[cfg(any(eth_v1b, eth_v1c))] - config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - - let dma = T::regs().ethernet_dma(); - let mac = T::regs().ethernet_mac(); - - // Reset and wait - dma.dmabmr().modify(|w| w.set_sr(true)); - while dma.dmabmr().read().sr() {} - - mac.maccr().modify(|w| { - w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times - w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping - w.set_fes(Fes::FES100); // fast ethernet speed - w.set_dm(Dm::FULL_DUPLEX); // full duplex - // TODO: Carrier sense ? ECRSFD - }); - - // Set the mac to pass all multicast packets - mac.macffr().modify(|w| { - w.set_pam(true); - }); - - // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, - // so the LR write must happen after the HR write. - mac.maca0hr() - .modify(|w| w.set_maca0h(u16::from(mac_addr[4]) | (u16::from(mac_addr[5]) << 8))); - mac.maca0lr().write(|w| { - w.set_maca0l( - u32::from(mac_addr[0]) - | (u32::from(mac_addr[1]) << 8) - | (u32::from(mac_addr[2]) << 16) - | (u32::from(mac_addr[3]) << 24), - ) - }); - - // pause time - mac.macfcr().modify(|w| w.set_pt(0x100)); - - // Transfer and Forward, Receive and Forward - dma.dmaomr().modify(|w| { - w.set_tsf(Tsf::STORE_FORWARD); - w.set_rsf(Rsf::STORE_FORWARD); - }); - - dma.dmabmr().modify(|w| { - w.set_pbl(Pbl::PBL32) // programmable burst length - 32 ? - }); - - // TODO MTU size setting not found for v1 ethernet, check if correct - - let hclk = ::frequency(); - let hclk_mhz = hclk.0 / 1_000_000; - - // Set the MDC clock frequency in the range 1MHz - 2.5MHz - let clock_range = match hclk_mhz { - 0..=24 => panic!("Invalid HCLK frequency - should be at least 25 MHz."), - 25..=34 => Cr::CR_20_35, // Divide by 16 - 35..=59 => Cr::CR_35_60, // Divide by 26 - 60..=99 => Cr::CR_60_100, // Divide by 42 - 100..=149 => Cr::CR_100_150, // Divide by 62 - 150..=216 => Cr::CR_150_168, // Divide by 102 - _ => { - panic!("HCLK results in MDC clock > 2.5MHz even for the highest CSR clock divider") - } - }; - - let pins = [ - ref_clk.map_into(), - mdio.map_into(), - mdc.map_into(), - crs.map_into(), - rx_d0.map_into(), - rx_d1.map_into(), - tx_d0.map_into(), - tx_d1.map_into(), - tx_en.map_into(), - ]; - - let mut this = Self { - _peri: peri, - pins, - phy: phy, - station_management: EthernetStationManagement { - peri: PhantomData, - clock_range: clock_range, - }, - mac_addr, - tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), - rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), - }; - - fence(Ordering::SeqCst); - - let mac = T::regs().ethernet_mac(); - let dma = T::regs().ethernet_dma(); - - mac.maccr().modify(|w| { - w.set_re(true); - w.set_te(true); - }); - dma.dmaomr().modify(|w| { - w.set_ftf(Ftf::FLUSH); // flush transmit fifo (queue) - w.set_st(St::STARTED); // start transmitting channel - w.set_sr(DmaomrSr::STARTED); // start receiving channel - }); - - this.rx.demand_poll(); - - // Enable interrupts - dma.dmaier().modify(|w| { - w.set_nise(true); - w.set_rie(true); - w.set_tie(true); - }); - - this.phy.phy_reset(&mut this.station_management); - this.phy.phy_init(&mut this.station_management); - - interrupt::ETH.unpend(); - unsafe { interrupt::ETH.enable() }; - - this - } -} - -/// Ethernet station management interface. -pub struct EthernetStationManagement { - peri: PhantomData, - clock_range: Cr, -} - -unsafe impl StationManagement for EthernetStationManagement { - fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16 { - let mac = T::regs().ethernet_mac(); - - mac.macmiiar().modify(|w| { - w.set_pa(phy_addr); - w.set_mr(reg); - w.set_mw(Mw::READ); // read operation - w.set_cr(self.clock_range); - w.set_mb(MbProgress::BUSY); // indicate that operation is in progress - }); - while mac.macmiiar().read().mb() == MbProgress::BUSY {} - mac.macmiidr().read().md() - } - - fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16) { - let mac = T::regs().ethernet_mac(); - - mac.macmiidr().write(|w| w.set_md(val)); - mac.macmiiar().modify(|w| { - w.set_pa(phy_addr); - w.set_mr(reg); - w.set_mw(Mw::WRITE); // write - w.set_cr(self.clock_range); - w.set_mb(MbProgress::BUSY); - }); - while mac.macmiiar().read().mb() == MbProgress::BUSY {} - } -} - -impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { - fn drop(&mut self) { - let dma = T::regs().ethernet_dma(); - let mac = T::regs().ethernet_mac(); - - // Disable the TX DMA and wait for any previous transmissions to be completed - dma.dmaomr().modify(|w| w.set_st(St::STOPPED)); - - // Disable MAC transmitter and receiver - mac.maccr().modify(|w| { - w.set_re(false); - w.set_te(false); - }); - - dma.dmaomr().modify(|w| w.set_sr(DmaomrSr::STOPPED)); - - critical_section::with(|_| { - for pin in self.pins.iter_mut() { - pin.set_as_disconnected(); - } - }) - } -} - -pin_trait!(RXClkPin, Instance); -pin_trait!(TXClkPin, Instance); -pin_trait!(RefClkPin, Instance); -pin_trait!(MDIOPin, Instance); -pin_trait!(MDCPin, Instance); -pin_trait!(RXDVPin, Instance); -pin_trait!(CRSPin, Instance); -pin_trait!(RXD0Pin, Instance); -pin_trait!(RXD1Pin, Instance); -pin_trait!(RXD2Pin, Instance); -pin_trait!(RXD3Pin, Instance); -pin_trait!(TXD0Pin, Instance); -pin_trait!(TXD1Pin, Instance); -pin_trait!(TXD2Pin, Instance); -pin_trait!(TXD3Pin, Instance); -pin_trait!(TXEnPin, Instance); diff --git a/src/eth/_v1/rx_desc.rs b/src/eth/_v1/rx_desc.rs deleted file mode 100644 index 2a46c18..0000000 --- a/src/eth/_v1/rx_desc.rs +++ /dev/null @@ -1,232 +0,0 @@ -use core::sync::atomic::{compiler_fence, fence, Ordering}; - -use stm32_metapac::eth::vals::{Rpd, Rps}; -use vcell::VolatileCell; - -use crate::eth::RX_BUFFER_SIZE; -use crate::pac::ETH; - -mod rx_consts { - /// Owned by DMA engine - pub const RXDESC_0_OWN: u32 = 1 << 31; - /// First descriptor - pub const RXDESC_0_FS: u32 = 1 << 9; - /// Last descriptor - pub const RXDESC_0_LS: u32 = 1 << 8; - /// Error summary - pub const RXDESC_0_ES: u32 = 1 << 15; - /// Frame length - pub const RXDESC_0_FL_MASK: u32 = 0x3FFF; - pub const RXDESC_0_FL_SHIFT: usize = 16; - - pub const RXDESC_1_RBS_MASK: u32 = 0x0fff; - /// Second address chained - pub const RXDESC_1_RCH: u32 = 1 << 14; - /// End Of Ring - pub const RXDESC_1_RER: u32 = 1 << 15; -} - -use rx_consts::*; - -use super::Packet; - -/// Receive Descriptor representation -/// -/// * rdes0: OWN and Status -/// * rdes1: allocated buffer length -/// * rdes2: data buffer address -/// * rdes3: next descriptor address -#[repr(C)] -pub(crate) struct RDes { - rdes0: VolatileCell, - rdes1: VolatileCell, - rdes2: VolatileCell, - rdes3: VolatileCell, -} - -impl RDes { - pub const fn new() -> Self { - Self { - rdes0: VolatileCell::new(0), - rdes1: VolatileCell::new(0), - rdes2: VolatileCell::new(0), - rdes3: VolatileCell::new(0), - } - } - - /// Return true if this RDes is acceptable to us - #[inline(always)] - fn valid(&self) -> bool { - // Write-back descriptor is valid if: - // - // Contains first buffer of packet AND contains last buf of - // packet AND no errors - (self.rdes0.get() & (RXDESC_0_ES | RXDESC_0_FS | RXDESC_0_LS)) == (RXDESC_0_FS | RXDESC_0_LS) - } - - /// Return true if this RDes is not currently owned by the DMA - #[inline(always)] - fn available(&self) -> bool { - self.rdes0.get() & RXDESC_0_OWN == 0 // Owned by us - } - - /// Configures the reception buffer address and length and passed descriptor ownership to the DMA - #[inline(always)] - fn set_ready(&self, buf: *mut u8) { - self.rdes1 - .set(self.rdes1.get() | (RX_BUFFER_SIZE as u32) & RXDESC_1_RBS_MASK); - self.rdes2.set(buf as u32); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - compiler_fence(Ordering::Release); - - self.rdes0.set(self.rdes0.get() | RXDESC_0_OWN); - - // Used to flush the store buffer as fast as possible to make the buffer available for the - // DMA. - fence(Ordering::SeqCst); - } - - // points to next descriptor (RCH) - #[inline(always)] - fn set_buffer2(&self, buffer: *const u8) { - self.rdes3.set(buffer as u32); - } - - #[inline(always)] - fn set_end_of_ring(&self) { - self.rdes1.set(self.rdes1.get() | RXDESC_1_RER); - } - - #[inline(always)] - fn packet_len(&self) -> usize { - ((self.rdes0.get() >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize - } - - fn setup(&self, next: Option<&Self>, buf: *mut u8) { - // Defer this initialization to this function, so we can have `RingEntry` on bss. - self.rdes1.set(self.rdes1.get() | RXDESC_1_RCH); - - match next { - Some(next) => self.set_buffer2(next as *const _ as *const u8), - None => { - self.set_buffer2(0 as *const u8); - self.set_end_of_ring(); - } - } - - self.set_ready(buf); - } -} - -/// Running state of the `RxRing` -#[derive(PartialEq, Eq, Debug)] -pub enum RunningState { - Unknown, - Stopped, - Running, -} - -/// Rx ring of descriptors and packets -pub(crate) struct RDesRing<'a> { - descriptors: &'a mut [RDes], - buffers: &'a mut [Packet], - index: usize, -} - -impl<'a> RDesRing<'a> { - pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { - assert!(descriptors.len() > 1); - assert!(descriptors.len() == buffers.len()); - - for (i, entry) in descriptors.iter().enumerate() { - entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr()); - } - - // Register rx descriptor start - ETH.ethernet_dma() - .dmardlar() - .write(|w| w.0 = descriptors.as_ptr() as u32); - // We already have fences in `set_owned`, which is called in `setup` - - Self { - descriptors, - buffers, - index: 0, - } - } - - pub(crate) fn demand_poll(&self) { - ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)); - } - - /// Get current `RunningState` - fn running_state(&self) -> RunningState { - match ETH.ethernet_dma().dmasr().read().rps() { - // Reset or Stop Receive Command issued - Rps::STOPPED => RunningState::Stopped, - // Fetching receive transfer descriptor - Rps::RUNNING_FETCHING => RunningState::Running, - // Waiting for receive packet - Rps::RUNNING_WAITING => RunningState::Running, - // Receive descriptor unavailable - Rps::SUSPENDED => RunningState::Stopped, - // Closing receive descriptor - Rps::_RESERVED_5 => RunningState::Running, - // Transferring the receive packet data from receive buffer to host memory - Rps::RUNNING_WRITING => RunningState::Running, - _ => RunningState::Unknown, - } - } - - /// Get a received packet if any, or None. - pub(crate) fn available(&mut self) -> Option<&mut [u8]> { - if self.running_state() != RunningState::Running { - self.demand_poll(); - } - - // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using - // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the - // buffer (I think .-.) - fence(Ordering::SeqCst); - - // We might have to process many packets, in case some have been rx'd but are invalid. - loop { - let descriptor = &mut self.descriptors[self.index]; - if !descriptor.available() { - return None; - } - - // If packet is invalid, pop it and try again. - if !descriptor.valid() { - warn!("invalid packet: {:08x}", descriptor.rdes0.get()); - self.pop_packet(); - continue; - } - - break; - } - - let descriptor = &mut self.descriptors[self.index]; - let len = descriptor.packet_len(); - return Some(&mut self.buffers[self.index].0[..len]); - } - - /// Pop the packet previously returned by `available`. - pub(crate) fn pop_packet(&mut self) { - let descriptor = &mut self.descriptors[self.index]; - assert!(descriptor.available()); - - self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); - - self.demand_poll(); - - // Increment index. - self.index += 1; - if self.index == self.descriptors.len() { - self.index = 0 - } - } -} diff --git a/src/eth/_v1/tx_desc.rs b/src/eth/_v1/tx_desc.rs deleted file mode 100644 index 1317d20..0000000 --- a/src/eth/_v1/tx_desc.rs +++ /dev/null @@ -1,171 +0,0 @@ -use core::sync::atomic::{compiler_fence, fence, Ordering}; - -use vcell::VolatileCell; - -use crate::eth::TX_BUFFER_SIZE; -use crate::pac::ETH; - -/// Transmit and Receive Descriptor fields -#[allow(dead_code)] -mod tx_consts { - pub const TXDESC_0_OWN: u32 = 1 << 31; - pub const TXDESC_0_IOC: u32 = 1 << 30; - // First segment of frame - pub const TXDESC_0_FS: u32 = 1 << 28; - // Last segment of frame - pub const TXDESC_0_LS: u32 = 1 << 29; - // Transmit end of ring - pub const TXDESC_0_TER: u32 = 1 << 21; - // Second address chained - pub const TXDESC_0_TCH: u32 = 1 << 20; - // Error status - pub const TXDESC_0_ES: u32 = 1 << 15; - - // Transmit buffer size - pub const TXDESC_1_TBS_SHIFT: usize = 0; - pub const TXDESC_1_TBS_MASK: u32 = 0x0fff << TXDESC_1_TBS_SHIFT; -} -use tx_consts::*; - -use super::Packet; - -/// Transmit Descriptor representation -/// -/// * tdes0: control -/// * tdes1: buffer lengths -/// * tdes2: data buffer address -/// * tdes3: next descriptor address -#[repr(C)] -pub(crate) struct TDes { - tdes0: VolatileCell, - tdes1: VolatileCell, - tdes2: VolatileCell, - tdes3: VolatileCell, -} - -impl TDes { - pub const fn new() -> Self { - Self { - tdes0: VolatileCell::new(0), - tdes1: VolatileCell::new(0), - tdes2: VolatileCell::new(0), - tdes3: VolatileCell::new(0), - } - } - - /// Return true if this TDes is not currently owned by the DMA - fn available(&self) -> bool { - (self.tdes0.get() & TXDESC_0_OWN) == 0 - } - - /// Pass ownership to the DMA engine - fn set_owned(&mut self) { - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - compiler_fence(Ordering::Release); - self.tdes0.set(self.tdes0.get() | TXDESC_0_OWN); - - // Used to flush the store buffer as fast as possible to make the buffer available for the - // DMA. - fence(Ordering::SeqCst); - } - - fn set_buffer1(&self, buffer: *const u8) { - self.tdes2.set(buffer as u32); - } - - fn set_buffer1_len(&self, len: usize) { - self.tdes1 - .set((self.tdes1.get() & !TXDESC_1_TBS_MASK) | ((len as u32) << TXDESC_1_TBS_SHIFT)); - } - - // points to next descriptor (RCH) - fn set_buffer2(&self, buffer: *const u8) { - self.tdes3.set(buffer as u32); - } - - fn set_end_of_ring(&self) { - self.tdes0.set(self.tdes0.get() | TXDESC_0_TER); - } - - // set up as a part fo the ring buffer - configures the tdes - fn setup(&self, next: Option<&Self>) { - // Defer this initialization to this function, so we can have `RingEntry` on bss. - self.tdes0.set(TXDESC_0_TCH | TXDESC_0_IOC | TXDESC_0_FS | TXDESC_0_LS); - match next { - Some(next) => self.set_buffer2(next as *const TDes as *const u8), - None => { - self.set_buffer2(0 as *const u8); - self.set_end_of_ring(); - } - } - } -} - -pub(crate) struct TDesRing<'a> { - descriptors: &'a mut [TDes], - buffers: &'a mut [Packet], - index: usize, -} - -impl<'a> TDesRing<'a> { - /// Initialise this TDesRing. Assume TDesRing is corrupt - pub(crate) fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { - assert!(descriptors.len() > 0); - assert!(descriptors.len() == buffers.len()); - - for (i, entry) in descriptors.iter().enumerate() { - entry.setup(descriptors.get(i + 1)); - } - - // Register txdescriptor start - ETH.ethernet_dma() - .dmatdlar() - .write(|w| w.0 = descriptors.as_ptr() as u32); - - Self { - descriptors, - buffers, - index: 0, - } - } - - pub(crate) fn len(&self) -> usize { - self.descriptors.len() - } - - /// Return the next available packet buffer for transmitting, or None - pub(crate) fn available(&mut self) -> Option<&mut [u8]> { - let descriptor = &mut self.descriptors[self.index]; - if descriptor.available() { - Some(&mut self.buffers[self.index].0) - } else { - None - } - } - - /// Transmit the packet written in a buffer returned by `available`. - pub(crate) fn transmit(&mut self, len: usize) { - let descriptor = &mut self.descriptors[self.index]; - assert!(descriptor.available()); - - descriptor.set_buffer1(self.buffers[self.index].0.as_ptr()); - descriptor.set_buffer1_len(len); - - descriptor.set_owned(); - - // Ensure changes to the descriptor are committed before DMA engine sees tail pointer store. - // This will generate an DMB instruction. - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - // Move the index to the next descriptor - self.index += 1; - if self.index == self.descriptors.len() { - self.index = 0 - } - // Request the DMA engine to poll the latest tx descriptor - ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) - } -} From d9469a473a206cfcd99c1ac12216db7e6f43ce0a Mon Sep 17 00:00:00 2001 From: Charles-Henri Mousset Date: Wed, 26 Mar 2025 08:09:14 +0100 Subject: [PATCH 3/4] [clnp] removed dbg print --- src/eth/v10m/mod.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/eth/v10m/mod.rs b/src/eth/v10m/mod.rs index abcb4df..2a7cb4f 100644 --- a/src/eth/v10m/mod.rs +++ b/src/eth/v10m/mod.rs @@ -13,7 +13,7 @@ use embassy_time::Timer; use {ch32_metapac as pac, embassy_net_driver_channel as ch}; use crate::eth::{Instance, StationManagement, PHY}; -use crate::{interrupt, println}; +use crate::interrupt; // If you change this update the docs of State const MTU: usize = 1514; @@ -69,7 +69,6 @@ unsafe fn ETH() { // reception if flags.rxif() { - // println!(">"); let sender = RX_CH.sender(); if let Err(_) = sender.try_send(pac::ETH.erxln().read().0) { // there is no room in the buffer for this packet, consider it lost and restart @@ -82,14 +81,12 @@ unsafe fn ETH() { // transmission done if flags.txif() { - // println!("<"); let sender = TX_CH.sender(); let _ = sender.try_send(()); } // link changed if flags.linkif() { - // println!("="); let sender = LINK_CH.sender(); let _ = sender.try_send(()); } @@ -97,7 +94,6 @@ unsafe fn ETH() { mac.eie().modify(|w| { w.set_intie(true); }); - // println!("#"); } // lock to the SMI control registers @@ -117,7 +113,6 @@ pub struct Runner<'d, P: PHY> { impl<'d, P: PHY> Runner<'d, P> { /// Run the driver. pub async fn run(mut self) -> ! { - println!("ETH: Runner run"); let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); let tx_receiver = TX_CH.receiver(); let rx_receiver = RX_CH.receiver(); @@ -140,8 +135,6 @@ impl<'d, P: PHY> Runner<'d, P> { }); rx_receiver.receive().await; let len = self.mac.erxln().read().0; - println!("RX: {len} B @ 0x{:X}", buf.as_ptr() as usize); - // println!("RX: {len} B @ 0x{:X} {:?}", buf.as_ptr() as usize, &buf[..len as usize]); rx_chan.rx_done(len as usize); } }, @@ -151,7 +144,6 @@ impl<'d, P: PHY> Runner<'d, P> { // start send buf let address: u16 = buf.as_ptr() as u16; let len: u16 = buf.len() as u16; - println!("TX: {len} B @0X{address:X}"); self.mac.etxst().write(|w| w.set_etxst(address)); self.mac.etxln().write(|w| w.set_etxln(len)); self.mac.econ1().modify(|w| { @@ -159,20 +151,17 @@ impl<'d, P: PHY> Runner<'d, P> { }); tx_receiver.receive().await; tx_chan.tx_done(); - // println!("TX done"); } }, ), async { loop { - println!("Link event: processing"); link_receiver.receive().await; if self.phy.link_up(&mut self.station_management) { state_chan.set_link_state(LinkState::Up); } else { state_chan.set_link_state(LinkState::Down); } - // println!("Link event done"); } }, ) From cc645bcc37b3143b307d3506baac810043c5d9da Mon Sep 17 00:00:00 2001 From: Charles-Henri Mousset Date: Wed, 26 Mar 2025 08:43:13 +0100 Subject: [PATCH 4/4] [fix] --- examples/ch32v208/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ch32v208/Cargo.toml b/examples/ch32v208/Cargo.toml index caffa5b..410a3b3 100644 --- a/examples/ch32v208/Cargo.toml +++ b/examples/ch32v208/Cargo.toml @@ -9,7 +9,7 @@ ch32-hal = { path = "../../", features = [ "memory-x", "embassy", "rt", - "time-driver-any", + "time-driver-tim1", ], default-features = false } embassy-executor = { version = "0.6.0", features = [ "integrated-timers", @@ -37,6 +37,7 @@ portable-atomic = { version = "*", features = ["critical-section"] } embassy-futures = "0.1.1" embassy-time = "0.3" smoltcp = { version = "*", default-features = false, features = [ + "socket-udp", "proto-ipv4", "medium-ethernet", "log",