-
Notifications
You must be signed in to change notification settings - Fork 2.1k
[RFC] netdev: change receive mechanism #11652
Description
Description
While working on #11483, I started evaluating some alternative receive procedures that could help to improve the performance of transceivers and maintainability.
Our current receive procedure using the netdev interface is the following:
- ISR from transceiver and message to netif thread (ISR offloading)
- Call
dev->driver->isr(dev)from thread context dev->event_callback(NETDEV_EVENT_RX_COMPLETE)is called fromdev->driver->isr(dev)function- The event callback gets the packet size with
dev->driver->recv(dev, NULL, 0) - The upper layer allocates a buffer and calls
dev->driver->recv(dev, buffer, pkt_size)
However, there are some disadvantages of this procedure:
- There are more SPI instructions and CPU cycles than needed when calling dev->driver->recv twice
- The implementation of the
dev->driver->recvfunction is tricky (usually radios don't understand the concept of "dropping" a packet, so there are blocks and chains of if-else in order to meet the requirements ofbuf != NULL,len != 0, etc. Also, some extra care is needed in order to avoid corrupting the frame buffer when reading the first received byte (PHY layer, Physical Service Data Unit length) withdev->driver->recv(dev, NULL, 0). - AFAIU, the concept of returning the packet length is for helping the upper layer (GNRC) to allocate the received packet. Considering in MAC layers like IEEE802.15.4 with ACK enabled half of transceiver traffic is Link Layer traffic, allocating packets for ACK is less efficient than having a fixed buffer.
Proposed procedure
I changed the receive procedure to indicate the rx of a packet, and modified the _recv function to simply write to a frame buffer (a.k.a rx_buf,127 bytes for IEEE802.15.4). I also added a PHY Indication function as a callback for the upper layer when a packet is received and a receive function:
The new procedure looks like:
- ISR from transceiver and message to netif thread (ISR offloading)
- Call
dev->driver->isr(dev)from thread context dev->event_callback(NETDEV_EVENT_RX_COMPLETE)is called fromdev->driver->isr(dev)function.- The event callback calls the
dev->driver->recv()function to write directly in a fixed size frame buffer (static or stack allocated), without the drop and pkt size logic. Save the pkt size in apsdu_lenvariable. - Call
phy_indication(dev, rx_buf, psdu_len)to indicate the upper layer a packet was received.
Results
For both setups (current dev->driver->recv and PHY indication), I'm using an AT86RF233 radio, a logic analyzer and GPIO pins to measure time. All times include gnrc_pktbuf allocation, which can be reduced for the PHY indication if the MAC layer is processed directly in rx_buf)
The logic for the measurement is:
- Set GPIO pin when RX_DONE is detected within the
dev->driver->isr(dev)function - Clear GPIO right after
netif->ops->recv()
For the PHY indication setup, I modified the dev->driver->recv function to (sort of) preserve the original behavior:
int _recv(...) {
if (buf != NULL) {
memcpy(buf, rx_buf, psdu_len);
}
return psdu_len;
}
Here is a graph comparing our current dev->driver->recv and the phy_indication mechanism for packets with different PSDU size:

This is the time overhead (%) of the current dev->driver->recv() procedure:

Note there's data duplication in rx_buf and the packet buffer, but could be reduced if the MAC layer is processed from the frame buffer
I've seen this pattern at least on OpenThread, OpenWSN and Linux (local buffers in stack). Contiki also has a similar approach.