}
 
 static int
-qlcnic_check_rx_tagging(struct qlcnic_adapter *adapter, struct sk_buff *skb)
+qlcnic_check_rx_tagging(struct qlcnic_adapter *adapter, struct sk_buff *skb,
+                       u16 *vlan_tag)
 {
-       u16 vlan_tag;
        struct ethhdr *eth_hdr;
 
-       if (!__vlan_get_tag(skb, &vlan_tag)) {
-               if (vlan_tag == adapter->pvid) {
-                       /* strip the tag from the packet and send it up */
-                       eth_hdr = (struct ethhdr *) skb->data;
-                       memmove(skb->data + VLAN_HLEN, eth_hdr, ETH_ALEN * 2);
-                       skb_pull(skb, VLAN_HLEN);
-                       return 0;
-               }
+       if (!__vlan_get_tag(skb, vlan_tag)) {
+               eth_hdr = (struct ethhdr *) skb->data;
+               memmove(skb->data + VLAN_HLEN, eth_hdr, ETH_ALEN * 2);
+               skb_pull(skb, VLAN_HLEN);
+       }
+       if (!adapter->pvid)
+               return 0;
+
+       if (*vlan_tag == adapter->pvid) {
+               /* Outer vlan tag. Packet should follow non-vlan path */
+               *vlan_tag = 0xffff;
+               return 0;
        }
        if (adapter->flags & QLCNIC_TAGGING_ENABLED)
                return 0;
 
-       return -EIO;
+       return -EINVAL;
 }
 
 static struct qlcnic_rx_buffer *
        struct sk_buff *skb;
        struct qlcnic_host_rds_ring *rds_ring;
        int index, length, cksum, pkt_offset;
+       u16 vid = 0xffff;
 
        if (unlikely(ring >= adapter->max_rds_rings))
                return NULL;
 
        skb->truesize = skb->len + sizeof(struct sk_buff);
 
-       if (unlikely(adapter->pvid)) {
-               if (qlcnic_check_rx_tagging(adapter, skb)) {
-                       adapter->stats.rxdropped++;
-                       dev_kfree_skb_any(skb);
-                       return buffer;
-               }
+       if (unlikely(qlcnic_check_rx_tagging(adapter, skb, &vid))) {
+               adapter->stats.rxdropped++;
+               dev_kfree_skb(skb);
+               return buffer;
        }
 
        skb->protocol = eth_type_trans(skb, netdev);
 
-       napi_gro_receive(&sds_ring->napi, skb);
+       if ((vid != 0xffff) && adapter->vlgrp)
+               vlan_hwaccel_receive_skb(skb, adapter->vlgrp, vid);
+       else
+               napi_gro_receive(&sds_ring->napi, skb);
 
        adapter->stats.rx_pkts++;
        adapter->stats.rxbytes += length;
        int index;
        u16 lro_length, length, data_offset;
        u32 seq_number;
+       u16 vid = 0xffff;
 
        if (unlikely(ring > adapter->max_rds_rings))
                return NULL;
 
        skb_pull(skb, l2_hdr_offset);
 
-       if (unlikely(adapter->pvid)) {
-               if (qlcnic_check_rx_tagging(adapter, skb)) {
-                       adapter->stats.rxdropped++;
-                       dev_kfree_skb_any(skb);
-                       return buffer;
-               }
+       if (unlikely(qlcnic_check_rx_tagging(adapter, skb, &vid))) {
+               adapter->stats.rxdropped++;
+               dev_kfree_skb(skb);
+               return buffer;
        }
+
        skb->protocol = eth_type_trans(skb, netdev);
 
        iph = (struct iphdr *)skb->data;
 
        length = skb->len;
 
-       netif_receive_skb(skb);
+       if ((vid != 0xffff) && adapter->vlgrp)
+               vlan_hwaccel_receive_skb(skb, adapter->vlgrp, vid);
+       else
+               netif_receive_skb(skb);
 
        adapter->stats.lro_pkts++;
        adapter->stats.lrobytes += length;
 
        return 0;
 }
 
+static void qlcnic_vlan_rx_register(struct net_device *netdev,
+               struct vlan_group *grp)
+{
+       struct qlcnic_adapter *adapter = netdev_priv(netdev);
+       adapter->vlgrp = grp;
+}
+
 static const struct net_device_ops qlcnic_netdev_ops = {
        .ndo_open          = qlcnic_open,
        .ndo_stop          = qlcnic_close,
        .ndo_set_mac_address    = qlcnic_set_mac,
        .ndo_change_mtu    = qlcnic_change_mtu,
        .ndo_tx_timeout    = qlcnic_tx_timeout,
+       .ndo_vlan_rx_register = qlcnic_vlan_rx_register,
 #ifdef CONFIG_NET_POLL_CONTROLLER
        .ndo_poll_controller = qlcnic_poll_controller,
 #endif
        SET_ETHTOOL_OPS(netdev, &qlcnic_ethtool_ops);
 
        netdev->features |= (NETIF_F_SG | NETIF_F_IP_CSUM |
-               NETIF_F_IPV6_CSUM | NETIF_F_GRO);
+               NETIF_F_IPV6_CSUM | NETIF_F_GRO | NETIF_F_HW_VLAN_RX);
        netdev->vlan_features |= (NETIF_F_SG | NETIF_F_IP_CSUM |
                NETIF_F_IPV6_CSUM);