net/i40e: Fix concurrency issues between config flow and XSK
authorMaxim Mikityanskiy <maximmi@mellanox.com>
Tue, 17 Dec 2019 16:20:45 +0000 (16:20 +0000)
committerDaniel Borkmann <daniel@iogearbox.net>
Thu, 19 Dec 2019 15:20:49 +0000 (16:20 +0100)
Use synchronize_rcu to wait until the XSK wakeup function finishes
before destroying the resources it uses:

1. i40e_down already calls synchronize_rcu. On i40e_down either
__I40E_VSI_DOWN or __I40E_CONFIG_BUSY is set. Check the latter in
i40e_xsk_wakeup (the former is already checked there).

2. After switching the XDP program, call synchronize_rcu to let
i40e_xsk_wakeup exit before the XDP program is freed.

3. Changing the number of channels brings the interface down (see
i40e_prep_for_reset and i40e_pf_quiesce_all_vsi).

4. Disabling UMEM sets __I40E_CONFIG_BUSY, too.

Signed-off-by: Maxim Mikityanskiy <maximmi@mellanox.com>
Signed-off-by: Björn Töpel <bjorn.topel@intel.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/20191217162023.16011-4-maximmi@mellanox.com
drivers/net/ethernet/intel/i40e/i40e.h
drivers/net/ethernet/intel/i40e/i40e_main.c
drivers/net/ethernet/intel/i40e/i40e_xsk.c

index cb6367334ca7816cbf9ae49e3e392c8f1200abef..4833187bd25911a760f3a7d05d0394353eec56d4 100644 (file)
@@ -1152,7 +1152,7 @@ void i40e_set_fec_in_flags(u8 fec_cfg, u32 *flags);
 
 static inline bool i40e_enabled_xdp_vsi(struct i40e_vsi *vsi)
 {
-       return !!vsi->xdp_prog;
+       return !!READ_ONCE(vsi->xdp_prog);
 }
 
 int i40e_create_queue_channel(struct i40e_vsi *vsi, struct i40e_channel *ch);
index 1ccabeafa44c4622b48167de77f8151e03d16fa9..2c5af6d4a6b1c641ea2c47f44469a530df363850 100644 (file)
@@ -6823,8 +6823,8 @@ 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)) {
-                       /* Make sure that in-progress ndo_xdp_xmit
-                        * calls are completed.
+                       /* Make sure that in-progress ndo_xdp_xmit and
+                        * ndo_xsk_wakeup calls are completed.
                         */
                        synchronize_rcu();
                        i40e_clean_tx_ring(vsi->xdp_rings[i]);
@@ -12546,8 +12546,12 @@ static int i40e_xdp_setup(struct i40e_vsi *vsi,
 
        old_prog = xchg(&vsi->xdp_prog, prog);
 
-       if (need_reset)
+       if (need_reset) {
+               if (!prog)
+                       /* Wait until ndo_xsk_wakeup completes. */
+                       synchronize_rcu();
                i40e_reset_and_rebuild(pf, true, true);
+       }
 
        for (i = 0; i < vsi->num_queue_pairs; i++)
                WRITE_ONCE(vsi->rx_rings[i]->xdp_prog, vsi->xdp_prog);
index d07e1a8904283684f448e4a1346db6cfc6d4d9eb..f73cd917c44f72740c3339bd042d3c6e2f70ace9 100644 (file)
@@ -787,8 +787,12 @@ int i40e_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags)
 {
        struct i40e_netdev_priv *np = netdev_priv(dev);
        struct i40e_vsi *vsi = np->vsi;
+       struct i40e_pf *pf = vsi->back;
        struct i40e_ring *ring;
 
+       if (test_bit(__I40E_CONFIG_BUSY, pf->state))
+               return -ENETDOWN;
+
        if (test_bit(__I40E_VSI_DOWN, vsi->state))
                return -ENETDOWN;