L2 Stack and Drivers¶
The L2 stack is designed to hide the whole networking link-layer part
and the related device drivers from the higher IP stack. This is made
through a unique object known as the “network interface object”:
struct net_if declared in
The IP layer is unaware of implementation details beyond the net_if
object and the generic API provided by the L2 layer in
Only the L2 layer can talk to the device driver, linked to the net_if object. The L2 layer dictates the API provided by the device driver, specific for that device, and optimized for working together.
Currently, there are L2 layers for Ethernet, IEEE 802.15.4 Soft-MAC, Bluetooth IPSP, and a dummy one, which is a generic layer example that can be used as a template for writing a new one.
L2 layer API¶
In order to create an L2 layer, or even a driver for a specific L2 layer, one needs to understand how the IP layer interacts with it and how the L2 layer is supposed to behave. The generic L2 API has 3 functions:
- recv: All device drivers, once they receive a packet which they put
struct net_pkt, will push this buffer to the IP core stack via
net_recv_data(). At this point, the IP core stack does not know what to do with it. Instead, it passes the buffer along to the L2 stack’s recv() function for handling. The L2 stack does what it needs to do with the packet, for example, parsing the link layer header, or handling link-layer only packets. The recv() function will return NET_DROP in case of an erroneous packet, NET_OK if the packet was fully consumed by the L2, or NET_CONTINUE if the IP stack should then handle it as an IP packet.
- reserve: Prior to creating any network buffer content, the Zephyr core stack needs to know how much dedicated buffer space is needed for the L2 layer (for example, space for the link layer header). This reserve function returns the number of bytes needed.
- send: Similar to recv, the IP core stack will call this function to actually send a packet. All relevant link-layer content will be generated and added by this function. As for recv, send returns a verdict and can decide to drop the packet via NET_DROP if something wrong happened, or will return NET_OK.
Network Device drivers¶
Network device drivers fully follows Zephyr device driver model as a basis. Please refer to Device Drivers and Device Model.
There are, however, two differences:
- the driver_api pointer must point to a valid
- The network device driver must use
NET_DEVICE_INIT_INSTANCE(). This macro will call the DEVICE_AND_API_INIT() macro, and also instantiate a unique
struct net_ifrelated to the created device driver instance.
Implementing a network device driver depends on the L2 stack it belongs to: Ethernet, IEEE 802.15.4, etc. In the next section, we will describe how a device driver should behave when receiving or sending a packet. The rest is really hardware dependent and thus does not need to be detailed here.
Ethernet device driver¶
On reception, it is up to the device driver to fill-in the buffer with
as many data fragments as required. The buffer itself is a
struct net_pkt and should be allocated through
net_pkt_get_reserve_rx(0)(). Then all fragments will be
net_pkt_get_reserve_data(0)(). Of course
the amount of required fragments depends on the size of the received
packet and on the size of a fragment, which is given by
Note that it is not up to the device driver to decide on the link-layer space to be reserved in the buffer. Hence the 0 given as parameter here. The Ethernet L2 layer will update such information once the packet’s Ethernet header has been successfully parsed.
net_recv_data() call fails, it will be up to the
device driver to unreference the buffer via
On sending, it is up to the device driver to send the buffer all at once, with all the fragments.
In case of a fully successful packet transmission only, the device
driver must unreference the buffer via
Each Ethernet device driver will need, in the end, to call
NET_DEVICE_INIT_INSTANCE() like this:
NET_DEVICE_INIT_INSTANCE(..., CONFIG_ETH_INIT_PRIORITY &the_valid_net_if_api_instance, ETHERNET_L2, NET_L2_GET_CTX_TYPE(ETHERNET_L2), 1500);
IEEE 802.15.4 device driver¶
Device drivers for IEEE 802.15.4 L2 work basically the same as for Ethernet. What has been described above, especially for recv, applies here as well. There are two specific differences however:
- It requires a dedicated device driver API:
struct ieee802154_radio_api, which overloads
struct net_if_api. This is because 802.15.4 L2 needs more from the device driver than just send and recv functions. This dedicated API is declared in
include/net/ieee802154_radio.h. Each and every IEEE 802.15.4 device driver must provide a valid pointer on such relevantly filled-in API structure.
- Sending a packet is slightly particular. IEEE 802.15.4 sends
relatively small frames, 127 bytes all inclusive: frame header,
payload and frame checksum. Buffer fragments are meant to fit such
frame size limitation. But a buffer containing an IPv6/UDP packet
might have more than one fragment. In the Ethernet device driver, it
is up to the driver to handle all fragments. IEEE 802.15.4 drivers
handle only one fragment at a time. This is why the
struct ieee802154_radio_apirequires a tx function pointer which differs from the
struct net_if_apisend function pointer. Instead, the IEEE 802.15.4 L2, provides a generic
ieee802154_radio_send()meant to be given as
struct net_ifsend function. It turn, the implementation of
ieee802154_radio_send()will ensure the same behavior: sending one fragment at a time through
struct ieee802154_radio_apitx function, and unreferencing the buffer only when all the transmission were successful.
Each IEEE 802.15.4 device driver, in the end, will need to call
NET_DEVICE_INIT_INSTANCE() that way:
NET_DEVICE_INIT_INSTANCE(..., the_device_init_prio, &the_valid_ieee802154_radio_api_instance, IEEE802154_L2, NET_L2_GET_CTX_TYPE(IEEE802154_L2), 125);