Netdev - Network Device Driver API

This is a generic low-level network driver interface.

About

This interface provides a uniform API for network stacks to interact with network device drivers. This interface is designed in a way, that it is completely agnostic to the used network stack. This way, device drivers for network devices (e.g. IEEE802.15.4 radios, Ethernet devices, …) have to implemented once and can be used with any supported network stack in RIOT.

The functions provided by the interface cover three major parts:

  1. sending and receiving of actual network data
  2. network device configuration through reading and setting device parameters
  3. event handling

The Interrupt Context Problem

Network devices are typically connected to the host CPU via some sort of bus, most commonly via SPI. This type of connection has the disadvantage, that the bus is not used by the network device alone, but it may be shared with other devices. This makes it necessary to synchronize access to the bus to prevent bus access collisions.

To illustrate this behavior, let’s look at a typical error situation, that leads to a very hard to find and debug latent failure: say we have two devices A and B on the same SPI bus. Our CPU is now transferring a chunk of 100 bytes to device A. After 20 bytes were transferred, device B triggers an external interrupt on the host CPU. The interrupt handling now typically requires the reading of some sort of status register on the ‘triggering’ device, device B in this case. So what would happen here, is that the device driver for device B would initiate a new SPI transfer on the already used bus to read B’s status register -> BAM.

The peripheral drivers for shared buses (i.e. SPI and I2C) implement access synchronization using mutexes, which are locked and unlocked in the driver’s require and release functions. The problem is now, that this type of synchronization does only work in thread context, but not in interrupt context. With reasonable effort and resource usage, we have no means of synchronizing the bus access also in interrupt context.

The solution to this problem as implemented by this interface is not to call any function that interacts with a device directly from interrupt context. Unfortunately this requires some added complexity for synchronization efforts between thread and interrupt context to be able to handle device events (i.e. external interrupts). See section for more information.

Context requirements

The netdev interface expects the network device drivers to run in thread context (see section above). The interface was however designed in a way, to allow more than one device driver to be serviced in the same thread.

The key design element for netdev is, that device drivers implementing this interface are not able to run stand-alone in a thread, but need some bootstrapping code. This bootstrapping code can be anything from a simple msg.h::msg_receive() loop (as done for the GNRC adaption) to a complete network stack that works without messaging entirely but is build on function call interfaces.

Sending and Receiving

Sending data using the netdev interface is straight forward: simply call the drivers netdev.h::netdev_driver::send function, passing it the data that should be sent. The caller of the netdev.h::netdev_driver::send function (e.g. a network stack) must hereby make sure, that the data is in the correct format expected by the specific network device driver. Typically, the data needs to contain a pre-filled link layer header as e.g. an IEEE802.15.4 or Ethernet header.

Receiving data using the netdev interface requires typically four steps:

  1. wait for a netdev.h::netdev_event_t::NETDEV_EVENT_RX_COMPLETE event
  2. call the netdev.h::netdev_driver::recv function with buf := NULL and len := 0 to get the size of the received data
  3. allocate a large enough buffer in some way
  4. call the netdev.h::netdev_driver::recv function a second time, passing the buffer and reading the received data into this buffer

This receive sequence can of course be simplified by skipping steps 2 and 3 when using fixed sized pre-allocated buffers or similar means. *

Note

The netdev.h::netdev_driver::send and netdev.h::netdev_driver::recv functions must never be called from interrupt context.

Device Configuration

The netdev interface covers a wide variety of network devices, which differ to some extend in their configuration parameters (e.g. radios vs. wired interfaces, channel selection vs. link status detection). To cover this variety, netdev provides a generic configuration interface by exposing simple netdev.h::netdev_driver::get and netdev.h::netdev_driver::set functions. These are based on a globally defined and extendable list of options as defined in netopt.h.

Every device driver can choose the options which it supports for reading and/or writing from this list. If an option is not supported by the device driver, the driver simply returns -ENOTSUP.

Note

The netdev.h::netdev_driver::get and netdev.h::netdev_driver::set functions must never be called from interrupt context.

Events

enum @143
NETDEV_TYPE_UNKNOWN
NETDEV_TYPE_RAW
NETDEV_TYPE_ETHERNET
NETDEV_TYPE_IEEE802154
NETDEV_TYPE_BLE
NETDEV_TYPE_CC110X
NETDEV_TYPE_LORA
NETDEV_TYPE_NRFMIN
NETDEV_TYPE_SLIP
NETDEV_TYPE_ESP_NOW
enum netdev_event_t
NETDEV_EVENT_ISR
driver needs it’s ISR handled
NETDEV_EVENT_RX_STARTED
started to receive a packet
NETDEV_EVENT_RX_COMPLETE
finished receiving a packet
NETDEV_EVENT_TX_STARTED
started to transfer a packet
NETDEV_EVENT_TX_COMPLETE
transfer packet complete
NETDEV_EVENT_TX_COMPLETE_DATA_PENDING
transfer packet complete and data pending flag
NETDEV_EVENT_TX_NOACK
ACK requested but not received.
NETDEV_EVENT_TX_MEDIUM_BUSY
couldn’t transfer packet
NETDEV_EVENT_LINK_UP
link established
NETDEV_EVENT_LINK_DOWN
link gone
NETDEV_EVENT_TX_TIMEOUT
timeout when sending
NETDEV_EVENT_RX_TIMEOUT
timeout when receiving
NETDEV_EVENT_CRC_ERROR
wrong CRC
NETDEV_EVENT_FHSS_CHANGE_CHANNEL
channel changed
NETDEV_EVENT_CAD_DONE
channel activity detection done
struct netdev netdev_t

Forward declaration for netdev struct.

void(* netdev_event_cb_t()

Event callback for signaling event to upper layers.

Parameters

type:type of the event

struct netdev_driver netdev_driver_t

Structure to hold driver interface -> function mapping.

The send/receive functions expect/return a full ethernet frame (dst mac, src mac, ethertype, payload, no checksum).

int netdev_get_notsup(netdev.h::netdev_t * dev, netopt.h::netopt_t opt, void * value, msp430_types.h::size_t max_len)

Convenience function for declaring get() as not supported in general.

Parameters

dev:ignored
opt:ignored
value:ignored
max_len:ignored

Return values

  • always returns -ENOTSUP
int netdev_set_notsup(netdev.h::netdev_t * dev, netopt.h::netopt_t opt, const void * value, msp430_types.h::size_t value_len)

Convenience function for declaring set() as not supported in general.

Parameters

dev:ignored
opt:ignored
value:ignored
value_len:ignored

Return values

  • always returns -ENOTSUP
struct netdev_radio_rx_info

Received packet status information for most radios.

May be different for certain radios.

int16_t rssi

RSSI of a received packet in dBm.

uint8_t lqi

LQI of a received packet.

struct netdev

Structure to hold driver state.

Supposed to be extended by driver implementations. The extended structure should contain all variable driver state.

Contains a field context which is not used by the drivers, but supposed to be used by upper layers to store reference information.

const struct netdev_driver * driver

ptr to that driver’s interface.

netdev.h::netdev_event_cb_t event_callback

callback for device events

void * context

ptr to network stack context

struct netdev_driver

Structure to hold driver interface -> function mapping.

The send/receive functions expect/return a full ethernet frame (dst mac, src mac, ethertype, payload, no checksum).

int(* send()

Send frame.

Parameters

dev:Network device descriptor. Must not be NULL.
iolist:io vector list to send

Return values

  • negative errno on error
  • number of bytes sent
int(* recv()

Get a received frame.

Supposed to be called from netdev.h::netdev::event_callback

If buf == NULL and len == 0, returns the packet size without dropping it. If buf == NULL and len > 0, drops the packet and returns the packet size.

Parameters

dev:network device descriptor. Must not be NULL.
buf:buffer to write into or NULL to return the packet size.
len:maximum number of bytes to read. If buf is NULL the currently buffered packet is dropped when len > 0. Must not be 0 when buf != NULL.
info:status information for the received packet. Might be of different type for different netdev devices. May be NULL if not needed or applicable.

Return values

  • -ENOBUFS if supplied buffer is too small
  • number of bytes read if buf != NULL
  • packet size if buf == NULL
int(* init()

the driver’s initialization function

Parameters

dev:network device descriptor. Must not be NULL.

Return values

  • < 0 on error
  • 0 on success
void(* isr()

a driver’s user-space ISR handler

This function will be called from a network stack’s loop when being notified by netdev_isr.

It is supposed to call netdev.h::netdev::event_callback for each occurring event.

See receive packet flow description for details.

Parameters

dev:network device descriptor. Must not be NULL.

int(* get()

Get an option value from a given network device.

Parameters

dev:network device descriptor
opt:option type
value:pointer to store the option’s value in
max_len:maximal amount of byte that fit into value

Return values

  • number of bytes written to value
  • -ENOTSUP if opt is not provided by the device
int(* set()

Set an option value for a given network device.

Parameters

dev:network device descriptor
opt:option type
value:value to set
value_len:the length of value

Return values

  • number of bytes written to value
  • -ENOTSUP if opt is not configurable for the device
  • -EINVAL if value is an invalid value with regards to opt