-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Works on my hardware (1170EVK, and a Netgear R7000 router w/ stock firmware). Note that I'm throttling the ENET's receive speed to 10Mbit/s, full-duplex; see previous commit for more info.
- Loading branch information
Showing
1 changed file
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
//! Demonstrates a TCP loopback server with DHCP address assignment. | ||
//! | ||
//! The device acquires an IP address using DHCP. This can take a few seconds, so | ||
//! be patient. Plug it into your DHCP-capable router, and wait for it to assign | ||
//! an IP address. Check the device log for information on the assigned IP, or | ||
//! check your router. The LED turns on once DHCP assignment completes. | ||
//! | ||
//! Once the device has an IP, send it TCP segments with messages to port 5000. | ||
//! The device should send back the same message. | ||
#![no_std] | ||
#![no_main] | ||
|
||
#[rtic::app(device = board, peripherals = false, dispatchers = [BOARD_SWTASK0])] | ||
mod app { | ||
|
||
/// When using the LPUART backend, you want to occasionally poll | ||
/// for new log message. This interval (milliseconds) describes how | ||
/// long you should wait in between polls. | ||
/// | ||
/// This constant does nothing when the USBD backend is used, since | ||
/// the USBD backend uses its own timer for this purpose. | ||
const LPUART_POLL_INTERVAL_MS: u32 = board::PIT_FREQUENCY / 1_000 * 4; | ||
|
||
use board::smoltcp; | ||
use hal::enet; | ||
use imxrt_hal as hal; | ||
|
||
use smoltcp::iface::Config; | ||
use smoltcp::iface::Interface; | ||
use smoltcp::iface::SocketSet; | ||
use smoltcp::socket::dhcpv4; | ||
use smoltcp::socket::tcp; | ||
use smoltcp::time::Instant; | ||
use smoltcp::wire::EthernetAddress; | ||
use smoltcp::wire::IpCidr; | ||
use smoltcp::wire::IpListenEndpoint; | ||
|
||
#[local] | ||
struct Local { | ||
/// This timer tells us how frequently to poll | ||
/// for logs. It's only used with the LPUART | ||
/// logging backend. | ||
poll_log: hal::pit::Pit<1>, | ||
/// For realizing blocking delays in the idle loop. | ||
delay: hal::timer::BlockingGpt<1, { board::GPT1_FREQUENCY }>, | ||
/// The ethernet instance. | ||
/// | ||
/// Taken by the idle task. | ||
enet: Option<imxrt_ral::enet::ENET>, | ||
led: board::Led, | ||
} | ||
|
||
#[shared] | ||
struct Shared { | ||
/// The poller drives the logging backend. | ||
poller: board::logging::Poller, | ||
} | ||
|
||
#[init] | ||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { | ||
let ( | ||
board::Common { | ||
pit: (_, mut poll_log, _, _), | ||
gpt1, | ||
usb1, | ||
usbnc1, | ||
usbphy1, | ||
mut dma, | ||
.. | ||
}, | ||
board::Specifics { | ||
led, console, enet, .. | ||
}, | ||
) = board::new(); | ||
|
||
// We only need the extra timer when the LPUART backend is used. | ||
// The USBD backend uses the USB peripheral's internal timer to | ||
// track time for us. | ||
if board::logging::BACKEND == board::logging::Backend::Lpuart { | ||
poll_log.set_load_timer_value(LPUART_POLL_INTERVAL_MS); | ||
poll_log.set_interrupt_enable(true); | ||
poll_log.enable(); | ||
} else { | ||
poll_log.disable(); | ||
} | ||
|
||
let usbd = hal::usbd::Instances { | ||
usb: usb1, | ||
usbnc: usbnc1, | ||
usbphy: usbphy1, | ||
}; | ||
|
||
let dma_a = dma[board::BOARD_DMA_A_INDEX].take().unwrap(); | ||
let poller = board::logging::init( | ||
board::logging::Frontend::Log, | ||
board::logging::BACKEND, | ||
console, | ||
dma_a, | ||
usbd, | ||
); | ||
|
||
let delay = hal::timer::Blocking::<_, { board::GPT1_FREQUENCY }>::from_gpt(gpt1); | ||
|
||
( | ||
Shared { poller }, | ||
Local { | ||
poll_log, | ||
delay, | ||
enet: Some(enet), | ||
led, | ||
}, | ||
init::Monotonics(), | ||
) | ||
} | ||
|
||
#[idle(local = [ | ||
delay, enet, led, | ||
txdt: enet::TransmitBuffers<2, 1520> = enet::TransmitBuffers::new(), | ||
rxdt: enet::ReceiveBuffers<2, 1520> = enet::ReceiveBuffers::new(), | ||
tcp_buf: [u8; 1024] = [0; 1024], | ||
msg: [u8; 512] = [0; 512], | ||
])] | ||
fn idle(cx: idle::Context) -> ! { | ||
// Ethernet setup happens here, after init, in order to use interrupt-driven logging. | ||
let idle::LocalResources { | ||
enet, | ||
delay, | ||
led, | ||
txdt, | ||
rxdt, | ||
tcp_buf, | ||
msg, | ||
} = cx.local; | ||
let enet = enet.take().unwrap(); | ||
|
||
delay.block_ms(2000); | ||
let mut dev = enet::Enet::new( | ||
enet, | ||
txdt.take(), | ||
rxdt.take(), | ||
board::ENET_BUS_FREQUENCY, | ||
&board::ENET_MAC, | ||
); | ||
delay.block_ms(200); | ||
|
||
log::info!("Initializing the PHY, and waiting for link..."); | ||
// Call blocks until the entire link is up! TODO(mciantyre) not block while the link isn't available. | ||
while let Err(what) = board::init_enet_phy(&mut dev) { | ||
log::error!("Failed to initialize PHY: {what}"); | ||
delay.block_ms(2000); | ||
} | ||
|
||
dev.set_duplex(hal::enet::Duplex::Full); | ||
dev.enable_mib(true); | ||
dev.enable_rmii_mode(true); | ||
dev.enable_10t_mode(true); | ||
dev.enable_mac(true); | ||
|
||
let mut time: i64 = 0; | ||
|
||
let mut iface = Interface::new( | ||
Config::new(EthernetAddress(board::ENET_MAC).into()), | ||
&mut dev, | ||
Instant::from_millis(time), | ||
); | ||
|
||
let tcp_split = tcp_buf.len() / 2; | ||
let tcp_splits = tcp_buf.split_at_mut(tcp_split); | ||
let mut tcp_socket = tcp::Socket::new( | ||
tcp::SocketBuffer::new(tcp_splits.0), | ||
tcp::SocketBuffer::new(tcp_splits.1), | ||
); | ||
tcp_socket.set_nagle_enabled(false); | ||
if let Err(err) = tcp_socket.listen(IpListenEndpoint { | ||
addr: None, | ||
port: 5000, | ||
}) { | ||
log::error!("Failed to listen on TCP socket: {err}"); | ||
panic!(); | ||
} | ||
|
||
let dhcp_socket = dhcpv4::Socket::new(); | ||
|
||
let mut sockets: [_; 2] = Default::default(); | ||
let mut sockets = SocketSet::new(sockets.as_mut_slice()); | ||
let tcp_handle = sockets.add(tcp_socket); | ||
let dhcp_handle = sockets.add(dhcp_socket); | ||
|
||
log::info!("Waiting for DHCP assignment..."); | ||
loop { | ||
time += 100; | ||
delay.block_ms(100); | ||
|
||
iface.poll(Instant::from_millis(time), &mut dev, &mut sockets); | ||
let dhcp_socket: &mut dhcpv4::Socket = sockets.get_mut(dhcp_handle); | ||
if let Some(dhcpv4::Event::Configured(config)) = dhcp_socket.poll() { | ||
led.set(); | ||
log::info!("Received IP assignment! Address is {:?}", config.address); | ||
iface.update_ip_addrs(|ips| { | ||
ips.push(IpCidr::Ipv4(config.address)).unwrap(); | ||
}); | ||
break; | ||
} | ||
} | ||
|
||
log::info!("Entering TCP loopback mode using port 5000"); | ||
let msg = msg.as_mut_slice(); | ||
loop { | ||
time += 10; | ||
delay.block_ms(10); | ||
if iface.poll(Instant::from_millis(time), &mut dev, &mut sockets) { | ||
let tcp_socket: &mut tcp::Socket = sockets.get_mut(tcp_handle); | ||
let available = match tcp_socket.recv_slice(msg) { | ||
Err(err) => { | ||
log::error!("TCP receive error {err:?}"); | ||
continue; | ||
} | ||
Ok(n) => n, | ||
}; | ||
|
||
if 0 == available { | ||
continue; | ||
} | ||
|
||
log::info!("Received {available} bytes from a client"); | ||
if let Some(remote) = tcp_socket.remote_endpoint() { | ||
log::info!("Remote client: {remote:?}"); | ||
} else { | ||
log::warn!("Not sure of the remote's IP address!"); | ||
} | ||
|
||
if let Err(err) = tcp_socket.send_slice(&msg[..available]) { | ||
log::error!("TCP send error {err:?}"); | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// This interrupt fires | ||
/// | ||
/// - when log messages have been written (to the USB host). | ||
/// - every few milliseconds; check the imxrt-log docs for the | ||
/// specific number. | ||
#[task(binds = BOARD_USB1, priority = 1)] | ||
fn usb_interrupt(_: usb_interrupt::Context) { | ||
poll_logger::spawn().unwrap(); | ||
} | ||
|
||
/// This interrupt fires | ||
/// | ||
/// - when log messages have been written. | ||
/// | ||
/// Notice how there's no "periodic" or "time" component here. | ||
/// When using the LPUART backend, you should use another time | ||
/// source, or a polling loop, to make sure poll periodically | ||
/// happens. Without this, you won't see your log messages. | ||
#[task(binds = BOARD_DMA_A, priority = 1)] | ||
fn dma_interrupt(_: dma_interrupt::Context) { | ||
poll_logger::spawn().unwrap(); | ||
} | ||
|
||
/// Actually performs the poll call. | ||
#[task(shared = [poller], priority = 2)] | ||
fn poll_logger(mut cx: poll_logger::Context) { | ||
cx.shared.poller.lock(|poller| poller.poll()); | ||
} | ||
|
||
/// Periodically runs when using the LPUART logger to drain the log queue. | ||
#[task(binds = BOARD_PIT, local = [poll_log], priority = 1)] | ||
fn pit_interrupt(cx: pit_interrupt::Context) { | ||
let pit_interrupt::LocalResources { poll_log } = cx.local; | ||
while poll_log.is_elapsed() { | ||
poll_log.clear_elapsed(); | ||
} | ||
poll_logger::spawn().unwrap(); | ||
} | ||
} |