igc: Add support for TX timestamping
authorVinicius Costa Gomes <vinicius.gomes@intel.com>
Mon, 2 Dec 2019 23:19:51 +0000 (15:19 -0800)
committerJeff Kirsher <jeffrey.t.kirsher@intel.com>
Mon, 6 Jan 2020 22:39:17 +0000 (14:39 -0800)
This adds support for timestamping packets being transmitted.

Based on the code from i210. The basic differences is that i225 has 4
registers to store the transmit timestamps (i210 has one). Right now,
we only support retrieving from one register, support for using the
other registers will be added later.

Signed-off-by: Vinicius Costa Gomes <vinicius.gomes@intel.com>
Tested-by: Aaron Brown <aaron.f.brown@intel.com>
Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
drivers/net/ethernet/intel/igc/igc.h
drivers/net/ethernet/intel/igc/igc_defines.h
drivers/net/ethernet/intel/igc/igc_main.c
drivers/net/ethernet/intel/igc/igc_ptp.c

index 9b0d1cbf941f5608d2e84e0162a5ae6e0a10006c..52066bdbbad0ac57f675d3b1b9de6b0d4cfeb8d7 100644 (file)
@@ -558,6 +558,8 @@ void igc_ptp_rx_pktstamp(struct igc_q_vector *q_vector, void *va,
                         struct sk_buff *skb);
 int igc_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr);
 int igc_ptp_get_ts_config(struct net_device *netdev, struct ifreq *ifr);
+void igc_ptp_tx_hang(struct igc_adapter *adapter);
+
 #define igc_rx_pg_size(_ring) (PAGE_SIZE << igc_rx_pg_order(_ring))
 
 #define IGC_TXD_DCMD   (IGC_ADVTXD_DCMD_EOP | IGC_ADVTXD_DCMD_RS)
index 9dede618362f054fcf01d410b816ad9dc8af656e..586fa14098eb80e81edab319b5fbaa4be54c0342 100644 (file)
 #define IGC_TSYNCRXCFG_PTP_V1_SYNC_MESSAGE     0x00
 #define IGC_TSYNCRXCFG_PTP_V1_DELAY_REQ_MESSAGE        0x01
 
+/* Immediate Interrupt Receive */
+#define IGC_IMIR_CLEAR_MASK    0xF001FFFF /* IMIR Reg Clear Mask */
+#define IGC_IMIR_PORT_BYPASS   0x20000 /* IMIR Port Bypass Bit */
+#define IGC_IMIR_PRIORITY_SHIFT        29 /* IMIR Priority Shift */
+#define IGC_IMIREXT_CLEAR_MASK 0x7FFFF /* IMIREXT Reg Clear Mask */
+
 /* Immediate Interrupt Receive Extended */
 #define IGC_IMIREXT_CTRL_BP    0x00080000  /* Bypass check of ctrl bits */
 #define IGC_IMIREXT_SIZE_BP    0x00001000  /* Packet size bypass */
 
+/* Time Sync Transmit Control bit definitions */
+#define IGC_TSYNCTXCTL_VALID                   0x00000001  /* Tx timestamp valid */
+#define IGC_TSYNCTXCTL_ENABLED                 0x00000010  /* enable Tx timestamping */
+#define IGC_TSYNCTXCTL_MAX_ALLOWED_DLY_MASK    0x0000F000  /* max delay */
+#define IGC_TSYNCTXCTL_SYNC_COMP_ERR           0x20000000  /* sync err */
+#define IGC_TSYNCTXCTL_SYNC_COMP               0x40000000  /* sync complete */
+#define IGC_TSYNCTXCTL_START_SYNC              0x80000000  /* initiate sync */
+
 /* Receive Checksum Control */
 #define IGC_RXCSUM_CRCOFL      0x00000800   /* CRC32 offload enable */
 #define IGC_RXCSUM_PCSD                0x00002000   /* packet checksum disabled */
index 504dbb74ca759ca7de204e16369ee92bc1b884eb..c359f3d9fb2575a4c5cc211aeaf65c296c4760e4 100644 (file)
@@ -987,6 +987,11 @@ static inline int igc_maybe_stop_tx(struct igc_ring *tx_ring, const u16 size)
        return __igc_maybe_stop_tx(tx_ring, size);
 }
 
+#define IGC_SET_FLAG(_input, _flag, _result) \
+       (((_flag) <= (_result)) ?                               \
+        ((u32)((_input) & (_flag)) * ((_result) / (_flag))) :  \
+        ((u32)((_input) & (_flag)) / ((_flag) / (_result))))
+
 static u32 igc_tx_cmd_type(struct sk_buff *skb, u32 tx_flags)
 {
        /* set type for advanced descriptor with frame checksum insertion */
@@ -994,6 +999,10 @@ static u32 igc_tx_cmd_type(struct sk_buff *skb, u32 tx_flags)
                       IGC_ADVTXD_DCMD_DEXT |
                       IGC_ADVTXD_DCMD_IFCS;
 
+       /* set timestamp bit if present */
+       cmd_type |= IGC_SET_FLAG(tx_flags, IGC_TX_FLAGS_TSTAMP,
+                                (IGC_ADVTXD_MAC_TSTAMP));
+
        return cmd_type;
 }
 
@@ -1192,6 +1201,26 @@ static netdev_tx_t igc_xmit_frame_ring(struct sk_buff *skb,
        first->bytecount = skb->len;
        first->gso_segs = 1;
 
+       if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) {
+               struct igc_adapter *adapter = netdev_priv(tx_ring->netdev);
+
+               /* FIXME: add support for retrieving timestamps from
+                * the other timer registers before skipping the
+                * timestamping request.
+                */
+               if (adapter->tstamp_config.tx_type == HWTSTAMP_TX_ON &&
+                   !test_and_set_bit_lock(__IGC_PTP_TX_IN_PROGRESS,
+                                          &adapter->state)) {
+                       skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+                       tx_flags |= IGC_TX_FLAGS_TSTAMP;
+
+                       adapter->ptp_tx_skb = skb_get(skb);
+                       adapter->ptp_tx_start = jiffies;
+               } else {
+                       adapter->tx_hwtstamp_skipped++;
+               }
+       }
+
        /* record initial flags and protocol */
        first->tx_flags = tx_flags;
        first->protocol = protocol;
@@ -3648,6 +3677,22 @@ int igc_del_mac_steering_filter(struct igc_adapter *adapter,
                                        IGC_MAC_STATE_QUEUE_STEERING | flags);
 }
 
+static void igc_tsync_interrupt(struct igc_adapter *adapter)
+{
+       struct igc_hw *hw = &adapter->hw;
+       u32 tsicr = rd32(IGC_TSICR);
+       u32 ack = 0;
+
+       if (tsicr & IGC_TSICR_TXTS) {
+               /* retrieve hardware timestamp */
+               schedule_work(&adapter->ptp_tx_work);
+               ack |= IGC_TSICR_TXTS;
+       }
+
+       /* acknowledge the interrupts */
+       wr32(IGC_TSICR, ack);
+}
+
 /**
  * igc_msix_other - msix other interrupt handler
  * @irq: interrupt number
@@ -3675,6 +3720,9 @@ static irqreturn_t igc_msix_other(int irq, void *data)
                        mod_timer(&adapter->watchdog_timer, jiffies + 1);
        }
 
+       if (icr & IGC_ICR_TS)
+               igc_tsync_interrupt(adapter);
+
        wr32(IGC_EIMS, adapter->eims_other);
 
        return IRQ_HANDLED;
@@ -4004,6 +4052,8 @@ no_wait:
                wr32(IGC_ICS, IGC_ICS_RXDMT0);
        }
 
+       igc_ptp_tx_hang(adapter);
+
        /* Reset the timer */
        if (!test_bit(__IGC_DOWN, &adapter->state)) {
                if (adapter->flags & IGC_FLAG_NEED_LINK_UPDATE)
index 093ffc9312dfafa28a3611aa1e92db255fb436f1..79ffb833aa80c6afecaaaa80defa6d954f33f754 100644 (file)
@@ -284,6 +284,12 @@ static void igc_ptp_enable_tstamp_all_rxqueues(struct igc_adapter *adapter,
  * @adapter: networking device structure
  * @config: hwtstamp configuration
  *
+ * Outgoing time stamping can be enabled and disabled. Play nice and
+ * disable it when requested, although it shouldn't case any overhead
+ * when no packet needs it. At most one packet in the queue may be
+ * marked for time stamping, otherwise it would be impossible to tell
+ * for sure to which packet the hardware time stamp belongs.
+ *
  * Incoming time stamping has to be configured via the hardware
  * filters. Not all combinations are supported, in particular event
  * type has to be specified. Matching the kind of event packet is
@@ -294,6 +300,7 @@ static void igc_ptp_enable_tstamp_all_rxqueues(struct igc_adapter *adapter,
 static int igc_ptp_set_timestamp_mode(struct igc_adapter *adapter,
                                      struct hwtstamp_config *config)
 {
+       u32 tsync_tx_ctl = IGC_TSYNCTXCTL_ENABLED;
        u32 tsync_rx_ctl = IGC_TSYNCRXCTL_ENABLED;
        struct igc_hw *hw = &adapter->hw;
        u32 tsync_rx_cfg = 0;
@@ -305,6 +312,15 @@ static int igc_ptp_set_timestamp_mode(struct igc_adapter *adapter,
        if (config->flags)
                return -EINVAL;
 
+       switch (config->tx_type) {
+       case HWTSTAMP_TX_OFF:
+               tsync_tx_ctl = 0;
+       case HWTSTAMP_TX_ON:
+               break;
+       default:
+               return -ERANGE;
+       }
+
        switch (config->rx_filter) {
        case HWTSTAMP_FILTER_NONE:
                tsync_rx_ctl = 0;
@@ -368,6 +384,15 @@ static int igc_ptp_set_timestamp_mode(struct igc_adapter *adapter,
                }
        }
 
+       if (tsync_tx_ctl)
+               tsync_tx_ctl = IGC_TSYNCTXCTL_ENABLED;
+
+       /* enable/disable TX */
+       regval = rd32(IGC_TSYNCTXCTL);
+       regval &= ~IGC_TSYNCTXCTL_ENABLED;
+       regval |= tsync_tx_ctl;
+       wr32(IGC_TSYNCTXCTL, regval);
+
        /* enable/disable RX */
        regval = rd32(IGC_TSYNCRXCTL);
        regval &= ~(IGC_TSYNCRXCTL_ENABLED | IGC_TSYNCRXCTL_TYPE_MASK);
@@ -442,8 +467,75 @@ void igc_ptp_tx_hang(struct igc_adapter *adapter)
        }
 }
 
+/**
+ * igc_ptp_tx_hwtstamp - utility function which checks for TX time stamp
+ * @adapter: Board private structure
+ *
+ * If we were asked to do hardware stamping and such a time stamp is
+ * available, then it must have been for this skb here because we only
+ * allow only one such packet into the queue.
+ */
+static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter)
+{
+       struct sk_buff *skb = adapter->ptp_tx_skb;
+       struct skb_shared_hwtstamps shhwtstamps;
+       struct igc_hw *hw = &adapter->hw;
+       u64 regval;
+
+       regval = rd32(IGC_TXSTMPL);
+       regval |= (u64)rd32(IGC_TXSTMPH) << 32;
+       igc_ptp_systim_to_hwtstamp(adapter, &shhwtstamps, regval);
+
+       /* Clear the lock early before calling skb_tstamp_tx so that
+        * applications are not woken up before the lock bit is clear. We use
+        * a copy of the skb pointer to ensure other threads can't change it
+        * while we're notifying the stack.
+        */
+       adapter->ptp_tx_skb = NULL;
+       clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state);
+
+       /* Notify the stack and free the skb after we've unlocked */
+       skb_tstamp_tx(skb, &shhwtstamps);
+       dev_kfree_skb_any(skb);
+}
+
+/**
+ * igc_ptp_tx_work
+ * @work: pointer to work struct
+ *
+ * This work function polls the TSYNCTXCTL valid bit to determine when a
+ * timestamp has been taken for the current stored skb.
+ */
 void igc_ptp_tx_work(struct work_struct *work)
 {
+       struct igc_adapter *adapter = container_of(work, struct igc_adapter,
+                                                  ptp_tx_work);
+       struct igc_hw *hw = &adapter->hw;
+       u32 tsynctxctl;
+
+       if (!adapter->ptp_tx_skb)
+               return;
+
+       if (time_is_before_jiffies(adapter->ptp_tx_start +
+                                  IGC_PTP_TX_TIMEOUT)) {
+               dev_kfree_skb_any(adapter->ptp_tx_skb);
+               adapter->ptp_tx_skb = NULL;
+               clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state);
+               adapter->tx_hwtstamp_timeouts++;
+               /* Clear the tx valid bit in TSYNCTXCTL register to enable
+                * interrupt
+                */
+               rd32(IGC_TXSTMPH);
+               dev_warn(&adapter->pdev->dev, "clearing Tx timestamp hang\n");
+               return;
+       }
+
+       tsynctxctl = rd32(IGC_TSYNCTXCTL);
+       if (tsynctxctl & IGC_TSYNCTXCTL_VALID)
+               igc_ptp_tx_hwtstamp(adapter);
+       else
+               /* reschedule to check later */
+               schedule_work(&adapter->ptp_tx_work);
 }
 
 /**