diff options
Diffstat (limited to 'drivers/net/ethernet/intel/igc/igc_ptp.c')
| -rw-r--r-- | drivers/net/ethernet/intel/igc/igc_ptp.c | 142 | 
1 files changed, 102 insertions, 40 deletions
| diff --git a/drivers/net/ethernet/intel/igc/igc_ptp.c b/drivers/net/ethernet/intel/igc/igc_ptp.c index 4e10ced736db..32ef112f8291 100644 --- a/drivers/net/ethernet/intel/igc/igc_ptp.c +++ b/drivers/net/ethernet/intel/igc/igc_ptp.c @@ -536,9 +536,34 @@ static void igc_ptp_enable_rx_timestamp(struct igc_adapter *adapter)  	wr32(IGC_TSYNCRXCTL, val);  } +static void igc_ptp_clear_tx_tstamp(struct igc_adapter *adapter) +{ +	unsigned long flags; + +	spin_lock_irqsave(&adapter->ptp_tx_lock, flags); + +	dev_kfree_skb_any(adapter->ptp_tx_skb); +	adapter->ptp_tx_skb = NULL; + +	spin_unlock_irqrestore(&adapter->ptp_tx_lock, flags); +} +  static void igc_ptp_disable_tx_timestamp(struct igc_adapter *adapter)  {  	struct igc_hw *hw = &adapter->hw; +	int i; + +	/* Clear the flags first to avoid new packets to be enqueued +	 * for TX timestamping. +	 */ +	for (i = 0; i < adapter->num_tx_queues; i++) { +		struct igc_ring *tx_ring = adapter->tx_ring[i]; + +		clear_bit(IGC_RING_FLAG_TX_HWTSTAMP, &tx_ring->flags); +	} + +	/* Now we can clean the pending TX timestamp requests. */ +	igc_ptp_clear_tx_tstamp(adapter);  	wr32(IGC_TSYNCTXCTL, 0);  } @@ -546,12 +571,23 @@ static void igc_ptp_disable_tx_timestamp(struct igc_adapter *adapter)  static void igc_ptp_enable_tx_timestamp(struct igc_adapter *adapter)  {  	struct igc_hw *hw = &adapter->hw; +	int i;  	wr32(IGC_TSYNCTXCTL, IGC_TSYNCTXCTL_ENABLED | IGC_TSYNCTXCTL_TXSYNSIG);  	/* Read TXSTMP registers to discard any timestamp previously stored. */  	rd32(IGC_TXSTMPL);  	rd32(IGC_TXSTMPH); + +	/* The hardware is ready to accept TX timestamp requests, +	 * notify the transmit path. +	 */ +	for (i = 0; i < adapter->num_tx_queues; i++) { +		struct igc_ring *tx_ring = adapter->tx_ring[i]; + +		set_bit(IGC_RING_FLAG_TX_HWTSTAMP, &tx_ring->flags); +	} +  }  /** @@ -603,6 +639,7 @@ static int igc_ptp_set_timestamp_mode(struct igc_adapter *adapter,  	return 0;  } +/* Requires adapter->ptp_tx_lock held by caller. */  static void igc_ptp_tx_timeout(struct igc_adapter *adapter)  {  	struct igc_hw *hw = &adapter->hw; @@ -610,7 +647,6 @@ static void igc_ptp_tx_timeout(struct igc_adapter *adapter)  	dev_kfree_skb_any(adapter->ptp_tx_skb);  	adapter->ptp_tx_skb = NULL;  	adapter->tx_hwtstamp_timeouts++; -	clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state);  	/* Clear the tx valid bit in TSYNCTXCTL register to enable interrupt. */  	rd32(IGC_TXSTMPH);  	netdev_warn(adapter->netdev, "Tx timestamp timeout\n"); @@ -618,20 +654,20 @@ static void igc_ptp_tx_timeout(struct igc_adapter *adapter)  void igc_ptp_tx_hang(struct igc_adapter *adapter)  { -	bool timeout = time_is_before_jiffies(adapter->ptp_tx_start + -					      IGC_PTP_TX_TIMEOUT); +	unsigned long flags; -	if (!test_bit(__IGC_PTP_TX_IN_PROGRESS, &adapter->state)) -		return; +	spin_lock_irqsave(&adapter->ptp_tx_lock, flags); -	/* If we haven't received a timestamp within the timeout, it is -	 * reasonable to assume that it will never occur, so we can unlock the -	 * timestamp bit when this occurs. -	 */ -	if (timeout) { -		cancel_work_sync(&adapter->ptp_tx_work); -		igc_ptp_tx_timeout(adapter); -	} +	if (!adapter->ptp_tx_skb) +		goto unlock; + +	if (time_is_after_jiffies(adapter->ptp_tx_start + IGC_PTP_TX_TIMEOUT)) +		goto unlock; + +	igc_ptp_tx_timeout(adapter); + +unlock: +	spin_unlock_irqrestore(&adapter->ptp_tx_lock, flags);  }  /** @@ -641,20 +677,57 @@ void igc_ptp_tx_hang(struct igc_adapter *adapter)   * 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. + * + * Context: Expects adapter->ptp_tx_lock to be held by caller.   */  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; +	u32 tsynctxctl;  	int adjust = 0;  	u64 regval;  	if (WARN_ON_ONCE(!skb))  		return; -	regval = rd32(IGC_TXSTMPL); -	regval |= (u64)rd32(IGC_TXSTMPH) << 32; +	tsynctxctl = rd32(IGC_TSYNCTXCTL); +	tsynctxctl &= IGC_TSYNCTXCTL_TXTT_0; +	if (tsynctxctl) { +		regval = rd32(IGC_TXSTMPL); +		regval |= (u64)rd32(IGC_TXSTMPH) << 32; +	} else { +		/* There's a bug in the hardware that could cause +		 * missing interrupts for TX timestamping. The issue +		 * is that for new interrupts to be triggered, the +		 * IGC_TXSTMPH_0 register must be read. +		 * +		 * To avoid discarding a valid timestamp that just +		 * happened at the "wrong" time, we need to confirm +		 * that there was no timestamp captured, we do that by +		 * assuming that no two timestamps in sequence have +		 * the same nanosecond value. +		 * +		 * So, we read the "low" register, read the "high" +		 * register (to latch a new timestamp) and read the +		 * "low" register again, if "old" and "new" versions +		 * of the "low" register are different, a valid +		 * timestamp was captured, we can read the "high" +		 * register again. +		 */ +		u32 txstmpl_old, txstmpl_new; + +		txstmpl_old = rd32(IGC_TXSTMPL); +		rd32(IGC_TXSTMPH); +		txstmpl_new = rd32(IGC_TXSTMPL); + +		if (txstmpl_old == txstmpl_new) +			return; + +		regval = txstmpl_new; +		regval |= (u64)rd32(IGC_TXSTMPH) << 32; +	}  	if (igc_ptp_systim_to_hwtstamp(adapter, &shhwtstamps, regval))  		return; @@ -676,13 +749,7 @@ static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter)  	shhwtstamps.hwtstamp =  		ktime_add_ns(shhwtstamps.hwtstamp, adjust); -	/* 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); @@ -690,27 +757,25 @@ static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter)  }  /** - * igc_ptp_tx_work - * @work: pointer to work struct + * igc_ptp_tx_tstamp_event + * @adapter: board private structure   * - * This work function polls the TSYNCTXCTL valid bit to determine when a - * timestamp has been taken for the current stored skb. + * Called when a TX timestamp interrupt happens to retrieve the + * timestamp and send it up to the socket.   */ -static void igc_ptp_tx_work(struct work_struct *work) +void igc_ptp_tx_tstamp_event(struct igc_adapter *adapter)  { -	struct igc_adapter *adapter = container_of(work, struct igc_adapter, -						   ptp_tx_work); -	struct igc_hw *hw = &adapter->hw; -	u32 tsynctxctl; +	unsigned long flags; -	if (!test_bit(__IGC_PTP_TX_IN_PROGRESS, &adapter->state)) -		return; +	spin_lock_irqsave(&adapter->ptp_tx_lock, flags); -	tsynctxctl = rd32(IGC_TSYNCTXCTL); -	if (WARN_ON_ONCE(!(tsynctxctl & IGC_TSYNCTXCTL_TXTT_0))) -		return; +	if (!adapter->ptp_tx_skb) +		goto unlock;  	igc_ptp_tx_hwtstamp(adapter); + +unlock: +	spin_unlock_irqrestore(&adapter->ptp_tx_lock, flags);  }  /** @@ -959,8 +1024,8 @@ void igc_ptp_init(struct igc_adapter *adapter)  		return;  	} +	spin_lock_init(&adapter->ptp_tx_lock);  	spin_lock_init(&adapter->tmreg_lock); -	INIT_WORK(&adapter->ptp_tx_work, igc_ptp_tx_work);  	adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;  	adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF; @@ -1020,10 +1085,7 @@ void igc_ptp_suspend(struct igc_adapter *adapter)  	if (!(adapter->ptp_flags & IGC_PTP_ENABLED))  		return; -	cancel_work_sync(&adapter->ptp_tx_work); -	dev_kfree_skb_any(adapter->ptp_tx_skb); -	adapter->ptp_tx_skb = NULL; -	clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state); +	igc_ptp_clear_tx_tstamp(adapter);  	if (pci_device_is_present(adapter->pdev)) {  		igc_ptp_time_save(adapter); |