1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/net/ethernet/intel/igc/igc_tsn.c
Faizal Rahim 6c3fc0b1c3 igc: Fix qbv tx latency by setting gtxoffset
A large tx latency issue was discovered during testing when only QBV was
enabled. The issue occurs because gtxoffset was not set when QBV is
active, it was only set when launch time is active.

The patch "igc: Correct the launchtime offset" only sets gtxoffset when
the launchtime_enable field is set by the user. Enabling launchtime_enable
ultimately sets the register IGC_TXQCTL_QUEUE_MODE_LAUNCHT (referred to as
LaunchT in the SW user manual).

Section 7.5.2.6 of the IGC i225/6 SW User Manual Rev 1.2.4 states:
"The latency between transmission scheduling (launch time) and the
time the packet is transmitted to the network is listed in Table 7-61."

However, the patch misinterprets the phrase "launch time" in that section
by assuming it specifically refers to the LaunchT register, whereas it
actually denotes the generic term for when a packet is released from the
internal buffer to the MAC transmit logic.

This launch time, as per that section, also implicitly refers to the QBV
gate open time, where a packet waits in the buffer for the QBV gate to
open. Therefore, latency applies whenever QBV is in use. TSN features such
as QBU and QAV reuse QBV, making the latency universal to TSN features.

Discussed with i226 HW owner (Shalev, Avi) and we were in agreement that
the term "launch time" used in Section 7.5.2.6 is not clear and can be
easily misinterpreted. Avi will update this section to:
"When TQAVCTRL.TRANSMIT_MODE = TSN, the latency between transmission
scheduling and the time the packet is transmitted to the network is listed
in Table 7-61."

Fix this issue by using igc_tsn_is_tx_mode_in_tsn() as a condition to
write to gtxoffset, aligning with the newly updated SW User Manual.

Tested:
1. Enrol taprio on talker board
   base-time 0
   cycle-time 1000000
   flags 0x2
   index 0 cmd S gatemask 0x1 interval1
   index 0 cmd S gatemask 0x1 interval2

   Note:
   interval1 = interval for a 64 bytes packet to go through
   interval2 = cycle-time - interval1

2. Take tcpdump on listener board

3. Use udp tai app on talker to send packets to listener

4. Check the timestamp on listener via wireshark

Test Result:
100 Mbps: 113 ~193 ns
1000 Mbps: 52 ~ 84 ns
2500 Mbps: 95 ~ 223 ns

Note that the test result is similar to the patch "igc: Correct the
launchtime offset".

Fixes: 790835fcc0 ("igc: Correct the launchtime offset")
Signed-off-by: Faizal Rahim <faizal.abdul.rahim@linux.intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Acked-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>
Tested-by: Mor Bar-Gabay <morx.bar.gabay@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
2024-08-07 13:30:23 -07:00

398 lines
10 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;
}
static bool igc_tsn_is_tx_mode_in_tsn(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
return !!(rd32(IGC_TQAVCTRL) & IGC_TQAVCTRL_TRANSMIT_MODE_TSN);
}
void igc_tsn_adjust_txtime_offset(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
u16 txoffset;
if (!igc_tsn_is_tx_mode_in_tsn(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);
}
static void igc_tsn_restore_retx_default(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
u32 retxctl;
retxctl = rd32(IGC_RETX_CTL) & IGC_RETX_CTL_WATERMARK_MASK;
wr32(IGC_RETX_CTL, retxctl);
}
bool igc_tsn_is_taprio_activated_by_user(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
return (rd32(IGC_BASET_H) || rd32(IGC_BASET_L)) &&
adapter->taprio_offload_enable;
}
/* 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);
if (igc_is_device_id_i226(hw))
igc_tsn_restore_retx_default(adapter);
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;
}
/* To partially fix i226 HW errata, reduce MAC internal buffering from 192 Bytes
* to 88 Bytes by setting RETX_CTL register using the recommendation from:
* a) Ethernet Controller I225/I226 Specification Update Rev 2.1
* Item 9: TSN: Packet Transmission Might Cross the Qbv Window
* b) I225/6 SW User Manual Rev 1.2.4: Section 8.11.5 Retry Buffer Control
*/
static void igc_tsn_set_retx_qbvfullthreshold(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
u32 retxctl, watermark;
retxctl = rd32(IGC_RETX_CTL);
watermark = retxctl & IGC_RETX_CTL_WATERMARK_MASK;
/* Set QBVFULLTH value using watermark and set QBVFULLEN */
retxctl |= (watermark << IGC_RETX_CTL_QBVFULLTH_SHIFT) |
IGC_RETX_CTL_QBVFULLEN;
wr32(IGC_RETX_CTL, retxctl);
}
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);
if (igc_is_device_id_i226(hw))
igc_tsn_set_retx_qbvfullthreshold(adapter);
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 * 0x7736);
} 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);
} 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;
}
static bool igc_tsn_will_tx_mode_change(struct igc_adapter *adapter)
{
bool any_tsn_enabled = !!(igc_tsn_new_flags(adapter) &
IGC_FLAG_TSN_ANY_ENABLED);
return (any_tsn_enabled && !igc_tsn_is_tx_mode_in_tsn(adapter)) ||
(!any_tsn_enabled && igc_tsn_is_tx_mode_in_tsn(adapter));
}
int igc_tsn_offload_apply(struct igc_adapter *adapter)
{
/* Per I225/6 HW Design Section 7.5.2.1 guideline, if tx mode change
* from legacy->tsn or tsn->legacy, then reset adapter is needed.
*/
if (netif_running(adapter->netdev) &&
igc_tsn_will_tx_mode_change(adapter)) {
schedule_work(&adapter->reset_task);
return 0;
}
igc_tsn_reset(adapter);
return 0;
}