i40e/i40evf: Fix mixed size frags and linearization

This patch fixes a bug where the i40e Tx queue will hang if this
skb is passed to the driver.

With mixed size fragments while using TSO there was a corner case
where we needed to linearize but we were not. This was seen with
iSCSI traffic and could be reproduced with a frag list that looks
like this:

num_frags = 17, gso_segs = 17, hdr_len = 66,
skb_shinfo(skb)->gso_size = 1448
size = 3002, j = 1, frag_size = 2936, num_frags = 17
size = 4268, j = 1, frag_size = 4096, num_frags = 16
size = 5534, j = 1, frag_size = 4096, num_frags = 15
size = 5352, j = 1, frag_size = 4096, num_frags = 14
size = 5170, j = 1, frag_size = 4096, num_frags = 13
size = 3468, j = 1, frag_size = 2576, num_frags = 12
size = 750, j = 1, frag_size = 112, num_frags = 11
size = 862, j = 2, frag_size = 112, num_frags = 10
size = 974, j = 3, frag_size = 112, num_frags = 9
size = 1126, j = 4, frag_size = 152, num_frags = 8
size = 1330, j = 5, frag_size = 204, num_frags = 7
size = 1534, j = 6, frag_size = 204, num_frags = 6
size = 356, j = 1, frag_size = 204, num_frags = 5
size = 560, j = 2, frag_size = 204, num_frags = 4
size = 764, j = 3, frag_size = 204, num_frags = 3
size = 968, j = 4, frag_size = 204, num_frags = 2
size = 1140, j = 5, frag_size = 172, num_frags = 1
result: linearize = 0, j = 6

Change-ID: I79bb1aeab0af255fe2ce28e93672a85d85bf47e8
Signed-off-by: Anjali Singhai Jain <anjali.singhai@intel.com>
Signed-off-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
This commit is contained in:
Anjali Singhai Jain 2015-05-08 15:35:52 -07:00 committed by Jeff Kirsher
parent 6e54030932
commit 30520831f0
2 changed files with 20 additions and 30 deletions

View file

@ -2410,14 +2410,12 @@ static int i40e_maybe_stop_tx(struct i40e_ring *tx_ring, int size)
* i40e_chk_linearize - Check if there are more than 8 fragments per packet * i40e_chk_linearize - Check if there are more than 8 fragments per packet
* @skb: send buffer * @skb: send buffer
* @tx_flags: collected send information * @tx_flags: collected send information
* @hdr_len: size of the packet header
* *
* Note: Our HW can't scatter-gather more than 8 fragments to build * Note: Our HW can't scatter-gather more than 8 fragments to build
* a packet on the wire and so we need to figure out the cases where we * a packet on the wire and so we need to figure out the cases where we
* need to linearize the skb. * need to linearize the skb.
**/ **/
static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags, static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags)
const u8 hdr_len)
{ {
struct skb_frag_struct *frag; struct skb_frag_struct *frag;
bool linearize = false; bool linearize = false;
@ -2429,7 +2427,7 @@ static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags,
gso_segs = skb_shinfo(skb)->gso_segs; gso_segs = skb_shinfo(skb)->gso_segs;
if (tx_flags & (I40E_TX_FLAGS_TSO | I40E_TX_FLAGS_FSO)) { if (tx_flags & (I40E_TX_FLAGS_TSO | I40E_TX_FLAGS_FSO)) {
u16 j = 1; u16 j = 0;
if (num_frags < (I40E_MAX_BUFFER_TXD)) if (num_frags < (I40E_MAX_BUFFER_TXD))
goto linearize_chk_done; goto linearize_chk_done;
@ -2440,21 +2438,18 @@ static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags,
goto linearize_chk_done; goto linearize_chk_done;
} }
frag = &skb_shinfo(skb)->frags[0]; frag = &skb_shinfo(skb)->frags[0];
size = hdr_len;
/* we might still have more fragments per segment */ /* we might still have more fragments per segment */
do { do {
size += skb_frag_size(frag); size += skb_frag_size(frag);
frag++; j++; frag++; j++;
if ((size >= skb_shinfo(skb)->gso_size) &&
(j < I40E_MAX_BUFFER_TXD)) {
size = (size % skb_shinfo(skb)->gso_size);
j = (size) ? 1 : 0;
}
if (j == I40E_MAX_BUFFER_TXD) { if (j == I40E_MAX_BUFFER_TXD) {
if (size < skb_shinfo(skb)->gso_size) { linearize = true;
linearize = true; break;
break;
}
j = 1;
size -= skb_shinfo(skb)->gso_size;
if (size)
j++;
size += hdr_len;
} }
num_frags--; num_frags--;
} while (num_frags); } while (num_frags);
@ -2724,7 +2719,7 @@ static netdev_tx_t i40e_xmit_frame_ring(struct sk_buff *skb,
if (tsyn) if (tsyn)
tx_flags |= I40E_TX_FLAGS_TSYN; tx_flags |= I40E_TX_FLAGS_TSYN;
if (i40e_chk_linearize(skb, tx_flags, hdr_len)) if (i40e_chk_linearize(skb, tx_flags))
if (skb_linearize(skb)) if (skb_linearize(skb))
goto out_drop; goto out_drop;

View file

@ -1619,14 +1619,12 @@ static void i40e_create_tx_ctx(struct i40e_ring *tx_ring,
* i40e_chk_linearize - Check if there are more than 8 fragments per packet * i40e_chk_linearize - Check if there are more than 8 fragments per packet
* @skb: send buffer * @skb: send buffer
* @tx_flags: collected send information * @tx_flags: collected send information
* @hdr_len: size of the packet header
* *
* Note: Our HW can't scatter-gather more than 8 fragments to build * Note: Our HW can't scatter-gather more than 8 fragments to build
* a packet on the wire and so we need to figure out the cases where we * a packet on the wire and so we need to figure out the cases where we
* need to linearize the skb. * need to linearize the skb.
**/ **/
static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags, static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags)
const u8 hdr_len)
{ {
struct skb_frag_struct *frag; struct skb_frag_struct *frag;
bool linearize = false; bool linearize = false;
@ -1638,7 +1636,7 @@ static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags,
gso_segs = skb_shinfo(skb)->gso_segs; gso_segs = skb_shinfo(skb)->gso_segs;
if (tx_flags & (I40E_TX_FLAGS_TSO | I40E_TX_FLAGS_FSO)) { if (tx_flags & (I40E_TX_FLAGS_TSO | I40E_TX_FLAGS_FSO)) {
u16 j = 1; u16 j = 0;
if (num_frags < (I40E_MAX_BUFFER_TXD)) if (num_frags < (I40E_MAX_BUFFER_TXD))
goto linearize_chk_done; goto linearize_chk_done;
@ -1649,21 +1647,18 @@ static bool i40e_chk_linearize(struct sk_buff *skb, u32 tx_flags,
goto linearize_chk_done; goto linearize_chk_done;
} }
frag = &skb_shinfo(skb)->frags[0]; frag = &skb_shinfo(skb)->frags[0];
size = hdr_len;
/* we might still have more fragments per segment */ /* we might still have more fragments per segment */
do { do {
size += skb_frag_size(frag); size += skb_frag_size(frag);
frag++; j++; frag++; j++;
if ((size >= skb_shinfo(skb)->gso_size) &&
(j < I40E_MAX_BUFFER_TXD)) {
size = (size % skb_shinfo(skb)->gso_size);
j = (size) ? 1 : 0;
}
if (j == I40E_MAX_BUFFER_TXD) { if (j == I40E_MAX_BUFFER_TXD) {
if (size < skb_shinfo(skb)->gso_size) { linearize = true;
linearize = true; break;
break;
}
j = 1;
size -= skb_shinfo(skb)->gso_size;
if (size)
j++;
size += hdr_len;
} }
num_frags--; num_frags--;
} while (num_frags); } while (num_frags);
@ -1950,7 +1945,7 @@ static netdev_tx_t i40e_xmit_frame_ring(struct sk_buff *skb,
else if (tso) else if (tso)
tx_flags |= I40E_TX_FLAGS_TSO; tx_flags |= I40E_TX_FLAGS_TSO;
if (i40e_chk_linearize(skb, tx_flags, hdr_len)) if (i40e_chk_linearize(skb, tx_flags))
if (skb_linearize(skb)) if (skb_linearize(skb))
goto out_drop; goto out_drop;