Skip to content

Commit 9c50e2b

Browse files
vcgomesanguy11
authored andcommitted
igc: Fix race condition in PTP tx code
Currently, the igc driver supports timestamping only one tx packet at a time. During the transmission flow, the skb that requires hardware timestamping is saved in adapter->ptp_tx_skb. Once hardware has the timestamp, an interrupt is delivered, and adapter->ptp_tx_work is scheduled. In igc_ptp_tx_work(), we read the timestamp register, update adapter->ptp_tx_skb, and notify the network stack. While the thread executing the transmission flow (the user process running in kernel mode) and the thread executing ptp_tx_work don't access adapter->ptp_tx_skb concurrently, there are two other places where adapter->ptp_tx_skb is accessed: igc_ptp_tx_hang() and igc_ptp_suspend(). igc_ptp_tx_hang() is executed by the adapter->watchdog_task worker thread which runs periodically so it is possible we have two threads accessing ptp_tx_skb at the same time. Consider the following scenario: right after __IGC_PTP_TX_IN_PROGRESS is set in igc_xmit_frame_ring(), igc_ptp_tx_hang() is executed. Since adapter->ptp_tx_start hasn't been written yet, this is considered a timeout and adapter->ptp_tx_skb is cleaned up. This patch fixes the issue described above by adding the ptp_tx_lock to protect access to ptp_tx_skb and ptp_tx_start fields from igc_adapter. Since igc_xmit_frame_ring() called in atomic context by the networking stack, ptp_tx_lock is defined as a spinlock, and the irq safe variants of lock/unlock are used. With the introduction of the ptp_tx_lock, the __IGC_PTP_TX_IN_PROGRESS flag doesn't provide much of a use anymore so this patch gets rid of it. Fixes: 2c344ae ("igc: Add support for TX timestamping") Signed-off-by: Andre Guedes <andre.guedes@intel.com> Signed-off-by: Vinicius Costa Gomes <vinicius.gomes@intel.com> Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> Tested-by: Naama Meir <naamax.meir@linux.intel.com> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
1 parent 2ba7e7e commit 9c50e2b

3 files changed

Lines changed: 41 additions & 30 deletions

File tree

drivers/net/ethernet/intel/igc/igc.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ struct igc_adapter {
229229
struct ptp_clock *ptp_clock;
230230
struct ptp_clock_info ptp_caps;
231231
struct work_struct ptp_tx_work;
232+
/* Access to ptp_tx_skb and ptp_tx_start are protected by the
233+
* ptp_tx_lock.
234+
*/
235+
spinlock_t ptp_tx_lock;
232236
struct sk_buff *ptp_tx_skb;
233237
struct hwtstamp_config tstamp_config;
234238
unsigned long ptp_tx_start;
@@ -401,7 +405,6 @@ enum igc_state_t {
401405
__IGC_TESTING,
402406
__IGC_RESETTING,
403407
__IGC_DOWN,
404-
__IGC_PTP_TX_IN_PROGRESS,
405408
};
406409

407410
enum igc_tx_flags {

drivers/net/ethernet/intel/igc/igc_main.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,9 +1590,10 @@ static netdev_tx_t igc_xmit_frame_ring(struct sk_buff *skb,
15901590
* the other timer registers before skipping the
15911591
* timestamping request.
15921592
*/
1593-
if (adapter->tstamp_config.tx_type == HWTSTAMP_TX_ON &&
1594-
!test_and_set_bit_lock(__IGC_PTP_TX_IN_PROGRESS,
1595-
&adapter->state)) {
1593+
unsigned long flags;
1594+
1595+
spin_lock_irqsave(&adapter->ptp_tx_lock, flags);
1596+
if (adapter->tstamp_config.tx_type == HWTSTAMP_TX_ON && !adapter->ptp_tx_skb) {
15961597
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
15971598
tx_flags |= IGC_TX_FLAGS_TSTAMP;
15981599

@@ -1601,6 +1602,8 @@ static netdev_tx_t igc_xmit_frame_ring(struct sk_buff *skb,
16011602
} else {
16021603
adapter->tx_hwtstamp_skipped++;
16031604
}
1605+
1606+
spin_unlock_irqrestore(&adapter->ptp_tx_lock, flags);
16041607
}
16051608

16061609
if (skb_vlan_tag_present(skb)) {

drivers/net/ethernet/intel/igc/igc_ptp.c

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -603,35 +603,35 @@ static int igc_ptp_set_timestamp_mode(struct igc_adapter *adapter,
603603
return 0;
604604
}
605605

606+
/* Requires adapter->ptp_tx_lock held by caller. */
606607
static void igc_ptp_tx_timeout(struct igc_adapter *adapter)
607608
{
608609
struct igc_hw *hw = &adapter->hw;
609610

610611
dev_kfree_skb_any(adapter->ptp_tx_skb);
611612
adapter->ptp_tx_skb = NULL;
612613
adapter->tx_hwtstamp_timeouts++;
613-
clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state);
614614
/* Clear the tx valid bit in TSYNCTXCTL register to enable interrupt. */
615615
rd32(IGC_TXSTMPH);
616616
netdev_warn(adapter->netdev, "Tx timestamp timeout\n");
617617
}
618618

619619
void igc_ptp_tx_hang(struct igc_adapter *adapter)
620620
{
621-
bool timeout = time_is_before_jiffies(adapter->ptp_tx_start +
622-
IGC_PTP_TX_TIMEOUT);
621+
unsigned long flags;
623622

624-
if (!test_bit(__IGC_PTP_TX_IN_PROGRESS, &adapter->state))
625-
return;
623+
spin_lock_irqsave(&adapter->ptp_tx_lock, flags);
626624

627-
/* If we haven't received a timestamp within the timeout, it is
628-
* reasonable to assume that it will never occur, so we can unlock the
629-
* timestamp bit when this occurs.
630-
*/
631-
if (timeout) {
632-
cancel_work_sync(&adapter->ptp_tx_work);
633-
igc_ptp_tx_timeout(adapter);
634-
}
625+
if (!adapter->ptp_tx_skb)
626+
goto unlock;
627+
628+
if (time_is_after_jiffies(adapter->ptp_tx_start + IGC_PTP_TX_TIMEOUT))
629+
goto unlock;
630+
631+
igc_ptp_tx_timeout(adapter);
632+
633+
unlock:
634+
spin_unlock_irqrestore(&adapter->ptp_tx_lock, flags);
635635
}
636636

637637
/**
@@ -641,6 +641,8 @@ void igc_ptp_tx_hang(struct igc_adapter *adapter)
641641
* If we were asked to do hardware stamping and such a time stamp is
642642
* available, then it must have been for this skb here because we only
643643
* allow only one such packet into the queue.
644+
*
645+
* Context: Expects adapter->ptp_tx_lock to be held by caller.
644646
*/
645647
static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter)
646648
{
@@ -676,13 +678,7 @@ static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter)
676678
shhwtstamps.hwtstamp =
677679
ktime_add_ns(shhwtstamps.hwtstamp, adjust);
678680

679-
/* Clear the lock early before calling skb_tstamp_tx so that
680-
* applications are not woken up before the lock bit is clear. We use
681-
* a copy of the skb pointer to ensure other threads can't change it
682-
* while we're notifying the stack.
683-
*/
684681
adapter->ptp_tx_skb = NULL;
685-
clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state);
686682

687683
/* Notify the stack and free the skb after we've unlocked */
688684
skb_tstamp_tx(skb, &shhwtstamps);
@@ -693,24 +689,33 @@ static void igc_ptp_tx_hwtstamp(struct igc_adapter *adapter)
693689
* igc_ptp_tx_work
694690
* @work: pointer to work struct
695691
*
696-
* This work function polls the TSYNCTXCTL valid bit to determine when a
697-
* timestamp has been taken for the current stored skb.
692+
* This work function checks the TSYNCTXCTL valid bit to determine when
693+
* a timestamp has been taken for the current stored skb.
698694
*/
699695
static void igc_ptp_tx_work(struct work_struct *work)
700696
{
701697
struct igc_adapter *adapter = container_of(work, struct igc_adapter,
702698
ptp_tx_work);
703699
struct igc_hw *hw = &adapter->hw;
700+
unsigned long flags;
704701
u32 tsynctxctl;
705702

706-
if (!test_bit(__IGC_PTP_TX_IN_PROGRESS, &adapter->state))
707-
return;
703+
spin_lock_irqsave(&adapter->ptp_tx_lock, flags);
704+
705+
if (!adapter->ptp_tx_skb)
706+
goto unlock;
708707

709708
tsynctxctl = rd32(IGC_TSYNCTXCTL);
710-
if (WARN_ON_ONCE(!(tsynctxctl & IGC_TSYNCTXCTL_TXTT_0)))
711-
return;
709+
tsynctxctl &= IGC_TSYNCTXCTL_TXTT_0;
710+
if (!tsynctxctl) {
711+
WARN_ONCE(1, "Received a TSTAMP interrupt but no TSTAMP is ready.\n");
712+
goto unlock;
713+
}
712714

713715
igc_ptp_tx_hwtstamp(adapter);
716+
717+
unlock:
718+
spin_unlock_irqrestore(&adapter->ptp_tx_lock, flags);
714719
}
715720

716721
/**
@@ -959,6 +964,7 @@ void igc_ptp_init(struct igc_adapter *adapter)
959964
return;
960965
}
961966

967+
spin_lock_init(&adapter->ptp_tx_lock);
962968
spin_lock_init(&adapter->tmreg_lock);
963969
INIT_WORK(&adapter->ptp_tx_work, igc_ptp_tx_work);
964970

@@ -1023,7 +1029,6 @@ void igc_ptp_suspend(struct igc_adapter *adapter)
10231029
cancel_work_sync(&adapter->ptp_tx_work);
10241030
dev_kfree_skb_any(adapter->ptp_tx_skb);
10251031
adapter->ptp_tx_skb = NULL;
1026-
clear_bit_unlock(__IGC_PTP_TX_IN_PROGRESS, &adapter->state);
10271032

10281033
if (pci_device_is_present(adapter->pdev)) {
10291034
igc_ptp_time_save(adapter);

0 commit comments

Comments
 (0)