ixgbe: fix race condition with PTP_TX_IN_PROGRESS bits
authorJacob Keller <jacob.e.keller@intel.com>
Wed, 3 May 2017 17:28:53 +0000 (10:28 -0700)
committerJeff Kirsher <jeffrey.t.kirsher@intel.com>
Tue, 13 Jun 2017 23:11:47 +0000 (16:11 -0700)
Hardware related to the ixgbe driver is limited to handling a single Tx
timestamp request at a time. Thus, the driver ignores requests for Tx
timestamp while waiting for the current request to finish. It uses
a state bit lock which enforces that only one timestamp request is
honored at a time.

Unfortunately this suffers from a simple race condition. The bit lock is
not cleared until after skb_tstamp_tx() is called notifying applications
of a new Tx timestamp. Even a well behaved application sending only one
packet at a time and waiting for a response can wake up and send a new
packet before the bit lock is cleared. This results in needlessly
dropping some Tx timestamp requests.

We can fix this by unlocking the state bit as soon as we read the
Timestamp register, as this is the first point at which it is safe to
unlock.

To avoid issues with the skb pointer, we'll use a copy of the pointer
and set the global variable in the driver structure to NULL first. This
ensures that the next timestamp request does not modify our local copy
of the skb pointer.

This ensures that well behaved applications do not accidentally race
with the unlock bit. Obviously an application which sends multiple Tx
timestamp requests at once will still only timestamp one packet at
a time. Unfortunately there is nothing we can do about this.

Reported-by: David Mirabito <davidm@metamako.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Andrew Bowers <andrewx.bowers@intel.com>
Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c

index d44c728fdc0be1b598b4f74a9f76b212c71ff3d9..4a2000bfd4aec39e49b777d7128e8366e63a1301 100644 (file)
@@ -672,17 +672,26 @@ static void ixgbe_ptp_clear_tx_timestamp(struct ixgbe_adapter *adapter)
  */
 static void ixgbe_ptp_tx_hwtstamp(struct ixgbe_adapter *adapter)
 {
+       struct sk_buff *skb = adapter->ptp_tx_skb;
        struct ixgbe_hw *hw = &adapter->hw;
        struct skb_shared_hwtstamps shhwtstamps;
        u64 regval = 0;
 
        regval |= (u64)IXGBE_READ_REG(hw, IXGBE_TXSTMPL);
        regval |= (u64)IXGBE_READ_REG(hw, IXGBE_TXSTMPH) << 32;
-
        ixgbe_ptp_convert_to_hwtstamp(adapter, &shhwtstamps, regval);
-       skb_tstamp_tx(adapter->ptp_tx_skb, &shhwtstamps);
 
-       ixgbe_ptp_clear_tx_timestamp(adapter);
+       /* Handle cleanup of the ptp_tx_skb ourselves, and unlock the state
+        * bit prior to notifying the stack via skb_tstamp_tx(). This prevents
+        * well behaved applications from attempting to timestamp again prior
+        * to the lock bit being clear.
+        */
+       adapter->ptp_tx_skb = NULL;
+       clear_bit_unlock(__IXGBE_PTP_TX_IN_PROGRESS, &adapter->state);
+
+       /* Notify the stack and then free the skb after we've unlocked */
+       skb_tstamp_tx(skb, &shhwtstamps);
+       dev_kfree_skb_any(skb);
 }
 
 /**