i40e: fix XDP_REDIRECT/XDP xmit ring cleanup race [Linux 5.0]

This Linux kernel change "i40e: fix XDP_REDIRECT/XDP xmit ring cleanup race" is included in the Linux 5.0 release. This change is authored by Björn Töpel <bjorn.topel [at] intel.com> on Thu Feb 14 14:03:02 2019 +0100. The commit for this change in Linux stable tree is 59eb2a8 (patch).

i40e: fix XDP_REDIRECT/XDP xmit ring cleanup race

When the driver clears the XDP xmit ring due to re-configuration or
teardown, in-progress ndo_xdp_xmit must be taken into consideration.

The ndo_xdp_xmit function is typically called from a NAPI context that
the driver does not control. Therefore, we must be careful not to
clear the XDP ring, while the call is on-going. This patch adds a
synchronize_rcu() to wait for napi(s) (preempt-disable regions and
softirqs), prior clearing the queue. Further, the __I40E_CONFIG_BUSY
flag is checked in the ndo_xdp_xmit implementation to avoid touching
the XDP xmit queue during re-configuration.

Fixes: d9314c474d4f ("i40e: add support for XDP_REDIRECT")
Fixes: 123cecd427b6 ("i40e: added queue pair disable/enable functions")
Reported-by: Maciej Fijalkowski <[email protected]>
Signed-off-by: Björn Töpel <[email protected]>
Signed-off-by: Jeff Kirsher <[email protected]>

There are 18 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/net/ethernet/intel/i40e/i40e_main.c | 14 ++++++++++++--
 drivers/net/ethernet/intel/i40e/i40e_txrx.c |  4 +++-
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index 3a0990d..e4ff531 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -6728,8 +6728,13 @@ void i40e_down(struct i40e_vsi *vsi)

    for (i = 0; i < vsi->num_queue_pairs; i++) {
        i40e_clean_tx_ring(vsi->tx_rings[i]);
-       if (i40e_enabled_xdp_vsi(vsi))
+       if (i40e_enabled_xdp_vsi(vsi)) {
+           /* Make sure that in-progress ndo_xdp_xmit
+            * calls are completed.
+            */
+           synchronize_rcu();
            i40e_clean_tx_ring(vsi->xdp_rings[i]);
+       }
        i40e_clean_rx_ring(vsi->rx_rings[i]);
    }

@@ -11966,8 +11971,13 @@ static void i40e_queue_pair_reset_stats(struct i40e_vsi *vsi, int queue_pair)
 static void i40e_queue_pair_clean_rings(struct i40e_vsi *vsi, int queue_pair)
 {
    i40e_clean_tx_ring(vsi->tx_rings[queue_pair]);
-   if (i40e_enabled_xdp_vsi(vsi))
+   if (i40e_enabled_xdp_vsi(vsi)) {
+       /* Make sure that in-progress ndo_xdp_xmit calls are
+        * completed.
+        */
+       synchronize_rcu();
        i40e_clean_tx_ring(vsi->xdp_rings[queue_pair]);
+   }
    i40e_clean_rx_ring(vsi->rx_rings[queue_pair]);
 }

diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
index a7e14e9..6c97667 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
@@ -3709,6 +3709,7 @@ int i40e_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames,
    struct i40e_netdev_priv *np = netdev_priv(dev);
    unsigned int queue_index = smp_processor_id();
    struct i40e_vsi *vsi = np->vsi;
+   struct i40e_pf *pf = vsi->back;
    struct i40e_ring *xdp_ring;
    int drops = 0;
    int i;
@@ -3716,7 +3717,8 @@ int i40e_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames,
    if (test_bit(__I40E_VSI_DOWN, vsi->state))
        return -ENETDOWN;

-   if (!i40e_enabled_xdp_vsi(vsi) || queue_index >= vsi->num_queue_pairs)
+   if (!i40e_enabled_xdp_vsi(vsi) || queue_index >= vsi->num_queue_pairs ||
+       test_bit(__I40E_CONFIG_BUSY, pf->state))
        return -ENXIO;

    if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))

Leave a Reply

Your email address will not be published. Required fields are marked *