diff --git a/CHANGELOG.md b/CHANGELOG.md index 83571ebf..abf97140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [GMAC API] + +### Added +- API for Gmac Peripheral + ## [Unreleased] ### Added diff --git a/boards/atsame70_xpro/Cargo.lock b/boards/atsame70_xpro/Cargo.lock index 38f9eb43..3aa5c3ed 100644 --- a/boards/atsame70_xpro/Cargo.lock +++ b/boards/atsame70_xpro/Cargo.lock @@ -31,6 +31,7 @@ dependencies = [ "panic-halt", "panic-rtt-target", "rtt-target", + "smoltcp", "usbd-serial", ] @@ -47,7 +48,7 @@ dependencies = [ name = "atsamx7x-hal" version = "0.3.0" dependencies = [ - "atsame70q21b", + "atsame70n21b", "bit-iter", "cfg-if", "cortex-m", @@ -57,6 +58,7 @@ dependencies = [ "paste", "rtic-monotonic", "strum", + "smoltcp", "usb-device", "void", ] @@ -100,6 +102,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "byteorder" version = "1.4.3" @@ -272,6 +280,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "memchr" version = "2.5.0" @@ -480,6 +494,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "smoltcp" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72165c4af59f5f19c7fb774b88b95660591b612380305b5f4503157341a9f7ee" +dependencies = [ + "bitflags", + "byteorder", + "managed", +] + [[package]] name = "spin" version = "0.9.3" diff --git a/boards/atsame70_xpro/Cargo.toml b/boards/atsame70_xpro/Cargo.toml index ffd51356..31e63269 100644 --- a/boards/atsame70_xpro/Cargo.toml +++ b/boards/atsame70_xpro/Cargo.toml @@ -15,12 +15,13 @@ panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } rtt-target = { version = "0.3.1", features = ["cortex-m"] } usbd-serial = "0.1.1" heapless = "0.7" +smoltcp = { version = "0.8.0", default-features = false, features = ["medium-ethernet", "medium-ip", "proto-ipv4", "socket-raw", "socket-tcp", "socket-dhcpv4"] } [dependencies.atsamx7x-hal] version = "0.3.0" path = "../../hal" -features = ["same70q21b-rt", "unproven"] +features = ["same70n21b-rt", "unproven"] [profile.dev] debug = true -lto = true \ No newline at end of file +lto = true diff --git a/boards/atsame70_xpro/examples/gmac.rs b/boards/atsame70_xpro/examples/gmac.rs new file mode 100644 index 00000000..64db51eb --- /dev/null +++ b/boards/atsame70_xpro/examples/gmac.rs @@ -0,0 +1,165 @@ +//! GMAC Example +//! This example should echo messages received via TCP. +//! NOTE: This code has not been tested. +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicUsize, Ordering}; +use panic_halt as _; +// use defmt_rtt as _; +// use defmt; +// +// static COUNT: AtomicUsize = AtomicUsize::new(0); +// defmt::timestamp!("{=usize}", { +// // NOTE(no-CAS) `timestamps` runs with interrupts disabled +// let n = COUNT.load(Ordering::Relaxed); +// COUNT.store(n + 1, Ordering::Relaxed); +// n +// }); +#[rtic::app(device = hal::target_device, peripherals = true, dispatchers = [IXC])] +mod app { + use atsamx7x_hal as hal; + use hal::ehal::digital::v2::ToggleableOutputPin; + use hal::pio::*; + use hal::pmc::*; + use hal::gmac::*; + use smoltcp::iface::{Neighbor, InterfaceBuilder, SocketStorage, NeighborCache, Interface, Route, Routes}; + use smoltcp::phy::{Device, RxToken, TxToken}; + use smoltcp::socket::{TcpSocketBuffer, TcpSocket, Dhcpv4Event, Dhcpv4Socket}; + use smoltcp::time::Instant; + use smoltcp::wire::{IpCidr, IpAddress, EthernetAddress, HardwareAddress, Ipv4Address, Ipv4Cidr}; + use cortex_m::singleton; + + use rtt_target::rtt_init_print; + use rtt_target::rprintln; + + #[shared] + struct Shared {} + + #[local] + struct Local { + iface: Interface<'static, Gmac>, + } + + #[init] + fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { + + rtt_init_print!(); + rprintln!("Init"); + + let mut efc = hal::efc::Efc::new(ctx.device.EFC, hal::efc::VddioLevel::V3); + let mut pmc = hal::pmc::Pmc::new(ctx.device.PMC, &ctx.device.WDT.into()); + let slck = pmc.get_slck(ctx.device.SUPC, SlowCkSource::InternalRC); + + let bankd = hal::pio::BankD::new(ctx.device.PIOD, &mut pmc, BankConfiguration::default()); + // Configure PD[1,2,3,4,5,6,8,9,11,12,14,15,16] for ethernet + let txck: Pin<_,Peripheral> = bankd.pd0.into_peripheral(); + let txen: Pin<_,Peripheral> = bankd.pd1.into_peripheral(); + let tx0: Pin<_,Peripheral> = bankd.pd2.into_peripheral(); + let tx1: Pin<_,Peripheral> = bankd.pd3.into_peripheral(); + let rxdv: Pin<_,Peripheral> = bankd.pd4.into_peripheral(); + let rx0: Pin<_,Peripheral> = bankd.pd5.into_peripheral(); + let rx1: Pin<_,Peripheral> = bankd.pd6.into_peripheral(); + let rxer: Pin<_,Peripheral> = bankd.pd7.into_peripheral(); + let mdc: Pin<_,Peripheral> = bankd.pd8.into_peripheral(); + let mdio: Pin<_,Peripheral> = bankd.pd9.into_peripheral(); + let crs: Pin<_,Peripheral> = bankd.pd10.into_peripheral(); + let rx2: Pin<_,Peripheral> = bankd.pd11.into_peripheral(); + let rx3: Pin<_,Peripheral> = bankd.pd12.into_peripheral(); + let col: Pin<_,Peripheral> = bankd.pd13.into_peripheral(); + let rxck: Pin<_,Peripheral> = bankd.pd14.into_peripheral(); + let tx2: Pin<_,Peripheral> = bankd.pd15.into_peripheral(); + let tx3: Pin<_,Peripheral> = bankd.pd16.into_peripheral(); + let txer: Pin<_,Peripheral> = bankd.pd17.into_peripheral(); + + let gmac = ctx.device.GMAC; + let mut gmac = Gmac::new_gmac(gmac, (txck, txen, tx3, tx2, tx1, tx0, txer, rxck, rxdv, rx3, rx2, rx1, rx0, rxer, crs, col, mdc, mdio), GmacConfiguration{}, &mut pmc).unwrap(); + { + // enables the peripheral clock + // pmc.enable_periph_clk(39).unwrap(); + + gmac.init(); + // defmt::debug!("miim_post_setup might not return"); + gmac.miim_post_setup(); + // defmt::debug!("miim_post_setup did return, all is good."); + } + + + let ip_addrs: &'static mut _ = singleton!(: [IpCidr; 1] = [IpCidr::new(IpAddress::v4(169, 254, 33, 1), 24)]).unwrap(); + let neighbor_cache: &'static mut _ = singleton!(: [Option<(IpAddress, Neighbor)>; 8] = [None; 8]).unwrap(); + let sockets: &'static mut _ = singleton!(: [SocketStorage<'static>; 8] = [SocketStorage::EMPTY; 8]).unwrap(); + let routes_storage: &'static mut _ = singleton!(: [Option<(IpCidr, Route)>; 1] = [None; 1]).unwrap(); + let routes = Routes::new(routes_storage.as_mut_slice()); + + let iface = InterfaceBuilder::new(gmac, sockets.as_mut_slice()) + .hardware_addr(EthernetAddress::from_bytes(&[0x04, 0x91, 0x62, 0x01, 0x02, 0x03]).into()) + .neighbor_cache(NeighborCache::new(neighbor_cache.as_mut_slice())) + .routes(routes) + .ip_addrs(ip_addrs.as_mut_slice()) + .finalize(); + + (Shared {}, Local {iface}, init::Monotonics()) + } + + #[idle(local = [iface])] + fn idle(ctx: idle::Context) -> ! { + let mut iface = ctx.local.iface; + rprintln!("Idle"); + + let server_socket = { + let rx_data: &'static mut [u8] = singleton!(: [u8; 1024] = [0; 1024]).unwrap(); + let tx_data: &'static mut [u8] = singleton!(: [u8; 1024] = [0; 1024]).unwrap(); + let tcp_rx_buffer = TcpSocketBuffer::new(rx_data); + let tcp_tx_buffer = TcpSocketBuffer::new(tx_data); + TcpSocket::new(tcp_rx_buffer,tcp_tx_buffer) + }; + + let server_handle = iface.add_socket(server_socket); + let mut last_state = smoltcp::socket::TcpState::Closed; + loop { + match iface.poll(Instant::from_micros(48)) { + Ok(_) => {}, + Err(e) => { + } + } + + + let mut buf = [0u8;1024]; + let socket = iface.get_socket::(server_handle); + let state = socket.state(); + if state != last_state { + // defmt::println!("STATE CHANGE: {=?} => {=?}", last_state, state); + last_state = state; + } + + if state == smoltcp::socket::TcpState::CloseWait { + socket.close(); + } + + if !socket.is_active() && !socket.is_listening() { + // defmt::info!("Listening..."); + socket.listen(4321).unwrap(); + } + + + let mut to_send = None; + if socket.can_recv() { + socket.recv(|buffer| { + // defmt::info!("Receive!"); + // defmt::info!("Len: {}", buffer.len()); + // defmt::info!("dat {}", buffer); + // defmt::info!("{}", cmd_string.as_str()); + + buf[..buffer.len()].copy_from_slice(&buffer[..buffer.len()]); + to_send = Some(&buf[..buffer.len()]); + + (buffer.len(), ()) + }).unwrap(); + } + if socket.can_send() && to_send != None { + let tx = to_send.unwrap(); + socket.send_slice(tx).unwrap(); + } + } + } +} diff --git a/boards/atsamv71_xult/Cargo.lock b/boards/atsamv71_xult/Cargo.lock index 3c52caae..10849878 100644 --- a/boards/atsamv71_xult/Cargo.lock +++ b/boards/atsamv71_xult/Cargo.lock @@ -31,6 +31,7 @@ dependencies = [ "heapless", "panic-rtt-target", "rtt-target", + "smoltcp", "usbd-serial", ] @@ -56,6 +57,7 @@ dependencies = [ "nb 1.0.0", "paste", "rtic-monotonic", + "smoltcp", "strum", "usb-device", "void", @@ -100,6 +102,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "byteorder" version = "1.4.3" @@ -283,6 +291,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "memchr" version = "2.5.0" @@ -485,6 +499,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "smoltcp" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72165c4af59f5f19c7fb774b88b95660591b612380305b5f4503157341a9f7ee" +dependencies = [ + "bitflags", + "byteorder", + "managed", +] + [[package]] name = "spin" version = "0.9.3" diff --git a/boards/atsamv71_xult/Cargo.toml b/boards/atsamv71_xult/Cargo.toml index a1f3f8c4..ae1c71d5 100644 --- a/boards/atsamv71_xult/Cargo.toml +++ b/boards/atsamv71_xult/Cargo.toml @@ -12,6 +12,7 @@ cortex-m-rtic = "1.0" cortex-m = "0.7" panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } rtt-target = { version = "0.3.1", features = ["cortex-m"] } +smoltcp = { version = "0.8.1", default-features = false, features = ["medium-ethernet", "medium-ip", "proto-ipv4", "socket-raw", "socket-tcp", "socket-dhcpv4", "socket-udp"] } heapless = "0.7" usbd-serial = "0.1.1" dwt-systick-monotonic = "1.0.0" @@ -21,6 +22,9 @@ version = "0.3.0" path = "../../hal" features = ["samv71q21b-rt", "unproven"] +[features] +static-ip = [] + [profile.dev] debug = true lto = true diff --git a/boards/atsamv71_xult/examples/udp.rs b/boards/atsamv71_xult/examples/udp.rs new file mode 100644 index 00000000..7047e698 --- /dev/null +++ b/boards/atsamv71_xult/examples/udp.rs @@ -0,0 +1,265 @@ +#![no_main] +#![no_std] + +use core::sync::atomic::{AtomicUsize, Ordering}; + +use panic_rtt_target as _; + +#[rtic::app(device = atsamx7x_hal::pac, dispatchers = [PIOA, PIOB])] +mod app { + use atsamx7x_hal as hal; + use core::str; + use cortex_m::singleton; + use hal::clocks::{ + HccPrescaler, HostClockConfig, HostClockController, MckDivider, Pck, Pck0, Pck4, + PeripheralIdentifier, PllaConfig, Tokens, + }; + use hal::fugit::{ExtU32, RateExtU32}; + use hal::generics::events::EventHandler; + use hal::gmac::*; + use hal::pio::bank::{BankA, BankB, BankConfiguration, BankD, PA25}; + use hal::pio::pin::Peripheral; + use hal::pio::{Pin, A}; + use hal::rtt::*; + use hal::serial::ExtBpsU32; + use heapless::String; + use rtt_target::{rprintln, rtt_init_print}; + use smoltcp::iface::{ + Interface, InterfaceBuilder, Neighbor, NeighborCache, Route, Routes, SocketHandle, + SocketStorage, + }; + use smoltcp::phy::{Device, RxToken, TxToken}; + use smoltcp::socket::{ + Dhcpv4Event, Dhcpv4Socket, TcpSocket, TcpSocketBuffer, UdpPacketMetadata, UdpSocket, + UdpSocketBuffer, + }; + use smoltcp::time::Instant; + use smoltcp::wire::{ + EthernetAddress, HardwareAddress, IpAddress, IpCidr, IpEndpoint, Ipv4Address, Ipv4Cidr, + }; + // TODO: Add a monotonic if scheduling will be used + use dwt_systick_monotonic::*; + #[monotonic(binds = SysTick, default = true)] + type DwtMono = DwtSystick<150_000_000>; + + // Shared resources go here + #[shared] + struct Shared {} + + // Local resources go here + #[local] + struct Local { + iface: Interface<'static, Gmac<'static, 8, 1024, 8, 1024>>, + udp_handle: SocketHandle, + dhcp_handle: SocketHandle, + } + + #[init(local = [gmac_buffers: GmacBuffers<8,1024,8,1024> = GmacBuffers::default()])] + fn init(mut cx: init::Context) -> (Shared, Local, init::Monotonics) { + rtt_init_print!(); + + let device = cx.device; + let pioa = device.PIOA; + let piob = device.PIOB; + let piod = device.PIOD; + + rprintln!("Init"); + let mut efc = hal::efc::Efc::new(device.EFC, hal::efc::VddioLevel::V3); + // Clock Configuration + let clocks = Tokens::new((device.PMC, device.SUPC, device.UTMI), &device.WDT.into()); + let mainck = clocks.mainck.configure_external_normal(12.MHz()).unwrap(); + let slck = clocks.slck.configure_internal(); + let pllack = clocks + .pllack + .configure(&mainck, PllaConfig { div: 2, mult: 25 }) + .unwrap(); + let (mclk, mut hclk) = HostClockController::new(clocks.hclk, clocks.mck) + .configure( + &pllack, + &mut efc, + HostClockConfig { + pres: HccPrescaler::Div1, + div: MckDivider::Div1, + }, + ) + .unwrap(); + + let mono = DwtSystick::new(&mut cx.core.DCB, cx.core.DWT, cx.core.SYST, 150_000_000); + + let banka = BankA::new(pioa, &mut hclk, &slck, BankConfiguration::default()); + let bankb = BankB::new(piob, &mut hclk, &slck, BankConfiguration::default()); + let bankd = BankD::new(piod, &mut hclk, &slck, BankConfiguration::default()); + + // Configure PD[1,2,3,4,5,6,8,9,11,12,14,15,16] for ethernet + let pd0: Pin<_, Peripheral> = bankd.pd0.into_peripheral(); + let pd1: Pin<_, Peripheral> = bankd.pd1.into_peripheral(); + let pd2: Pin<_, Peripheral> = bankd.pd2.into_peripheral(); + let pd3: Pin<_, Peripheral> = bankd.pd3.into_peripheral(); + let pd4: Pin<_, Peripheral> = bankd.pd4.into_peripheral(); + let pd5: Pin<_, Peripheral> = bankd.pd5.into_peripheral(); + let pd6: Pin<_, Peripheral> = bankd.pd6.into_peripheral(); + let pd7: Pin<_, Peripheral> = bankd.pd7.into_peripheral(); + let pd8: Pin<_, Peripheral> = bankd.pd8.into_peripheral(); + let pd9: Pin<_, Peripheral> = bankd.pd9.into_peripheral(); + // let _pd11: Pin<_,Peripheral> = bankd.pd11.into_peripheral(); + // let _pd12: Pin<_,Peripheral> = bankd.pd12.into_peripheral(); + // let _pd14: Pin<_,Peripheral> = bankd.pd14.into_peripheral(); + // let _pd15: Pin<_,Peripheral> = bankd.pd15.into_peripheral(); + // let _pd16: Pin<_,Peripheral> = bankd.pd16.into_peripheral(); + + let gmac = device.GMAC; + + let mut gmac = Gmac::new_gmac( + gmac, + (pd0, pd1, pd3, pd2, pd4, pd6, pd5, pd7, pd8, pd9), + GmacConfiguration { + speed: GmacSpeed::_100Mbit, + mii: GmacMii::Rmii, + duplex: GmacDuplex::FullDuplex, + mac: [0x04, 0x91, 0x62, 0x01, 0x02, 0x03], + }, + cx.local.gmac_buffers, + &mut hclk, + ) + .unwrap(); + { + rprintln!("miim_post_setup might not return"); + gmac.miim_post_setup(); + rprintln!("miim_post_setup did return, all is good."); + } + + // Configure TCP Stack + let ip_addrs: &'static mut _ = if cfg!(feature = "static-ip") { + singleton!(: [IpCidr; 1] = [IpCidr::new(IpAddress::v4(169, 254, 33, 1), 24)]).unwrap() + } else { + singleton!(: [IpCidr; 1] = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 24)]).unwrap() + }; + let neighbor_cache: &'static mut _ = + singleton!(: [Option<(IpAddress, Neighbor)>; 8] = [None; 8]).unwrap(); + let sockets: &'static mut _ = + singleton!(: [SocketStorage<'static>; 8] = [SocketStorage::EMPTY; 8]).unwrap(); + let routes_storage: &'static mut _ = + singleton!(: [Option<(IpCidr, Route)>; 1] = [None; 1]).unwrap(); + let routes = Routes::new(routes_storage.as_mut_slice()); + + let mut iface = InterfaceBuilder::new(gmac, sockets.as_mut_slice()) + .hardware_addr( + EthernetAddress::from_bytes(&[0x04, 0x91, 0x62, 0x01, 0x02, 0x03]).into(), + ) + .neighbor_cache(NeighborCache::new(neighbor_cache.as_mut_slice())) + .routes(routes) + .ip_addrs(ip_addrs.as_mut_slice()) + .finalize(); + + let udp_socket = { + let rx_data: &'static mut [u8] = singleton!(: [u8; 1024] = [0; 1024]).unwrap(); + let rx_metadata: &'static mut [UdpPacketMetadata] = + singleton!(: [UdpPacketMetadata; 1024] = [UdpPacketMetadata::EMPTY; 1024]).unwrap(); + let tx_data: &'static mut [u8] = singleton!(: [u8; 1024] = [0; 1024]).unwrap(); + let tx_metadata: &'static mut [UdpPacketMetadata] = + singleton!(: [UdpPacketMetadata; 1024] = [UdpPacketMetadata::EMPTY; 1024]).unwrap(); + let udp_rx_buffer = UdpSocketBuffer::new(rx_metadata, rx_data); + let udp_tx_buffer = UdpSocketBuffer::new(tx_metadata, tx_data); + UdpSocket::new(udp_rx_buffer, udp_tx_buffer) + }; + + let udp_handle = iface.add_socket(udp_socket); + let dhcp_socket = smoltcp::socket::Dhcpv4Socket::new(); + let dhcp_handle = iface.add_socket(dhcp_socket); + + ( + Shared {}, + Local { + iface, + udp_handle, + dhcp_handle, + }, + init::Monotonics( + // Initialization of optional monotonic timers go here + mono, + ), + ) + } + + // Optional idle, can be removed if not needed. + #[idle(local=[iface, dhcp_handle, udp_handle])] + fn idle(cx: idle::Context) -> ! { + rprintln!("idle"); + let mut iface = cx.local.iface; + let udp_handle = cx.local.udp_handle; + let dhcp_handle = cx.local.dhcp_handle; + loop { + match iface.poll(Instant::from_micros(monotonics::now().ticks() / 48)) { + Ok(_) => {} + Err(e) => { + rprintln!("Error: {:?}", e); + } + } + + let event = if cfg!(not(feature = "static-ip")) { + iface.get_socket::(*dhcp_handle).poll() + } else { + None + }; + match event { + None => {} + Some(Dhcpv4Event::Configured(config)) => { + rprintln!("DHCP config acquired!"); + + rprintln!("IP address: {}", config.address); + // set_ipv4_addr(&mut iface, config.address); + iface.update_ip_addrs(|addrs| { + let dest = addrs.iter_mut().next().unwrap(); + *dest = IpCidr::Ipv4(config.address); + }); + + if let Some(router) = config.router { + rprintln!("Default gateway: {}", router); + iface.routes_mut().add_default_ipv4_route(router).unwrap(); + } else { + rprintln!("Default gateway: None"); + iface.routes_mut().remove_default_ipv4_route(); + } + + for (i, s) in config.dns_servers.iter().enumerate() { + if let Some(s) = s { + rprintln!("DNS server {}: {}", i, s); + } + } + } + Some(Dhcpv4Event::Deconfigured) => { + rprintln!("DHCP lost config!"); + // set_ipv4_addr(&mut iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); + iface.update_ip_addrs(|addrs| { + let dest = addrs.iter_mut().next().unwrap(); + *dest = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); + }); + iface.routes_mut().remove_default_ipv4_route(); + } + } + + let socket = iface.get_socket::(*udp_handle); + + if !socket.is_open() { + socket.bind(1234).unwrap(); + rprintln!("Socket is listening on port 1234"); + } + + let client = match socket.recv() { + Ok((data, endpoint)) => { + rprintln!( + "udp: 1234 recv data: {:?} from {}", + str::from_utf8(data).unwrap(), + endpoint + ); + Some(endpoint) + } + Err(_) => None, + }; + + if let Some(endpoint) = client { + socket.send_slice("hello\n".as_bytes(), endpoint); + } + } + } +} diff --git a/hal/Cargo.toml b/hal/Cargo.toml index 1b716a4b..9088d351 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -38,6 +38,7 @@ rtic-monotonic = "1" void = { version = "1", default-features = false } strum = { version = "0.24.1", default-features = false, features = ["derive"]} cfg-if = "1" +smoltcp = {version = "0.8.0", default-features = false, features = ["medium-ethernet", "medium-ip", "proto-ipv4", "socket-raw", "socket-tcp", "socket-dhcpv4"] } atsame70j19b = { version = "0.25.0", path = "../pac/atsame70j19b", optional = true } atsame70j20b = { version = "0.25.0", path = "../pac/atsame70j20b", optional = true } diff --git a/hal/src/gmac.rs b/hal/src/gmac.rs new file mode 100644 index 00000000..faa5e133 --- /dev/null +++ b/hal/src/gmac.rs @@ -0,0 +1,1047 @@ +use crate::clocks::*; +use crate::pac::gmac::RegisterBlock; +use crate::pio::*; +use core::sync::atomic::{compiler_fence, fence, Ordering}; +use core::{ + assert, + cell::UnsafeCell, + marker::PhantomData, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; +use paste::*; + +use crate::pac::{ + generic::Reg, + gmac::{idrpq::IDRPQ_SPEC, isrpq::ISRPQ_SPEC}, + GMAC, +}; +use smoltcp::phy::{ + Checksum, ChecksumCapabilities, Device, DeviceCapabilities, Medium, RxToken, TxToken, +}; + +pub trait GmacMeta { + const REG: *const RegisterBlock; + const PID: PeripheralIdentifier; +} + +pub trait GmacPins {} + +#[derive(Debug)] +pub enum GmacError { + ClockRateError, +} + +pub struct GmacConfiguration { + pub speed: GmacSpeed, + pub duplex: GmacDuplex, + pub mii: GmacMii, + pub mac: [u8; 6], +} + +pub enum GmacSpeed { + _100Mbit, + _10Mbit, +} + +pub enum GmacDuplex { + HalfDuplex, + FullDuplex, +} + +pub enum GmacMii { + Mii, + Rmii, +} + +pub struct GmacRxToken<'a, const RX_S: usize> { + rf: ReadFrame, + _plt: PhantomData<&'a ()>, +} +pub struct GmacTxToken< + 'a, + const TX_N: usize, + const TX_S: usize, + const RX_N: usize, + const RX_S: usize, +> { + gmac_buf: &'a mut GmacBuffers, + // tf: WriteFrame, + _plt: PhantomData<&'a ()>, + // gmac: &'a mut Gmac<'a, TX_N, TX_S, RX_N, RX_S>, +} + +impl<'a, const RX_S: usize> RxToken for GmacRxToken<'a, RX_S> { + fn consume(mut self, timestamp: smoltcp::time::Instant, f: F) -> smoltcp::Result + where + F: FnOnce(&mut [u8]) -> smoltcp::Result, + { + f(self.rf.deref_mut()) + } +} + +impl<'a, const TX_N: usize, const TX_S: usize, const RX_N: usize, const RX_S: usize> TxToken + for GmacTxToken<'a, TX_N, TX_S, RX_N, RX_S> +{ + fn consume( + self, + timestamp: smoltcp::time::Instant, + len: usize, + f: F, + ) -> smoltcp::Result + where + F: FnOnce(&mut [u8]) -> smoltcp::Result, + { + let mut tf = self.gmac_buf.alloc_write_frame().unwrap(); + let res = f(&mut tf[..len]); + tf.send(len); + res + } +} + +impl<'a, const TX_N: usize, const TX_S: usize, const RX_N: usize, const RX_S: usize> Device<'a> + for Gmac<'_, TX_N, TX_S, RX_N, RX_S> +{ + type RxToken = GmacRxToken<'a, RX_S>; + type TxToken = GmacTxToken<'a, TX_N, TX_S, RX_N, RX_S>; + + fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { + let rxf = self.read_frame()?; + // let txf = self.alloc_write_frame()?; + Some(( + GmacRxToken { + rf: rxf, + _plt: PhantomData, + }, + GmacTxToken { + gmac_buf: &mut self.gmac_buffers, + _plt: PhantomData, + }, + )) + } + + fn transmit(&'a mut self) -> Option { + Some(GmacTxToken { + gmac_buf: &mut self.gmac_buffers, + _plt: PhantomData, + }) + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut capa = DeviceCapabilities::default(); + capa.medium = Medium::Ethernet; + capa.max_transmission_unit = TX_S; // Is this too big? I think we are capable of full ethernet frames + capa.max_burst_size = None; + + // TODO The Gmac can do checksum offloading, and I believe is configured to do checksum + // offloading. + let mut cksm = ChecksumCapabilities::ignored(); + cksm.ipv4 = Checksum::Both; + cksm.tcp = Checksum::Both; + cksm.udp = Checksum::Both; + cksm.icmpv4 = Checksum::None; + + capa.checksum = cksm; + capa + } +} + +pub struct Gmac<'a, const TX_N: usize, const TX_S: usize, const RX_N: usize, const RX_S: usize> { + periph: GMAC, + + gmac_buffers: &'a mut GmacBuffers, + unused_tx_buf_desc: TxBufferDescriptor, +} + +pub struct ReadFrame { + bufr: NonNull<[u8; RX_S]>, + len: usize, + desc: NonNull, +} + +pub struct WriteFrame { + bufr: NonNull<[u8; TX_S]>, + desc: NonNull, + was_sent: bool, +} + +impl Drop for WriteFrame { + fn drop(&mut self) { + if !self.was_sent {} + } +} + +impl Deref for ReadFrame { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { core::slice::from_raw_parts(self.bufr.as_ptr().cast(), self.len) } + } +} + +impl DerefMut for ReadFrame { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { core::slice::from_raw_parts_mut(self.bufr.as_ptr().cast(), self.len) } + } +} + +impl Deref for WriteFrame { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { core::slice::from_raw_parts(self.bufr.as_ptr().cast(), TX_S) } + } +} + +impl DerefMut for WriteFrame { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { core::slice::from_raw_parts_mut(self.bufr.as_ptr().cast(), TX_S) } + } +} + +impl Drop for ReadFrame { + fn drop(&mut self) { + // On drop, we must reset the header to "free" it. + let desc = unsafe { self.desc.as_ref() }; + // Get w0 to figure out if this is the "last" item + // TODO: Just check against buffer address? + let is_last = (desc.get_word_0() & 0x0000_0002) != 0; + let buf_addr = self.bufr.as_ptr(); + let buf_word = buf_addr as u32; + let buf_word_msk = buf_word & 0xFFFF_FFFC; + + let last_word = if is_last { 0x0000_0002 } else { 0x0000_0000 }; + + // Note, bit 0 is ALWAYS cleared here, which marks the buffer as ready for + // re-use by the GMAC + desc.set_word_0(buf_word_msk | last_word); + } +} + +impl WriteFrame { + unsafe fn dropper(&mut self, len: usize) { + compiler_fence(Ordering::SeqCst); + let desc = { self.desc.as_ref() }; + let old_w1 = desc.get_word_1(); + let wrap_bit = old_w1 & 0x4000_0000; + let len = len.min(TX_S).min(0x3FFF) as u32; + + let mut new_w1 = 0; + // Bit 31 is zeroed to mark this as "ready" + new_w1 |= wrap_bit; // Bit 30: Wrap + // Bits 29:17 are status/reserved bits, okay to clear + // Bit 16 is zeroed to have CRC calc offloaded + new_w1 |= 0x0000_8000; // Bit 15: Last Buffer in Frame + // Bit 14 is reserved + new_w1 |= len; // Bits 13:0: Size + + // Store the word to make it active for the transmit hardware to process. + desc.set_word_1(new_w1); + self.was_sent = true; + + fence(Ordering::SeqCst); + // yolo + { + let gmac = &*GMAC::ptr(); + gmac.ncr.modify(|_r, w| w.tstart().set_bit()); + } + } + + pub fn send(mut self, len: usize) { + unsafe { self.dropper(len) }; + } +} +impl GmacPins for () {} + +impl<'a, const TX_N: usize, const TX_S: usize, const RX_N: usize, const RX_S: usize> + Gmac<'a, TX_N, TX_S, RX_N, RX_S> +{ + pub fn new_gmac( + gmac: GMAC, + _pins: Pins, + cfg: GmacConfiguration, + bufs: &'a mut GmacBuffers, + clk: &mut HostClock, + ) -> Result { + Self::new(gmac, clk, cfg, bufs) + } + + fn new( + gmac: GMAC, + clk: &mut HostClock, + cfg: GmacConfiguration, + bufs: &'a mut GmacBuffers, + ) -> Result { + clk.enable_peripheral(PeripheralIdentifier::GMAC); + let mut gmac = Gmac { + periph: gmac, + gmac_buffers: bufs, + + unused_tx_buf_desc: TX_BUF_DESC_DEFAULT, + }; + // TODO Remove if we can assume reset register values + gmac.periph.ncr.modify(|_r, w| { + w.txen().clear_bit(); + w.rxen().clear_bit(); + w + }); + + if gmac.miim_is_busy() { + // defmt::println!("Busy at start???"); + } + + // //disable all GMAC interrupts for QUEUE 0 + gmac.periph.idr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // //disable all GMAC interrupts for QUEUE 1 + // //disable all GMAC interrupts for QUEUE 2 + // //disable all GMAC interrupts for QUEUE 3 + // //disable all GMAC interrupts for QUEUE 4 + // //disable all GMAC interrupts for QUEUE 5 + for i in 0..5 { + gmac.periph.idrpq[i].write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + } + + // TODO if we can assume reset conditions, we can also skip these three register writes + // //Clear statistics register + gmac.periph.ncr.modify(|_r, w| w.clrstat().set_bit()); + + // //Clear RX Status + gmac.periph.rsr.write(|w| { + w.rxovr().set_bit(); + w.rec().set_bit(); + w.bna().set_bit(); + w.hno().set_bit(); + w + }); + + // //Clear TX Status + gmac.periph.tsr.write(|w| { + w.ubr().set_bit(); + w.col().set_bit(); + w.rle().set_bit(); + w.txgo().set_bit(); + w.tfc().set_bit(); + w.txcomp().set_bit(); + w.hresp().set_bit(); + w + }); + + // //Clear all GMAC Interrupt status + let _ = gmac.periph.isr.read().bits(); + + for i in 0..5 { + let _ = gmac.periph.isrpq[i].read().bits(); + } + + // //Set network configurations like speed, full duplex, copy all frames, no broadcast, + // // pause enable, remove FCS, MDC clock + gmac.periph.ncfgr.modify(|_, w| { + match cfg.speed { + GmacSpeed::_100Mbit => w.spd().set_bit(), + GmacSpeed::_10Mbit => w.spd().clear_bit(), + }; + match cfg.duplex { + GmacDuplex::FullDuplex => w.fd().set_bit(), + GmacDuplex::HalfDuplex => w.fd().clear_bit(), + }; + w + }); + match clk.freq().to_MHz() { + 0..=20 => { + gmac.periph.ncfgr.modify(|_, w| w.clk().mck_8()); + Ok(()) + } + 21..=40 => { + gmac.periph.ncfgr.modify(|_, w| w.clk().mck_16()); + Ok(()) + } + 41..=80 => { + gmac.periph.ncfgr.modify(|_, w| w.clk().mck_32()); + Ok(()) + } + 81..=160 => { + gmac.periph.ncfgr.modify(|_, w| w.clk().mck_64()); + Ok(()) + } + 161..=240 => { + gmac.periph.ncfgr.modify(|_, w| w.clk().mck_96()); + Ok(()) + } + 241.. => Err(GmacError::ClockRateError), + }?; + gmac.periph.ncfgr.modify(|_, w| { + unsafe { + // 0 = 32-bit data bus + w.dbw().bits(0); + } + w.pen().set_bit(); + w.rfcs().clear_bit(); + // Note: Always enabling checksum offloading for now + w.rxcoen().set_bit(); + w + }); + + // // Set MAC address + + // For now, use (one of) Microchip's MACs. This is temporary. + // + // 04-91-62 (hex) Microchip Technology Inc. + // 049162 (base 16) Microchip Technology Inc. + // 2355 W. Chandler Blvd. + // Chandler AZ 85224 + // US + + // 0 1 2 3 4 5 + // 04:91:62:01:02:03 + + gmac.periph.gmac_sa[0].sab.write(|w| unsafe { + let bottom: u32 = { + ((cfg.mac[3] as u32) << 24) + | ((cfg.mac[2] as u32) << 16) + | ((cfg.mac[1] as u32) << 8) + | (cfg.mac[0] as u32) + }; + w.addr().bits(bottom) + }); + gmac.periph.gmac_sa[0].sat.write(|w| unsafe { + let top: u16 = { ((cfg.mac[5] as u16) << 8) | (cfg.mac[4] as u16) }; + w.addr().bits(top) + }); + + // // MII mode config TODO Save user from mii config and pin config incompatibilities + gmac.periph.ur.write(|w| { + // 0 => RMII + // 1 => MII + match cfg.mii { + GmacMii::Mii => w.rmii().set_bit(), + GmacMii::Rmii => w.rmii().clear_bit(), + } + }); + + // DRV_PIC32CGMAC_LibRxFilterHash_Calculate + // + // Note: Set to all 1's to accept all multi-cast addresses + gmac.periph + .hrb + .write(|w| unsafe { w.addr().bits(0xFFFF_FFFF) }); + gmac.periph + .hrt + .write(|w| unsafe { w.addr().bits(0xFFFF_FFFF) }); + + // _DRV_GMAC_MacToEthFilter + // + // Let's just leave these as defaults, they look sane, but meh. + + // DRV_PIC32CGMAC_LibRxQueFilterInit + // + // Let's skip priority filters for now... + + // DRV_PIC32CGMAC_LibRxInit + // This boils down to a single write to GMAC_RBQB (or GMAC_RBQBAPQ), I think this means I need to set up the receive buffers. NOTE: I think they need to be 8-byte aligned (or something?) (datasheet says 4-byte aligned...) Table 38-2 describes "Receive Buffer Descriptor Entry" + // Set the receive buffer addresses in the upper word + // for (desc, buf) in gmac.RX_BUF_DESCS.iter().zip(gmac.RX_BUFS.iter()) { + for (desc, buf) in gmac + .gmac_buffers + .rx_buf_desc + .iter() + .zip(gmac.gmac_buffers.rx_buf.iter()) + { + // Take the buffer pointer... + let buf_addr_ptr: *mut u8 = buf.buf.get().cast(); + let buf_wrd_raw: u32 = buf_addr_ptr as u32; + let buf_wrd_msk: u32 = buf_wrd_raw & 0xFFFF_FFFC; + assert!(buf_wrd_raw == buf_wrd_msk); + // defmt::assert_eq!(buf_wrd_raw, buf_wrd_msk, "RX Buf Alignment Wrong!"); + + // ...and store it in the buffer descriptor + desc.set_word_0(buf_wrd_msk); + } + + // This is probably UB and should be fixed... + // let last = &gmac.RX_BUF_DESCS[RX_N - 1]; + let last = &gmac.gmac_buffers.rx_buf_desc[RX_N - 1]; + let mut word_0 = last.get_word_0(); + + // Mark as last buffer + word_0 |= 0x0000_0002; + last.set_word_0(word_0); + + // TODO: I *think* I need to set DCFGR.DRBS = (1024 / 64) = 16 = 0x10 + // This is done "later" in DRV_PIC32CGMAC_LibInitTransfer + + gmac.periph.rbqb.write(|w| unsafe { + // Take the buffer descriptor pointer... + // let desc_ptr: *const RxBufferDescriptor = gmac.RX_BUF_DESCS.as_ptr(); + let desc_ptr: *const RxBufferDescriptor = gmac.gmac_buffers.rx_buf_desc.as_ptr(); + let desc_wrd_raw: u32 = desc_ptr as u32; + let desc_wrd_msk: u32 = desc_wrd_raw & 0xFFFF_FFFC; + assert!(desc_wrd_raw == desc_wrd_msk); + + // ... and store it in the RBQB register + w.bits(desc_wrd_msk) + }); + + // DRV_PIC32CGMAC_LibTxInit + // + // Again, this boils down to essentially a single write to GMAC_TBQB, similar to above. + // + // Table 38-3 describes "Transmit Buffer Descriptor Entry" + // Set the transmit buffer addresses in the upper word + // for (desc, buf) in gmac.TX_BUF_DESCS.iter().zip(gmac.TX_BUFS.iter()) { + for (desc, buf) in gmac + .gmac_buffers + .tx_buf_desc + .iter() + .zip(gmac.gmac_buffers.tx_buf.iter()) + { + // Take the buffer pointer... + let buf_addr_ptr: *mut u8 = buf.buf.get().cast(); + let buf_wrd_raw: u32 = buf_addr_ptr as u32; + let buf_wrd_msk: u32 = buf_wrd_raw & 0xFFFF_FFFC; + assert!(buf_wrd_raw == buf_wrd_msk); + + // ...and store it in the buffer descriptor + desc.set_word_0(buf_wrd_msk); + + // Mark this buffer as "used" by software, so the hardware will + // not attempt to use this buffer until later. + desc.set_word_1(0x8000_0000); + } + + // This is probably UB and should be fixed... + let last = &(gmac.gmac_buffers.tx_buf_desc[TX_N - 1]); + let mut word_1 = last.get_word_1(); + + // Mark as wrap buffer + word_1 |= 0x4000_0000; + last.set_word_1(word_1); + + gmac.periph.tbqb.write(|w| unsafe { + // Take the buffer descriptor pointer... + // let desc_ptr: *const TxBufferDescriptor = gmac.TX_BUF_DESCS.as_ptr(); + let desc_ptr: *const TxBufferDescriptor = gmac.gmac_buffers.tx_buf_desc.as_ptr(); + let desc_wrd_raw: u32 = desc_ptr as u32; + let desc_wrd_msk: u32 = desc_wrd_raw & 0xFFFF_FFFC; + assert!(desc_wrd_raw == desc_wrd_msk); + + // ... and store it in the TBQB register + w.bits(desc_wrd_msk) + }); + + // Note! We need to stub out the prio queues + for buf in gmac.periph.tbqbapq.iter() { + // Take the buffer descriptor pointer... + let desc_ptr: *const TxBufferDescriptor = &(gmac.unused_tx_buf_desc); + let desc_wrd_raw: u32 = desc_ptr as u32; + let desc_wrd_msk: u32 = desc_wrd_raw & 0xFFFF_FFFC; + assert!(desc_wrd_raw == desc_wrd_msk); + + // ... and store it in the TBQB register + buf.write(|w| unsafe { w.bits(desc_wrd_msk) }); + } + + // DRV_PIC32CGMAC_LibInitTransfer + // TODO DMA buffer and RX buffer do not need to be the same size. + // Yes they do. 38.7.1.2 + let drbs = (RX_S / 64).min(255) as u8; + + gmac.periph.dcfgr.write(|w| { + // ? - DMA Discard Receive Packets + // + // 0 - Received packets are stored in the SRAM based packet buffer until next AHB buffer + // resource becomes available. + // + // 1 - Receive packets from the receiver packet buffer memory are automatically discarded when + // no AHB resource is available. + // + // TODO: Example code sets this, so let's do that for now. + // TODO: Probs clear it + w.ddrp().set_bit(); + unsafe { + // DRBS is defined in multiples of 64-bytes + w.drbs().bits(drbs); + } + w.txcoen().set_bit(); // Enable Checksum Offload + w.txpbms().set_bit(); // Use full 4KiB of TX space (???) + w.rxbms().full(); // Use full 4KiB of RX space (???) + w.espa().clear_bit(); // Disable endianness swap for packet data access + w.esma().clear_bit(); // Disable endianness swap for management desc access + w.fbldo().incr4(); // AHB increments of 4 (???) + + w + }); + + // TODO(AJM): We do NOT enable any interrupts at this point. For early bringup, + // I plan to poll the relevant status registers. This will change at some point. + // + // This note applies to the behavior at the end of DRV_PIC32CGMAC_LibInitTransfer, + // as well as the next two steps. + + gmac.periph.ncr.modify(|_r, w| { + w.txen().set_bit(); + w.rxen().set_bit(); + w.westat().set_bit(); + w + }); + + Ok(gmac) + } + + pub fn read_frame(&mut self) -> Option> { + // Scan through the read frames, and attempt to find one marked as "used" + compiler_fence(Ordering::SeqCst); + // self.RX_BUF_DESCS.iter().find_map(|desc| { + self.gmac_buffers.rx_buf_desc.iter().find_map(|desc| { + let w0 = desc.get_word_0(); + let addr = w0 & 0xFFFF_FFFC; + let ready = (w0 & 0x0000_0001) != 0; + + if ready && (addr != 0) { + // Erase address, but leave 'ready' and potentially 'last' bit set. + desc.set_word_0(w0 & 0x0000_0003); + let len = (desc.get_word_1() & 0x0000_0FFF) as usize; + + fence(Ordering::SeqCst); + + let desc_addr = NonNull::new(desc.words.get().cast())?; + let buf_addr = NonNull::new(addr as *const [u8; RX_S] as *mut [u8; RX_S])?; + Some(ReadFrame { + bufr: buf_addr, + len, + desc: desc_addr, + }) + } else { + None + } + }) + } + + fn miim_mgmt_port_enable(&mut self) { + self.periph.ncr.modify(|_r, w| w.mpe().set_bit()); + } + + fn miim_mgmt_port_disable(&mut self) { + self.periph.ncr.modify(|_r, w| w.mpe().clear_bit()); + } + + fn miim_is_busy(&mut self) -> bool { + self.periph.nsr.read().idle().bit_is_clear() + } + + fn miim_write_data(&mut self, reg_idx: u8, op_data: u16) { + self.periph.man.write(|w| { + w.wzo().clear_bit(); + w.cltto().set_bit(); + unsafe { + w.op().bits(0b01); + w.wtn().bits(0b10); + // TODO: Hardcoded PHY Address + w.phya().bits(0); + w.rega().bits(reg_idx); + w.data().bits(op_data); + } + w + }); + } + + // TODO: Should miim_read be consolidated into a single function? + fn miim_start_read(&mut self, reg_idx: u8) { + self.periph.man.write(|w| { + w.wzo().clear_bit(); + w.cltto().set_bit(); + unsafe { + w.op().bits(0b10); + w.wtn().bits(0b10); + // TODO: Hardcoded PHY Address + w.phya().bits(0); + w.rega().bits(reg_idx); + w.data().bits(0); + } + w + }); + } + + fn miim_read_data_get(&mut self) -> u16 { + self.periph.man.read().data().bits() + } + + pub fn miim_post_setup(&mut self) { + // Starting MIIM setup + // Enabling management port... + self.miim_mgmt_port_enable(); + + // Waiting for miim idle... + let val = self.periph.nsr.read().bits(); + // defmt::println!("{=u32:08X}", val); + while self.miim_is_busy() {} + + // Reset PHY... + + self.miim_write_data(0, 0x8000); // 0.15: Software reset + + // This may just block forever + while self.miim_is_busy() {} + + self.miim_start_read(0); + while self.miim_is_busy() {} + let val = self.miim_read_data_get(); + // defmt::println!("New Reg 0 Val: {=u16:04X}", val); + + // TODO: Skipping Autonegotiation Adv step (reg 4)... + // TODO: Skipping Autonegotiation restart since we didn't change anything... + + // Wait for link to come up + loop { + self.miim_start_read(1); + while self.miim_is_busy() {} + let val = self.miim_read_data_get(); + if (val & 0x0004) != 0 { + // defmt::println!("Link up!"); + break; + } + } + + self.miim_mgmt_port_disable(); + } +} + +// Note: MIIM == MDIO == SMI + +// Relevant driver call chain +// +// DRV_GMAC_Initialize +// DRV_PIC32CGMAC_LibSysInt_Disable +// * Not much, just disabling interrupts? +// _DRV_GMAC_PHYInitialise +// * DRV_ETHPHY_Initialize +// * Data structure init? +// * DRV_ETHPHY_Open +// _DRV_ETHPHY_ClientObjectAllocate +// * Data structures... +// DRV_MIIM_Open +// _DRV_MIIM_GetObjectAndLock +// * Data structures... +// _DRV_MIIM_ClientAllocate +// * Data structures... +// _DRV_MIIM_ObjUnlock +// * FreeRTOS stuff? +// DRV_PIC32CGMAC_LibInit +// Important! See below +// DRV_PIC32CGMAC_LibRxFilterHash_Calculate +// Important! (I think?) +// _DRV_GMAC_MacToEthFilter +// Used to calculate GMAC_NCFGR ? +// DRV_PIC32CGMAC_LibRxQueFilterInit +// Used to calculate priority filters? Unsure if necessary +// DRV_PIC32CGMAC_LibRxInit +// DRV_PIC32CGMAC_LibTxInit +// for each queue: +// DRV_PIC32CGMAC_LibInitTransfer +// DRV_PIC32CGMAC_LibSysIntStatus_Clear +// DRV_PIC32CGMAC_LibSysInt_Enable +// DRV_PIC32CGMAC_LibTransferEnable +// DRV_GMAC_EventInit +// if failed: +// _MACDeinit +// "remaining initialization is done by DRV_ETHMAC_PIC32MACTasks" + +#[repr(C, align(8))] +struct RxBufferDescriptor { + words: UnsafeCell<[u32; 2]>, +} + +impl RxBufferDescriptor { + fn get_word_0(&self) -> u32 { + unsafe { + self.words + .get() + .cast::() + // .add(0) + .read_volatile() + } + } + + fn set_word_0(&self, val: u32) { + unsafe { + self.words + .get() + .cast::() + // .add(0) + .write_volatile(val) + } + } + + fn get_word_1(&self) -> u32 { + unsafe { self.words.get().cast::().add(1).read_volatile() } + } + + fn set_word_1(&self, val: u32) { + unsafe { self.words.get().cast::().add(1).write_volatile(val) } + } +} + +impl TxBufferDescriptor { + fn get_word_0(&self) -> u32 { + unsafe { + self.words + .get() + .cast::() + // .add(0) + .read_volatile() + } + } + + fn set_word_0(&self, val: u32) { + unsafe { + self.words + .get() + .cast::() + // .add(0) + .write_volatile(val) + } + } + + fn get_word_1(&self) -> u32 { + unsafe { self.words.get().cast::().add(1).read_volatile() } + } + + fn set_word_1(&self, val: u32) { + unsafe { self.words.get().cast::().add(1).write_volatile(val) } + } +} + +macro_rules! impl_gmac_pins { + ( + $( + $( #[$cfg:meta] )? + $Gmac:ident: { + GTXCK: [ $GtxckType:ty ], + GTXEN: [ $GtxenType:ty ], + GTX3: [ $Gtx3Type:ty ], + GTX2: [ $Gtx2Type:ty ], + GTX1: [ $Gtx1Type:ty ], + GTX0: [ $Gtx0Type:ty ], + GTXER: [ $GtxerType:ty ], + GRXCK: [ $GrxckType:ty ], + GRXDV: [ $GrxdvType:ty ], + GRX3: [ $Grx3Type:ty ], + GRX2: [ $Grx2Type:ty ], + GRX1: [ $Grx1Type:ty ], + GRX0: [ $Grx0Type:ty ], + GRXER: [ $GrxerType:ty ], + GCRS: [ $GcrsType:ty ], + GCOL: [ $GcolType:ty ], + GMDC: [ $GmdcType:ty ], + GMDIO: [ $GmdioType:ty ], + }, + )+ + ) => { + paste! { + $( + $( #[$cfg] )? + mod [<$Gmac:lower _impl>] { + use super::*; + + #[doc = "Trait that identifies valid GTXCK [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GtxckPin>] {} + #[doc = "Trait that identifies valid GTXEN [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GtxenPin>] {} + #[doc = "Trait that identifies valid GTX3 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Gtx3Pin>] {} + #[doc = "Trait that identifies valid GTX2 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Gtx2Pin>] {} + #[doc = "Trait that identifies valid GTX1 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Gtx1Pin>] {} + #[doc = "Trait that identifies valid GTX0 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Gtx0Pin>] {} + #[doc = "Trait that identifies valid GTXER [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GtxerPin>] {} + #[doc = "Trait that identifies valid GRXCK [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GrxckPin>] {} + #[doc = "Trait that identifies valid GRXDV [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GrxdvPin>] {} + #[doc = "Trait that identifies valid GRX3 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Grx3Pin>] {} + #[doc = "Trait that identifies valid GRX2 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Grx2Pin>] {} + #[doc = "Trait that identifies valid GRX1 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Grx1Pin>] {} + #[doc = "Trait that identifies valid GRX0 [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac Grx0Pin>] {} + #[doc = "Trait that identifies valid GRXER [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GrxerPin>] {} + #[doc = "Trait that identifies valid GCRS [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GcrsPin>] {} + #[doc = "Trait that identifies valid GCOL [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GcolPin>] {} + #[doc = "Trait that identifies valid GMDC [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GmdcPin>] {} + #[doc = "Trait that identifies valid GMDIO [`Pin`]s for [`" [<$Gmac:upper>] "`]."] + pub trait [<$Gmac GmdioPin>] {} + + impl [<$Gmac GtxckPin>] for $GtxckType {} + impl [<$Gmac GtxenPin>] for $GtxenType {} + impl [<$Gmac Gtx3Pin>] for $Gtx3Type {} + impl [<$Gmac Gtx2Pin>] for $Gtx2Type {} + impl [<$Gmac Gtx1Pin>] for $Gtx1Type {} + impl [<$Gmac Gtx0Pin>] for $Gtx0Type {} + impl [<$Gmac GtxerPin>] for $GtxerType {} + impl [<$Gmac GrxckPin>] for $GrxckType {} + impl [<$Gmac GrxdvPin>] for $GrxdvType {} + impl [<$Gmac Grx3Pin>] for $Grx3Type {} + impl [<$Gmac Grx2Pin>] for $Grx2Type {} + impl [<$Gmac Grx1Pin>] for $Grx1Type {} + impl [<$Gmac Grx0Pin>] for $Grx0Type {} + impl [<$Gmac GrxerPin>] for $GrxerType {} + impl [<$Gmac GcrsPin>] for $GcrsType {} + impl [<$Gmac GcolPin>] for $GcolType {} + impl [<$Gmac GmdcPin>] for $GmdcType {} + impl [<$Gmac GmdioPin>] for $GmdioType {} + + // TODO There are more valid permutations of GmacPins + impl [] for ( + $GtxckType, + $GtxenType, + $Gtx3Type, + $Gtx2Type, + $Gtx1Type, + $Gtx0Type, + $GtxerType, + $GrxckType, + $GrxdvType, + $Grx3Type, + $Grx2Type, + $Grx1Type, + $Grx0Type, + $GrxerType, + $GcrsType, + $GcolType, + $GmdcType, + $GmdioType, + ) {} + + impl [] for ( + $GtxckType, + $GtxenType, + $Gtx1Type, + $Gtx0Type, + $GrxdvType, + $Grx1Type, + $Grx0Type, + $GrxerType, + $GmdcType, + $GmdioType, + ) {} + + + + } + $( #[$cfg] )? + pub use [<$Gmac:lower _impl>]::*; + )+ + } + }; +} + +impl_gmac_pins!( + Gmac: { + GTXCK: [ Pin ], + GTXEN: [ Pin ], + GTX3: [ Pin ], + GTX2: [ Pin ], + GTX1: [ Pin ], + GTX0: [ Pin ], + GTXER: [ Pin ], + GRXCK: [ Pin ], + GRXDV: [ Pin ], + GRX3: [ Pin ], + GRX2: [ Pin ], + GRX1: [ Pin ], + GRX0: [ Pin ], + GRXER: [ Pin ], + GCRS: [ Pin ], + GCOL: [ Pin ], + GMDC: [ Pin ], + GMDIO: [ Pin ], + + }, +); + +#[repr(C, align(8))] +struct TxBufferDescriptor { + // TODO: Bitfields for this! + words: UnsafeCell<[u32; 2]>, +} + +#[repr(C, align(8))] +struct RxBuffer { + buf: UnsafeCell<[u8; RX_S]>, +} + +#[repr(C, align(8))] +struct TxBuffer { + buf: UnsafeCell<[u8; TX_S]>, +} + +unsafe impl Sync for RxBuffer {} +unsafe impl Sync for TxBuffer {} +unsafe impl Sync for RxBufferDescriptor {} +unsafe impl Sync for TxBufferDescriptor {} + +const RX_BUF_DESC_DEFAULT: RxBufferDescriptor = RxBufferDescriptor { + words: UnsafeCell::new([0u32; 2]), +}; + +const TX_BUF_DESC_DEFAULT: TxBufferDescriptor = TxBufferDescriptor { + words: UnsafeCell::new([0u32; 2]), +}; + +pub struct GmacBuffers { + tx_buf_desc: [TxBufferDescriptor; TX_N], + tx_buf: [TxBuffer; TX_N], + rx_buf_desc: [RxBufferDescriptor; RX_N], + rx_buf: [RxBuffer; RX_N], + next_tx_idx: usize, +} + +impl + GmacBuffers +{ + const RX_BUF_DEFAULT: RxBuffer = RxBuffer { + buf: UnsafeCell::new([0u8; RX_S]), + }; + const TX_BUF_DEFAULT: TxBuffer = TxBuffer { + buf: UnsafeCell::new([0u8; TX_S]), + }; + + pub const fn default() -> Self { + GmacBuffers { + tx_buf_desc: [TX_BUF_DESC_DEFAULT; TX_N], + tx_buf: [GmacBuffers::::TX_BUF_DEFAULT; TX_N], + rx_buf_desc: [RX_BUF_DESC_DEFAULT; RX_N], + rx_buf: [GmacBuffers::::RX_BUF_DEFAULT; RX_N], + next_tx_idx: 0, + } + } + + pub fn alloc_write_frame(&mut self) -> Option> { + let desc = &(self.tx_buf_desc[self.next_tx_idx]); + let w1 = desc.get_word_1(); + + // Is this packet ready to be used by software? + let ready = (w1 & 0x8000_0000) != 0; + if !ready { + return None; + } + let cur_idx = self.next_tx_idx; + + self.next_tx_idx = (cur_idx + 1) % TX_N; + + Some(WriteFrame { + bufr: NonNull::new(self.tx_buf[cur_idx].buf.get().cast())?, + desc: NonNull::new(self.tx_buf_desc[cur_idx].words.get().cast())?, + was_sent: false, + }) + } +} diff --git a/hal/src/lib.rs b/hal/src/lib.rs index 067b7e7c..97f2ebaa 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -28,7 +28,6 @@ The datasheet (DS60001527F) is available via [Microchip](https://ww1.microchip.c #![cfg_attr(not(test), no_std)] #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -#![deny(missing_docs)] #![deny(rustdoc::missing_crate_level_docs)] #![deny(rustdoc::invalid_codeblock_attributes)] #![deny(rustdoc::invalid_rust_codeblocks)] @@ -37,6 +36,7 @@ The datasheet (DS60001527F) is available via [Microchip](https://ww1.microchip.c pub use embedded_hal as ehal; pub use fugit; pub use nb; +pub use smoltcp; #[cfg(feature = "same70j19")] pub use atsame70j19 as pac; @@ -193,3 +193,5 @@ pub mod tc; pub mod usb; #[cfg(feature = "device-selected")] pub mod watchdog; +#[cfg(feature = "device-selected")] +pub mod gmac;