[netdevice] Ensure driver transmit() and poll() will not be re-entered

When CONSOLE_SYSLOG is used, a DBG() from within a network device
driver may cause its transmit() or poll() methods to be unexpectedly
re-entered.  Since these methods are not intended to be re-entrant,
this can lead to undefined behaviour.

Add an explicit re-entrancy guard to both methods.  Note that this
must operate at a per-netdevice level, since there are legitimate
circumstances under which the netdev_tx() or netdev_poll() functions
may be re-entered (e.g. when using VLAN devices).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/329/head
Michael Brown 2021-04-10 16:53:52 +01:00
parent 0be8491b71
commit 78749542fc
2 changed files with 38 additions and 7 deletions

View File

@ -451,6 +451,12 @@ struct net_device {
*/
#define NETDEV_IRQ_UNSUPPORTED 0x0008
/** Network device transmission is in progress */
#define NETDEV_TX_IN_PROGRESS 0x0010
/** Network device poll is in progress */
#define NETDEV_POLL_IN_PROGRESS 0x0020
/** Link-layer protocol table */
#define LL_PROTOCOLS __table ( struct ll_protocol, "ll_protocols" )

View File

@ -297,30 +297,45 @@ int netdev_tx ( struct net_device *netdev, struct io_buffer *iobuf ) {
/* Enqueue packet */
list_add_tail ( &iobuf->list, &netdev->tx_queue );
/* Guard against re-entry */
if ( netdev->state & NETDEV_TX_IN_PROGRESS ) {
rc = -EBUSY;
goto err_busy;
}
netdev->state |= NETDEV_TX_IN_PROGRESS;
/* Avoid calling transmit() on unopened network devices */
if ( ! netdev_is_open ( netdev ) ) {
rc = -ENETUNREACH;
goto err;
goto err_closed;
}
/* Discard packet (for test purposes) if applicable */
if ( ( rc = inject_fault ( NETDEV_DISCARD_RATE ) ) != 0 )
goto err;
goto err_fault;
/* Map for DMA, if required */
if ( netdev->dma && ( ! dma_mapped ( &iobuf->map ) ) ) {
if ( ( rc = iob_map_tx ( iobuf, netdev->dma ) ) != 0 )
goto err;
goto err_map;
}
/* Transmit packet */
if ( ( rc = netdev->op->transmit ( netdev, iobuf ) ) != 0 )
goto err;
goto err_transmit;
/* Clear in-progress flag */
netdev->state &= ~NETDEV_TX_IN_PROGRESS;
profile_stop ( &net_tx_profiler );
return 0;
err:
err_transmit:
err_map:
err_fault:
err_closed:
netdev->state &= ~NETDEV_TX_IN_PROGRESS;
err_busy:
netdev_tx_complete_err ( netdev, iobuf, rc );
return rc;
}
@ -552,8 +567,18 @@ void netdev_rx_err ( struct net_device *netdev,
*/
void netdev_poll ( struct net_device *netdev ) {
if ( netdev_is_open ( netdev ) )
/* Avoid calling poll() on unopened network devices */
if ( ! netdev_is_open ( netdev ) )
return;
/* Guard against re-entry */
if ( netdev->state & NETDEV_POLL_IN_PROGRESS )
return;
/* Poll device */
netdev->state |= NETDEV_POLL_IN_PROGRESS;
netdev->op->poll ( netdev );
netdev->state &= ~NETDEV_POLL_IN_PROGRESS;
}
/**