mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
Add a "Performance tuning" section to the network driver guide covering the knobs that matter most for throughput on Wi-Fi lower-half drivers whose MAC runs on a companion core: RX quota as backpressure, keeping the RX thread priority at or below the vendor packet-delivery task, and capping the TCP window / send buffer (backed by the shared IOB pool) on lossy wireless paths. Signed-off-by: raul_chen <raul_chen@realsil.com.cn>
286 lines
11 KiB
ReStructuredText
286 lines
11 KiB
ReStructuredText
.. _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/<architecture>/src/<hardware>`` or ``drivers/net``
|
|
directory for the specific processor ``<architecture>`` and for
|
|
the specific ``<chip>`` 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.
|
|
9. Find a suitable ``rxtype`` for the driver, and set it in the driver
|
|
initialization function. There are some types of receive notification
|
|
methods, defined in ``enum netdev_rx_e``, like ``NETDEV_RX_WORK``,
|
|
``NETDEV_RX_THREAD`` and ``NETDEV_RX_THREAD_RSS``. Choose the one that
|
|
fits your driver best.
|
|
10. Find a suitable ``priority`` for the driver, and set it in the driver
|
|
initialization function. This is the priority for the receive
|
|
notification work queue or thread. when ``rxtype`` is ``NETDEV_RX_WORK``,
|
|
it is the work queue ``qid``; when ``rxtype`` is ``NETDEV_RX_THREAD``,
|
|
it is the thread priority.
|
|
|
|
- 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.
|
|
|
|
Performance tuning
|
|
==================
|
|
|
|
The following knobs matter most for throughput, especially for Wi-Fi drivers
|
|
where the MAC runs on a companion core and packets are delivered to the host
|
|
over an IPC:
|
|
|
|
- **RX quota as backpressure.** The RX quota bounds how many received packets
|
|
the driver holds before the upper half drains them. It matters most for
|
|
traffic without transport flow control (e.g. UDP): when the peer sends
|
|
faster than the socket is drained, a large quota lets the driver copy many
|
|
packets into the stack only to drop them at the bounded socket receive
|
|
buffer -- wasted work that also steals CPU from the packets that do get
|
|
delivered. A smaller quota drops the excess earlier and cheaply, at
|
|
``netpkt_alloc``. For TCP the advertised window already throttles the peer,
|
|
so the quota rarely fills.
|
|
|
|
- **RX thread priority.** With ``NETDEV_RX_THREAD``, keep ``priority`` at or
|
|
below the vendor task that moves packets from the device (companion core)
|
|
into the driver. A higher priority (e.g. ``HPWORK``) starves that task,
|
|
the device-to-host ring backs up, and packets are dropped upstream of the
|
|
driver before ``receive`` ever sees them.
|
|
|
|
- **Socket receive buffer.** ``CONFIG_NET_RECV_BUFSIZE`` is the default
|
|
SO_RCVBUF: it both caps the advertised TCP window and bounds the UDP
|
|
read-ahead, both drawn from the shared IOB pool (``CONFIG_IOB_NBUFFERS`` x
|
|
``CONFIG_IOB_BUFSIZE``). On a lossy wireless path a large TCP window lets
|
|
the peer put more in flight than the link can smooth (bursty loss and RTO
|
|
stalls), so cap it near the out-of-order reassembly capacity and bound TX
|
|
with ``CONFIG_NET_SEND_BUFSIZE``. That same cap limits UDP read-ahead, so a
|
|
UDP-throughput-critical socket should raise its own buffer via
|
|
``setsockopt(SO_RCVBUF)`` rather than changing the TCP-safe default.
|
|
Enlarging the buffer helps only up to the rate the socket can be drained;
|
|
sustained overload is dropped regardless. Keeping out-of-order reassembly
|
|
(``CONFIG_NET_TCP_OUT_OF_ORDER``) enabled lets a single loss recover in one
|
|
round trip.
|
|
|
|
"Lower Half" Example
|
|
====================
|
|
|
|
.. code-block:: c
|
|
|
|
struct <chip>_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 = <chip>_ifup,
|
|
.ifdown = <chip>_ifdown,
|
|
.transmit = <chip>_transmit,
|
|
.receive = <chip>_receive,
|
|
.addmac = <chip>_addmac,
|
|
.rmmac = <chip>_rmmac,
|
|
.ioctl = <chip>_ioctl
|
|
};
|
|
|
|
/* The Wi-Fi driver registration function can be implemented as follows,
|
|
* where <chip> refers to the chip name. netdev_lower_register() is the
|
|
* network device interface provided by upper-half drivers to register
|
|
* network device drivers.
|
|
*/
|
|
|
|
int <chip>_netdev_init(FAR struct <chip>_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;
|
|
dev->rxtype = NETDEV_RX_WORK;
|
|
dev->priority = HPWORK;
|
|
|
|
return netdev_lower_register(dev, NET_LL_ETHERNET);
|
|
}
|
|
|
|
/* The transmit function can be implemented as follows, where <chip>
|
|
* refers to the chip name.
|
|
*/
|
|
|
|
static int <chip>_transmit(FAR struct netdev_lowerhalf_s *dev,
|
|
FAR netpkt_t *pkt)
|
|
{
|
|
FAR struct <chip>_priv_s *priv = (FAR struct <chip>_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 <chip>_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 <chip>_txdone_interrupt(FAR struct <chip>_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 <chip>
|
|
* refers to the chip name.
|
|
*/
|
|
|
|
static void <chip>_rxready_interrupt(FAR struct <chip>_priv_s *priv)
|
|
{
|
|
FAR struct netdev_lowerhalf_s *dev = &priv->dev;
|
|
netdev_lower_rxready(dev);
|
|
}
|
|
|
|
static FAR netpkt_t *<chip>_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 `<chip>_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 <chip>_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 `<chip>_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 <chip>_rxhead_s), len, 0);
|
|
#endif
|
|
}
|
|
|
|
return pkt;
|
|
}
|