466 lines
12 KiB
C
466 lines
12 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;
|
||
|
|
||
|
if (adapter->strict_priority_enable)
|
||
|
new_flags |= IGC_FLAG_TSN_LEGACY_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;
|
||
|
}
|
||
|
|
||
|
static void igc_tsn_tx_arb(struct igc_adapter *adapter, u16 *queue_per_tc)
|
||
|
{
|
||
|
struct igc_hw *hw = &adapter->hw;
|
||
|
u32 txarb;
|
||
|
|
||
|
txarb = rd32(IGC_TXARB);
|
||
|
|
||
|
txarb &= ~(IGC_TXARB_TXQ_PRIO_0_MASK |
|
||
|
IGC_TXARB_TXQ_PRIO_1_MASK |
|
||
|
IGC_TXARB_TXQ_PRIO_2_MASK |
|
||
|
IGC_TXARB_TXQ_PRIO_3_MASK);
|
||
|
|
||
|
txarb |= IGC_TXARB_TXQ_PRIO_0(queue_per_tc[3]);
|
||
|
txarb |= IGC_TXARB_TXQ_PRIO_1(queue_per_tc[2]);
|
||
|
txarb |= IGC_TXARB_TXQ_PRIO_2(queue_per_tc[1]);
|
||
|
txarb |= IGC_TXARB_TXQ_PRIO_3(queue_per_tc[0]);
|
||
|
|
||
|
wr32(IGC_TXARB, txarb);
|
||
|
}
|
||
|
|
||
|
/* Returns the TSN specific registers to their default values after
|
||
|
* the adapter is reset.
|
||
|
*/
|
||
|
static int igc_tsn_disable_offload(struct igc_adapter *adapter)
|
||
|
{
|
||
|
u16 queue_per_tc[4] = { 3, 2, 1, 0 };
|
||
|
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);
|
||
|
|
||
|
/* Reset mqprio TC configuration. */
|
||
|
netdev_reset_tc(adapter->netdev);
|
||
|
|
||
|
/* Restore the default Tx arbitration: Priority 0 has the highest
|
||
|
* priority and is assigned to queue 0 and so on and so forth.
|
||
|
*/
|
||
|
igc_tsn_tx_arb(adapter, queue_per_tc);
|
||
|
|
||
|
adapter->flags &= ~IGC_FLAG_TSN_QBV_ENABLED;
|
||
|
adapter->flags &= ~IGC_FLAG_TSN_LEGACY_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);
|
||
|
|
||
|
if (adapter->strict_priority_enable) {
|
||
|
int err;
|
||
|
|
||
|
err = netdev_set_num_tc(adapter->netdev, adapter->num_tc);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
for (i = 0; i < adapter->num_tc; i++) {
|
||
|
err = netdev_set_tc_queue(adapter->netdev, i, 1,
|
||
|
adapter->queue_per_tc[i]);
|
||
|
if (err)
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* In case the card is configured with less than four queues. */
|
||
|
for (; i < IGC_MAX_TX_QUEUES; i++)
|
||
|
adapter->queue_per_tc[i] = i;
|
||
|
|
||
|
/* Configure queue priorities according to the user provided
|
||
|
* mapping.
|
||
|
*/
|
||
|
igc_tsn_tx_arb(adapter, adapter->queue_per_tc);
|
||
|
|
||
|
/* Enable legacy TSN mode which will do strict priority without
|
||
|
* any other TSN features.
|
||
|
*/
|
||
|
tqavctrl = rd32(IGC_TQAVCTRL);
|
||
|
tqavctrl |= IGC_TQAVCTRL_TRANSMIT_MODE_TSN;
|
||
|
tqavctrl &= ~IGC_TQAVCTRL_ENHANCED_QAV;
|
||
|
wr32(IGC_TQAVCTRL, tqavctrl);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|