]> Pileus Git - ~andy/linux/blobdiff - drivers/net/ethernet/emulex/benet/be_main.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[~andy/linux] / drivers / net / ethernet / emulex / benet / be_main.c
index 1c734915933f4c77c8b76eedaa800bfde2bd6e6b..4babc8a4a54396b9dfbb64dd0292f5ade612021c 100644 (file)
@@ -639,13 +639,8 @@ static inline u16 be_get_tx_vlan_tag(struct be_adapter *adapter,
        return vlan_tag;
 }
 
-static int be_vlan_tag_chk(struct be_adapter *adapter, struct sk_buff *skb)
-{
-       return vlan_tx_tag_present(skb) || adapter->pvid;
-}
-
 static void wrb_fill_hdr(struct be_adapter *adapter, struct be_eth_hdr_wrb *hdr,
-               struct sk_buff *skb, u32 wrb_cnt, u32 len)
+               struct sk_buff *skb, u32 wrb_cnt, u32 len, bool skip_hw_vlan)
 {
        u16 vlan_tag;
 
@@ -672,8 +667,9 @@ static void wrb_fill_hdr(struct be_adapter *adapter, struct be_eth_hdr_wrb *hdr,
                AMAP_SET_BITS(struct amap_eth_hdr_wrb, vlan_tag, hdr, vlan_tag);
        }
 
+       /* To skip HW VLAN tagging: evt = 1, compl = 0 */
+       AMAP_SET_BITS(struct amap_eth_hdr_wrb, complete, hdr, !skip_hw_vlan);
        AMAP_SET_BITS(struct amap_eth_hdr_wrb, event, hdr, 1);
-       AMAP_SET_BITS(struct amap_eth_hdr_wrb, complete, hdr, 1);
        AMAP_SET_BITS(struct amap_eth_hdr_wrb, num_wrb, hdr, wrb_cnt);
        AMAP_SET_BITS(struct amap_eth_hdr_wrb, len, hdr, len);
 }
@@ -696,7 +692,8 @@ static void unmap_tx_frag(struct device *dev, struct be_eth_wrb *wrb,
 }
 
 static int make_tx_wrbs(struct be_adapter *adapter, struct be_queue_info *txq,
-               struct sk_buff *skb, u32 wrb_cnt, bool dummy_wrb)
+               struct sk_buff *skb, u32 wrb_cnt, bool dummy_wrb,
+               bool skip_hw_vlan)
 {
        dma_addr_t busaddr;
        int i, copied = 0;
@@ -745,7 +742,7 @@ static int make_tx_wrbs(struct be_adapter *adapter, struct be_queue_info *txq,
                queue_head_inc(txq);
        }
 
-       wrb_fill_hdr(adapter, hdr, first_skb, wrb_cnt, copied);
+       wrb_fill_hdr(adapter, hdr, first_skb, wrb_cnt, copied, skip_hw_vlan);
        be_dws_cpu_to_le(hdr, sizeof(*hdr));
 
        return copied;
@@ -762,7 +759,8 @@ dma_err:
 }
 
 static struct sk_buff *be_insert_vlan_in_pkt(struct be_adapter *adapter,
-                                            struct sk_buff *skb)
+                                            struct sk_buff *skb,
+                                            bool *skip_hw_vlan)
 {
        u16 vlan_tag = 0;
 
@@ -777,9 +775,67 @@ static struct sk_buff *be_insert_vlan_in_pkt(struct be_adapter *adapter,
                        skb->vlan_tci = 0;
        }
 
+       if (qnq_async_evt_rcvd(adapter) && adapter->pvid) {
+               if (!vlan_tag)
+                       vlan_tag = adapter->pvid;
+               if (skip_hw_vlan)
+                       *skip_hw_vlan = true;
+       }
+
+       if (vlan_tag) {
+               skb = __vlan_put_tag(skb, htons(ETH_P_8021Q), vlan_tag);
+               if (unlikely(!skb))
+                       return skb;
+
+               skb->vlan_tci = 0;
+       }
+
+       /* Insert the outer VLAN, if any */
+       if (adapter->qnq_vid) {
+               vlan_tag = adapter->qnq_vid;
+               skb = __vlan_put_tag(skb, htons(ETH_P_8021Q), vlan_tag);
+               if (unlikely(!skb))
+                       return skb;
+               if (skip_hw_vlan)
+                       *skip_hw_vlan = true;
+       }
+
        return skb;
 }
 
+static bool be_ipv6_exthdr_check(struct sk_buff *skb)
+{
+       struct ethhdr *eh = (struct ethhdr *)skb->data;
+       u16 offset = ETH_HLEN;
+
+       if (eh->h_proto == htons(ETH_P_IPV6)) {
+               struct ipv6hdr *ip6h = (struct ipv6hdr *)(skb->data + offset);
+
+               offset += sizeof(struct ipv6hdr);
+               if (ip6h->nexthdr != NEXTHDR_TCP &&
+                   ip6h->nexthdr != NEXTHDR_UDP) {
+                       struct ipv6_opt_hdr *ehdr =
+                               (struct ipv6_opt_hdr *) (skb->data + offset);
+
+                       /* offending pkt: 2nd byte following IPv6 hdr is 0xff */
+                       if (ehdr->hdrlen == 0xff)
+                               return true;
+               }
+       }
+       return false;
+}
+
+static int be_vlan_tag_tx_chk(struct be_adapter *adapter, struct sk_buff *skb)
+{
+       return vlan_tx_tag_present(skb) || adapter->pvid || adapter->qnq_vid;
+}
+
+static int be_ipv6_tx_stall_chk(struct be_adapter *adapter, struct sk_buff *skb)
+{
+       return BE3_chip(adapter) &&
+               be_ipv6_exthdr_check(skb);
+}
+
 static netdev_tx_t be_xmit(struct sk_buff *skb,
                        struct net_device *netdev)
 {
@@ -790,33 +846,64 @@ static netdev_tx_t be_xmit(struct sk_buff *skb,
        u32 wrb_cnt = 0, copied = 0;
        u32 start = txq->head, eth_hdr_len;
        bool dummy_wrb, stopped = false;
+       bool skip_hw_vlan = false;
+       struct vlan_ethhdr *veh = (struct vlan_ethhdr *)skb->data;
 
        eth_hdr_len = ntohs(skb->protocol) == ETH_P_8021Q ?
                VLAN_ETH_HLEN : ETH_HLEN;
 
-       /* HW has a bug which considers padding bytes as legal
-        * and modifies the IPv4 hdr's 'tot_len' field
+       /* For padded packets, BE HW modifies tot_len field in IP header
+        * incorrecly when VLAN tag is inserted by HW.
         */
-       if (skb->len <= 60 && be_vlan_tag_chk(adapter, skb) &&
-                       is_ipv4_pkt(skb)) {
+       if (skb->len <= 60 && vlan_tx_tag_present(skb) && is_ipv4_pkt(skb)) {
                ip = (struct iphdr *)ip_hdr(skb);
                pskb_trim(skb, eth_hdr_len + ntohs(ip->tot_len));
        }
 
+       /* If vlan tag is already inlined in the packet, skip HW VLAN
+        * tagging in UMC mode
+        */
+       if ((adapter->function_mode & UMC_ENABLED) &&
+           veh->h_vlan_proto == htons(ETH_P_8021Q))
+                       skip_hw_vlan = true;
+
        /* HW has a bug wherein it will calculate CSUM for VLAN
         * pkts even though it is disabled.
         * Manually insert VLAN in pkt.
         */
        if (skb->ip_summed != CHECKSUM_PARTIAL &&
-                       be_vlan_tag_chk(adapter, skb)) {
-               skb = be_insert_vlan_in_pkt(adapter, skb);
+                       vlan_tx_tag_present(skb)) {
+               skb = be_insert_vlan_in_pkt(adapter, skb, &skip_hw_vlan);
+               if (unlikely(!skb))
+                       goto tx_drop;
+       }
+
+       /* HW may lockup when VLAN HW tagging is requested on
+        * certain ipv6 packets. Drop such pkts if the HW workaround to
+        * skip HW tagging is not enabled by FW.
+        */
+       if (unlikely(be_ipv6_tx_stall_chk(adapter, skb) &&
+                    (adapter->pvid || adapter->qnq_vid) &&
+                    !qnq_async_evt_rcvd(adapter)))
+               goto tx_drop;
+
+       /* Manual VLAN tag insertion to prevent:
+        * ASIC lockup when the ASIC inserts VLAN tag into
+        * certain ipv6 packets. Insert VLAN tags in driver,
+        * and set event, completion, vlan bits accordingly
+        * in the Tx WRB.
+        */
+       if (be_ipv6_tx_stall_chk(adapter, skb) &&
+           be_vlan_tag_tx_chk(adapter, skb)) {
+               skb = be_insert_vlan_in_pkt(adapter, skb, &skip_hw_vlan);
                if (unlikely(!skb))
                        goto tx_drop;
        }
 
        wrb_cnt = wrb_cnt_for_skb(adapter, skb, &dummy_wrb);
 
-       copied = make_tx_wrbs(adapter, txq, skb, wrb_cnt, dummy_wrb);
+       copied = make_tx_wrbs(adapter, txq, skb, wrb_cnt, dummy_wrb,
+                             skip_hw_vlan);
        if (copied) {
                int gso_segs = skb_shinfo(skb)->gso_segs;