[v6] net/i40e: add outer VLAN processing

Message ID 20220610155216.81289-1-kevinx.liu@intel.com (mailing list archive)
State Superseded, archived
Delegated to: Qi Zhang
Headers
Series [v6] net/i40e: add outer VLAN processing |

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Kevin Liu June 10, 2022, 3:52 p.m. UTC
  From: Robin Zhang <robinx.zhang@intel.com>

Outer VLAN processing is supported after firmware v8.4, kernel driver
also change the default behavior to support this feature. To align with
kernel driver, add support for outer VLAN processing in DPDK.

But it is forbidden for firmware to change the Inner/Outer VLAN
configuration while there are MAC/VLAN filters in the switch table.
Therefore, we need to clear the MAC table before setting config,
and then restore the MAC table after setting.

This will not impact on an old firmware.

Signed-off-by: Robin Zhang <robinx.zhang@intel.com>
Signed-off-by: Kevin Liu <kevinx.liu@intel.com>
---
 drivers/net/i40e/i40e_ethdev.c | 94 ++++++++++++++++++++++++++++++++--
 drivers/net/i40e/i40e_ethdev.h |  3 ++
 2 files changed, 92 insertions(+), 5 deletions(-)
  

Comments

Qi Zhang June 10, 2022, 8:06 a.m. UTC | #1
> -----Original Message-----
> From: Kevin Liu <kevinx.liu@intel.com>
> Sent: Friday, June 10, 2022 11:52 PM
> To: dev@dpdk.org
> Cc: Zhang, Yuying <yuying.zhang@intel.com>; Xing, Beilei
> <beilei.xing@intel.com>; Yang, SteveX <stevex.yang@intel.com>; Zhang,
> RobinX <robinx.zhang@intel.com>; Liu, KevinX <kevinx.liu@intel.com>
> Subject: [PATCH v6] net/i40e: add outer VLAN processing
> 
> From: Robin Zhang <robinx.zhang@intel.com>
> 
> Outer VLAN processing is supported after firmware v8.4, kernel driver also
> change the default behavior to support this feature. To align with kernel driver,
> add support for outer VLAN processing in DPDK.
> 
> But it is forbidden for firmware to change the Inner/Outer VLAN configuration
> while there are MAC/VLAN filters in the switch table.
> Therefore, we need to clear the MAC table before setting config, and then
> restore the MAC table after setting.
> 
> This will not impact on an old firmware.
> 
> Signed-off-by: Robin Zhang <robinx.zhang@intel.com>
> Signed-off-by: Kevin Liu <kevinx.liu@intel.com>
> ---
>  drivers/net/i40e/i40e_ethdev.c | 94 ++++++++++++++++++++++++++++++++--
>  drivers/net/i40e/i40e_ethdev.h |  3 ++
>  2 files changed, 92 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/net/i40e/i40e_ethdev.c b/drivers/net/i40e/i40e_ethdev.c
> index 755786dc10..c708bdb0d4 100644
> --- a/drivers/net/i40e/i40e_ethdev.c
> +++ b/drivers/net/i40e/i40e_ethdev.c
> @@ -2575,6 +2575,7 @@ i40e_dev_close(struct rte_eth_dev *dev)
>  	struct i40e_hw *hw = I40E_DEV_PRIVATE_TO_HW(dev->data-
> >dev_private);
>  	struct rte_pci_device *pci_dev = RTE_ETH_DEV_TO_PCI(dev);
>  	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
> +	struct rte_eth_rxmode *rxmode = &dev->data->dev_conf.rxmode;
>  	struct i40e_filter_control_settings settings;
>  	struct rte_flow *p_flow;
>  	uint32_t reg;
> @@ -2587,6 +2588,18 @@ i40e_dev_close(struct rte_eth_dev *dev)
>  	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
>  		return 0;
> 
> +	/*
> +	 * It is a workaround, when firmware > v8.3, if the double VLAN
> +	 * is disabled when the program exits, an abnormal error will occur
> +	 * on the NIC. Need to enable double VLAN when dev is closed.
> +	 */
> +	if (pf->is_outer_vlan_processing) {
> +		if (!(rxmode->offloads &
> RTE_ETH_RX_OFFLOAD_VLAN_EXTEND)) {
> +			rxmode->offloads |=
> RTE_ETH_RX_OFFLOAD_VLAN_EXTEND;
> +			i40e_vlan_offload_set(dev,
> RTE_ETH_VLAN_EXTEND_MASK);
> +		}
> +	}
> +
>  	ret = rte_eth_switch_domain_free(pf->switch_domain_id);
>  	if (ret)
>  		PMD_INIT_LOG(WARNING, "failed to free switch domain: %d",
> ret); @@ -3909,6 +3922,7 @@ i40e_vlan_tpid_set(struct rte_eth_dev *dev,
>  	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data-
> >dev_private);
>  	int qinq = dev->data->dev_conf.rxmode.offloads &
>  		   RTE_ETH_RX_OFFLOAD_VLAN_EXTEND;
> +	u16 sw_flags = 0, valid_flags = 0;
>  	int ret = 0;
> 
>  	if ((vlan_type != RTE_ETH_VLAN_TYPE_INNER && @@ -3927,15
> +3941,32 @@ i40e_vlan_tpid_set(struct rte_eth_dev *dev,
>  	/* 802.1ad frames ability is added in NVM API 1.7*/
>  	if (hw->flags & I40E_HW_FLAG_802_1AD_CAPABLE) {
>  		if (qinq) {
> +			if (pf->is_outer_vlan_processing) {
> +				sw_flags =
> I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
> +				valid_flags =
> I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
> +			}
>  			if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
>  				hw->first_tag = rte_cpu_to_le_16(tpid);
>  			else if (vlan_type == RTE_ETH_VLAN_TYPE_INNER)
>  				hw->second_tag = rte_cpu_to_le_16(tpid);
>  		} else {
> -			if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
> -				hw->second_tag = rte_cpu_to_le_16(tpid);
> +			/*
> +			 * When firmware > 8.3, if tpid is equal to 0x88A8,
> +			 * indicates that the disable double VLAN operation is
> in progress.
> +			 * Need set switch configuration back to default.
> +			 */
> +			if (pf->is_outer_vlan_processing && tpid ==
> RTE_ETHER_TYPE_QINQ) {
> +				sw_flags = 0;
> +				valid_flags =
> I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
> +				if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
> +					hw->first_tag =
> rte_cpu_to_le_16(tpid);
> +			} else {
> +				if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
> +					hw->second_tag =
> rte_cpu_to_le_16(tpid);
> +			}
>  		}
> -		ret = i40e_aq_set_switch_config(hw, 0, 0, 0, NULL);
> +		ret = i40e_aq_set_switch_config(hw, sw_flags,
> +						valid_flags, 0, NULL);
>  		if (ret != I40E_SUCCESS) {
>  			PMD_DRV_LOG(ERR,
>  				    "Set switch config failed aq_err: %d", @@ -
> 3987,8 +4018,13 @@ static int  i40e_vlan_offload_set(struct rte_eth_dev
> *dev, int mask)  {
>  	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data-
> >dev_private);
> +	struct i40e_mac_filter_info *mac_filter;
>  	struct i40e_vsi *vsi = pf->main_vsi;
>  	struct rte_eth_rxmode *rxmode;
> +	struct i40e_mac_filter *f;
> +	int i, num;
> +	void *temp;
> +	int ret;
> 
>  	rxmode = &dev->data->dev_conf.rxmode;
>  	if (mask & RTE_ETH_VLAN_FILTER_MASK) { @@ -4007,6 +4043,33
> @@ i40e_vlan_offload_set(struct rte_eth_dev *dev, int mask)
>  	}
> 
>  	if (mask & RTE_ETH_VLAN_EXTEND_MASK) {
> +		i = 0;
> +		num = vsi->mac_num;
> +		mac_filter = rte_zmalloc("mac_filter_info_data",
> +				 num * sizeof(*mac_filter), 0);
> +		if (mac_filter == NULL) {
> +			PMD_DRV_LOG(ERR, "failed to allocate memory");
> +			return I40E_ERR_NO_MEMORY;
> +		}
> +
> +		/*
> +		 * Outer VLAN processing is supported after firmware v8.4,
> kernel driver
> +		 * also change the default behavior to support this feature. To
> align with
> +		 * kernel driver, set switch config in 'i40e_vlan_tpie_set' to
> support for
> +		 * outer VLAN processing. But it is forbidden for firmware to
> change the
> +		 * Inner/Outer VLAN configuration while there are MAC/VLAN
> filters in the
> +		 * switch table. Therefore, we need to clear the MAC table
> before setting
> +		 * config, and then restore the MAC table after setting. This
> feature is
> +		 * recommended to be used in firmware v8.6.
> +		 */
> +		/* Remove all existing mac */
> +		RTE_TAILQ_FOREACH_SAFE(f, &vsi->mac_list, next, temp) {
> +			mac_filter[i] = f->mac_info;
> +			ret = i40e_vsi_delete_mac(vsi, &f-
> >mac_info.mac_addr);
> +			if (ret)
> +				PMD_DRV_LOG(ERR, "i40e vsi delete mac
> fail.");
> +			i++;
> +		}
>  		if (rxmode->offloads &
> RTE_ETH_RX_OFFLOAD_VLAN_EXTEND) {
>  			i40e_vsi_config_double_vlan(vsi, TRUE);
>  			/* Set global registers with default ethertype. */ @@
> -4014,9 +4077,19 @@ i40e_vlan_offload_set(struct rte_eth_dev *dev, int
> mask)
>  					   RTE_ETHER_TYPE_VLAN);
>  			i40e_vlan_tpid_set(dev, RTE_ETH_VLAN_TYPE_INNER,
>  					   RTE_ETHER_TYPE_VLAN);
> -		}
> -		else
> +		} else {
> +			if (pf->is_outer_vlan_processing)
> +				i40e_vlan_tpid_set(dev,
> RTE_ETH_VLAN_TYPE_OUTER,
> +					   RTE_ETHER_TYPE_QINQ);
>  			i40e_vsi_config_double_vlan(vsi, FALSE);
> +		}
> +		/* Restore all mac */
> +		for (i = 0; i < num; i++) {
> +			ret = i40e_vsi_add_mac(vsi, &mac_filter[i]);
> +			if (ret)
> +				PMD_DRV_LOG(ERR, "i40e vsi add mac fail.");
> +		}
> +		rte_free(mac_filter);
>  	}
> 
>  	if (mask & RTE_ETH_QINQ_STRIP_MASK) {
> @@ -4846,6 +4919,17 @@ i40e_pf_parameter_init(struct rte_eth_dev *dev)
>  		return -EINVAL;
>  	}
> 
> +	/**
> +	 * Enable outer VLAN processing if firmware version is greater
> +	 * than v8.3
> +	 */
> +	if (hw->aq.fw_maj_ver > 8 ||
> +	    (hw->aq.fw_maj_ver == 8 && hw->aq.fw_min_ver > 3)) {
> +		pf->is_outer_vlan_processing = true;

Why not just name the flag as "fw8_3gt?

Then you don't need to repeat explain "when firmware > 8.3" above the is_outer_vlan_processing flag.
  
Kevin Liu June 10, 2022, 8:14 a.m. UTC | #2
> -----Original Message-----
> From: Zhang, Qi Z <qi.z.zhang@intel.com>
> Sent: 2022年6月10日 16:07
> To: Liu, KevinX <kevinx.liu@intel.com>; dev@dpdk.org
> Cc: Zhang, Yuying <yuying.zhang@intel.com>; Xing, Beilei
> <beilei.xing@intel.com>; Yang, SteveX <stevex.yang@intel.com>; Zhang,
> RobinX <robinx.zhang@intel.com>; Liu, KevinX <kevinx.liu@intel.com>
> Subject: RE: [PATCH v6] net/i40e: add outer VLAN processing
> 
> 
> 
> > -----Original Message-----
> > From: Kevin Liu <kevinx.liu@intel.com>
> > Sent: Friday, June 10, 2022 11:52 PM
> > To: dev@dpdk.org
> > Cc: Zhang, Yuying <yuying.zhang@intel.com>; Xing, Beilei
> > <beilei.xing@intel.com>; Yang, SteveX <stevex.yang@intel.com>; Zhang,
> > RobinX <robinx.zhang@intel.com>; Liu, KevinX <kevinx.liu@intel.com>
> > Subject: [PATCH v6] net/i40e: add outer VLAN processing
> >
> > From: Robin Zhang <robinx.zhang@intel.com>
> >
> > Outer VLAN processing is supported after firmware v8.4, kernel driver
> > also change the default behavior to support this feature. To align
> > with kernel driver, add support for outer VLAN processing in DPDK.
> >
> > But it is forbidden for firmware to change the Inner/Outer VLAN
> > configuration while there are MAC/VLAN filters in the switch table.
> > Therefore, we need to clear the MAC table before setting config, and
> > then restore the MAC table after setting.
> >
> > This will not impact on an old firmware.
> >
> > Signed-off-by: Robin Zhang <robinx.zhang@intel.com>
> > Signed-off-by: Kevin Liu <kevinx.liu@intel.com>
> > ---
> >  drivers/net/i40e/i40e_ethdev.c | 94
> > ++++++++++++++++++++++++++++++++--
> >  drivers/net/i40e/i40e_ethdev.h |  3 ++
> >  2 files changed, 92 insertions(+), 5 deletions(-)
> >
> > diff --git a/drivers/net/i40e/i40e_ethdev.c
> > b/drivers/net/i40e/i40e_ethdev.c index 755786dc10..c708bdb0d4 100644
> > --- a/drivers/net/i40e/i40e_ethdev.c
> > +++ b/drivers/net/i40e/i40e_ethdev.c
> > @@ -2575,6 +2575,7 @@ i40e_dev_close(struct rte_eth_dev *dev)
> >  	struct i40e_hw *hw = I40E_DEV_PRIVATE_TO_HW(dev->data-
> > >dev_private);
> >  	struct rte_pci_device *pci_dev = RTE_ETH_DEV_TO_PCI(dev);
> >  	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
> > +	struct rte_eth_rxmode *rxmode = &dev->data->dev_conf.rxmode;
> >  	struct i40e_filter_control_settings settings;
> >  	struct rte_flow *p_flow;
> >  	uint32_t reg;
> > @@ -2587,6 +2588,18 @@ i40e_dev_close(struct rte_eth_dev *dev)
> >  	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
> >  		return 0;
> >
> > +	/*
> > +	 * It is a workaround, when firmware > v8.3, if the double VLAN
> > +	 * is disabled when the program exits, an abnormal error will occur
> > +	 * on the NIC. Need to enable double VLAN when dev is closed.
> > +	 */
> > +	if (pf->is_outer_vlan_processing) {
> > +		if (!(rxmode->offloads &
> > RTE_ETH_RX_OFFLOAD_VLAN_EXTEND)) {
> > +			rxmode->offloads |=
> > RTE_ETH_RX_OFFLOAD_VLAN_EXTEND;
> > +			i40e_vlan_offload_set(dev,
> > RTE_ETH_VLAN_EXTEND_MASK);
> > +		}
> > +	}
> > +
> >  	ret = rte_eth_switch_domain_free(pf->switch_domain_id);
> >  	if (ret)
> >  		PMD_INIT_LOG(WARNING, "failed to free switch
> domain: %d", ret); @@
> > -3909,6 +3922,7 @@ i40e_vlan_tpid_set(struct rte_eth_dev *dev,
> >  	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data-
> > >dev_private);
> >  	int qinq = dev->data->dev_conf.rxmode.offloads &
> >  		   RTE_ETH_RX_OFFLOAD_VLAN_EXTEND;
> > +	u16 sw_flags = 0, valid_flags = 0;
> >  	int ret = 0;
> >
> >  	if ((vlan_type != RTE_ETH_VLAN_TYPE_INNER && @@ -3927,15
> > +3941,32 @@ i40e_vlan_tpid_set(struct rte_eth_dev *dev,
> >  	/* 802.1ad frames ability is added in NVM API 1.7*/
> >  	if (hw->flags & I40E_HW_FLAG_802_1AD_CAPABLE) {
> >  		if (qinq) {
> > +			if (pf->is_outer_vlan_processing) {
> > +				sw_flags =
> > I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
> > +				valid_flags =
> > I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
> > +			}
> >  			if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
> >  				hw->first_tag = rte_cpu_to_le_16(tpid);
> >  			else if (vlan_type == RTE_ETH_VLAN_TYPE_INNER)
> >  				hw->second_tag = rte_cpu_to_le_16(tpid);
> >  		} else {
> > -			if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
> > -				hw->second_tag = rte_cpu_to_le_16(tpid);
> > +			/*
> > +			 * When firmware > 8.3, if tpid is equal to 0x88A8,
> > +			 * indicates that the disable double VLAN operation is
> > in progress.
> > +			 * Need set switch configuration back to default.
> > +			 */
> > +			if (pf->is_outer_vlan_processing && tpid ==
> > RTE_ETHER_TYPE_QINQ) {
> > +				sw_flags = 0;
> > +				valid_flags =
> > I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
> > +				if (vlan_type ==
> RTE_ETH_VLAN_TYPE_OUTER)
> > +					hw->first_tag =
> > rte_cpu_to_le_16(tpid);
> > +			} else {
> > +				if (vlan_type ==
> RTE_ETH_VLAN_TYPE_OUTER)
> > +					hw->second_tag =
> > rte_cpu_to_le_16(tpid);
> > +			}
> >  		}
> > -		ret = i40e_aq_set_switch_config(hw, 0, 0, 0, NULL);
> > +		ret = i40e_aq_set_switch_config(hw, sw_flags,
> > +						valid_flags, 0, NULL);
> >  		if (ret != I40E_SUCCESS) {
> >  			PMD_DRV_LOG(ERR,
> >  				    "Set switch config failed aq_err: %d", @@ -
> > 3987,8 +4018,13 @@ static int  i40e_vlan_offload_set(struct
> > rte_eth_dev *dev, int mask)  {
> >  	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data-
> > >dev_private);
> > +	struct i40e_mac_filter_info *mac_filter;
> >  	struct i40e_vsi *vsi = pf->main_vsi;
> >  	struct rte_eth_rxmode *rxmode;
> > +	struct i40e_mac_filter *f;
> > +	int i, num;
> > +	void *temp;
> > +	int ret;
> >
> >  	rxmode = &dev->data->dev_conf.rxmode;
> >  	if (mask & RTE_ETH_VLAN_FILTER_MASK) { @@ -4007,6 +4043,33
> @@
> > i40e_vlan_offload_set(struct rte_eth_dev *dev, int mask)
> >  	}
> >
> >  	if (mask & RTE_ETH_VLAN_EXTEND_MASK) {
> > +		i = 0;
> > +		num = vsi->mac_num;
> > +		mac_filter = rte_zmalloc("mac_filter_info_data",
> > +				 num * sizeof(*mac_filter), 0);
> > +		if (mac_filter == NULL) {
> > +			PMD_DRV_LOG(ERR, "failed to allocate memory");
> > +			return I40E_ERR_NO_MEMORY;
> > +		}
> > +
> > +		/*
> > +		 * Outer VLAN processing is supported after firmware v8.4,
> > kernel driver
> > +		 * also change the default behavior to support this feature.
> To
> > align with
> > +		 * kernel driver, set switch config in 'i40e_vlan_tpie_set' to
> > support for
> > +		 * outer VLAN processing. But it is forbidden for firmware to
> > change the
> > +		 * Inner/Outer VLAN configuration while there are
> MAC/VLAN
> > filters in the
> > +		 * switch table. Therefore, we need to clear the MAC table
> > before setting
> > +		 * config, and then restore the MAC table after setting. This
> > feature is
> > +		 * recommended to be used in firmware v8.6.
> > +		 */
> > +		/* Remove all existing mac */
> > +		RTE_TAILQ_FOREACH_SAFE(f, &vsi->mac_list, next, temp) {
> > +			mac_filter[i] = f->mac_info;
> > +			ret = i40e_vsi_delete_mac(vsi, &f-
> > >mac_info.mac_addr);
> > +			if (ret)
> > +				PMD_DRV_LOG(ERR, "i40e vsi delete mac
> > fail.");
> > +			i++;
> > +		}
> >  		if (rxmode->offloads &
> > RTE_ETH_RX_OFFLOAD_VLAN_EXTEND) {
> >  			i40e_vsi_config_double_vlan(vsi, TRUE);
> >  			/* Set global registers with default ethertype. */ @@
> > -4014,9 +4077,19 @@ i40e_vlan_offload_set(struct rte_eth_dev *dev, int
> > mask)
> >  					   RTE_ETHER_TYPE_VLAN);
> >  			i40e_vlan_tpid_set(dev,
> RTE_ETH_VLAN_TYPE_INNER,
> >  					   RTE_ETHER_TYPE_VLAN);
> > -		}
> > -		else
> > +		} else {
> > +			if (pf->is_outer_vlan_processing)
> > +				i40e_vlan_tpid_set(dev,
> > RTE_ETH_VLAN_TYPE_OUTER,
> > +					   RTE_ETHER_TYPE_QINQ);
> >  			i40e_vsi_config_double_vlan(vsi, FALSE);
> > +		}
> > +		/* Restore all mac */
> > +		for (i = 0; i < num; i++) {
> > +			ret = i40e_vsi_add_mac(vsi, &mac_filter[i]);
> > +			if (ret)
> > +				PMD_DRV_LOG(ERR, "i40e vsi add mac fail.");
> > +		}
> > +		rte_free(mac_filter);
> >  	}
> >
> >  	if (mask & RTE_ETH_QINQ_STRIP_MASK) { @@ -4846,6 +4919,17 @@
> > i40e_pf_parameter_init(struct rte_eth_dev *dev)
> >  		return -EINVAL;
> >  	}
> >
> > +	/**
> > +	 * Enable outer VLAN processing if firmware version is greater
> > +	 * than v8.3
> > +	 */
> > +	if (hw->aq.fw_maj_ver > 8 ||
> > +	    (hw->aq.fw_maj_ver == 8 && hw->aq.fw_min_ver > 3)) {
> > +		pf->is_outer_vlan_processing = true;
> 
> Why not just name the flag as "fw8_3gt?
> 
> Then you don't need to repeat explain "when firmware > 8.3" above the
> is_outer_vlan_processing flag.
> 
That's a good idea, I will send v7, thank you!
  

Patch

diff --git a/drivers/net/i40e/i40e_ethdev.c b/drivers/net/i40e/i40e_ethdev.c
index 755786dc10..c708bdb0d4 100644
--- a/drivers/net/i40e/i40e_ethdev.c
+++ b/drivers/net/i40e/i40e_ethdev.c
@@ -2575,6 +2575,7 @@  i40e_dev_close(struct rte_eth_dev *dev)
 	struct i40e_hw *hw = I40E_DEV_PRIVATE_TO_HW(dev->data->dev_private);
 	struct rte_pci_device *pci_dev = RTE_ETH_DEV_TO_PCI(dev);
 	struct rte_intr_handle *intr_handle = pci_dev->intr_handle;
+	struct rte_eth_rxmode *rxmode = &dev->data->dev_conf.rxmode;
 	struct i40e_filter_control_settings settings;
 	struct rte_flow *p_flow;
 	uint32_t reg;
@@ -2587,6 +2588,18 @@  i40e_dev_close(struct rte_eth_dev *dev)
 	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
 		return 0;
 
+	/*
+	 * It is a workaround, when firmware > v8.3, if the double VLAN
+	 * is disabled when the program exits, an abnormal error will occur
+	 * on the NIC. Need to enable double VLAN when dev is closed.
+	 */
+	if (pf->is_outer_vlan_processing) {
+		if (!(rxmode->offloads & RTE_ETH_RX_OFFLOAD_VLAN_EXTEND)) {
+			rxmode->offloads |= RTE_ETH_RX_OFFLOAD_VLAN_EXTEND;
+			i40e_vlan_offload_set(dev, RTE_ETH_VLAN_EXTEND_MASK);
+		}
+	}
+
 	ret = rte_eth_switch_domain_free(pf->switch_domain_id);
 	if (ret)
 		PMD_INIT_LOG(WARNING, "failed to free switch domain: %d", ret);
@@ -3909,6 +3922,7 @@  i40e_vlan_tpid_set(struct rte_eth_dev *dev,
 	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
 	int qinq = dev->data->dev_conf.rxmode.offloads &
 		   RTE_ETH_RX_OFFLOAD_VLAN_EXTEND;
+	u16 sw_flags = 0, valid_flags = 0;
 	int ret = 0;
 
 	if ((vlan_type != RTE_ETH_VLAN_TYPE_INNER &&
@@ -3927,15 +3941,32 @@  i40e_vlan_tpid_set(struct rte_eth_dev *dev,
 	/* 802.1ad frames ability is added in NVM API 1.7*/
 	if (hw->flags & I40E_HW_FLAG_802_1AD_CAPABLE) {
 		if (qinq) {
+			if (pf->is_outer_vlan_processing) {
+				sw_flags = I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
+				valid_flags = I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
+			}
 			if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
 				hw->first_tag = rte_cpu_to_le_16(tpid);
 			else if (vlan_type == RTE_ETH_VLAN_TYPE_INNER)
 				hw->second_tag = rte_cpu_to_le_16(tpid);
 		} else {
-			if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
-				hw->second_tag = rte_cpu_to_le_16(tpid);
+			/*
+			 * When firmware > 8.3, if tpid is equal to 0x88A8,
+			 * indicates that the disable double VLAN operation is in progress.
+			 * Need set switch configuration back to default.
+			 */
+			if (pf->is_outer_vlan_processing && tpid == RTE_ETHER_TYPE_QINQ) {
+				sw_flags = 0;
+				valid_flags = I40E_AQ_SET_SWITCH_CFG_OUTER_VLAN;
+				if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
+					hw->first_tag = rte_cpu_to_le_16(tpid);
+			} else {
+				if (vlan_type == RTE_ETH_VLAN_TYPE_OUTER)
+					hw->second_tag = rte_cpu_to_le_16(tpid);
+			}
 		}
-		ret = i40e_aq_set_switch_config(hw, 0, 0, 0, NULL);
+		ret = i40e_aq_set_switch_config(hw, sw_flags,
+						valid_flags, 0, NULL);
 		if (ret != I40E_SUCCESS) {
 			PMD_DRV_LOG(ERR,
 				    "Set switch config failed aq_err: %d",
@@ -3987,8 +4018,13 @@  static int
 i40e_vlan_offload_set(struct rte_eth_dev *dev, int mask)
 {
 	struct i40e_pf *pf = I40E_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+	struct i40e_mac_filter_info *mac_filter;
 	struct i40e_vsi *vsi = pf->main_vsi;
 	struct rte_eth_rxmode *rxmode;
+	struct i40e_mac_filter *f;
+	int i, num;
+	void *temp;
+	int ret;
 
 	rxmode = &dev->data->dev_conf.rxmode;
 	if (mask & RTE_ETH_VLAN_FILTER_MASK) {
@@ -4007,6 +4043,33 @@  i40e_vlan_offload_set(struct rte_eth_dev *dev, int mask)
 	}
 
 	if (mask & RTE_ETH_VLAN_EXTEND_MASK) {
+		i = 0;
+		num = vsi->mac_num;
+		mac_filter = rte_zmalloc("mac_filter_info_data",
+				 num * sizeof(*mac_filter), 0);
+		if (mac_filter == NULL) {
+			PMD_DRV_LOG(ERR, "failed to allocate memory");
+			return I40E_ERR_NO_MEMORY;
+		}
+
+		/*
+		 * Outer VLAN processing is supported after firmware v8.4, kernel driver
+		 * also change the default behavior to support this feature. To align with
+		 * kernel driver, set switch config in 'i40e_vlan_tpie_set' to support for
+		 * outer VLAN processing. But it is forbidden for firmware to change the
+		 * Inner/Outer VLAN configuration while there are MAC/VLAN filters in the
+		 * switch table. Therefore, we need to clear the MAC table before setting
+		 * config, and then restore the MAC table after setting. This feature is
+		 * recommended to be used in firmware v8.6.
+		 */
+		/* Remove all existing mac */
+		RTE_TAILQ_FOREACH_SAFE(f, &vsi->mac_list, next, temp) {
+			mac_filter[i] = f->mac_info;
+			ret = i40e_vsi_delete_mac(vsi, &f->mac_info.mac_addr);
+			if (ret)
+				PMD_DRV_LOG(ERR, "i40e vsi delete mac fail.");
+			i++;
+		}
 		if (rxmode->offloads & RTE_ETH_RX_OFFLOAD_VLAN_EXTEND) {
 			i40e_vsi_config_double_vlan(vsi, TRUE);
 			/* Set global registers with default ethertype. */
@@ -4014,9 +4077,19 @@  i40e_vlan_offload_set(struct rte_eth_dev *dev, int mask)
 					   RTE_ETHER_TYPE_VLAN);
 			i40e_vlan_tpid_set(dev, RTE_ETH_VLAN_TYPE_INNER,
 					   RTE_ETHER_TYPE_VLAN);
-		}
-		else
+		} else {
+			if (pf->is_outer_vlan_processing)
+				i40e_vlan_tpid_set(dev, RTE_ETH_VLAN_TYPE_OUTER,
+					   RTE_ETHER_TYPE_QINQ);
 			i40e_vsi_config_double_vlan(vsi, FALSE);
+		}
+		/* Restore all mac */
+		for (i = 0; i < num; i++) {
+			ret = i40e_vsi_add_mac(vsi, &mac_filter[i]);
+			if (ret)
+				PMD_DRV_LOG(ERR, "i40e vsi add mac fail.");
+		}
+		rte_free(mac_filter);
 	}
 
 	if (mask & RTE_ETH_QINQ_STRIP_MASK) {
@@ -4846,6 +4919,17 @@  i40e_pf_parameter_init(struct rte_eth_dev *dev)
 		return -EINVAL;
 	}
 
+	/**
+	 * Enable outer VLAN processing if firmware version is greater
+	 * than v8.3
+	 */
+	if (hw->aq.fw_maj_ver > 8 ||
+	    (hw->aq.fw_maj_ver == 8 && hw->aq.fw_min_ver > 3)) {
+		pf->is_outer_vlan_processing = true;
+	} else {
+		pf->is_outer_vlan_processing = false;
+	}
+
 	return 0;
 }
 
diff --git a/drivers/net/i40e/i40e_ethdev.h b/drivers/net/i40e/i40e_ethdev.h
index a1ebdc093c..531ada447d 100644
--- a/drivers/net/i40e/i40e_ethdev.h
+++ b/drivers/net/i40e/i40e_ethdev.h
@@ -1188,6 +1188,9 @@  struct i40e_pf {
 	/* Switch Domain Id */
 	uint16_t switch_domain_id;
 
+	/* The enable flag for outer VLAN processing */
+	bool is_outer_vlan_processing;
+
 	struct i40e_vf_msg_cfg vf_msg_cfg;
 	uint64_t prev_rx_bytes;
 	uint64_t prev_tx_bytes;