diff options
author | David S. Miller <davem@davemloft.net> | 2013-04-30 03:50:54 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-04-30 03:55:20 -0400 |
commit | 58717686cf7c7f5a70b3a8907ade8a3ce74306b1 (patch) | |
tree | 57f5d78524bfd5e65aa9f3943da9c1a3e6ff65b7 /drivers/net/ethernet/emulex/benet/be_main.c | |
parent | cff63a52924c6a78fa525c67d81480c85736ff3c (diff) | |
parent | 79f632c71bea0d0864d84d6a4ce78da5a9430f5b (diff) | |
download | linux-58717686cf7c7f5a70b3a8907ade8a3ce74306b1.tar.bz2 |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
Conflicts:
drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c
drivers/net/ethernet/emulex/benet/be.h
include/net/tcp.h
net/mac802154/mac802154.h
Most conflicts were minor overlapping stuff.
The be2net driver brought in some fixes that added __vlan_put_tag
calls, which in net-next take an additional argument.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet/emulex/benet/be_main.c')
-rw-r--r-- | drivers/net/ethernet/emulex/benet/be_main.c | 121 |
1 files changed, 104 insertions, 17 deletions
diff --git a/drivers/net/ethernet/emulex/benet/be_main.c b/drivers/net/ethernet/emulex/benet/be_main.c index 1c734915933f..4babc8a4a543 100644 --- a/drivers/net/ethernet/emulex/benet/be_main.c +++ b/drivers/net/ethernet/emulex/benet/be_main.c @@ -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; |