Commit a68de58d authored by Jesse Brandeburg's avatar Jesse Brandeburg Committed by Jeff Kirsher

i40e: fix race in hang check

The driver was having some issues with false Tx hang detection. This
makes the driver a little more direct with the checks for progress
forward by directly checking the head write back address and tail register
when determining progress.  This avoids Tx hangs where the software
gets behind, because we are directly checking hardware state when
determining hang state.

Change-ID: I774f0e861c9e8ab5ccb213634100fe15440ae24a
Signed-off-by: default avatarJesse Brandeburg <jesse.brandeburg@intel.com>
Tested-by: default avatarJim Young <james.m.young@intel.com>
Signed-off-by: default avatarJeff Kirsher <jeffrey.t.kirsher@intel.com>
parent 71da6197
...@@ -585,6 +585,20 @@ void i40e_free_tx_resources(struct i40e_ring *tx_ring) ...@@ -585,6 +585,20 @@ void i40e_free_tx_resources(struct i40e_ring *tx_ring)
} }
} }
/**
* i40e_get_head - Retrieve head from head writeback
* @tx_ring: tx ring to fetch head of
*
* Returns value of Tx ring head based on value stored
* in head write-back location
**/
static inline u32 i40e_get_head(struct i40e_ring *tx_ring)
{
void *head = (struct i40e_tx_desc *)tx_ring->desc + tx_ring->count;
return le32_to_cpu(*(volatile __le32 *)head);
}
/** /**
* i40e_get_tx_pending - how many tx descriptors not processed * i40e_get_tx_pending - how many tx descriptors not processed
* @tx_ring: the ring of descriptors * @tx_ring: the ring of descriptors
...@@ -594,10 +608,16 @@ void i40e_free_tx_resources(struct i40e_ring *tx_ring) ...@@ -594,10 +608,16 @@ void i40e_free_tx_resources(struct i40e_ring *tx_ring)
**/ **/
static u32 i40e_get_tx_pending(struct i40e_ring *ring) static u32 i40e_get_tx_pending(struct i40e_ring *ring)
{ {
u32 ntu = ((ring->next_to_clean <= ring->next_to_use) u32 head, tail;
? ring->next_to_use
: ring->next_to_use + ring->count); head = i40e_get_head(ring);
return ntu - ring->next_to_clean; tail = readl(ring->tail);
if (head != tail)
return (head < tail) ?
tail - head : (tail + ring->count - head);
return 0;
} }
/** /**
...@@ -606,6 +626,8 @@ static u32 i40e_get_tx_pending(struct i40e_ring *ring) ...@@ -606,6 +626,8 @@ static u32 i40e_get_tx_pending(struct i40e_ring *ring)
**/ **/
static bool i40e_check_tx_hang(struct i40e_ring *tx_ring) static bool i40e_check_tx_hang(struct i40e_ring *tx_ring)
{ {
u32 tx_done = tx_ring->stats.packets;
u32 tx_done_old = tx_ring->tx_stats.tx_done_old;
u32 tx_pending = i40e_get_tx_pending(tx_ring); u32 tx_pending = i40e_get_tx_pending(tx_ring);
struct i40e_pf *pf = tx_ring->vsi->back; struct i40e_pf *pf = tx_ring->vsi->back;
bool ret = false; bool ret = false;
...@@ -623,41 +645,25 @@ static bool i40e_check_tx_hang(struct i40e_ring *tx_ring) ...@@ -623,41 +645,25 @@ static bool i40e_check_tx_hang(struct i40e_ring *tx_ring)
* run the check_tx_hang logic with a transmit completion * run the check_tx_hang logic with a transmit completion
* pending but without time to complete it yet. * pending but without time to complete it yet.
*/ */
if ((tx_ring->tx_stats.tx_done_old == tx_ring->stats.packets) && if ((tx_done_old == tx_done) && tx_pending) {
(tx_pending >= I40E_MIN_DESC_PENDING)) {
/* make sure it is true for two checks in a row */ /* make sure it is true for two checks in a row */
ret = test_and_set_bit(__I40E_HANG_CHECK_ARMED, ret = test_and_set_bit(__I40E_HANG_CHECK_ARMED,
&tx_ring->state); &tx_ring->state);
} else if ((tx_ring->tx_stats.tx_done_old == tx_ring->stats.packets) && } else if (tx_done_old == tx_done &&
(tx_pending < I40E_MIN_DESC_PENDING) && (tx_pending < I40E_MIN_DESC_PENDING) && (tx_pending > 0)) {
(tx_pending > 0)) {
if (I40E_DEBUG_FLOW & pf->hw.debug_mask) if (I40E_DEBUG_FLOW & pf->hw.debug_mask)
dev_info(tx_ring->dev, "HW needs some more descs to do a cacheline flush. tx_pending %d, queue %d", dev_info(tx_ring->dev, "HW needs some more descs to do a cacheline flush. tx_pending %d, queue %d",
tx_pending, tx_ring->queue_index); tx_pending, tx_ring->queue_index);
pf->tx_sluggish_count++; pf->tx_sluggish_count++;
} else { } else {
/* update completed stats and disarm the hang check */ /* update completed stats and disarm the hang check */
tx_ring->tx_stats.tx_done_old = tx_ring->stats.packets; tx_ring->tx_stats.tx_done_old = tx_done;
clear_bit(__I40E_HANG_CHECK_ARMED, &tx_ring->state); clear_bit(__I40E_HANG_CHECK_ARMED, &tx_ring->state);
} }
return ret; return ret;
} }
/**
* i40e_get_head - Retrieve head from head writeback
* @tx_ring: tx ring to fetch head of
*
* Returns value of Tx ring head based on value stored
* in head write-back location
**/
static inline u32 i40e_get_head(struct i40e_ring *tx_ring)
{
void *head = (struct i40e_tx_desc *)tx_ring->desc + tx_ring->count;
return le32_to_cpu(*(volatile __le32 *)head);
}
#define WB_STRIDE 0x3 #define WB_STRIDE 0x3
/** /**
......
...@@ -125,6 +125,20 @@ void i40evf_free_tx_resources(struct i40e_ring *tx_ring) ...@@ -125,6 +125,20 @@ void i40evf_free_tx_resources(struct i40e_ring *tx_ring)
} }
} }
/**
* i40e_get_head - Retrieve head from head writeback
* @tx_ring: tx ring to fetch head of
*
* Returns value of Tx ring head based on value stored
* in head write-back location
**/
static inline u32 i40e_get_head(struct i40e_ring *tx_ring)
{
void *head = (struct i40e_tx_desc *)tx_ring->desc + tx_ring->count;
return le32_to_cpu(*(volatile __le32 *)head);
}
/** /**
* i40e_get_tx_pending - how many tx descriptors not processed * i40e_get_tx_pending - how many tx descriptors not processed
* @tx_ring: the ring of descriptors * @tx_ring: the ring of descriptors
...@@ -134,10 +148,16 @@ void i40evf_free_tx_resources(struct i40e_ring *tx_ring) ...@@ -134,10 +148,16 @@ void i40evf_free_tx_resources(struct i40e_ring *tx_ring)
**/ **/
static u32 i40e_get_tx_pending(struct i40e_ring *ring) static u32 i40e_get_tx_pending(struct i40e_ring *ring)
{ {
u32 ntu = ((ring->next_to_clean <= ring->next_to_use) u32 head, tail;
? ring->next_to_use
: ring->next_to_use + ring->count); head = i40e_get_head(ring);
return ntu - ring->next_to_clean; tail = readl(ring->tail);
if (head != tail)
return (head < tail) ?
tail - head : (tail + ring->count - head);
return 0;
} }
/** /**
...@@ -146,6 +166,8 @@ static u32 i40e_get_tx_pending(struct i40e_ring *ring) ...@@ -146,6 +166,8 @@ static u32 i40e_get_tx_pending(struct i40e_ring *ring)
**/ **/
static bool i40e_check_tx_hang(struct i40e_ring *tx_ring) static bool i40e_check_tx_hang(struct i40e_ring *tx_ring)
{ {
u32 tx_done = tx_ring->stats.packets;
u32 tx_done_old = tx_ring->tx_stats.tx_done_old;
u32 tx_pending = i40e_get_tx_pending(tx_ring); u32 tx_pending = i40e_get_tx_pending(tx_ring);
bool ret = false; bool ret = false;
...@@ -162,36 +184,20 @@ static bool i40e_check_tx_hang(struct i40e_ring *tx_ring) ...@@ -162,36 +184,20 @@ static bool i40e_check_tx_hang(struct i40e_ring *tx_ring)
* run the check_tx_hang logic with a transmit completion * run the check_tx_hang logic with a transmit completion
* pending but without time to complete it yet. * pending but without time to complete it yet.
*/ */
if ((tx_ring->tx_stats.tx_done_old == tx_ring->stats.packets) && if ((tx_done_old == tx_done) && tx_pending) {
(tx_pending >= I40E_MIN_DESC_PENDING)) {
/* make sure it is true for two checks in a row */ /* make sure it is true for two checks in a row */
ret = test_and_set_bit(__I40E_HANG_CHECK_ARMED, ret = test_and_set_bit(__I40E_HANG_CHECK_ARMED,
&tx_ring->state); &tx_ring->state);
} else if (!(tx_ring->tx_stats.tx_done_old == tx_ring->stats.packets) || } else if (tx_done_old == tx_done &&
!(tx_pending < I40E_MIN_DESC_PENDING) || (tx_pending < I40E_MIN_DESC_PENDING) && (tx_pending > 0)) {
!(tx_pending > 0)) {
/* update completed stats and disarm the hang check */ /* update completed stats and disarm the hang check */
tx_ring->tx_stats.tx_done_old = tx_ring->stats.packets; tx_ring->tx_stats.tx_done_old = tx_done;
clear_bit(__I40E_HANG_CHECK_ARMED, &tx_ring->state); clear_bit(__I40E_HANG_CHECK_ARMED, &tx_ring->state);
} }
return ret; return ret;
} }
/**
* i40e_get_head - Retrieve head from head writeback
* @tx_ring: tx ring to fetch head of
*
* Returns value of Tx ring head based on value stored
* in head write-back location
**/
static inline u32 i40e_get_head(struct i40e_ring *tx_ring)
{
void *head = (struct i40e_tx_desc *)tx_ring->desc + tx_ring->count;
return le32_to_cpu(*(volatile __le32 *)head);
}
#define WB_STRIDE 0x3 #define WB_STRIDE 0x3
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment