The flags IGC_TXQCTL_STRICT_CYCLE and IGC_TXQCTL_STRICT_END prevent the packet transmission over slot and cycle boundaries. This is important for taprio offload where the slots and cycles correspond to the slots and cycles configured for the network. However, the Qbv offload feature of the i225 is also used for enabling TX launchtime / ETF offload. In that case, however, the cycle has no meaning for the network and is only used internally to adapt the base time register after a second has passed. Enabling strict mode in this case would unnecessarily prevent the transmission of certain packets (i.e. at the boundary of a second) and thus interferes with the ETF qdisc that promises transmission at a certain point in time. Similar to ETF, this also applies to CBS offload that also should not be influenced by strict mode unless taprio offload would be enabled at the same time. This fully reverts commitd8f45be01d
("igc: Use strict cycles for Qbv scheduling") but its commit message only describes what was already implemented before that commit. The difference to a plain revert of that commit is that it now copes with the base_time = 0 case that was fixed with commite17090eb24
("igc: allow BaseTime 0 enrollment for Qbv") In particular, enabling strict mode leads to TX hang situations under high traffic if taprio is applied WITHOUT taprio offload but WITH ETF offload, e.g. as in sudo tc qdisc replace dev enp1s0 parent root handle 100 taprio \ num_tc 1 \ map 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ queues 1@0 \ base-time 0 \ sched-entry S 01 300000 \ flags 0x1 \ txtime-delay 500000 \ clockid CLOCK_TAI sudo tc qdisc replace dev enp1s0 parent 100:1 etf \ clockid CLOCK_TAI \ delta 500000 \ offload \ skip_sock_check and traffic generator sudo trafgen -i traffic.cfg -o enp1s0 --cpp -n0 -q -t1400ns with traffic.cfg #define ETH_P_IP 0x0800 { /* Ethernet Header */ 0x30, 0x1f, 0x9a, 0xd0, 0xf0, 0x0e, # MAC Dest - adapt as needed 0x24, 0x5e, 0xbe, 0x57, 0x2e, 0x36, # MAC Src - adapt as needed const16(ETH_P_IP), /* IPv4 Header */ 0b01000101, 0, # IPv4 version, IHL, TOS const16(1028), # IPv4 total length (UDP length + 20 bytes (IP header)) const16(2), # IPv4 ident 0b01000000, 0, # IPv4 flags, fragmentation off 64, # IPv4 TTL 17, # Protocol UDP csumip(14, 33), # IPv4 checksum /* UDP Header */ 10, 0, 48, 1, # IP Src - adapt as needed 10, 0, 48, 10, # IP Dest - adapt as needed const16(5555), # UDP Src Port const16(6666), # UDP Dest Port const16(1008), # UDP length (UDP header 8 bytes + payload length) csumudp(14, 34), # UDP checksum /* Payload */ fill('W', 1000), } and the observed message with that is for example igc 0000:01:00.0 enp1s0: Detected Tx Unit Hang Tx Queue <0> TDH <d0> TDT <f0> next_to_use <f0> next_to_clean <d0> buffer_info[next_to_clean] time_stamp <ffff661f> next_to_watch <00000000245a4efb> jiffies <ffff6e48> desc.status <1048000> Fixes:d8f45be01d
("igc: Use strict cycles for Qbv scheduling") Signed-off-by: Florian Kauer <florian.kauer@linutronix.de> Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> Tested-by: Naama Meir <naamax.meir@linux.intel.com> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
350 lines
8.9 KiB
C
350 lines
8.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2019 Intel Corporation */
|
|
|
|
#include "igc.h"
|
|
#include "igc_hw.h"
|
|
#include "igc_tsn.h"
|
|
|
|
static bool is_any_launchtime(struct igc_adapter *adapter)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < adapter->num_tx_queues; i++) {
|
|
struct igc_ring *ring = adapter->tx_ring[i];
|
|
|
|
if (ring->launchtime_enable)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool is_cbs_enabled(struct igc_adapter *adapter)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < adapter->num_tx_queues; i++) {
|
|
struct igc_ring *ring = adapter->tx_ring[i];
|
|
|
|
if (ring->cbs_enable)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static unsigned int igc_tsn_new_flags(struct igc_adapter *adapter)
|
|
{
|
|
unsigned int new_flags = adapter->flags & ~IGC_FLAG_TSN_ANY_ENABLED;
|
|
|
|
if (adapter->taprio_offload_enable)
|
|
new_flags |= IGC_FLAG_TSN_QBV_ENABLED;
|
|
|
|
if (is_any_launchtime(adapter))
|
|
new_flags |= IGC_FLAG_TSN_QBV_ENABLED;
|
|
|
|
if (is_cbs_enabled(adapter))
|
|
new_flags |= IGC_FLAG_TSN_QAV_ENABLED;
|
|
|
|
return new_flags;
|
|
}
|
|
|
|
void igc_tsn_adjust_txtime_offset(struct igc_adapter *adapter)
|
|
{
|
|
struct igc_hw *hw = &adapter->hw;
|
|
u16 txoffset;
|
|
|
|
if (!is_any_launchtime(adapter))
|
|
return;
|
|
|
|
switch (adapter->link_speed) {
|
|
case SPEED_10:
|
|
txoffset = IGC_TXOFFSET_SPEED_10;
|
|
break;
|
|
case SPEED_100:
|
|
txoffset = IGC_TXOFFSET_SPEED_100;
|
|
break;
|
|
case SPEED_1000:
|
|
txoffset = IGC_TXOFFSET_SPEED_1000;
|
|
break;
|
|
case SPEED_2500:
|
|
txoffset = IGC_TXOFFSET_SPEED_2500;
|
|
break;
|
|
default:
|
|
txoffset = 0;
|
|
break;
|
|
}
|
|
|
|
wr32(IGC_GTXOFFSET, txoffset);
|
|
}
|
|
|
|
/* Returns the TSN specific registers to their default values after
|
|
* the adapter is reset.
|
|
*/
|
|
static int igc_tsn_disable_offload(struct igc_adapter *adapter)
|
|
{
|
|
struct igc_hw *hw = &adapter->hw;
|
|
u32 tqavctrl;
|
|
int i;
|
|
|
|
wr32(IGC_GTXOFFSET, 0);
|
|
wr32(IGC_TXPBS, I225_TXPBSIZE_DEFAULT);
|
|
wr32(IGC_DTXMXPKTSZ, IGC_DTXMXPKTSZ_DEFAULT);
|
|
|
|
tqavctrl = rd32(IGC_TQAVCTRL);
|
|
tqavctrl &= ~(IGC_TQAVCTRL_TRANSMIT_MODE_TSN |
|
|
IGC_TQAVCTRL_ENHANCED_QAV | IGC_TQAVCTRL_FUTSCDDIS);
|
|
|
|
wr32(IGC_TQAVCTRL, tqavctrl);
|
|
|
|
for (i = 0; i < adapter->num_tx_queues; i++) {
|
|
wr32(IGC_TXQCTL(i), 0);
|
|
wr32(IGC_STQT(i), 0);
|
|
wr32(IGC_ENDQT(i), NSEC_PER_SEC);
|
|
}
|
|
|
|
wr32(IGC_QBVCYCLET_S, 0);
|
|
wr32(IGC_QBVCYCLET, NSEC_PER_SEC);
|
|
|
|
adapter->flags &= ~IGC_FLAG_TSN_QBV_ENABLED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int igc_tsn_enable_offload(struct igc_adapter *adapter)
|
|
{
|
|
struct igc_hw *hw = &adapter->hw;
|
|
u32 tqavctrl, baset_l, baset_h;
|
|
u32 sec, nsec, cycle;
|
|
ktime_t base_time, systim;
|
|
int i;
|
|
|
|
wr32(IGC_TSAUXC, 0);
|
|
wr32(IGC_DTXMXPKTSZ, IGC_DTXMXPKTSZ_TSN);
|
|
wr32(IGC_TXPBS, IGC_TXPBSIZE_TSN);
|
|
|
|
for (i = 0; i < adapter->num_tx_queues; i++) {
|
|
struct igc_ring *ring = adapter->tx_ring[i];
|
|
u32 txqctl = 0;
|
|
u16 cbs_value;
|
|
u32 tqavcc;
|
|
|
|
wr32(IGC_STQT(i), ring->start_time);
|
|
wr32(IGC_ENDQT(i), ring->end_time);
|
|
|
|
if (adapter->taprio_offload_enable) {
|
|
/* If taprio_offload_enable is set we are in "taprio"
|
|
* mode and we need to be strict about the
|
|
* cycles: only transmit a packet if it can be
|
|
* completed during that cycle.
|
|
*
|
|
* If taprio_offload_enable is NOT true when
|
|
* enabling TSN offload, the cycle should have
|
|
* no external effects, but is only used internally
|
|
* to adapt the base time register after a second
|
|
* has passed.
|
|
*
|
|
* Enabling strict mode in this case would
|
|
* unnecessarily prevent the transmission of
|
|
* certain packets (i.e. at the boundary of a
|
|
* second) and thus interfere with the launchtime
|
|
* feature that promises transmission at a
|
|
* certain point in time.
|
|
*/
|
|
txqctl |= IGC_TXQCTL_STRICT_CYCLE |
|
|
IGC_TXQCTL_STRICT_END;
|
|
}
|
|
|
|
if (ring->launchtime_enable)
|
|
txqctl |= IGC_TXQCTL_QUEUE_MODE_LAUNCHT;
|
|
|
|
/* Skip configuring CBS for Q2 and Q3 */
|
|
if (i > 1)
|
|
goto skip_cbs;
|
|
|
|
if (ring->cbs_enable) {
|
|
if (i == 0)
|
|
txqctl |= IGC_TXQCTL_QAV_SEL_CBS0;
|
|
else
|
|
txqctl |= IGC_TXQCTL_QAV_SEL_CBS1;
|
|
|
|
/* According to i225 datasheet section 7.5.2.7, we
|
|
* should set the 'idleSlope' field from TQAVCC
|
|
* register following the equation:
|
|
*
|
|
* value = link-speed 0x7736 * BW * 0.2
|
|
* ---------- * ----------------- (E1)
|
|
* 100Mbps 2.5
|
|
*
|
|
* Note that 'link-speed' is in Mbps.
|
|
*
|
|
* 'BW' is the percentage bandwidth out of full
|
|
* link speed which can be found with the
|
|
* following equation. Note that idleSlope here
|
|
* is the parameter from this function
|
|
* which is in kbps.
|
|
*
|
|
* BW = idleSlope
|
|
* ----------------- (E2)
|
|
* link-speed * 1000
|
|
*
|
|
* That said, we can come up with a generic
|
|
* equation to calculate the value we should set
|
|
* it TQAVCC register by replacing 'BW' in E1 by E2.
|
|
* The resulting equation is:
|
|
*
|
|
* value = link-speed * 0x7736 * idleSlope * 0.2
|
|
* ------------------------------------- (E3)
|
|
* 100 * 2.5 * link-speed * 1000
|
|
*
|
|
* 'link-speed' is present in both sides of the
|
|
* fraction so it is canceled out. The final
|
|
* equation is the following:
|
|
*
|
|
* value = idleSlope * 61036
|
|
* ----------------- (E4)
|
|
* 2500000
|
|
*
|
|
* NOTE: For i225, given the above, we can see
|
|
* that idleslope is represented in
|
|
* 40.959433 kbps units by the value at
|
|
* the TQAVCC register (2.5Gbps / 61036),
|
|
* which reduces the granularity for
|
|
* idleslope increments.
|
|
*
|
|
* In i225 controller, the sendSlope and loCredit
|
|
* parameters from CBS are not configurable
|
|
* by software so we don't do any
|
|
* 'controller configuration' in respect to
|
|
* these parameters.
|
|
*/
|
|
cbs_value = DIV_ROUND_UP_ULL(ring->idleslope
|
|
* 61036ULL, 2500000);
|
|
|
|
tqavcc = rd32(IGC_TQAVCC(i));
|
|
tqavcc &= ~IGC_TQAVCC_IDLESLOPE_MASK;
|
|
tqavcc |= cbs_value | IGC_TQAVCC_KEEP_CREDITS;
|
|
wr32(IGC_TQAVCC(i), tqavcc);
|
|
|
|
wr32(IGC_TQAVHC(i),
|
|
0x80000000 + ring->hicredit * 0x7735);
|
|
} else {
|
|
/* Disable any CBS for the queue */
|
|
txqctl &= ~(IGC_TXQCTL_QAV_SEL_MASK);
|
|
|
|
/* Set idleSlope to zero. */
|
|
tqavcc = rd32(IGC_TQAVCC(i));
|
|
tqavcc &= ~(IGC_TQAVCC_IDLESLOPE_MASK |
|
|
IGC_TQAVCC_KEEP_CREDITS);
|
|
wr32(IGC_TQAVCC(i), tqavcc);
|
|
|
|
/* Set hiCredit to zero. */
|
|
wr32(IGC_TQAVHC(i), 0);
|
|
}
|
|
skip_cbs:
|
|
wr32(IGC_TXQCTL(i), txqctl);
|
|
}
|
|
|
|
tqavctrl = rd32(IGC_TQAVCTRL) & ~IGC_TQAVCTRL_FUTSCDDIS;
|
|
|
|
tqavctrl |= IGC_TQAVCTRL_TRANSMIT_MODE_TSN | IGC_TQAVCTRL_ENHANCED_QAV;
|
|
|
|
adapter->qbv_count++;
|
|
|
|
cycle = adapter->cycle_time;
|
|
base_time = adapter->base_time;
|
|
|
|
nsec = rd32(IGC_SYSTIML);
|
|
sec = rd32(IGC_SYSTIMH);
|
|
|
|
systim = ktime_set(sec, nsec);
|
|
if (ktime_compare(systim, base_time) > 0) {
|
|
s64 n = div64_s64(ktime_sub_ns(systim, base_time), cycle);
|
|
|
|
base_time = ktime_add_ns(base_time, (n + 1) * cycle);
|
|
|
|
/* Increase the counter if scheduling into the past while
|
|
* Gate Control List (GCL) is running.
|
|
*/
|
|
if ((rd32(IGC_BASET_H) || rd32(IGC_BASET_L)) &&
|
|
(adapter->tc_setup_type == TC_SETUP_QDISC_TAPRIO) &&
|
|
(adapter->qbv_count > 1))
|
|
adapter->qbv_config_change_errors++;
|
|
} else {
|
|
if (igc_is_device_id_i226(hw)) {
|
|
ktime_t adjust_time, expires_time;
|
|
|
|
/* According to datasheet section 7.5.2.9.3.3, FutScdDis bit
|
|
* has to be configured before the cycle time and base time.
|
|
* Tx won't hang if a GCL is already running,
|
|
* so in this case we don't need to set FutScdDis.
|
|
*/
|
|
if (!(rd32(IGC_BASET_H) || rd32(IGC_BASET_L)))
|
|
tqavctrl |= IGC_TQAVCTRL_FUTSCDDIS;
|
|
|
|
nsec = rd32(IGC_SYSTIML);
|
|
sec = rd32(IGC_SYSTIMH);
|
|
systim = ktime_set(sec, nsec);
|
|
|
|
adjust_time = adapter->base_time;
|
|
expires_time = ktime_sub_ns(adjust_time, systim);
|
|
hrtimer_start(&adapter->hrtimer, expires_time, HRTIMER_MODE_REL);
|
|
}
|
|
}
|
|
|
|
wr32(IGC_TQAVCTRL, tqavctrl);
|
|
|
|
wr32(IGC_QBVCYCLET_S, cycle);
|
|
wr32(IGC_QBVCYCLET, cycle);
|
|
|
|
baset_h = div_s64_rem(base_time, NSEC_PER_SEC, &baset_l);
|
|
wr32(IGC_BASET_H, baset_h);
|
|
|
|
/* In i226, Future base time is only supported when FutScdDis bit
|
|
* is enabled and only active for re-configuration.
|
|
* In this case, initialize the base time with zero to create
|
|
* "re-configuration" scenario then only set the desired base time.
|
|
*/
|
|
if (tqavctrl & IGC_TQAVCTRL_FUTSCDDIS)
|
|
wr32(IGC_BASET_L, 0);
|
|
wr32(IGC_BASET_L, baset_l);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int igc_tsn_reset(struct igc_adapter *adapter)
|
|
{
|
|
unsigned int new_flags;
|
|
int err = 0;
|
|
|
|
new_flags = igc_tsn_new_flags(adapter);
|
|
|
|
if (!(new_flags & IGC_FLAG_TSN_ANY_ENABLED))
|
|
return igc_tsn_disable_offload(adapter);
|
|
|
|
err = igc_tsn_enable_offload(adapter);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
adapter->flags = new_flags;
|
|
|
|
return err;
|
|
}
|
|
|
|
int igc_tsn_offload_apply(struct igc_adapter *adapter)
|
|
{
|
|
struct igc_hw *hw = &adapter->hw;
|
|
|
|
/* Per I225/6 HW Design Section 7.5.2.1, transmit mode
|
|
* cannot be changed dynamically. Require reset the adapter.
|
|
*/
|
|
if (netif_running(adapter->netdev) &&
|
|
(igc_is_device_id_i225(hw) || !adapter->qbv_count)) {
|
|
schedule_work(&adapter->reset_task);
|
|
return 0;
|
|
}
|
|
|
|
igc_tsn_reset(adapter);
|
|
|
|
return 0;
|
|
}
|