summaryrefslogtreecommitdiff
path: root/drivers/net
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/ethernet/intel/igc/igc.h7
-rw-r--r--drivers/net/ethernet/intel/igc/igc_ethtool.c2
-rw-r--r--drivers/net/ethernet/intel/igc/igc_main.c74
-rw-r--r--drivers/net/ethernet/intel/igc/igc_ptp.c25
-rw-r--r--drivers/net/ethernet/intel/igc/igc_tsn.c42
5 files changed, 118 insertions, 32 deletions
diff --git a/drivers/net/ethernet/intel/igc/igc.h b/drivers/net/ethernet/intel/igc/igc.h
index 00a5ee487812..639a50c02537 100644
--- a/drivers/net/ethernet/intel/igc/igc.h
+++ b/drivers/net/ethernet/intel/igc/igc.h
@@ -14,6 +14,7 @@
#include <linux/timecounter.h>
#include <linux/net_tstamp.h>
#include <linux/bitfield.h>
+#include <linux/hrtimer.h>
#include "igc_hw.h"
@@ -101,6 +102,8 @@ struct igc_ring {
u32 start_time;
u32 end_time;
u32 max_sdu;
+ bool oper_gate_closed; /* Operating gate. True if the TX Queue is closed */
+ bool admin_gate_closed; /* Future gate. True if the TX Queue will be closed */
/* CBS parameters */
bool cbs_enable; /* indicates if CBS is enabled */
@@ -160,6 +163,7 @@ struct igc_adapter {
struct timer_list watchdog_timer;
struct timer_list dma_err_timer;
struct timer_list phy_info_timer;
+ struct hrtimer hrtimer;
u32 wol;
u32 en_mng_pt;
@@ -184,10 +188,13 @@ struct igc_adapter {
u32 max_frame_size;
u32 min_frame_size;
+ int tc_setup_type;
ktime_t base_time;
ktime_t cycle_time;
bool qbv_enable;
u32 qbv_config_change_errors;
+ bool qbv_transition;
+ unsigned int qbv_count;
/* OS defined structs */
struct pci_dev *pdev;
diff --git a/drivers/net/ethernet/intel/igc/igc_ethtool.c b/drivers/net/ethernet/intel/igc/igc_ethtool.c
index 0e2cb00622d1..93bce729be76 100644
--- a/drivers/net/ethernet/intel/igc/igc_ethtool.c
+++ b/drivers/net/ethernet/intel/igc/igc_ethtool.c
@@ -1708,6 +1708,8 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev,
/* twisted pair */
cmd->base.port = PORT_TP;
cmd->base.phy_address = hw->phy.addr;
+ ethtool_link_ksettings_add_link_mode(cmd, supported, TP);
+ ethtool_link_ksettings_add_link_mode(cmd, advertising, TP);
/* advertising link modes */
if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF)
diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c
index 019ce91c45aa..281a0e35b9d1 100644
--- a/drivers/net/ethernet/intel/igc/igc_main.c
+++ b/drivers/net/ethernet/intel/igc/igc_main.c
@@ -711,7 +711,6 @@ static void igc_configure_tx_ring(struct igc_adapter *adapter,
/* disable the queue */
wr32(IGC_TXDCTL(reg_idx), 0);
wrfl();
- mdelay(10);
wr32(IGC_TDLEN(reg_idx),
ring->count * sizeof(union igc_adv_tx_desc));
@@ -1573,16 +1572,12 @@ done:
first->bytecount = skb->len;
first->gso_segs = 1;
- if (tx_ring->max_sdu > 0) {
- u32 max_sdu = 0;
-
- max_sdu = tx_ring->max_sdu +
- (skb_vlan_tagged(first->skb) ? VLAN_HLEN : 0);
+ if (adapter->qbv_transition || tx_ring->oper_gate_closed)
+ goto out_drop;
- if (first->bytecount > max_sdu) {
- adapter->stats.txdrop++;
- goto out_drop;
- }
+ if (tx_ring->max_sdu > 0 && first->bytecount > tx_ring->max_sdu) {
+ adapter->stats.txdrop++;
+ goto out_drop;
}
if (unlikely(test_bit(IGC_RING_FLAG_TX_HWTSTAMP, &tx_ring->flags) &&
@@ -3012,8 +3007,8 @@ static bool igc_clean_tx_irq(struct igc_q_vector *q_vector, int napi_budget)
time_after(jiffies, tx_buffer->time_stamp +
(adapter->tx_timeout_factor * HZ)) &&
!(rd32(IGC_STATUS) & IGC_STATUS_TXOFF) &&
- (rd32(IGC_TDH(tx_ring->reg_idx)) !=
- readl(tx_ring->tail))) {
+ (rd32(IGC_TDH(tx_ring->reg_idx)) != readl(tx_ring->tail)) &&
+ !tx_ring->oper_gate_closed) {
/* detected Tx unit hang */
netdev_err(tx_ring->netdev,
"Detected Tx Unit Hang\n"
@@ -6103,6 +6098,8 @@ static int igc_tsn_clear_schedule(struct igc_adapter *adapter)
adapter->base_time = 0;
adapter->cycle_time = NSEC_PER_SEC;
adapter->qbv_config_change_errors = 0;
+ adapter->qbv_transition = false;
+ adapter->qbv_count = 0;
for (i = 0; i < adapter->num_tx_queues; i++) {
struct igc_ring *ring = adapter->tx_ring[i];
@@ -6110,6 +6107,8 @@ static int igc_tsn_clear_schedule(struct igc_adapter *adapter)
ring->start_time = 0;
ring->end_time = NSEC_PER_SEC;
ring->max_sdu = 0;
+ ring->oper_gate_closed = false;
+ ring->admin_gate_closed = false;
}
return 0;
@@ -6121,6 +6120,7 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
bool queue_configured[IGC_MAX_TX_QUEUES] = { };
struct igc_hw *hw = &adapter->hw;
u32 start_time = 0, end_time = 0;
+ struct timespec64 now;
size_t n;
int i;
@@ -6150,6 +6150,8 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
adapter->cycle_time = qopt->cycle_time;
adapter->base_time = qopt->base_time;
+ igc_ptp_read(adapter, &now);
+
for (n = 0; n < qopt->num_entries; n++) {
struct tc_taprio_sched_entry *e = &qopt->entries[n];
@@ -6184,7 +6186,10 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
ring->start_time = start_time;
ring->end_time = end_time;
- queue_configured[i] = true;
+ if (ring->start_time >= adapter->cycle_time)
+ queue_configured[i] = false;
+ else
+ queue_configured[i] = true;
}
start_time += e->interval;
@@ -6194,8 +6199,20 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
* If not, set the start and end time to be end time.
*/
for (i = 0; i < adapter->num_tx_queues; i++) {
+ struct igc_ring *ring = adapter->tx_ring[i];
+
+ if (!is_base_time_past(qopt->base_time, &now)) {
+ ring->admin_gate_closed = false;
+ } else {
+ ring->oper_gate_closed = false;
+ ring->admin_gate_closed = false;
+ }
+
if (!queue_configured[i]) {
- struct igc_ring *ring = adapter->tx_ring[i];
+ if (!is_base_time_past(qopt->base_time, &now))
+ ring->admin_gate_closed = true;
+ else
+ ring->oper_gate_closed = true;
ring->start_time = end_time;
ring->end_time = end_time;
@@ -6207,7 +6224,7 @@ static int igc_save_qbv_schedule(struct igc_adapter *adapter,
struct net_device *dev = adapter->netdev;
if (qopt->max_sdu[i])
- ring->max_sdu = qopt->max_sdu[i] + dev->hard_header_len;
+ ring->max_sdu = qopt->max_sdu[i] + dev->hard_header_len - ETH_TLEN;
else
ring->max_sdu = 0;
}
@@ -6327,6 +6344,8 @@ static int igc_setup_tc(struct net_device *dev, enum tc_setup_type type,
{
struct igc_adapter *adapter = netdev_priv(dev);
+ adapter->tc_setup_type = type;
+
switch (type) {
case TC_QUERY_CAPS:
return igc_tc_query_caps(adapter, type_data);
@@ -6574,6 +6593,27 @@ static const struct xdp_metadata_ops igc_xdp_metadata_ops = {
.xmo_rx_timestamp = igc_xdp_rx_timestamp,
};
+static enum hrtimer_restart igc_qbv_scheduling_timer(struct hrtimer *timer)
+{
+ struct igc_adapter *adapter = container_of(timer, struct igc_adapter,
+ hrtimer);
+ unsigned int i;
+
+ adapter->qbv_transition = true;
+ for (i = 0; i < adapter->num_tx_queues; i++) {
+ struct igc_ring *tx_ring = adapter->tx_ring[i];
+
+ if (tx_ring->admin_gate_closed) {
+ tx_ring->admin_gate_closed = false;
+ tx_ring->oper_gate_closed = true;
+ } else {
+ tx_ring->oper_gate_closed = false;
+ }
+ }
+ adapter->qbv_transition = false;
+ return HRTIMER_NORESTART;
+}
+
/**
* igc_probe - Device Initialization Routine
* @pdev: PCI device information struct
@@ -6752,6 +6792,9 @@ static int igc_probe(struct pci_dev *pdev,
INIT_WORK(&adapter->reset_task, igc_reset_task);
INIT_WORK(&adapter->watchdog_task, igc_watchdog_task);
+ hrtimer_init(&adapter->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ adapter->hrtimer.function = &igc_qbv_scheduling_timer;
+
/* Initialize link properties that are user-changeable */
adapter->fc_autoneg = true;
hw->mac.autoneg = true;
@@ -6855,6 +6898,7 @@ static void igc_remove(struct pci_dev *pdev)
cancel_work_sync(&adapter->reset_task);
cancel_work_sync(&adapter->watchdog_task);
+ hrtimer_cancel(&adapter->hrtimer);
/* Release control of h/w to f/w. If f/w is AMT enabled, this
* would have already happened in close and is redundant.
diff --git a/drivers/net/ethernet/intel/igc/igc_ptp.c b/drivers/net/ethernet/intel/igc/igc_ptp.c
index 32ef112f8291..f0b979a70655 100644
--- a/drivers/net/ethernet/intel/igc/igc_ptp.c
+++ b/drivers/net/ethernet/intel/igc/igc_ptp.c
@@ -356,16 +356,35 @@ static int igc_ptp_feature_enable_i225(struct ptp_clock_info *ptp,
tsim &= ~IGC_TSICR_TT0;
}
if (on) {
+ struct timespec64 safe_start;
int i = rq->perout.index;
igc_pin_perout(igc, i, pin, use_freq);
- igc->perout[i].start.tv_sec = rq->perout.start.sec;
+ igc_ptp_read(igc, &safe_start);
+
+ /* PPS output start time is triggered by Target time(TT)
+ * register. Programming any past time value into TT
+ * register will cause PPS to never start. Need to make
+ * sure we program the TT register a time ahead in
+ * future. There isn't a stringent need to fire PPS out
+ * right away. Adding +2 seconds should take care of
+ * corner cases. Let's say if the SYSTIML is close to
+ * wrap up and the timer keeps ticking as we program the
+ * register, adding +2seconds is safe bet.
+ */
+ safe_start.tv_sec += 2;
+
+ if (rq->perout.start.sec < safe_start.tv_sec)
+ igc->perout[i].start.tv_sec = safe_start.tv_sec;
+ else
+ igc->perout[i].start.tv_sec = rq->perout.start.sec;
igc->perout[i].start.tv_nsec = rq->perout.start.nsec;
igc->perout[i].period.tv_sec = ts.tv_sec;
igc->perout[i].period.tv_nsec = ts.tv_nsec;
- wr32(trgttimh, rq->perout.start.sec);
+ wr32(trgttimh, (u32)igc->perout[i].start.tv_sec);
/* For now, always select timer 0 as source. */
- wr32(trgttiml, rq->perout.start.nsec | IGC_TT_IO_TIMER_SEL_SYSTIM0);
+ wr32(trgttiml, (u32)(igc->perout[i].start.tv_nsec |
+ IGC_TT_IO_TIMER_SEL_SYSTIM0));
if (use_freq)
wr32(freqout, ns);
tsauxc |= tsauxc_mask;
diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c
index 94a2b0dfb54d..3cdb0c988728 100644
--- a/drivers/net/ethernet/intel/igc/igc_tsn.c
+++ b/drivers/net/ethernet/intel/igc/igc_tsn.c
@@ -114,7 +114,6 @@ static int igc_tsn_disable_offload(struct igc_adapter *adapter)
static int igc_tsn_enable_offload(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
- bool tsn_mode_reconfig = false;
u32 tqavctrl, baset_l, baset_h;
u32 sec, nsec, cycle;
ktime_t base_time, systim;
@@ -228,11 +227,10 @@ skip_cbs:
tqavctrl = rd32(IGC_TQAVCTRL) & ~IGC_TQAVCTRL_FUTSCDDIS;
- if (tqavctrl & IGC_TQAVCTRL_TRANSMIT_MODE_TSN)
- tsn_mode_reconfig = true;
-
tqavctrl |= IGC_TQAVCTRL_TRANSMIT_MODE_TSN | IGC_TQAVCTRL_ENHANCED_QAV;
+ adapter->qbv_count++;
+
cycle = adapter->cycle_time;
base_time = adapter->base_time;
@@ -249,17 +247,29 @@ skip_cbs:
* Gate Control List (GCL) is running.
*/
if ((rd32(IGC_BASET_H) || rd32(IGC_BASET_L)) &&
- tsn_mode_reconfig)
+ (adapter->tc_setup_type == TC_SETUP_QDISC_TAPRIO) &&
+ (adapter->qbv_count > 1))
adapter->qbv_config_change_errors++;
} else {
- /* 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 there is a GCL is already running,
- * so in this case we don't need to set FutScdDis.
- */
- if (igc_is_device_id_i226(hw) &&
- !(rd32(IGC_BASET_H) || rd32(IGC_BASET_L)))
- tqavctrl |= IGC_TQAVCTRL_FUTSCDDIS;
+ 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);
@@ -305,7 +315,11 @@ int igc_tsn_offload_apply(struct igc_adapter *adapter)
{
struct igc_hw *hw = &adapter->hw;
- if (netif_running(adapter->netdev) && igc_is_device_id_i225(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;
}