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..410a3b3 100644 --- a/examples/ch32v208/Cargo.toml +++ b/examples/ch32v208/Cargo.toml @@ -9,13 +9,15 @@ ch32-hal = { path = "../../", features = [ "memory-x", "embassy", "rt", + "time-driver-tim1", ], default-features = false } embassy-executor = { version = "0.6.0", features = [ "integrated-timers", - "arch-riscv32", + "arch-spin", "executor-thread", ] } -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 = "*" @@ -23,6 +25,25 @@ qingke = "*" 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" +smoltcp = { version = "*", default-features = false, features = [ + "socket-udp", + "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. lto = true diff --git a/examples/ch32v208/src/bin/eth.rs b/examples/ch32v208/src/bin/eth.rs new file mode 100644 index 0000000..3dcf0ab --- /dev/null +++ b/examples/ch32v208/src/bin/eth.rs @@ -0,0 +1,120 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{self, Stack, StackResources}; +use embassy_time::{Duration, Timer}; +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 static_cell::StaticCell; +use {ch32_hal as hal, panic_halt as _}; + +#[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::main(entry = "ch32_hal::entry")] +async fn main(spawner: Spawner) -> ! { + // configure core speed at 60MHz + 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(100)).await; // let some time to the debug interface to start + println!("Hello CH32!"); + + // Ethernet setup + let mac_addr = eth::get_mac(); + println!("mac_addr: {mac_addr:?}"); + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init_with(|| State::<2, 2>::new()); + let phy = GenericSMI::new(0); + let (device, runner) = hal::eth::new(mac_addr, state, core_freq, phy).await.unwrap(); + let _station_management: EthernetStationManagement = EthernetStationManagement::new(); + spawner.spawn(ethernet_task(runner)).unwrap(); + + // 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 + 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); + + // 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/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..2a7cb4f --- /dev/null +++ b/src/eth/v10m/mod.rs @@ -0,0 +1,311 @@ +#![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; + +// 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() { + 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() { + let sender = TX_CH.sender(); + let _ = sender.try_send(()); + } + + // link changed + if flags.linkif() { + let sender = LINK_CH.sender(); + let _ = sender.try_send(()); + } + fence(Ordering::SeqCst); + mac.eie().modify(|w| { + w.set_intie(true); + }); +} + +// 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) -> ! { + 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; + 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; + 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(); + } + }, + ), + async { + loop { + 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); + } + } + }, + ) + .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;