Network Device Driver API

This is a generic low-level network driver interface. More...

Detailed Description

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 Events 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_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 send() function, passing it the data that should be sent. The caller of the 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_EVENT_RX_COMPLETE event
  2. call the 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 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 send() and 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 get() and 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 get() and set() functions must never be called from interrupt context.

Events

Network devices typically signal events by triggering external interrupts on certain dedicated GPIO pins (in case of external devices), or signal them by triggering internal interrupts directly (in case of register mapped devices). As stated above, we are not allowed to do any kind of interaction with our network device that involves bus access when in interrupt mode. To circumvent this, the

  1. an interrupt is triggered
  2. the drivers interrupt routine calls the registered netdev->event_callback() function with event:= NETDEV_EVENT_ISR as argument
  3. the netdev->event_callback() (as it is implemented by the 'user' code) notifies the thread that hosts the device driver. This can be done in many ways, e.g. by using messaging, mutexes, thread flags and more
  4. the hosting thread is scheduled and calls the netdev interfaces isr() function
  5. now the driver can actual start to handle the interrupt, by e.g. reading status registers and triggering any subsequent actions like signaling a NETDEV_EVENT_RX_COMPLETE

The way that is used for waking up the hosting thread and telling is to call the isr() function is completely up to the netdev external code and can be done in many ways (e.g. sending messages, # setting thread flags, unlocking mutexes, etc.).

Any event that is not of type NETDEV_EVENT_ISR is expected to be triggered from thread context. This enables the code that sits on top of netdev to perform the necessary actions right away, as for example reading the received data from the network device or similar.

Note
The netdev_event_cb_t function runs in interrupt context when called for NETDEV_EVENT_ISR, but it must run in thread context for all other events.

Example

The following example illustrates a receive sequence triggered by an external interrupt:

  1. packet arrives for device
  2. The driver previously registered an ISR for handling received packets. This ISR then calls netdev->event_callback() with event:=NETDEV_EVENT_ISR (from Interrupt Service Routine) which wakes up event handler
  3. event handler calls netdev->driver->isr() (from thread context)
  4. netdev->driver->isr() calls netdev->event_callback() with event:=NETDEV_EVENT_RX_COMPLETE
  5. netdev->event_callback() uses netdev->driver->recv() to fetch packet
riot-netdev-rx.svg
RX event example

Files

file  eth.h
 Definitions for netdev common ethernet code.
 
file  drivers/include/net/netdev/ieee802154.h
 Definitions for netdev common IEEE 802.15.4 code.
 
file  netdev.h
 Definitions low-level network driver interface.
 

Data Structures

struct  netdev_radio_rx_info
 Received packet status information for most radios. More...
 
struct  netdev
 Structure to hold driver state. More...
 
struct  netdev_driver
 Structure to hold driver interface -> function mapping. More...
 

Typedefs

typedef struct netdev netdev_t
 Forward declaration for netdev struct.
 
typedef void(* netdev_event_cb_t) (netdev_t *dev, netdev_event_t event)
 Event callback for signaling event to upper layers. More...
 
typedef struct netdev_driver netdev_driver_t
 Structure to hold driver interface -> function mapping. More...
 

Enumerations

enum  {
  NETDEV_TYPE_UNKNOWN, NETDEV_TYPE_RAW, NETDEV_TYPE_ETHERNET, NETDEV_TYPE_IEEE802154,
  NETDEV_TYPE_CC110X, NETDEV_TYPE_LORA, NETDEV_TYPE_NRFMIN, NETDEV_TYPE_SLIP
}
 
enum  netdev_event_t {
  NETDEV_EVENT_ISR, NETDEV_EVENT_RX_STARTED, NETDEV_EVENT_RX_COMPLETE, NETDEV_EVENT_TX_STARTED,
  NETDEV_EVENT_TX_COMPLETE, NETDEV_EVENT_TX_COMPLETE_DATA_PENDING, NETDEV_EVENT_TX_NOACK, NETDEV_EVENT_TX_MEDIUM_BUSY,
  NETDEV_EVENT_LINK_UP, NETDEV_EVENT_LINK_DOWN, NETDEV_EVENT_TX_TIMEOUT, NETDEV_EVENT_RX_TIMEOUT,
  NETDEV_EVENT_CRC_ERROR, NETDEV_EVENT_FHSS_CHANGE_CHANNEL, NETDEV_EVENT_CAD_DONE
}
 Possible event types that are send from the device driver to the upper layer. More...
 

Typedef Documentation

◆ 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).

◆ netdev_event_cb_t

typedef void(* netdev_event_cb_t) (netdev_t *dev, netdev_event_t event)

Event callback for signaling event to upper layers.

Parameters
[in]typetype of the event

Definition at line 263 of file netdev.h.

Enumeration Type Documentation

◆ netdev_event_t

Possible event types that are send from the device driver to the upper layer.

Enumerator
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

Definition at line 224 of file netdev.h.