]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[netdevice] Add netdev_tx_defer() to allow drivers to defer transmissions
authorMichael Brown <mcb30@ipxe.org>
Wed, 1 May 2013 13:05:42 +0000 (14:05 +0100)
committerMichael Brown <mcb30@ipxe.org>
Wed, 1 May 2013 13:05:42 +0000 (14:05 +0100)
Devices with small transmit descriptor rings may temporarily run out
of space.  Provide netdev_tx_defer() to allow drivers to defer packets
for retransmission as soon as a descriptor becomes available.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/netdevice.h
src/net/netdevice.c

index e5dbd996b5ddd95826f677bd15bfd581251d6920..21754fe67fadcdd169429751698542a9f3888724 100644 (file)
@@ -346,6 +346,8 @@ struct net_device {
        size_t max_pkt_len;
        /** TX packet queue */
        struct list_head tx_queue;
+       /** Deferred TX packet queue */
+       struct list_head tx_deferred;
        /** RX packet queue */
        struct list_head rx_queue;
        /** TX statistics */
@@ -605,6 +607,8 @@ netdev_rx_frozen ( struct net_device *netdev ) {
 extern void netdev_link_err ( struct net_device *netdev, int rc );
 extern void netdev_link_down ( struct net_device *netdev );
 extern int netdev_tx ( struct net_device *netdev, struct io_buffer *iobuf );
+extern void netdev_tx_defer ( struct net_device *netdev,
+                             struct io_buffer *iobuf );
 extern void netdev_tx_err ( struct net_device *netdev,
                            struct io_buffer *iobuf, int rc );
 extern void netdev_tx_complete_err ( struct net_device *netdev,
index 88ca93f98ef788eb546373c5b22751812ede5e95..1191ebc190bea70d256b0028c0af03e47e7824c5 100644 (file)
@@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/tables.h>
 #include <ipxe/process.h>
 #include <ipxe/init.h>
+#include <ipxe/malloc.h>
 #include <ipxe/device.h>
 #include <ipxe/errortab.h>
 #include <ipxe/vlan.h>
@@ -212,6 +213,43 @@ int netdev_tx ( struct net_device *netdev, struct io_buffer *iobuf ) {
        return rc;
 }
 
+/**
+ * Defer transmitted packet
+ *
+ * @v netdev           Network device
+ * @v iobuf            I/O buffer
+ *
+ * Drivers may call netdev_tx_defer() if there is insufficient space
+ * in the transmit descriptor ring.  Any packets deferred in this way
+ * will be automatically retransmitted as soon as space becomes
+ * available (i.e. as soon as the driver calls netdev_tx_complete()).
+ *
+ * The packet must currently be in the network device's TX queue.
+ *
+ * Drivers utilising netdev_tx_defer() must ensure that space in the
+ * transmit descriptor ring is freed up @b before calling
+ * netdev_tx_complete().  For example, if the ring is modelled using a
+ * producer counter and a consumer counter, then the consumer counter
+ * must be incremented before the call to netdev_tx_complete().
+ * Failure to do this will cause the retransmitted packet to be
+ * immediately redeferred (which will result in out-of-order
+ * transmissions and other nastiness).
+ */
+void netdev_tx_defer ( struct net_device *netdev, struct io_buffer *iobuf ) {
+
+       /* Catch data corruption as early as possible */
+       list_check_contains_entry ( iobuf, &netdev->tx_queue, list );
+
+       /* Remove from transmit queue */
+       list_del ( &iobuf->list );
+
+       /* Add to deferred transmit queue */
+       list_add_tail ( &iobuf->list, &netdev->tx_deferred );
+
+       /* Record "out of space" statistic */
+       netdev_tx_err ( netdev, NULL, -ENOBUFS );
+}
+
 /**
  * Discard transmitted packet
  *
@@ -257,6 +295,13 @@ void netdev_tx_complete_err ( struct net_device *netdev,
        /* Dequeue and free I/O buffer */
        list_del ( &iobuf->list );
        netdev_tx_err ( netdev, iobuf, rc );
+
+       /* Transmit first pending packet, if any */
+       if ( ( iobuf = list_first_entry ( &netdev->tx_deferred,
+                                         struct io_buffer, list ) ) != NULL ) {
+               list_del ( &iobuf->list );
+               netdev_tx ( netdev, iobuf );
+       }
 }
 
 /**
@@ -270,9 +315,9 @@ void netdev_tx_complete_err ( struct net_device *netdev,
 void netdev_tx_complete_next_err ( struct net_device *netdev, int rc ) {
        struct io_buffer *iobuf;
 
-       list_for_each_entry ( iobuf, &netdev->tx_queue, list ) {
+       if ( ( iobuf = list_first_entry ( &netdev->tx_queue, struct io_buffer,
+                                         list ) ) != NULL ) {
                netdev_tx_complete_err ( netdev, iobuf, rc );
-               return;
        }
 }
 
@@ -283,10 +328,15 @@ void netdev_tx_complete_next_err ( struct net_device *netdev, int rc ) {
  */
 static void netdev_tx_flush ( struct net_device *netdev ) {
 
-       /* Discard any packets in the TX queue */
+       /* Discard any packets in the TX queue.  This will also cause
+        * any packets in the deferred TX queue to be discarded
+        * automatically.
+        */
        while ( ! list_empty ( &netdev->tx_queue ) ) {
                netdev_tx_complete_next_err ( netdev, -ECANCELED );
        }
+       assert ( list_empty ( &netdev->tx_queue ) );
+       assert ( list_empty ( &netdev->tx_deferred ) );
 }
 
 /**
@@ -424,6 +474,7 @@ struct net_device * alloc_netdev ( size_t priv_size ) {
                ref_init ( &netdev->refcnt, free_netdev );
                netdev->link_rc = -EUNKNOWN_LINK_STATUS;
                INIT_LIST_HEAD ( &netdev->tx_queue );
+               INIT_LIST_HEAD ( &netdev->tx_deferred );
                INIT_LIST_HEAD ( &netdev->rx_queue );
                netdev_settings_init ( netdev );
                netdev->priv = ( ( ( void * ) netdev ) + sizeof ( *netdev ) );
@@ -817,3 +868,36 @@ __weak struct net_device * vlan_find ( struct net_device *trunk __unused,
 
 /** Networking stack process */
 PERMANENT_PROCESS ( net_process, net_step );
+
+/**
+ * Discard some cached network device data
+ *
+ * @ret discarded      Number of cached items discarded
+ */
+static unsigned int net_discard ( void ) {
+       struct net_device *netdev;
+       struct io_buffer *iobuf;
+       unsigned int discarded = 0;
+
+       /* Try to drop one deferred TX packet from each network device */
+       for_each_netdev ( netdev ) {
+               if ( ( iobuf = list_first_entry ( &netdev->tx_deferred,
+                                                 struct io_buffer,
+                                                 list ) ) != NULL ) {
+
+                       /* Discard first deferred packet */
+                       list_del ( &iobuf->list );
+                       free ( iobuf );
+
+                       /* Report discard */
+                       discarded++;
+               }
+       }
+
+       return discarded;
+}
+
+/** Network device cache discarder */
+struct cache_discarder net_discarder __cache_discarder ( CACHE_NORMAL ) = {
+       .discard = net_discard,
+};