#[cfg(not(any(feature = "ethernet", feature = "wifi")))]
compile_error!(
"the `net` feature on its own compiles a priority walk with no interface to find: \
enable `ethernet`, `wifi`, or both alongside it"
);
#[cfg(feature = "ethernet")]
use core::ops::ControlFlow;
use rpi_hal::timer::Timer;
#[cfg(feature = "ethernet")]
use rpi_hal::usb::dwc2::{Channel, Dwc2Host};
#[cfg(feature = "ethernet")]
use rpi_hal::usb::ethernet::Ethernet;
#[cfg(feature = "ethernet")]
use rpi_hal::usb::lan7800::Lan7800;
#[cfg(feature = "ethernet")]
use rpi_hal::usb::lan9514::Lan9514;
#[cfg(feature = "ethernet")]
use rpi_hal::usb::{Bus, Event};
#[cfg(feature = "wifi")]
use rpi_hal::wifi::Wifi;
use crate::logln;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Connection {
Ethernet,
Wifi,
}
impl Connection {
pub fn parse(name: &str) -> Option<Self> {
match name {
"ethernet" => Some(Connection::Ethernet),
"wifi" => Some(Connection::Wifi),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Connection::Ethernet => "ethernet",
Connection::Wifi => "wifi",
}
}
}
pub const MAX_CONNECTIONS: usize = 4;
#[derive(Clone, Copy, Debug)]
pub struct Config {
pub order: [Option<Connection>; MAX_CONNECTIONS],
pub chip_timeout_ms: u32,
pub link_timeout_ms: u32,
}
impl Default for Config {
fn default() -> Self {
Self {
order: [
Some(Connection::Ethernet),
Some(Connection::Wifi),
None,
None,
],
chip_timeout_ms: 10_000,
link_timeout_ms: 5_000,
}
}
}
pub struct Hardware<'a> {
#[cfg(feature = "ethernet")]
usb: Option<&'a Dwc2Host>,
#[cfg(feature = "wifi")]
wifi: Option<&'a mut dyn FnMut() -> Option<Wifi>>,
}
impl<'a> Hardware<'a> {
pub fn new() -> Self {
Self {
#[cfg(feature = "ethernet")]
usb: None,
#[cfg(feature = "wifi")]
wifi: None,
}
}
#[cfg(feature = "ethernet")]
pub fn usb(mut self, dwc2: &'a Dwc2Host) -> Self {
self.usb = Some(dwc2);
self
}
#[cfg(feature = "wifi")]
pub fn wifi(mut self, bring_up: &'a mut dyn FnMut() -> Option<Wifi>) -> Self {
self.wifi = Some(bring_up);
self
}
}
impl Default for Hardware<'_> {
fn default() -> Self {
Self::new()
}
}
#[allow(clippy::large_enum_variant)]
pub enum Interface {
#[cfg(feature = "ethernet")]
Lan9514(Lan9514, [u8; 6]),
#[cfg(feature = "ethernet")]
Lan7800(Lan7800, [u8; 6]),
#[cfg(feature = "wifi")]
Wifi(Wifi, [u8; 6]),
}
impl Interface {
pub fn connection(&self) -> Connection {
match self {
#[cfg(feature = "ethernet")]
Interface::Lan9514(..) | Interface::Lan7800(..) => Connection::Ethernet,
#[cfg(feature = "wifi")]
Interface::Wifi(..) => Connection::Wifi,
}
}
pub fn name(&self) -> &'static str {
match self {
#[cfg(feature = "ethernet")]
Interface::Lan9514(..) => "LAN9514",
#[cfg(feature = "ethernet")]
Interface::Lan7800(..) => "LAN7800",
#[cfg(feature = "wifi")]
Interface::Wifi(..) => "Wi-Fi",
}
}
pub fn mac(&self) -> [u8; 6] {
match self {
#[cfg(feature = "ethernet")]
Interface::Lan9514(_, mac) | Interface::Lan7800(_, mac) => *mac,
#[cfg(feature = "wifi")]
Interface::Wifi(_, mac) => *mac,
}
}
}
#[cfg_attr(not(feature = "ethernet"), allow(unused_variables))]
pub fn discover(
hardware: &mut Hardware<'_>,
timer: &Timer,
mac: [u8; 6],
config: &Config,
) -> Option<Interface> {
for entry in config.order.iter().flatten() {
match entry {
Connection::Ethernet => {
#[cfg(feature = "ethernet")]
if let Some(interface) = discover_ethernet(hardware, timer, mac, config) {
return Some(interface);
}
#[cfg(not(feature = "ethernet"))]
logln!(
"net: `ethernet` is in the connection order, but this image was \
built without the `ethernet` feature"
);
}
Connection::Wifi => {
#[cfg(feature = "wifi")]
if let Some(interface) = discover_wifi(hardware, timer) {
return Some(interface);
}
#[cfg(not(feature = "wifi"))]
logln!(
"net: `wifi` is in the connection order, but this image was built \
without the `wifi` feature"
);
}
}
}
logln!("net: no interface came up");
None
}
#[cfg(feature = "wifi")]
fn discover_wifi(hardware: &mut Hardware<'_>, timer: &Timer) -> Option<Interface> {
let Some(bring_up) = hardware.wifi.as_mut() else {
logln!(
"net: `wifi` is in the connection order, but the board offered no way to \
bring the radio up"
);
return None;
};
let mut wifi = bring_up()?;
let mut mac = [0u8; 6];
match wifi.get_iovar("cur_etheraddr", &mut mac, timer) {
Ok(6) => Some(Interface::Wifi(wifi, mac)),
Ok(n) => {
logln!("net: the radio joined but reported a {n}-byte address");
None
}
Err(e) => {
logln!("net: the radio joined but would not report its address: {e:?}");
None
}
}
}
#[cfg(feature = "ethernet")]
fn discover_ethernet(
hardware: &mut Hardware<'_>,
timer: &Timer,
mac: [u8; 6],
config: &Config,
) -> Option<Interface> {
let Some(dwc2) = hardware.usb else {
logln!("net: `ethernet` is in the connection order, but the board offered no USB host");
return None;
};
let mut waited_ms = 0;
while !dwc2.port_connected() {
if waited_ms >= config.chip_timeout_ms {
logln!(
"net: nothing on the USB root port within {}ms — USB power \
or the DWC2 bring-up is the suspect, not the chip",
config.chip_timeout_ms
);
return None;
}
timer.delay_ms(POLL_INTERVAL_MS);
waited_ms += POLL_INTERVAL_MS;
}
logln!("net: root port reported a device after {waited_ms}ms");
let mut bus = Bus::new(dwc2);
let mut found = None;
if let Err(e) = bus.enumerate(timer, |channel, timer, event| {
found = claim(channel, timer, event, mac);
stop_when(&found)
}) {
logln!("net: enumeration failed: {e:?}");
return None;
}
while found.is_none() {
if waited_ms >= config.chip_timeout_ms {
logln!(
"net: no ethernet chip within {}ms — a board without one, \
or one slower than that deadline",
config.chip_timeout_ms
);
return None;
}
timer.delay_ms(POLL_INTERVAL_MS);
waited_ms += POLL_INTERVAL_MS;
if let Err(e) = bus.poll(timer, |channel, timer, event| {
found = claim(channel, timer, event, mac);
stop_when(&found)
}) {
logln!("net: bus poll failed: {e:?}");
}
}
let mut interface = found?;
logln!("net: found {}", interface.name());
let Some(mut channel) = dwc2.alloc_channel() else {
logln!("net: no free host channel to probe the link with");
return None;
};
if !link_up_within(&mut interface, &mut channel, timer, mac, config) {
return None;
}
Some(interface)
}
#[cfg(feature = "ethernet")]
fn link_up_within(
interface: &mut Interface,
channel: &mut Channel,
timer: &Timer,
mac: [u8; 6],
config: &Config,
) -> bool {
let name = interface.name();
match interface {
Interface::Lan9514(dev, _) => probe_link(dev, channel, timer, mac, config, name),
Interface::Lan7800(dev, _) => probe_link(dev, channel, timer, mac, config, name),
#[cfg(feature = "wifi")]
Interface::Wifi(..) => false,
}
}
#[cfg(feature = "ethernet")]
fn probe_link<E: Ethernet>(
dev: &mut E,
channel: &mut Channel,
timer: &Timer,
mac: [u8; 6],
config: &Config,
name: &str,
) -> bool {
if let Err(e) = dev.start(channel, timer, mac) {
logln!("net: {name} would not start: {e:?}");
return false;
}
let mut waited_ms = 0;
loop {
match dev.is_link_up(channel, timer) {
Ok(true) => {
logln!("net: link up after {waited_ms}ms");
return true;
}
Ok(false) => {}
Err(e) => {
logln!("net: link check failed: {e:?}");
return false;
}
}
if waited_ms >= config.link_timeout_ms {
logln!(
"net: no link within {}ms — the chip is there and nothing \
is plugged into it",
config.link_timeout_ms
);
return false;
}
timer.delay_ms(POLL_INTERVAL_MS);
waited_ms += POLL_INTERVAL_MS;
}
}
#[cfg(feature = "ethernet")]
const POLL_INTERVAL_MS: u32 = 250;
#[cfg(feature = "ethernet")]
fn stop_when(found: &Option<Interface>) -> ControlFlow<()> {
if found.is_some() {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}
#[cfg(feature = "ethernet")]
fn claim(channel: &mut Channel, timer: &Timer, event: Event, mac: [u8; 6]) -> Option<Interface> {
let Event::Attached(device) = event else {
return None;
};
match Lan9514::from_device(channel, timer, device) {
Ok(Some(dev)) => return Some(Interface::Lan9514(dev, mac)),
Ok(None) => {}
Err(e) => {
logln!("net: LAN9514 setup failed: {e:?}");
return None;
}
}
match Lan7800::from_device(channel, timer, device) {
Ok(Some(dev)) => Some(Interface::Lan7800(dev, mac)),
Ok(None) => None,
Err(e) => {
logln!("net: LAN7800 setup failed: {e:?}");
None
}
}
}