Skip to content
Snippets Groups Projects
Commit d0f84e54 authored by Hauke Petersen's avatar Hauke Petersen
Browse files

drivers/netdev2: added more comprehensive doc

parent 4bc1d291
No related branches found
No related tags found
No related merge requests found
/*
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de>
* 2015 Ell-i open source co-operative
* 2015 Freie Universität Berlin
* 2015-2017 Freie Universität Berlin
* 2014 Martine Lenders <mlenders@inf.fu-berlin.de>
*
* This file is subject to the terms and conditions of the GNU Lesser General
......@@ -15,20 +15,165 @@
* @brief This is a generic low-level network driver interface
* @{
*
* This interface is supposed to be a low-level interface for network drivers.
* # About
*
* Example call flow:
* 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)
* 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
* `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
*
......@@ -158,7 +303,8 @@ typedef struct netdev_driver {
* @pre `(dev != NULL)`
* @pre `(buf != NULL) && (len > 0)`
*
* Supposed to be called from @ref netdev_t::event_callback().
* 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.
......@@ -190,10 +336,12 @@ typedef struct netdev_driver {
*
* @pre `(dev != NULL)`
*
* This function will be called from a network stack's loop when being notified
* by netdev_isr.
* 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() for each occuring event.
* It is supposed to call
* @ref netdev_t::event_callback "netdev->event_callback()" for each
* occurring event.
*
* See receive packet flow description for details.
*
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment