#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>
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
*
/* 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 );
+ }
}
/**
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;
}
}
*/
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 ) );
}
/**
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 ) );
/** 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,
+};