/* * Copyright (C) 2015 Kaspar Schleiser * 2015 Ell-i open source co-operative * 2015-2017 Freie Universität Berlin * 2014 Martine Lenders * * This file is subject to the terms and conditions of the GNU Lesser General * Public License v2.1. See the file LICENSE in the top level directory for * more details. */ /** * @defgroup drivers_netdev_api Network Device Driver API * @ingroup drivers_netdev * @brief 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 * @ref netdev_sec_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 @ref netdev_driver_t::send "send()" function, passing it the data * that should be sent. The caller of the @ref netdev_driver_t::send "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 @ref NETDEV_EVENT_RX_COMPLETE event * 2. call the @ref netdev_driver_t::recv "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 @ref netdev_driver_t::recv "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 @ref netdev_driver_t::send "send()" and * @ref netdev_driver_t::recv "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 @ref netdev_driver_t::get "get()" and * @ref netdev_driver_t::set "set()" functions. These are based on a globally * defined and **extendable** list of options as defined in @ref 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 @ref netdev_driver_t::get "get()" and * @ref netdev_driver_t::set "set()" functions **must** never be called * from interrupt context. * * * # Events {#netdev_sec_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 @ref * netdev_t::event_callback "netdev->event_callback()" function with * `event:=` @ref NETDEV_EVENT_ISR as argument * 3. the @ref netdev_t::event_callback "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 * @ref netdev_driver_t::isr "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 @ref NETDEV_EVENT_RX_COMPLETE * * The way that is used for waking up the hosting thread and telling is to call * the @ref netdev_driver_t::isr "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 @ref 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 @ref netdev_event_cb_t function runs in interrupt context when * called for @ref 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 @ref netdev_t::event_callback "netdev->event_callback()" * with `event:= `@ref NETDEV_EVENT_ISR (from Interrupt Service Routine) * which wakes up event handler * 3. event handler calls @ref netdev_driver_t::isr "netdev->driver->isr()" * (from thread context) * 4. @ref netdev_driver_t::isr "netdev->driver->isr()" calls * @ref netdev_t::event_callback "netdev->event_callback()" with * `event:= `@ref NETDEV_EVENT_RX_COMPLETE * 5. @ref netdev_t::event_callback "netdev->event_callback()" uses * @ref netdev_driver_t::recv "netdev->driver->recv()" to fetch packet * * ![RX event example](riot-netdev-rx.svg) * * @file * @brief Definitions low-level network driver interface * * @author Kaspar Schleiser * @author Martine Lenders * @author Hauke Petersen */ #ifndef NET_NETDEV_H #define NET_NETDEV_H #ifdef __cplusplus extern "C" { #endif #include #include #include "net/netopt.h" #ifdef MODULE_NETSTATS_L2 #include "net/netstats.h" #endif #ifdef MODULE_L2FILTER #include "net/l2filter.h" #endif 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, }; /** * @brief Possible event types that are send from the device driver to the * upper layer */ typedef enum { 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 */ /* expand this list if needed */ } netdev_event_t; /** * @brief Received packet status information for most radios * * May be different for certain radios. */ struct netdev_radio_rx_info { uint8_t rssi; /**< RSSI of a received packet */ uint8_t lqi; /**< LQI of a received packet */ }; /** * @brief Forward declaration for netdev struct */ typedef struct netdev netdev_t; /** * @brief Event callback for signaling event to upper layers * * @param[in] type type of the event */ typedef void (*netdev_event_cb_t)(netdev_t *dev, netdev_event_t event); /** * @brief Structure to hold driver state * * Supposed to be extended by driver implementations. * The extended structure should contain all variable driver state. * * Contains a field @p context which is not used by the drivers, but supposed to * be used by upper layers to store reference information. */ struct netdev { const struct netdev_driver *driver; /**< ptr to that driver's interface. */ netdev_event_cb_t event_callback; /**< callback for device events */ void* context; /**< ptr to network stack context */ #ifdef MODULE_NETSTATS_L2 netstats_t stats; /**< transceiver's statistics */ #endif #ifdef MODULE_L2FILTER l2filter_t filter[L2FILTER_LISTSIZE]; /**< link layer address filters */ #endif }; /** * @brief 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). */ typedef struct netdev_driver { /** * @brief Send frame * * @pre `(dev != NULL)` * @pre `(count == 0) || (vector != NULL)` * (`(count != 0) => (vector != NULL)`) * * @param[in] dev network device descriptor * @param[in] vector io vector array to send * @param[in] count nr of entries in vector * * @return number of bytes sent, or `< 0` on error */ int (*send)(netdev_t *dev, const struct iovec *vector, unsigned count); /** * @brief Get a received frame * * @pre `(dev != NULL)` * @pre `(buf != NULL) && (len > 0)` * * Supposed to be called from * @ref netdev_t::event_callback "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. * * @param[in] dev network device descriptor * @param[out] buf buffer to write into or NULL * @param[in] len maximum number of bytes to read * @param[out] 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 `< 0` on error * @return number of bytes read if buf != NULL * @return packet size if buf == NULL */ int (*recv)(netdev_t *dev, void *buf, size_t len, void *info); /** * @brief the driver's initialization function * * @pre `(dev != NULL)` * * @return `< 0` on error, 0 on success */ int (*init)(netdev_t *dev); /** * @brief a driver's user-space ISR handler * * @pre `(dev != NULL)` * * This function will be called from a network stack's loop when being * notified by netdev_isr. * * It is supposed to call * @ref netdev_t::event_callback "netdev->event_callback()" for each * occurring event. * * See receive packet flow description for details. * * @param[in] dev network device descriptor */ void (*isr)(netdev_t *dev); /** * @brief Get an option value from a given network device * * @pre `(dev != NULL)` * * @param[in] dev network device descriptor * @param[in] opt option type * @param[out] value pointer to store the option's value in * @param[in] max_len maximal amount of byte that fit into @p value * * @return number of bytes written to @p value * @return `< 0` on error, 0 on success */ int (*get)(netdev_t *dev, netopt_t opt, void *value, size_t max_len); /** * @brief Set an option value for a given network device * * @pre `(dev != NULL)` * * @param[in] dev network device descriptor * @param[in] opt option type * @param[in] value value to set * @param[in] value_len the length of @p value * * @return number of bytes used from @p value * @return `< 0` on error, 0 on success */ int (*set)(netdev_t *dev, netopt_t opt, const void *value, size_t value_len); } netdev_driver_t; #ifdef __cplusplus } #endif #endif /* NET_NETDEV_H */ /** @} */