Skip to content

[RFC] netdev: change receive mechanism #11652

@jia200x

Description

@jia200x

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:

  1. ISR from transceiver and message to netif thread (ISR offloading)
  2. Call dev->driver->isr(dev) from thread context
  3. dev->event_callback(NETDEV_EVENT_RX_COMPLETE) is called from dev->driver->isr(dev) function
  4. The event callback gets the packet size with dev->driver->recv(dev, NULL, 0)
  5. 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->recv function 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 of buf != 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) with dev->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:

  1. ISR from transceiver and message to netif thread (ISR offloading)
  2. Call dev->driver->isr(dev) from thread context
  3. dev->event_callback(NETDEV_EVENT_RX_COMPLETE) is called from dev->driver->isr(dev) function.
  4. 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 a psdu_len variable.
  5. 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:

  1. Set GPIO pin when RX_DONE is detected within the dev->driver->isr(dev) function
  2. 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.

Useful links

Metadata

Metadata

Labels

Discussion: RFCThe issue/PR is used as a discussion starting point about the item of the issue/PRState: staleState: The issue / PR has no activity for >185 days

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions