diff --git a/Documentation/components/net/index.rst b/Documentation/components/net/index.rst index 4f9fc7f6cc9..95ee16515e9 100644 --- a/Documentation/components/net/index.rst +++ b/Documentation/components/net/index.rst @@ -10,6 +10,7 @@ Network Support pkt.rst nat.rst netdev.rst + netdriver.rst netguardsize.rst slip.rst wqueuedeadlocks.rst diff --git a/Documentation/components/net/netdev.rst b/Documentation/components/net/netdev.rst index 2bc0fe73912..1075ff9b30e 100644 --- a/Documentation/components/net/netdev.rst +++ b/Documentation/components/net/netdev.rst @@ -8,6 +8,13 @@ Network Devices interface and is passed to the network via ``netdev_register()``. +- ``include/nuttx/net/netdev_lowerhalf.h``. (Recommended for new + drivers, see :ref:`Network Drivers `) + This header file defines the interface between the network device + and the network stack. The network device is a lower-half driver + that provides the network stack with the ability to send and receive + packets. + IP Addresses ============ diff --git a/Documentation/components/net/netdriver.rst b/Documentation/components/net/netdriver.rst new file mode 100644 index 00000000000..763e15b8246 --- /dev/null +++ b/Documentation/components/net/netdriver.rst @@ -0,0 +1,237 @@ +.. _netdriver: + +=============== +Network Drivers +=============== + +The NuttX network driver is split into two parts: + +#. An "upper half", generic driver that provides the common network + interface to application level code, and +#. A "lower half", platform-specific driver that implements the + low-level timer controls to implement the network functionality. + +Files supporting network driver can be found in the following locations: + +- **Interface Definition**. The header file for the NuttX network + driver resides at ``include/nuttx/net/netdev_lowerhalf.h``. This + header file includes the interface between the "upper half" and + "lower half" drivers. +- **"Upper Half" Driver**. The generic, "upper half" network driver + resides at ``drivers/net/netdev_upperhalf.c``. +- **"Lower Half" Drivers**. Platform-specific network drivers reside + in ``arch//src/`` or ``drivers/net`` + directory for the specific processor ```` and for + the specific ```` network peripheral devices. + +**Special Note**: Not all network drivers are implemented with this +architecture. Known lower-half drivers: +``arch/sim/src/sim/sim_netdriver.c``, ``drivers/virtio/virtio-net.c`` + +How to change full network driver into lower-half one +===================================================== + +We have many network drivers that are implemented as full network drivers +with ``include/nuttx/net/netdev.h``, we can change them into lower-half +drivers to remove the common code (which is already in upper-half driver). +Here is a guide to do so: + +1. Change ``struct net_driver_s`` to ``struct netdev_lowerhalf_s`` in + the network driver structure. If you really need to touch some fields + inside ``struct net_driver_s`` like MAC address, you can access them + through ``struct netdev_lowerhalf_s::netdev``. +2. Change the function names called in the network driver file to the names + with prefix ``netdev_lower_``, e.g. ``netdev_lower_register`` and + ``netdev_lower_carrier_on``. +3. Change the core functions called by work queue like ``txpoll`` as + ``transmit`` and ``receive`` in the ``netdev_ops_s`` structure. You may + need to change ``memcpy`` for ``d_buf`` into ``netpkt_copyin`` and + ``netpkt_copyout``. + + - Note that the ``receive`` function just need to return the received + packet instead of calling functions like ``ipv4_input`` or doing reply. + The upper-half will call ``receive`` to get all packets until it + returns ``NULL`` and send these packets into the network stack. + - Also remember to call ``netpkt_free`` for the transmitted packets. + +4. Remove work queues related to send and receive, and replace them + with calling ``netdev_lower_txdone`` and ``netdev_lower_rxready``. + Then the upper-half driver will call ``transmit`` and ``receive`` to + send/get packets. +5. Remove any buffer related to ``d_buf``, and make sure ``d_buf`` is not + used in the lower-half driver. +6. Remove ``txavail`` function, the upper-half driver will call ``transmit`` + when it has packets to send. +7. Remove the statistics macros like ``NETDEV_TXPACKETS``, ``NETDEV_TXDONE``, + ``NETDEV_RXPACKETS`` or ``NETDEV_RXDROPPED``, these macros are well + handled in upper-half. But you may still keep macros like + ``NETDEV_TXTIMEOUTS`` and ``NETDEV_RXERRORS`` because the upper-half + cannot know whether these error happens. +8. Find a suitable ``quota`` for the driver, and set it in the driver + initialization function. The quota is the maximum number of buffers + that the driver can hold at the same time. For example, if the TX quota + is set to 5, it means that if the driver has 5 unreleased packets + (``netpkt_free``), the upper-half will not call ``transmit`` until they + are released. + + - Note: An exception is that if the net stack is replying for RX packet, + this replied packet will always be put into ``transmit``, which may + exceed the TX quota temporarily. + +"Lower Half" Example +==================== + +.. code-block:: c + + struct _priv_s + { + /* This holds the information visible to the NuttX network */ + + struct netdev_lowerhalf_s dev; + + ... + }; + + static const struct netdev_ops_s g_ops = + { + .ifup = _ifup, + .ifdown = _ifdown, + .transmit = _transmit, + .receive = _receive, + .addmac = _addmac, + .rmmac = _rmmac, + .ioctl = _ioctl + }; + + /* The Wi-Fi driver registration function can be implemented as follows, + * where refers to the chip name. netdev_lower_register() is the + * network device interface provided by upper-half drivers to register + * network device drivers. + */ + + int _netdev_init(FAR struct _priv_s *priv) + { + FAR struct netdev_lowerhalf_s *dev = &priv->dev; + + dev->ops = &g_ops; + + /* The maximum number of buffers that the driver can hold + * at the same time. For example, if the TX quota is set to 5, it + * means that if the driver has 5 unreleased packets (netpkt_free), + * the upper layer will not call transmit until they are released. + * After the rx quota is used up and no new buffer can be allocated + * (netpkt_alloc), it needs to notify the upper layer + * (netdev_lower_rxready) and restore the quota by submitting buffer + * back through receive function. + * If the driver processes each packet individually (without + * accumulating multiple packets before sending/receiving), it can be + * set to 1. + */ + + dev->quota[NETPKT_TX] = 1; + dev->quota[NETPKT_RX] = 1; + + return netdev_lower_register(dev, NET_LL_ETHERNET); + } + + /* The transmit function can be implemented as follows, where + * refers to the chip name. + */ + + static int _transmit(FAR struct netdev_lowerhalf_s *dev, + FAR netpkt_t *pkt) + { + FAR struct _priv_s *priv = (FAR struct _priv_s *)dev; + unsigned int len = netpkt_getdatalen(dev, pkt); + + #if you want to do offloading + if (!netpkt_is_fragmented(pkt)) + { + /* Contiguous memory, just use data pointer */ + + FAR uint8_t *databuf = netpkt_getdata(dev, pkt); + FAR uint8_t *devbuf = databuf - sizeof(struct _txhead_s); + + /* Do Transmit. Note: `databuf` points to the L2 data, and there is + * a reserved memory with size of `CONFIG_NET_LL_GUARDSIZE` before + * databuf to be used for driver header, drivers can just fill data + * there (`devbuf`) and start the transmission. + */ + + ... + } + else + #endif + { + /* Copyout the L2 data and transmit. */ + + uint8_t devbuf[1600]; + netpkt_copyout(dev, devbuf, pkt, len, 0); + + /* Do Transmit */ + + ... + } + + return OK; + } + + static void _txdone_interrupt(FAR struct _priv_s *priv) + { + FAR struct netdev_lowerhalf_s *dev = &priv->dev; + + /* Perform some processing in the driver (if necessary) */ + + ... + + /* Free the buffer and notify the upper layer */ + + netpkt_free(dev, pkt, NETPKT_TX); + netdev_lower_txdone(dev); + } + + /* The receive function can be implemented as follows, where + * refers to the chip name. + */ + + static void _rxready_interrupt(FAR struct _priv_s *priv) + { + FAR struct netdev_lowerhalf_s *dev = &priv->dev; + netdev_lower_rxready(dev); + } + + static FAR netpkt_t *_receive(FAR struct netdev_lowerhalf_s *dev) + { + /* It is also possible to allocate the pkt and receive the data in + * advance, and then call rxready and return pkt through receive + */ + + FAR netpkt_t *pkt = netpkt_alloc(dev, NETPKT_RX); + + if (pkt) + { + #if NETPKT_BUFLEN > 15xx && you want to do offloading + /* Write directly to the buffer inside pkt, len corresponds to the + * length of L2 data (need the NETPKT_BUFLEN to be large enough to + * hold the data). The `_rxhead_s` is the driver header before + * the actual data (maybe you don't have). + */ + + len = receive_data_into(netpkt_getbase(pkt)); + netpkt_resetreserved(&priv->dev, pkt, sizeof(struct _rxhead_s)); + netpkt_setdatalen(&priv->dev, pkt, len); + #else + uint8_t devbuf[1600]; + + /* Copy from src, len corresponds to the length of L2 data, you can + * always use this method to receive data. The `_rxhead_s` is + * the driver header before the actual data (maybe you don't have). + */ + + len = receive_data_into(devbuf); + netpkt_copyin(dev, pkt, devbuf + sizeof(struct _rxhead_s), len, 0); + #endif + } + + return pkt; + }