mirror of https://gitee.com/openkylin/linux.git
be2net: fix a Tx stall bug caused by a specific ipv6 packet
The ASIC could lockup in the transmit path when it tries to insert VLAN in a specific ipv6 packet. 1) Identify the packet which can cause this. 2) Check if the firmware provides a workaround to prevent this. 3) If workaround is not present, drop the packet. 4) If the firmware provides a workaround, insert the VLAN tag in the packet and inform the firmware to skip further VLAN insertions. Signed-off-by: Ajit Khaparde <ajit.khaparde@emulex.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
1297f9db4f
commit
bc0c3405ab
|
@ -328,6 +328,7 @@ enum vf_state {
|
||||||
#define BE_FLAGS_WORKER_SCHEDULED (1 << 3)
|
#define BE_FLAGS_WORKER_SCHEDULED (1 << 3)
|
||||||
#define BE_UC_PMAC_COUNT 30
|
#define BE_UC_PMAC_COUNT 30
|
||||||
#define BE_VF_UC_PMAC_COUNT 2
|
#define BE_VF_UC_PMAC_COUNT 2
|
||||||
|
#define BE_FLAGS_QNQ_ASYNC_EVT_RCVD (1 << 11)
|
||||||
|
|
||||||
struct phy_info {
|
struct phy_info {
|
||||||
u8 transceiver;
|
u8 transceiver;
|
||||||
|
@ -434,6 +435,7 @@ struct be_adapter {
|
||||||
u8 wol_cap;
|
u8 wol_cap;
|
||||||
bool wol;
|
bool wol;
|
||||||
u32 uc_macs; /* Count of secondary UC MAC programmed */
|
u32 uc_macs; /* Count of secondary UC MAC programmed */
|
||||||
|
u16 qnq_vid;
|
||||||
u32 msg_enable;
|
u32 msg_enable;
|
||||||
int be_get_temp_freq;
|
int be_get_temp_freq;
|
||||||
u16 max_mcast_mac;
|
u16 max_mcast_mac;
|
||||||
|
@ -648,6 +650,11 @@ static inline bool be_is_wol_excluded(struct be_adapter *adapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int qnq_async_evt_rcvd(struct be_adapter *adapter)
|
||||||
|
{
|
||||||
|
return adapter->flags & BE_FLAGS_QNQ_ASYNC_EVT_RCVD;
|
||||||
|
}
|
||||||
|
|
||||||
extern void be_cq_notify(struct be_adapter *adapter, u16 qid, bool arm,
|
extern void be_cq_notify(struct be_adapter *adapter, u16 qid, bool arm,
|
||||||
u16 num_popped);
|
u16 num_popped);
|
||||||
extern void be_link_status_update(struct be_adapter *adapter, u8 link_status);
|
extern void be_link_status_update(struct be_adapter *adapter, u8 link_status);
|
||||||
|
|
|
@ -263,6 +263,27 @@ static void be_async_grp5_evt_process(struct be_adapter *adapter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void be_async_dbg_evt_process(struct be_adapter *adapter,
|
||||||
|
u32 trailer, struct be_mcc_compl *cmp)
|
||||||
|
{
|
||||||
|
u8 event_type = 0;
|
||||||
|
struct be_async_event_qnq *evt = (struct be_async_event_qnq *) cmp;
|
||||||
|
|
||||||
|
event_type = (trailer >> ASYNC_TRAILER_EVENT_TYPE_SHIFT) &
|
||||||
|
ASYNC_TRAILER_EVENT_TYPE_MASK;
|
||||||
|
|
||||||
|
switch (event_type) {
|
||||||
|
case ASYNC_DEBUG_EVENT_TYPE_QNQ:
|
||||||
|
if (evt->valid)
|
||||||
|
adapter->qnq_vid = le16_to_cpu(evt->vlan_tag);
|
||||||
|
adapter->flags |= BE_FLAGS_QNQ_ASYNC_EVT_RCVD;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dev_warn(&adapter->pdev->dev, "Unknown debug event\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool is_link_state_evt(u32 trailer)
|
static inline bool is_link_state_evt(u32 trailer)
|
||||||
{
|
{
|
||||||
return ((trailer >> ASYNC_TRAILER_EVENT_CODE_SHIFT) &
|
return ((trailer >> ASYNC_TRAILER_EVENT_CODE_SHIFT) &
|
||||||
|
@ -277,6 +298,13 @@ static inline bool is_grp5_evt(u32 trailer)
|
||||||
ASYNC_EVENT_CODE_GRP_5);
|
ASYNC_EVENT_CODE_GRP_5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool is_dbg_evt(u32 trailer)
|
||||||
|
{
|
||||||
|
return (((trailer >> ASYNC_TRAILER_EVENT_CODE_SHIFT) &
|
||||||
|
ASYNC_TRAILER_EVENT_CODE_MASK) ==
|
||||||
|
ASYNC_EVENT_CODE_QNQ);
|
||||||
|
}
|
||||||
|
|
||||||
static struct be_mcc_compl *be_mcc_compl_get(struct be_adapter *adapter)
|
static struct be_mcc_compl *be_mcc_compl_get(struct be_adapter *adapter)
|
||||||
{
|
{
|
||||||
struct be_queue_info *mcc_cq = &adapter->mcc_obj.cq;
|
struct be_queue_info *mcc_cq = &adapter->mcc_obj.cq;
|
||||||
|
@ -325,6 +353,9 @@ int be_process_mcc(struct be_adapter *adapter)
|
||||||
else if (is_grp5_evt(compl->flags))
|
else if (is_grp5_evt(compl->flags))
|
||||||
be_async_grp5_evt_process(adapter,
|
be_async_grp5_evt_process(adapter,
|
||||||
compl->flags, compl);
|
compl->flags, compl);
|
||||||
|
else if (is_dbg_evt(compl->flags))
|
||||||
|
be_async_dbg_evt_process(adapter,
|
||||||
|
compl->flags, compl);
|
||||||
} else if (compl->flags & CQE_FLAGS_COMPLETED_MASK) {
|
} else if (compl->flags & CQE_FLAGS_COMPLETED_MASK) {
|
||||||
status = be_mcc_compl_process(adapter, compl);
|
status = be_mcc_compl_process(adapter, compl);
|
||||||
atomic_dec(&mcc_obj->q.used);
|
atomic_dec(&mcc_obj->q.used);
|
||||||
|
@ -1022,6 +1053,7 @@ int be_cmd_mccq_ext_create(struct be_adapter *adapter,
|
||||||
|
|
||||||
/* Subscribe to Link State and Group 5 Events(bits 1 and 5 set) */
|
/* Subscribe to Link State and Group 5 Events(bits 1 and 5 set) */
|
||||||
req->async_event_bitmap[0] = cpu_to_le32(0x00000022);
|
req->async_event_bitmap[0] = cpu_to_le32(0x00000022);
|
||||||
|
req->async_event_bitmap[0] |= cpu_to_le32(1 << ASYNC_EVENT_CODE_QNQ);
|
||||||
be_dws_cpu_to_le(ctxt, sizeof(req->context));
|
be_dws_cpu_to_le(ctxt, sizeof(req->context));
|
||||||
|
|
||||||
be_cmd_page_addrs_prepare(req->pages, ARRAY_SIZE(req->pages), q_mem);
|
be_cmd_page_addrs_prepare(req->pages, ARRAY_SIZE(req->pages), q_mem);
|
||||||
|
|
|
@ -84,6 +84,9 @@ struct be_mcc_compl {
|
||||||
#define ASYNC_EVENT_QOS_SPEED 0x1
|
#define ASYNC_EVENT_QOS_SPEED 0x1
|
||||||
#define ASYNC_EVENT_COS_PRIORITY 0x2
|
#define ASYNC_EVENT_COS_PRIORITY 0x2
|
||||||
#define ASYNC_EVENT_PVID_STATE 0x3
|
#define ASYNC_EVENT_PVID_STATE 0x3
|
||||||
|
#define ASYNC_EVENT_CODE_QNQ 0x6
|
||||||
|
#define ASYNC_DEBUG_EVENT_TYPE_QNQ 1
|
||||||
|
|
||||||
struct be_async_event_trailer {
|
struct be_async_event_trailer {
|
||||||
u32 code;
|
u32 code;
|
||||||
};
|
};
|
||||||
|
@ -144,6 +147,16 @@ struct be_async_event_grp5_pvid_state {
|
||||||
struct be_async_event_trailer trailer;
|
struct be_async_event_trailer trailer;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
/* async event indicating outer VLAN tag in QnQ */
|
||||||
|
struct be_async_event_qnq {
|
||||||
|
u8 valid; /* Indicates if outer VLAN is valid */
|
||||||
|
u8 rsvd0;
|
||||||
|
u16 vlan_tag;
|
||||||
|
u32 event_tag;
|
||||||
|
u8 rsvd1[4];
|
||||||
|
struct be_async_event_trailer trailer;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
struct be_mcc_mailbox {
|
struct be_mcc_mailbox {
|
||||||
struct be_mcc_wrb wrb;
|
struct be_mcc_wrb wrb;
|
||||||
struct be_mcc_compl compl;
|
struct be_mcc_compl compl;
|
||||||
|
|
|
@ -627,7 +627,7 @@ static inline u16 be_get_tx_vlan_tag(struct be_adapter *adapter,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wrb_fill_hdr(struct be_adapter *adapter, struct be_eth_hdr_wrb *hdr,
|
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;
|
u16 vlan_tag;
|
||||||
|
|
||||||
|
@ -654,8 +654,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);
|
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, 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, num_wrb, hdr, wrb_cnt);
|
||||||
AMAP_SET_BITS(struct amap_eth_hdr_wrb, len, hdr, len);
|
AMAP_SET_BITS(struct amap_eth_hdr_wrb, len, hdr, len);
|
||||||
}
|
}
|
||||||
|
@ -678,7 +679,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,
|
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;
|
dma_addr_t busaddr;
|
||||||
int i, copied = 0;
|
int i, copied = 0;
|
||||||
|
@ -727,7 +729,7 @@ static int make_tx_wrbs(struct be_adapter *adapter, struct be_queue_info *txq,
|
||||||
queue_head_inc(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));
|
be_dws_cpu_to_le(hdr, sizeof(*hdr));
|
||||||
|
|
||||||
return copied;
|
return copied;
|
||||||
|
@ -744,7 +746,8 @@ static int make_tx_wrbs(struct be_adapter *adapter, struct be_queue_info *txq,
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sk_buff *be_insert_vlan_in_pkt(struct be_adapter *adapter,
|
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;
|
u16 vlan_tag = 0;
|
||||||
|
|
||||||
|
@ -759,7 +762,65 @@ static struct sk_buff *be_insert_vlan_in_pkt(struct be_adapter *adapter,
|
||||||
skb->vlan_tci = 0;
|
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, vlan_tag);
|
||||||
|
if (unlikely(!skb))
|
||||||
return 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, 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,
|
static netdev_tx_t be_xmit(struct sk_buff *skb,
|
||||||
|
@ -772,6 +833,7 @@ static netdev_tx_t be_xmit(struct sk_buff *skb,
|
||||||
u32 wrb_cnt = 0, copied = 0;
|
u32 wrb_cnt = 0, copied = 0;
|
||||||
u32 start = txq->head, eth_hdr_len;
|
u32 start = txq->head, eth_hdr_len;
|
||||||
bool dummy_wrb, stopped = false;
|
bool dummy_wrb, stopped = false;
|
||||||
|
bool skip_hw_vlan = false;
|
||||||
|
|
||||||
eth_hdr_len = ntohs(skb->protocol) == ETH_P_8021Q ?
|
eth_hdr_len = ntohs(skb->protocol) == ETH_P_8021Q ?
|
||||||
VLAN_ETH_HLEN : ETH_HLEN;
|
VLAN_ETH_HLEN : ETH_HLEN;
|
||||||
|
@ -790,14 +852,37 @@ static netdev_tx_t be_xmit(struct sk_buff *skb,
|
||||||
*/
|
*/
|
||||||
if (skb->ip_summed != CHECKSUM_PARTIAL &&
|
if (skb->ip_summed != CHECKSUM_PARTIAL &&
|
||||||
vlan_tx_tag_present(skb)) {
|
vlan_tx_tag_present(skb)) {
|
||||||
skb = be_insert_vlan_in_pkt(adapter, 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))
|
if (unlikely(!skb))
|
||||||
goto tx_drop;
|
goto tx_drop;
|
||||||
}
|
}
|
||||||
|
|
||||||
wrb_cnt = wrb_cnt_for_skb(adapter, skb, &dummy_wrb);
|
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) {
|
if (copied) {
|
||||||
int gso_segs = skb_shinfo(skb)->gso_segs;
|
int gso_segs = skb_shinfo(skb)->gso_segs;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue