Commit 07d44190 authored by Sudheer Mogilappagari's avatar Sudheer Mogilappagari Committed by Jeff Kirsher

i40e/i40evf: Detect and recover hung queue scenario

In VFs, there is a known issue which can cause writebacks
to not occur when interrupts are disabled and there are
less than 4 descriptors resulting in TX timeout. Timeout
can also occur due to lost interrupt.

The current implementation for detecting and recovering
from hung queues in the PF is problematic because it actually
actively encourages lost interrupts.  By triggering a SW
interrupt, interrupts are forced on.  If we are already in
napi_poll and an interrupt fires, napi_poll will not be
rescheduled and the interrupt is effectively lost; thereby
potentially *causing* hung queues.

This patch checks whether packets are being processed between
every watchdog cycle and determine potential hung queue and
fires triggers SW interrupt only for that particular queue.
Signed-off-by: default avatarSudheer Mogilappagari <sudheer.mogilappagari@intel.com>
Tested-by: default avatarAndrew Bowers <andrewx.bowers@intel.com>
Signed-off-by: default avatarJeff Kirsher <jeffrey.t.kirsher@intel.com>
parent d95cd480
...@@ -4876,104 +4876,6 @@ static int i40e_pf_wait_queues_disabled(struct i40e_pf *pf) ...@@ -4876,104 +4876,6 @@ static int i40e_pf_wait_queues_disabled(struct i40e_pf *pf)
#endif #endif
/**
* i40e_detect_recover_hung_queue - Function to detect and recover hung_queue
* @q_idx: TX queue number
* @vsi: Pointer to VSI struct
*
* This function checks specified queue for given VSI. Detects hung condition.
* We proactively detect hung TX queues by checking if interrupts are disabled
* but there are pending descriptors. If it appears hung, attempt to recover
* by triggering a SW interrupt.
**/
static void i40e_detect_recover_hung_queue(int q_idx, struct i40e_vsi *vsi)
{
struct i40e_ring *tx_ring = NULL;
struct i40e_pf *pf;
u32 val, tx_pending;
int i;
pf = vsi->back;
/* now that we have an index, find the tx_ring struct */
for (i = 0; i < vsi->num_queue_pairs; i++) {
if (vsi->tx_rings[i] && vsi->tx_rings[i]->desc) {
if (q_idx == vsi->tx_rings[i]->queue_index) {
tx_ring = vsi->tx_rings[i];
break;
}
}
}
if (!tx_ring)
return;
/* Read interrupt register */
if (pf->flags & I40E_FLAG_MSIX_ENABLED)
val = rd32(&pf->hw,
I40E_PFINT_DYN_CTLN(tx_ring->q_vector->v_idx +
tx_ring->vsi->base_vector - 1));
else
val = rd32(&pf->hw, I40E_PFINT_DYN_CTL0);
tx_pending = i40e_get_tx_pending(tx_ring);
/* Interrupts are disabled and TX pending is non-zero,
* trigger the SW interrupt (don't wait). Worst case
* there will be one extra interrupt which may result
* into not cleaning any queues because queues are cleaned.
*/
if (tx_pending && (!(val & I40E_PFINT_DYN_CTLN_INTENA_MASK)))
i40e_force_wb(vsi, tx_ring->q_vector);
}
/**
* i40e_detect_recover_hung - Function to detect and recover hung_queues
* @pf: pointer to PF struct
*
* LAN VSI has netdev and netdev has TX queues. This function is to check
* each of those TX queues if they are hung, trigger recovery by issuing
* SW interrupt.
**/
static void i40e_detect_recover_hung(struct i40e_pf *pf)
{
struct net_device *netdev;
struct i40e_vsi *vsi;
unsigned int i;
/* Only for LAN VSI */
vsi = pf->vsi[pf->lan_vsi];
if (!vsi)
return;
/* Make sure, VSI state is not DOWN/RECOVERY_PENDING */
if (test_bit(__I40E_VSI_DOWN, vsi->back->state) ||
test_bit(__I40E_RESET_RECOVERY_PENDING, vsi->back->state))
return;
/* Make sure type is MAIN VSI */
if (vsi->type != I40E_VSI_MAIN)
return;
netdev = vsi->netdev;
if (!netdev)
return;
/* Bail out if netif_carrier is not OK */
if (!netif_carrier_ok(netdev))
return;
/* Go thru' TX queues for netdev */
for (i = 0; i < netdev->num_tx_queues; i++) {
struct netdev_queue *q;
q = netdev_get_tx_queue(netdev, i);
if (q)
i40e_detect_recover_hung_queue(i, vsi);
}
}
/** /**
* i40e_get_iscsi_tc_map - Return TC map for iSCSI APP * i40e_get_iscsi_tc_map - Return TC map for iSCSI APP
* @pf: pointer to PF * @pf: pointer to PF
...@@ -9695,7 +9597,7 @@ static void i40e_service_task(struct work_struct *work) ...@@ -9695,7 +9597,7 @@ static void i40e_service_task(struct work_struct *work)
if (test_and_set_bit(__I40E_SERVICE_SCHED, pf->state)) if (test_and_set_bit(__I40E_SERVICE_SCHED, pf->state))
return; return;
i40e_detect_recover_hung(pf); i40e_detect_recover_hung(pf->vsi[pf->lan_vsi]);
i40e_sync_filters_subtask(pf); i40e_sync_filters_subtask(pf);
i40e_reset_subtask(pf); i40e_reset_subtask(pf);
i40e_handle_mdd_event(pf); i40e_handle_mdd_event(pf);
......
...@@ -726,6 +726,59 @@ u32 i40e_get_tx_pending(struct i40e_ring *ring) ...@@ -726,6 +726,59 @@ u32 i40e_get_tx_pending(struct i40e_ring *ring)
return 0; return 0;
} }
/**
* i40e_detect_recover_hung - Function to detect and recover hung_queues
* @vsi: pointer to vsi struct with tx queues
*
* VSI has netdev and netdev has TX queues. This function is to check each of
* those TX queues if they are hung, trigger recovery by issuing SW interrupt.
**/
void i40e_detect_recover_hung(struct i40e_vsi *vsi)
{
struct i40e_ring *tx_ring = NULL;
struct net_device *netdev;
unsigned int i;
int packets;
if (!vsi)
return;
if (test_bit(__I40E_VSI_DOWN, vsi->state))
return;
netdev = vsi->netdev;
if (!netdev)
return;
if (!netif_carrier_ok(netdev))
return;
for (i = 0; i < vsi->num_queue_pairs; i++) {
tx_ring = vsi->tx_rings[i];
if (tx_ring && tx_ring->desc) {
/* If packet counter has not changed the queue is
* likely stalled, so force an interrupt for this
* queue.
*
* prev_pkt_ctr would be negative if there was no
* pending work.
*/
packets = tx_ring->stats.packets & INT_MAX;
if (tx_ring->tx_stats.prev_pkt_ctr == packets) {
i40e_force_wb(vsi, tx_ring->q_vector);
continue;
}
/* Memory barrier between read of packet count and call
* to i40e_get_tx_pending()
*/
smp_rmb();
tx_ring->tx_stats.prev_pkt_ctr =
i40e_get_tx_pending(tx_ring) ? packets : -1;
}
}
}
#define WB_STRIDE 4 #define WB_STRIDE 4
/** /**
...@@ -1163,6 +1216,7 @@ int i40e_setup_tx_descriptors(struct i40e_ring *tx_ring) ...@@ -1163,6 +1216,7 @@ int i40e_setup_tx_descriptors(struct i40e_ring *tx_ring)
tx_ring->next_to_use = 0; tx_ring->next_to_use = 0;
tx_ring->next_to_clean = 0; tx_ring->next_to_clean = 0;
tx_ring->tx_stats.prev_pkt_ctr = -1;
return 0; return 0;
err: err:
......
...@@ -333,6 +333,7 @@ struct i40e_tx_queue_stats { ...@@ -333,6 +333,7 @@ struct i40e_tx_queue_stats {
u64 tx_done_old; u64 tx_done_old;
u64 tx_linearize; u64 tx_linearize;
u64 tx_force_wb; u64 tx_force_wb;
int prev_pkt_ctr;
}; };
struct i40e_rx_queue_stats { struct i40e_rx_queue_stats {
...@@ -501,6 +502,7 @@ void i40e_free_rx_resources(struct i40e_ring *rx_ring); ...@@ -501,6 +502,7 @@ void i40e_free_rx_resources(struct i40e_ring *rx_ring);
int i40e_napi_poll(struct napi_struct *napi, int budget); int i40e_napi_poll(struct napi_struct *napi, int budget);
void i40e_force_wb(struct i40e_vsi *vsi, struct i40e_q_vector *q_vector); void i40e_force_wb(struct i40e_vsi *vsi, struct i40e_q_vector *q_vector);
u32 i40e_get_tx_pending(struct i40e_ring *ring); u32 i40e_get_tx_pending(struct i40e_ring *ring);
void i40e_detect_recover_hung(struct i40e_vsi *vsi);
int __i40e_maybe_stop_tx(struct i40e_ring *tx_ring, int size); int __i40e_maybe_stop_tx(struct i40e_ring *tx_ring, int size);
bool __i40e_chk_linearize(struct sk_buff *skb); bool __i40e_chk_linearize(struct sk_buff *skb);
......
...@@ -148,6 +148,59 @@ u32 i40evf_get_tx_pending(struct i40e_ring *ring, bool in_sw) ...@@ -148,6 +148,59 @@ u32 i40evf_get_tx_pending(struct i40e_ring *ring, bool in_sw)
return 0; return 0;
} }
/**
* i40evf_detect_recover_hung - Function to detect and recover hung_queues
* @vsi: pointer to vsi struct with tx queues
*
* VSI has netdev and netdev has TX queues. This function is to check each of
* those TX queues if they are hung, trigger recovery by issuing SW interrupt.
**/
void i40evf_detect_recover_hung(struct i40e_vsi *vsi)
{
struct i40e_ring *tx_ring = NULL;
struct net_device *netdev;
unsigned int i;
int packets;
if (!vsi)
return;
if (test_bit(__I40E_VSI_DOWN, vsi->state))
return;
netdev = vsi->netdev;
if (!netdev)
return;
if (!netif_carrier_ok(netdev))
return;
for (i = 0; i < vsi->back->num_active_queues; i++) {
tx_ring = &vsi->back->tx_rings[i];
if (tx_ring && tx_ring->desc) {
/* If packet counter has not changed the queue is
* likely stalled, so force an interrupt for this
* queue.
*
* prev_pkt_ctr would be negative if there was no
* pending work.
*/
packets = tx_ring->stats.packets & INT_MAX;
if (tx_ring->tx_stats.prev_pkt_ctr == packets) {
i40evf_force_wb(vsi, tx_ring->q_vector);
continue;
}
/* Memory barrier between read of packet count and call
* to i40evf_get_tx_pending()
*/
smp_rmb();
tx_ring->tx_stats.prev_pkt_ctr =
i40evf_get_tx_pending(tx_ring, false) ? packets : -1;
}
}
}
#define WB_STRIDE 4 #define WB_STRIDE 4
/** /**
...@@ -469,6 +522,7 @@ int i40evf_setup_tx_descriptors(struct i40e_ring *tx_ring) ...@@ -469,6 +522,7 @@ int i40evf_setup_tx_descriptors(struct i40e_ring *tx_ring)
tx_ring->next_to_use = 0; tx_ring->next_to_use = 0;
tx_ring->next_to_clean = 0; tx_ring->next_to_clean = 0;
tx_ring->tx_stats.prev_pkt_ctr = -1;
return 0; return 0;
err: err:
......
...@@ -313,6 +313,7 @@ struct i40e_tx_queue_stats { ...@@ -313,6 +313,7 @@ struct i40e_tx_queue_stats {
u64 tx_done_old; u64 tx_done_old;
u64 tx_linearize; u64 tx_linearize;
u64 tx_force_wb; u64 tx_force_wb;
int prev_pkt_ctr;
u64 tx_lost_interrupt; u64 tx_lost_interrupt;
}; };
...@@ -467,6 +468,7 @@ void i40evf_free_rx_resources(struct i40e_ring *rx_ring); ...@@ -467,6 +468,7 @@ void i40evf_free_rx_resources(struct i40e_ring *rx_ring);
int i40evf_napi_poll(struct napi_struct *napi, int budget); int i40evf_napi_poll(struct napi_struct *napi, int budget);
void i40evf_force_wb(struct i40e_vsi *vsi, struct i40e_q_vector *q_vector); void i40evf_force_wb(struct i40e_vsi *vsi, struct i40e_q_vector *q_vector);
u32 i40evf_get_tx_pending(struct i40e_ring *ring, bool in_sw); u32 i40evf_get_tx_pending(struct i40e_ring *ring, bool in_sw);
void i40evf_detect_recover_hung(struct i40e_vsi *vsi);
int __i40evf_maybe_stop_tx(struct i40e_ring *tx_ring, int size); int __i40evf_maybe_stop_tx(struct i40e_ring *tx_ring, int size);
bool __i40evf_chk_linearize(struct sk_buff *skb); bool __i40evf_chk_linearize(struct sk_buff *skb);
......
...@@ -1716,6 +1716,8 @@ static void i40evf_watchdog_task(struct work_struct *work) ...@@ -1716,6 +1716,8 @@ static void i40evf_watchdog_task(struct work_struct *work)
if (adapter->state == __I40EVF_RUNNING) if (adapter->state == __I40EVF_RUNNING)
i40evf_request_stats(adapter); i40evf_request_stats(adapter);
watchdog_done: watchdog_done:
if (adapter->state == __I40EVF_RUNNING)
i40evf_detect_recover_hung(&adapter->vsi);
clear_bit(__I40EVF_IN_CRITICAL_TASK, &adapter->crit_section); clear_bit(__I40EVF_IN_CRITICAL_TASK, &adapter->crit_section);
restart_watchdog: restart_watchdog:
if (adapter->state == __I40EVF_REMOVE) if (adapter->state == __I40EVF_REMOVE)
......
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