Skip to content

Commit 175c241

Browse files
zulkifl3anguy11
authored andcommitted
igc: Fix TX Hang issue when QBV Gate is closed
If a user schedules a Gate Control List (GCL) to close one of the QBV gates while also transmitting a packet to that closed gate, TX Hang will be happen. HW would not drop any packet when the gate is closed and keep queuing up in HW TX FIFO until the gate is re-opened. This patch implements the solution to drop the packet for the closed gate. This patch will also reset the adapter to perform SW initialization for each 1st Gate Control List (GCL) to avoid hang. This is due to the HW design, where changing to TSN transmit mode requires SW initialization. Intel Discrete I225/6 transmit mode cannot be changed when in dynamic mode according to Software User Manual Section 7.5.2.1. Subsequent Gate Control List (GCL) operations will proceed without a reset, as they already are in TSN Mode. Step to reproduce: DUT: 1) Configure GCL List with certain gate close. BASE=$(date +%s%N) tc qdisc replace dev $IFACE parent root handle 100 taprio \ num_tc 4 \ map 0 1 2 3 3 3 3 3 3 3 3 3 3 3 3 3 \ queues 1@0 1@1 1@2 1@3 \ base-time $BASE \ sched-entry S 0x8 500000 \ sched-entry S 0x4 500000 \ flags 0x2 2) Transmit the packet to closed gate. You may use udp_tai application to transmit UDP packet to any of the closed gate. ./udp_tai -i <interface> -P 100000 -p 90 -c 1 -t <0/1> -u 30004 Fixes: ec50a9d ("igc: Add support for taprio offloading") Co-developed-by: Tan Tee Min <tee.min.tan@linux.intel.com> Signed-off-by: Tan Tee Min <tee.min.tan@linux.intel.com> Tested-by: Chwee Lin Choong <chwee.lin.choong@intel.com> Signed-off-by: Muhammad Husaini Zulkifli <muhammad.husaini.zulkifli@intel.com> Tested-by: Naama Meir <naamax.meir@linux.intel.com> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
1 parent cca28ce commit 175c241

3 files changed

Lines changed: 87 additions & 18 deletions

File tree

drivers/net/ethernet/intel/igc/igc.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <linux/timecounter.h>
1515
#include <linux/net_tstamp.h>
1616
#include <linux/bitfield.h>
17+
#include <linux/hrtimer.h>
1718

1819
#include "igc_hw.h"
1920

@@ -101,6 +102,8 @@ struct igc_ring {
101102
u32 start_time;
102103
u32 end_time;
103104
u32 max_sdu;
105+
bool oper_gate_closed; /* Operating gate. True if the TX Queue is closed */
106+
bool admin_gate_closed; /* Future gate. True if the TX Queue will be closed */
104107

105108
/* CBS parameters */
106109
bool cbs_enable; /* indicates if CBS is enabled */
@@ -160,6 +163,7 @@ struct igc_adapter {
160163
struct timer_list watchdog_timer;
161164
struct timer_list dma_err_timer;
162165
struct timer_list phy_info_timer;
166+
struct hrtimer hrtimer;
163167

164168
u32 wol;
165169
u32 en_mng_pt;
@@ -189,6 +193,8 @@ struct igc_adapter {
189193
ktime_t cycle_time;
190194
bool qbv_enable;
191195
u32 qbv_config_change_errors;
196+
bool qbv_transition;
197+
unsigned int qbv_count;
192198

193199
/* OS defined structs */
194200
struct pci_dev *pdev;

drivers/net/ethernet/intel/igc/igc_main.c

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,9 @@ static netdev_tx_t igc_xmit_frame_ring(struct sk_buff *skb,
15721572
first->bytecount = skb->len;
15731573
first->gso_segs = 1;
15741574

1575+
if (adapter->qbv_transition || tx_ring->oper_gate_closed)
1576+
goto out_drop;
1577+
15751578
if (tx_ring->max_sdu > 0) {
15761579
u32 max_sdu = 0;
15771580

@@ -3011,8 +3014,8 @@ static bool igc_clean_tx_irq(struct igc_q_vector *q_vector, int napi_budget)
30113014
time_after(jiffies, tx_buffer->time_stamp +
30123015
(adapter->tx_timeout_factor * HZ)) &&
30133016
!(rd32(IGC_STATUS) & IGC_STATUS_TXOFF) &&
3014-
(rd32(IGC_TDH(tx_ring->reg_idx)) !=
3015-
readl(tx_ring->tail))) {
3017+
(rd32(IGC_TDH(tx_ring->reg_idx)) != readl(tx_ring->tail)) &&
3018+
!tx_ring->oper_gate_closed) {
30163019
/* detected Tx unit hang */
30173020
netdev_err(tx_ring->netdev,
30183021
"Detected Tx Unit Hang\n"
@@ -6102,13 +6105,17 @@ static int igc_tsn_clear_schedule(struct igc_adapter *adapter)
61026105
adapter->base_time = 0;
61036106
adapter->cycle_time = NSEC_PER_SEC;
61046107
adapter->qbv_config_change_errors = 0;
6108+
adapter->qbv_transition = false;
6109+
adapter->qbv_count = 0;
61056110

61066111
for (i = 0; i < adapter->num_tx_queues; i++) {
61076112
struct igc_ring *ring = adapter->tx_ring[i];
61086113

61096114
ring->start_time = 0;
61106115
ring->end_time = NSEC_PER_SEC;
61116116
ring->max_sdu = 0;
6117+
ring->oper_gate_closed = false;
6118+
ring->admin_gate_closed = false;
61126119
}
61136120

61146121
return 0;
@@ -6120,6 +6127,7 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
61206127
bool queue_configured[IGC_MAX_TX_QUEUES] = { };
61216128
struct igc_hw *hw = &adapter->hw;
61226129
u32 start_time = 0, end_time = 0;
6130+
struct timespec64 now;
61236131
size_t n;
61246132
int i;
61256133

@@ -6149,6 +6157,8 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
61496157
adapter->cycle_time = qopt->cycle_time;
61506158
adapter->base_time = qopt->base_time;
61516159

6160+
igc_ptp_read(adapter, &now);
6161+
61526162
for (n = 0; n < qopt->num_entries; n++) {
61536163
struct tc_taprio_sched_entry *e = &qopt->entries[n];
61546164

@@ -6183,7 +6193,10 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
61836193
ring->start_time = start_time;
61846194
ring->end_time = end_time;
61856195

6186-
queue_configured[i] = true;
6196+
if (ring->start_time >= adapter->cycle_time)
6197+
queue_configured[i] = false;
6198+
else
6199+
queue_configured[i] = true;
61876200
}
61886201

61896202
start_time += e->interval;
@@ -6193,8 +6206,20 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
61936206
* If not, set the start and end time to be end time.
61946207
*/
61956208
for (i = 0; i < adapter->num_tx_queues; i++) {
6209+
struct igc_ring *ring = adapter->tx_ring[i];
6210+
6211+
if (!is_base_time_past(qopt->base_time, &now)) {
6212+
ring->admin_gate_closed = false;
6213+
} else {
6214+
ring->oper_gate_closed = false;
6215+
ring->admin_gate_closed = false;
6216+
}
6217+
61966218
if (!queue_configured[i]) {
6197-
struct igc_ring *ring = adapter->tx_ring[i];
6219+
if (!is_base_time_past(qopt->base_time, &now))
6220+
ring->admin_gate_closed = true;
6221+
else
6222+
ring->oper_gate_closed = true;
61986223

61996224
ring->start_time = end_time;
62006225
ring->end_time = end_time;
@@ -6575,6 +6600,27 @@ static const struct xdp_metadata_ops igc_xdp_metadata_ops = {
65756600
.xmo_rx_timestamp = igc_xdp_rx_timestamp,
65766601
};
65776602

6603+
static enum hrtimer_restart igc_qbv_scheduling_timer(struct hrtimer *timer)
6604+
{
6605+
struct igc_adapter *adapter = container_of(timer, struct igc_adapter,
6606+
hrtimer);
6607+
unsigned int i;
6608+
6609+
adapter->qbv_transition = true;
6610+
for (i = 0; i < adapter->num_tx_queues; i++) {
6611+
struct igc_ring *tx_ring = adapter->tx_ring[i];
6612+
6613+
if (tx_ring->admin_gate_closed) {
6614+
tx_ring->admin_gate_closed = false;
6615+
tx_ring->oper_gate_closed = true;
6616+
} else {
6617+
tx_ring->oper_gate_closed = false;
6618+
}
6619+
}
6620+
adapter->qbv_transition = false;
6621+
return HRTIMER_NORESTART;
6622+
}
6623+
65786624
/**
65796625
* igc_probe - Device Initialization Routine
65806626
* @pdev: PCI device information struct
@@ -6753,6 +6799,9 @@ static int igc_probe(struct pci_dev *pdev,
67536799
INIT_WORK(&adapter->reset_task, igc_reset_task);
67546800
INIT_WORK(&adapter->watchdog_task, igc_watchdog_task);
67556801

6802+
hrtimer_init(&adapter->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
6803+
adapter->hrtimer.function = &igc_qbv_scheduling_timer;
6804+
67566805
/* Initialize link properties that are user-changeable */
67576806
adapter->fc_autoneg = true;
67586807
hw->mac.autoneg = true;
@@ -6856,6 +6905,7 @@ static void igc_remove(struct pci_dev *pdev)
68566905

68576906
cancel_work_sync(&adapter->reset_task);
68586907
cancel_work_sync(&adapter->watchdog_task);
6908+
hrtimer_cancel(&adapter->hrtimer);
68596909

68606910
/* Release control of h/w to f/w. If f/w is AMT enabled, this
68616911
* would have already happened in close and is redundant.

drivers/net/ethernet/intel/igc/igc_tsn.c

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ static int igc_tsn_disable_offload(struct igc_adapter *adapter)
114114
static int igc_tsn_enable_offload(struct igc_adapter *adapter)
115115
{
116116
struct igc_hw *hw = &adapter->hw;
117-
bool tsn_mode_reconfig = false;
118117
u32 tqavctrl, baset_l, baset_h;
119118
u32 sec, nsec, cycle;
120119
ktime_t base_time, systim;
@@ -228,11 +227,10 @@ static int igc_tsn_enable_offload(struct igc_adapter *adapter)
228227

229228
tqavctrl = rd32(IGC_TQAVCTRL) & ~IGC_TQAVCTRL_FUTSCDDIS;
230229

231-
if (tqavctrl & IGC_TQAVCTRL_TRANSMIT_MODE_TSN)
232-
tsn_mode_reconfig = true;
233-
234230
tqavctrl |= IGC_TQAVCTRL_TRANSMIT_MODE_TSN | IGC_TQAVCTRL_ENHANCED_QAV;
235231

232+
adapter->qbv_count++;
233+
236234
cycle = adapter->cycle_time;
237235
base_time = adapter->base_time;
238236

@@ -250,17 +248,28 @@ static int igc_tsn_enable_offload(struct igc_adapter *adapter)
250248
*/
251249
if ((rd32(IGC_BASET_H) || rd32(IGC_BASET_L)) &&
252250
(adapter->tc_setup_type == TC_SETUP_QDISC_TAPRIO) &&
253-
tsn_mode_reconfig)
251+
(adapter->qbv_count > 1))
254252
adapter->qbv_config_change_errors++;
255253
} else {
256-
/* According to datasheet section 7.5.2.9.3.3, FutScdDis bit
257-
* has to be configured before the cycle time and base time.
258-
* Tx won't hang if there is a GCL is already running,
259-
* so in this case we don't need to set FutScdDis.
260-
*/
261-
if (igc_is_device_id_i226(hw) &&
262-
!(rd32(IGC_BASET_H) || rd32(IGC_BASET_L)))
263-
tqavctrl |= IGC_TQAVCTRL_FUTSCDDIS;
254+
if (igc_is_device_id_i226(hw)) {
255+
ktime_t adjust_time, expires_time;
256+
257+
/* According to datasheet section 7.5.2.9.3.3, FutScdDis bit
258+
* has to be configured before the cycle time and base time.
259+
* Tx won't hang if a GCL is already running,
260+
* so in this case we don't need to set FutScdDis.
261+
*/
262+
if (!(rd32(IGC_BASET_H) || rd32(IGC_BASET_L)))
263+
tqavctrl |= IGC_TQAVCTRL_FUTSCDDIS;
264+
265+
nsec = rd32(IGC_SYSTIML);
266+
sec = rd32(IGC_SYSTIMH);
267+
systim = ktime_set(sec, nsec);
268+
269+
adjust_time = adapter->base_time;
270+
expires_time = ktime_sub_ns(adjust_time, systim);
271+
hrtimer_start(&adapter->hrtimer, expires_time, HRTIMER_MODE_REL);
272+
}
264273
}
265274

266275
wr32(IGC_TQAVCTRL, tqavctrl);
@@ -306,7 +315,11 @@ int igc_tsn_offload_apply(struct igc_adapter *adapter)
306315
{
307316
struct igc_hw *hw = &adapter->hw;
308317

309-
if (netif_running(adapter->netdev) && igc_is_device_id_i225(hw)) {
318+
/* Per I225/6 HW Design Section 7.5.2.1, transmit mode
319+
* cannot be changed dynamically. Require reset the adapter.
320+
*/
321+
if (netif_running(adapter->netdev) &&
322+
(igc_is_device_id_i225(hw) || !adapter->qbv_count)) {
310323
schedule_work(&adapter->reset_task);
311324
return 0;
312325
}

0 commit comments

Comments
 (0)