embassy-net-driver-channel

Crates

git

Versions

default

Flavors

Skip to main content

Crate embassy_net_driver_channel

Crate embassy_net_driver_channel 

Source
Expand description

§embassy-net-driver-channel

This crate provides a toolkit for implementing embassy-net drivers in a higher level way than implementing the xarxa-driver Driver trait directly.

The Driver trait is polling-based. To implement it, you must write the packet receive/transmit state machines by hand, and hook up the Wakers provided by embassy-net to the right interrupt handlers so that embassy-net knows when to poll your driver again to make more progress.

With embassy-net-driver-channel you get a “channel-like” interface instead, where you can send/receive packets to/from embassy-net. The intended usage is to spawn a “driver task” in the background that does this, passing packets between the hardware and the channel.

§A note about deadlocks

When implementing a driver using this crate, it might be tempting to write it in the most straightforward way:

loop {
    // Wait for either..
    match select(
        // ... the chip signaling an interrupt, indicating a packet is available to receive, or
        irq_pin.wait_for_low(),
        // ... a packet to send appearing, i.e. embassy-net wants to send a packet
        tx_chan.tx(),
    ).await {
        Either::First(_) => {
            // a packet is ready to be received!
            rx_chan.rx_ready().await; // wait for space in the rx queue
            let mut buf = PacketBuf::try_new().unwrap();
            let n = receive_packet_over_spi(&mut buf).await;
            buf.set_len(n);
            rx_chan.rx(buf).await;
        }
        Either::Second(buf) => {
            // a packet is ready to be sent!
            send_packet_over_spi(&buf).await;
        }
    }
}

However, this code has a latent deadlock bug. The symptom is it can hang at rx_chan.rx_ready().await under load.

The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the embassy-net task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue.

The fix is to make sure to always service the TX queue while you’re waiting for space to become available in the RX queue. For example, select on either “a packet to send is available” or “INT is low AND there is room in the RX queue”:

loop {
    // Wait for either..
    match select(
        async {
            // ... the chip signaling an interrupt, indicating a packet is available to receive
            irq_pin.wait_for_low().await;
            // *AND* there being room in the rx queue...
            rx_chan.rx_ready().await;
        },
        // ... or a packet to send appearing, i.e. embassy-net wants to send a packet
        tx_chan.tx(),
    ).await {
        Either::First(()) => {
            // a packet is ready to be received!
            let mut buf = PacketBuf::try_new().unwrap();
            let n = receive_packet_over_spi(&mut buf).await;
            buf.set_len(n);
            rx_chan.rx(buf).await;
        }
        Either::Second(buf) => {
            // a packet is ready to be sent!
            send_packet_over_spi(&buf).await;
        }
    }
}

§Examples

These embassy-net drivers are implemented using this crate. You can look at them for inspiration.

§Interoperability

This crate can run on any executor.

Re-exports§

pub use xarxa_driver as driver;

Structs§

Device
Channel device.
MulticastFilter
A snapshot of the multicast filter list, returned by StateRunner::multicast_filter.
Runner
Channel runner.
RxRunner
RX runner.
State
Channel state.
StateRunner
State runner.
TxRunner
TX runner.

Constants§

MULTICAST_FILTER_SIZE
Max multicast hardware addresses the multicast filter list holds.

Functions§

new
Create a channel.