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;