diff mbox

[net] i40evf: fix panic during MTU change

Message ID 1434670253-3177-1-git-send-email-mitch.a.williams@intel.com
State Superseded
Delegated to: Jeff Kirsher
Headers show

Commit Message

Mitch Williams June 18, 2015, 11:30 p.m. UTC
Down was requesting queue disables, but then exited immediately
without waiting for the queues to actually disable.  This could
allow any function called after i40evf_down to run immediately,
including i40evf_up, and causes a memory leak.

Removing the whole reinit_locked function is the best way
to go about this, and allows for the driver to handle the
state changes by requesting reset from the periodic timer.

Also, add a couple WARN_ONs in slow path to help us recognize
if we re-introduce this issue or missed any cases.

Signed-off-by: Mitch Williams <mitch.a.williams@intel.com>
Signed-off-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
---
 drivers/net/ethernet/intel/i40evf/i40e_txrx.c      |   4 +
 drivers/net/ethernet/intel/i40evf/i40evf.h         |   1 -
 drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c |   6 +-
 drivers/net/ethernet/intel/i40evf/i40evf_main.c    | 108 +++++++++------------
 4 files changed, 53 insertions(+), 66 deletions(-)

Comments

Mitch Williams June 19, 2015, 3:54 p.m. UTC | #1
Please ignore - v2 coming imminently due to checkpatch errors.
-Mitch

> -----Original Message-----
> From: Williams, Mitch A
> Sent: Thursday, June 18, 2015 4:31 PM
> To: intel-wired-lan@lists.osuosl.org
> Cc: Williams, Mitch A; Brandeburg, Jesse
> Subject: [net PATCH] i40evf: fix panic during MTU change
> 
> Down was requesting queue disables, but then exited immediately
> without waiting for the queues to actually disable.  This could
> allow any function called after i40evf_down to run immediately,
> including i40evf_up, and causes a memory leak.
> 
> Removing the whole reinit_locked function is the best way
> to go about this, and allows for the driver to handle the
> state changes by requesting reset from the periodic timer.
> 
> Also, add a couple WARN_ONs in slow path to help us recognize
> if we re-introduce this issue or missed any cases.
> 
> Signed-off-by: Mitch Williams <mitch.a.williams@intel.com>
> Signed-off-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
> ---
>  drivers/net/ethernet/intel/i40evf/i40e_txrx.c      |   4 +
>  drivers/net/ethernet/intel/i40evf/i40evf.h         |   1 -
>  drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c |   6 +-
>  drivers/net/ethernet/intel/i40evf/i40evf_main.c    | 108 +++++++++---------
> ---
>  4 files changed, 53 insertions(+), 66 deletions(-)
> 
> diff --git a/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
> b/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
> index 458fbb4..bd49232 100644
> --- a/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
> +++ b/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
> @@ -488,6 +488,8 @@ int i40evf_setup_tx_descriptors(struct i40e_ring
> *tx_ring)
>  	if (!dev)
>  		return -ENOMEM;
> 
> +	/* warn if we are about to overwrite the pointer */
> +	WARN_ON(tx_ring->tx_bi);
>  	bi_size = sizeof(struct i40e_tx_buffer) * tx_ring->count;
>  	tx_ring->tx_bi = kzalloc(bi_size, GFP_KERNEL);
>  	if (!tx_ring->tx_bi)
> @@ -648,6 +650,8 @@ int i40evf_setup_rx_descriptors(struct i40e_ring
> *rx_ring)
>  	struct device *dev = rx_ring->dev;
>  	int bi_size;
> 
> +	/* warn if we are about to overwrite the pointer */
> +	WARN_ON(rx_ring->rx_bi);
>  	bi_size = sizeof(struct i40e_rx_buffer) * rx_ring->count;
>  	rx_ring->rx_bi = kzalloc(bi_size, GFP_KERNEL);
>  	if (!rx_ring->rx_bi)
> diff --git a/drivers/net/ethernet/intel/i40evf/i40evf.h
> b/drivers/net/ethernet/intel/i40evf/i40evf.h
> index 1b98c25..fea3b75 100644
> --- a/drivers/net/ethernet/intel/i40evf/i40evf.h
> +++ b/drivers/net/ethernet/intel/i40evf/i40evf.h
> @@ -264,7 +264,6 @@ extern const char i40evf_driver_version[];
> 
>  int i40evf_up(struct i40evf_adapter *adapter);
>  void i40evf_down(struct i40evf_adapter *adapter);
> -void i40evf_reinit_locked(struct i40evf_adapter *adapter);
>  void i40evf_reset(struct i40evf_adapter *adapter);
>  void i40evf_set_ethtool_ops(struct net_device *netdev);
>  void i40evf_update_stats(struct i40evf_adapter *adapter);
> diff --git a/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
> b/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
> index f4e7766..2b53c87 100644
> --- a/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
> +++ b/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
> @@ -267,8 +267,10 @@ static int i40evf_set_ringparam(struct net_device
> *netdev,
>  	adapter->tx_desc_count = new_tx_count;
>  	adapter->rx_desc_count = new_rx_count;
> 
> -	if (netif_running(netdev))
> -		i40evf_reinit_locked(adapter);
> +	if (netif_running(netdev)) {
> +		adapter->flags |= I40EVF_FLAG_RESET_NEEDED;
> +		schedule_work(&adapter->reset_task);
> +	}
> 
>  	return 0;
>  }
> diff --git a/drivers/net/ethernet/intel/i40evf/i40evf_main.c
> b/drivers/net/ethernet/intel/i40evf/i40evf_main.c
> index 7c53aca..ec5f7ed 100644
> --- a/drivers/net/ethernet/intel/i40evf/i40evf_main.c
> +++ b/drivers/net/ethernet/intel/i40evf/i40evf_main.c
> @@ -170,7 +170,7 @@ static void i40evf_tx_timeout(struct net_device *netdev)
>  	struct i40evf_adapter *adapter = netdev_priv(netdev);
> 
>  	adapter->tx_timeout_count++;
> -	if (!(adapter->flags & I40EVF_FLAG_RESET_PENDING)) {
> +	if (!(adapter->flags & (I40EVF_FLAG_RESET_PENDING |
> I40EVF_FLAG_RESET_NEEDED))) {
>  		adapter->flags |= I40EVF_FLAG_RESET_NEEDED;
>  		schedule_work(&adapter->reset_task);
>  	}
> @@ -1470,8 +1470,8 @@ static void i40evf_configure_rss(struct i40evf_adapter
> *adapter)
>  	i40e_flush(hw);
>  }
> 
> -#define I40EVF_RESET_WAIT_MS 100
> -#define I40EVF_RESET_WAIT_COUNT 200
> +#define I40EVF_RESET_WAIT_MS 10
> +#define I40EVF_RESET_WAIT_COUNT 500
>  /**
>   * i40evf_reset_task - Call-back task to handle hardware reset
>   * @work: pointer to work_struct
> @@ -1495,10 +1495,17 @@ static void i40evf_reset_task(struct work_struct
> *work)
>  				&adapter->crit_section))
>  		usleep_range(500, 1000);
> 
> +	i40evf_misc_irq_disable(adapter);
>  	if (adapter->flags & I40EVF_FLAG_RESET_NEEDED) {
> -		dev_info(&adapter->pdev->dev, "Requesting reset from PF\n");
> +		adapter->flags &= ~I40EVF_FLAG_RESET_NEEDED;
> +		/* Restart the AQ here. If we have been reset but didn't
> +		 * detect it, or if the PF had to reinit, our AQ will be hosed.
> +		 */
> +		i40evf_shutdown_adminq(hw);
> +		i40evf_init_adminq(hw);
>  		i40evf_request_reset(adapter);
>  	}
> +	adapter->flags |= I40EVF_FLAG_RESET_PENDING;
> 
>  	/* poll until we see the reset actually happen */
>  	for (i = 0; i < I40EVF_RESET_WAIT_COUNT; i++) {
> @@ -1507,10 +1514,10 @@ static void i40evf_reset_task(struct work_struct
> *work)
>  		if ((rstat_val != I40E_VFR_VFACTIVE) &&
>  		    (rstat_val != I40E_VFR_COMPLETED))
>  			break;
> -		msleep(I40EVF_RESET_WAIT_MS);
> +		usleep_range(500, 1000);
>  	}
>  	if (i == I40EVF_RESET_WAIT_COUNT) {
> -		adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
> +		dev_info(&adapter->pdev->dev, "Never saw reset\n");
>  		goto continue_reset; /* act like the reset happened */
>  	}
> 
> @@ -1518,11 +1525,12 @@ static void i40evf_reset_task(struct work_struct
> *work)
>  	for (i = 0; i < I40EVF_RESET_WAIT_COUNT; i++) {
>  		rstat_val = rd32(hw, I40E_VFGEN_RSTAT) &
>  			    I40E_VFGEN_RSTAT_VFR_STATE_MASK;
> -		if ((rstat_val == I40E_VFR_VFACTIVE) ||
> -		    (rstat_val == I40E_VFR_COMPLETED))
> +		if (rstat_val == I40E_VFR_VFACTIVE)
>  			break;
>  		msleep(I40EVF_RESET_WAIT_MS);
>  	}
> +	/* extra wait to make sure minimum wait is met */
> +	msleep(I40EVF_RESET_WAIT_MS);
>  	if (i == I40EVF_RESET_WAIT_COUNT) {
>  		struct i40evf_mac_filter *f, *ftmp;
>  		struct i40evf_vlan_filter *fv, *fvtmp;
> @@ -1534,11 +1542,10 @@ static void i40evf_reset_task(struct work_struct
> *work)
> 
>  		if (netif_running(adapter->netdev)) {
>  			set_bit(__I40E_DOWN, &adapter->vsi.state);
> -			i40evf_irq_disable(adapter);
> -			i40evf_napi_disable_all(adapter);
> -			netif_tx_disable(netdev);
> -			netif_tx_stop_all_queues(netdev);
>  			netif_carrier_off(netdev);
> +			netif_tx_disable(netdev);
> +			i40evf_napi_disable_all(adapter);
> +			i40evf_irq_disable(adapter);
>  			i40evf_free_traffic_irqs(adapter);
>  			i40evf_free_all_tx_resources(adapter);
>  			i40evf_free_all_rx_resources(adapter);
> @@ -1550,6 +1557,7 @@ static void i40evf_reset_task(struct work_struct
> *work)
>  			list_del(&f->list);
>  			kfree(f);
>  		}
> +
>  		list_for_each_entry_safe(fv, fvtmp, &adapter->vlan_filter_list,
>  					 list) {
>  			list_del(&fv->list);
> @@ -1564,22 +1572,27 @@ static void i40evf_reset_task(struct work_struct
> *work)
>  		i40evf_shutdown_adminq(hw);
>  		adapter->netdev->flags &= ~IFF_UP;
>  		clear_bit(__I40EVF_IN_CRITICAL_TASK, &adapter->crit_section);
> +		adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
> +		dev_info(&adapter->pdev->dev, "Reset task did not complete, VF
> disabled\n");
>  		return; /* Do not attempt to reinit. It's dead, Jim. */
>  	}
> 
>  continue_reset:
> -	adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
> -
> -	i40evf_irq_disable(adapter);
> -
>  	if (netif_running(adapter->netdev)) {
> -		i40evf_napi_disable_all(adapter);
> -		netif_tx_disable(netdev);
> -		netif_tx_stop_all_queues(netdev);
>  		netif_carrier_off(netdev);
> +		netif_tx_stop_all_queues(netdev);
> +		i40evf_napi_disable_all(adapter);
>  	}
> +	i40evf_irq_disable(adapter);
> 
>  	adapter->state = __I40EVF_RESETTING;
> +	adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
> +
> +	/* free the tx/rx rings and descriptors, might be better to just
> +	 * re-use them sometime in the future
> +	 */
> +	i40evf_free_all_rx_resources(adapter);
> +	i40evf_free_all_tx_resources(adapter);
> 
>  	/* kill and reinit the admin queue */
>  	if (i40evf_shutdown_adminq(hw))
> @@ -1603,6 +1616,7 @@ continue_reset:
>  	adapter->aq_required = I40EVF_FLAG_AQ_ADD_MAC_FILTER;
>  	adapter->aq_required |= I40EVF_FLAG_AQ_ADD_VLAN_FILTER;
>  	clear_bit(__I40EVF_IN_CRITICAL_TASK, &adapter->crit_section);
> +	i40evf_misc_irq_enable(adapter);
> 
>  	mod_timer(&adapter->watchdog_timer, jiffies + 2);
> 
> @@ -1624,7 +1638,9 @@ continue_reset:
>  			goto reset_err;
> 
>  		i40evf_irq_enable(adapter, true);
> -	}
> +	} else
> + 		adapter->state = __I40EVF_DOWN;
> +
>  	return;
>  reset_err:
>  	dev_err(&adapter->pdev->dev, "failed to allocate resources during
> reinit\n");
> @@ -1667,6 +1683,11 @@ static void i40evf_adminq_task(struct work_struct
> *work)
>  			memset(event.msg_buf, 0, I40EVF_MAX_AQ_BUF_SIZE);
>  	} while (pending);
> 
> +	if ((adapter->flags &
> +	     (I40EVF_FLAG_RESET_PENDING | I40EVF_FLAG_RESET_NEEDED)) ||
> +	    adapter->state == __I40EVF_RESETTING)
> +		goto freedom;
> +
>  	/* check for error indications */
>  	val = rd32(hw, hw->aq.arq.len);
>  	oldval = val;
> @@ -1702,6 +1723,7 @@ static void i40evf_adminq_task(struct work_struct
> *work)
>  	if (oldval != val)
>  		wr32(hw, hw->aq.asq.len, val);
> 
> +freedom:
>  	kfree(event.msg_buf);
>  out:
>  	/* re-enable Admin queue interrupt cause */
> @@ -1897,47 +1919,6 @@ static struct net_device_stats
> *i40evf_get_stats(struct net_device *netdev)
>  }
> 
>  /**
> - * i40evf_reinit_locked - Software reinit
> - * @adapter: board private structure
> - *
> - * Reinititalizes the ring structures in response to a software
> configuration
> - * change. Roughly the same as close followed by open, but skips releasing
> - * and reallocating the interrupts.
> - **/
> -void i40evf_reinit_locked(struct i40evf_adapter *adapter)
> -{
> -	struct net_device *netdev = adapter->netdev;
> -	int err;
> -
> -	WARN_ON(in_interrupt());
> -
> -	i40evf_down(adapter);
> -
> -	/* allocate transmit descriptors */
> -	err = i40evf_setup_all_tx_resources(adapter);
> -	if (err)
> -		goto err_reinit;
> -
> -	/* allocate receive descriptors */
> -	err = i40evf_setup_all_rx_resources(adapter);
> -	if (err)
> -		goto err_reinit;
> -
> -	i40evf_configure(adapter);
> -
> -	err = i40evf_up_complete(adapter);
> -	if (err)
> -		goto err_reinit;
> -
> -	i40evf_irq_enable(adapter, true);
> -	return;
> -
> -err_reinit:
> -	dev_err(&adapter->pdev->dev, "failed to allocate resources during
> reinit\n");
> -	i40evf_close(netdev);
> -}
> -
> -/**
>   * i40evf_change_mtu - Change the Maximum Transfer Unit
>   * @netdev: network interface device structure
>   * @new_mtu: new value for maximum frame size
> @@ -1952,9 +1933,10 @@ static int i40evf_change_mtu(struct net_device
> *netdev, int new_mtu)
>  	if ((new_mtu < 68) || (max_frame > I40E_MAX_RXBUFFER))
>  		return -EINVAL;
> 
> -	/* must set new MTU before calling down or up */
>  	netdev->mtu = new_mtu;
> -	i40evf_reinit_locked(adapter);
> +	adapter->flags |= I40EVF_FLAG_RESET_NEEDED;
> +	schedule_work(&adapter->reset_task);
> +
>  	return 0;
>  }
> 
> --
> 2.4.1
diff mbox

Patch

diff --git a/drivers/net/ethernet/intel/i40evf/i40e_txrx.c b/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
index 458fbb4..bd49232 100644
--- a/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
+++ b/drivers/net/ethernet/intel/i40evf/i40e_txrx.c
@@ -488,6 +488,8 @@  int i40evf_setup_tx_descriptors(struct i40e_ring *tx_ring)
 	if (!dev)
 		return -ENOMEM;
 
+	/* warn if we are about to overwrite the pointer */
+	WARN_ON(tx_ring->tx_bi);
 	bi_size = sizeof(struct i40e_tx_buffer) * tx_ring->count;
 	tx_ring->tx_bi = kzalloc(bi_size, GFP_KERNEL);
 	if (!tx_ring->tx_bi)
@@ -648,6 +650,8 @@  int i40evf_setup_rx_descriptors(struct i40e_ring *rx_ring)
 	struct device *dev = rx_ring->dev;
 	int bi_size;
 
+	/* warn if we are about to overwrite the pointer */
+	WARN_ON(rx_ring->rx_bi);
 	bi_size = sizeof(struct i40e_rx_buffer) * rx_ring->count;
 	rx_ring->rx_bi = kzalloc(bi_size, GFP_KERNEL);
 	if (!rx_ring->rx_bi)
diff --git a/drivers/net/ethernet/intel/i40evf/i40evf.h b/drivers/net/ethernet/intel/i40evf/i40evf.h
index 1b98c25..fea3b75 100644
--- a/drivers/net/ethernet/intel/i40evf/i40evf.h
+++ b/drivers/net/ethernet/intel/i40evf/i40evf.h
@@ -264,7 +264,6 @@  extern const char i40evf_driver_version[];
 
 int i40evf_up(struct i40evf_adapter *adapter);
 void i40evf_down(struct i40evf_adapter *adapter);
-void i40evf_reinit_locked(struct i40evf_adapter *adapter);
 void i40evf_reset(struct i40evf_adapter *adapter);
 void i40evf_set_ethtool_ops(struct net_device *netdev);
 void i40evf_update_stats(struct i40evf_adapter *adapter);
diff --git a/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c b/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
index f4e7766..2b53c87 100644
--- a/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
+++ b/drivers/net/ethernet/intel/i40evf/i40evf_ethtool.c
@@ -267,8 +267,10 @@  static int i40evf_set_ringparam(struct net_device *netdev,
 	adapter->tx_desc_count = new_tx_count;
 	adapter->rx_desc_count = new_rx_count;
 
-	if (netif_running(netdev))
-		i40evf_reinit_locked(adapter);
+	if (netif_running(netdev)) {
+		adapter->flags |= I40EVF_FLAG_RESET_NEEDED;
+		schedule_work(&adapter->reset_task);
+	}
 
 	return 0;
 }
diff --git a/drivers/net/ethernet/intel/i40evf/i40evf_main.c b/drivers/net/ethernet/intel/i40evf/i40evf_main.c
index 7c53aca..ec5f7ed 100644
--- a/drivers/net/ethernet/intel/i40evf/i40evf_main.c
+++ b/drivers/net/ethernet/intel/i40evf/i40evf_main.c
@@ -170,7 +170,7 @@  static void i40evf_tx_timeout(struct net_device *netdev)
 	struct i40evf_adapter *adapter = netdev_priv(netdev);
 
 	adapter->tx_timeout_count++;
-	if (!(adapter->flags & I40EVF_FLAG_RESET_PENDING)) {
+	if (!(adapter->flags & (I40EVF_FLAG_RESET_PENDING | I40EVF_FLAG_RESET_NEEDED))) {
 		adapter->flags |= I40EVF_FLAG_RESET_NEEDED;
 		schedule_work(&adapter->reset_task);
 	}
@@ -1470,8 +1470,8 @@  static void i40evf_configure_rss(struct i40evf_adapter *adapter)
 	i40e_flush(hw);
 }
 
-#define I40EVF_RESET_WAIT_MS 100
-#define I40EVF_RESET_WAIT_COUNT 200
+#define I40EVF_RESET_WAIT_MS 10
+#define I40EVF_RESET_WAIT_COUNT 500
 /**
  * i40evf_reset_task - Call-back task to handle hardware reset
  * @work: pointer to work_struct
@@ -1495,10 +1495,17 @@  static void i40evf_reset_task(struct work_struct *work)
 				&adapter->crit_section))
 		usleep_range(500, 1000);
 
+	i40evf_misc_irq_disable(adapter);
 	if (adapter->flags & I40EVF_FLAG_RESET_NEEDED) {
-		dev_info(&adapter->pdev->dev, "Requesting reset from PF\n");
+		adapter->flags &= ~I40EVF_FLAG_RESET_NEEDED;
+		/* Restart the AQ here. If we have been reset but didn't
+		 * detect it, or if the PF had to reinit, our AQ will be hosed.
+		 */
+		i40evf_shutdown_adminq(hw);
+		i40evf_init_adminq(hw);
 		i40evf_request_reset(adapter);
 	}
+	adapter->flags |= I40EVF_FLAG_RESET_PENDING;
 
 	/* poll until we see the reset actually happen */
 	for (i = 0; i < I40EVF_RESET_WAIT_COUNT; i++) {
@@ -1507,10 +1514,10 @@  static void i40evf_reset_task(struct work_struct *work)
 		if ((rstat_val != I40E_VFR_VFACTIVE) &&
 		    (rstat_val != I40E_VFR_COMPLETED))
 			break;
-		msleep(I40EVF_RESET_WAIT_MS);
+		usleep_range(500, 1000);
 	}
 	if (i == I40EVF_RESET_WAIT_COUNT) {
-		adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
+		dev_info(&adapter->pdev->dev, "Never saw reset\n");
 		goto continue_reset; /* act like the reset happened */
 	}
 
@@ -1518,11 +1525,12 @@  static void i40evf_reset_task(struct work_struct *work)
 	for (i = 0; i < I40EVF_RESET_WAIT_COUNT; i++) {
 		rstat_val = rd32(hw, I40E_VFGEN_RSTAT) &
 			    I40E_VFGEN_RSTAT_VFR_STATE_MASK;
-		if ((rstat_val == I40E_VFR_VFACTIVE) ||
-		    (rstat_val == I40E_VFR_COMPLETED))
+		if (rstat_val == I40E_VFR_VFACTIVE)
 			break;
 		msleep(I40EVF_RESET_WAIT_MS);
 	}
+	/* extra wait to make sure minimum wait is met */
+	msleep(I40EVF_RESET_WAIT_MS);
 	if (i == I40EVF_RESET_WAIT_COUNT) {
 		struct i40evf_mac_filter *f, *ftmp;
 		struct i40evf_vlan_filter *fv, *fvtmp;
@@ -1534,11 +1542,10 @@  static void i40evf_reset_task(struct work_struct *work)
 
 		if (netif_running(adapter->netdev)) {
 			set_bit(__I40E_DOWN, &adapter->vsi.state);
-			i40evf_irq_disable(adapter);
-			i40evf_napi_disable_all(adapter);
-			netif_tx_disable(netdev);
-			netif_tx_stop_all_queues(netdev);
 			netif_carrier_off(netdev);
+			netif_tx_disable(netdev);
+			i40evf_napi_disable_all(adapter);
+			i40evf_irq_disable(adapter);
 			i40evf_free_traffic_irqs(adapter);
 			i40evf_free_all_tx_resources(adapter);
 			i40evf_free_all_rx_resources(adapter);
@@ -1550,6 +1557,7 @@  static void i40evf_reset_task(struct work_struct *work)
 			list_del(&f->list);
 			kfree(f);
 		}
+
 		list_for_each_entry_safe(fv, fvtmp, &adapter->vlan_filter_list,
 					 list) {
 			list_del(&fv->list);
@@ -1564,22 +1572,27 @@  static void i40evf_reset_task(struct work_struct *work)
 		i40evf_shutdown_adminq(hw);
 		adapter->netdev->flags &= ~IFF_UP;
 		clear_bit(__I40EVF_IN_CRITICAL_TASK, &adapter->crit_section);
+		adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
+		dev_info(&adapter->pdev->dev, "Reset task did not complete, VF disabled\n");
 		return; /* Do not attempt to reinit. It's dead, Jim. */
 	}
 
 continue_reset:
-	adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
-
-	i40evf_irq_disable(adapter);
-
 	if (netif_running(adapter->netdev)) {
-		i40evf_napi_disable_all(adapter);
-		netif_tx_disable(netdev);
-		netif_tx_stop_all_queues(netdev);
 		netif_carrier_off(netdev);
+		netif_tx_stop_all_queues(netdev);
+		i40evf_napi_disable_all(adapter);
 	}
+	i40evf_irq_disable(adapter);
 
 	adapter->state = __I40EVF_RESETTING;
+	adapter->flags &= ~I40EVF_FLAG_RESET_PENDING;
+
+	/* free the tx/rx rings and descriptors, might be better to just
+	 * re-use them sometime in the future
+	 */
+	i40evf_free_all_rx_resources(adapter);
+	i40evf_free_all_tx_resources(adapter);
 
 	/* kill and reinit the admin queue */
 	if (i40evf_shutdown_adminq(hw))
@@ -1603,6 +1616,7 @@  continue_reset:
 	adapter->aq_required = I40EVF_FLAG_AQ_ADD_MAC_FILTER;
 	adapter->aq_required |= I40EVF_FLAG_AQ_ADD_VLAN_FILTER;
 	clear_bit(__I40EVF_IN_CRITICAL_TASK, &adapter->crit_section);
+	i40evf_misc_irq_enable(adapter);
 
 	mod_timer(&adapter->watchdog_timer, jiffies + 2);
 
@@ -1624,7 +1638,9 @@  continue_reset:
 			goto reset_err;
 
 		i40evf_irq_enable(adapter, true);
-	}
+	} else
+ 		adapter->state = __I40EVF_DOWN;
+
 	return;
 reset_err:
 	dev_err(&adapter->pdev->dev, "failed to allocate resources during reinit\n");
@@ -1667,6 +1683,11 @@  static void i40evf_adminq_task(struct work_struct *work)
 			memset(event.msg_buf, 0, I40EVF_MAX_AQ_BUF_SIZE);
 	} while (pending);
 
+	if ((adapter->flags &
+	     (I40EVF_FLAG_RESET_PENDING | I40EVF_FLAG_RESET_NEEDED)) ||
+	    adapter->state == __I40EVF_RESETTING)
+		goto freedom;
+
 	/* check for error indications */
 	val = rd32(hw, hw->aq.arq.len);
 	oldval = val;
@@ -1702,6 +1723,7 @@  static void i40evf_adminq_task(struct work_struct *work)
 	if (oldval != val)
 		wr32(hw, hw->aq.asq.len, val);
 
+freedom:
 	kfree(event.msg_buf);
 out:
 	/* re-enable Admin queue interrupt cause */
@@ -1897,47 +1919,6 @@  static struct net_device_stats *i40evf_get_stats(struct net_device *netdev)
 }
 
 /**
- * i40evf_reinit_locked - Software reinit
- * @adapter: board private structure
- *
- * Reinititalizes the ring structures in response to a software configuration
- * change. Roughly the same as close followed by open, but skips releasing
- * and reallocating the interrupts.
- **/
-void i40evf_reinit_locked(struct i40evf_adapter *adapter)
-{
-	struct net_device *netdev = adapter->netdev;
-	int err;
-
-	WARN_ON(in_interrupt());
-
-	i40evf_down(adapter);
-
-	/* allocate transmit descriptors */
-	err = i40evf_setup_all_tx_resources(adapter);
-	if (err)
-		goto err_reinit;
-
-	/* allocate receive descriptors */
-	err = i40evf_setup_all_rx_resources(adapter);
-	if (err)
-		goto err_reinit;
-
-	i40evf_configure(adapter);
-
-	err = i40evf_up_complete(adapter);
-	if (err)
-		goto err_reinit;
-
-	i40evf_irq_enable(adapter, true);
-	return;
-
-err_reinit:
-	dev_err(&adapter->pdev->dev, "failed to allocate resources during reinit\n");
-	i40evf_close(netdev);
-}
-
-/**
  * i40evf_change_mtu - Change the Maximum Transfer Unit
  * @netdev: network interface device structure
  * @new_mtu: new value for maximum frame size
@@ -1952,9 +1933,10 @@  static int i40evf_change_mtu(struct net_device *netdev, int new_mtu)
 	if ((new_mtu < 68) || (max_frame > I40E_MAX_RXBUFFER))
 		return -EINVAL;
 
-	/* must set new MTU before calling down or up */
 	netdev->mtu = new_mtu;
-	i40evf_reinit_locked(adapter);
+	adapter->flags |= I40EVF_FLAG_RESET_NEEDED;
+	schedule_work(&adapter->reset_task);
+
 	return 0;
 }