net: dsa: sja1105: Add logic for TX timestamping
authorVladimir Oltean <olteanv@gmail.com>
Sat, 8 Jun 2019 12:04:35 +0000 (15:04 +0300)
committerDavid S. Miller <davem@davemloft.net>
Sat, 8 Jun 2019 22:20:40 +0000 (15:20 -0700)
On TX, timestamping is performed synchronously from the
port_deferred_xmit worker thread.
In management routes, the switch is requested to take egress timestamps
(again partial), which are reconstructed and appended to a clone of the
skb that was just sent.  The cloning is done by DSA and we retrieve the
pointer from the structure that DSA keeps in skb->cb.
Then these clones are enqueued to the socket's error queue for
application-level processing.

Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/sja1105/sja1105.h
drivers/net/dsa/sja1105/sja1105_main.c
drivers/net/dsa/sja1105/sja1105_ptp.c
drivers/net/dsa/sja1105/sja1105_ptp.h
drivers/net/dsa/sja1105/sja1105_spi.c
include/linux/dsa/sja1105.h

index 3c6296203c21ec6dd370a109bc4f48713d1eca87..5a4f83a3417be7c1f5ff85584c0f3705a5f0ef95 100644 (file)
@@ -33,6 +33,7 @@ struct sja1105_regs {
        u64 ptpclk;
        u64 ptpclkrate;
        u64 ptptsclk;
+       u64 ptpegr_ts[SJA1105_NUM_PORTS];
        u64 pad_mii_tx[SJA1105_NUM_PORTS];
        u64 cgu_idiv[SJA1105_NUM_PORTS];
        u64 rgmii_pad_mii_tx[SJA1105_NUM_PORTS];
@@ -56,6 +57,15 @@ struct sja1105_info {
         * switch core and device_id)
         */
        u64 part_no;
+       /* E/T and P/Q/R/S have partial timestamps of different sizes.
+        * They must be reconstructed on both families anyway to get the full
+        * 64-bit values back.
+        */
+       int ptp_ts_bits;
+       /* Also SPI commands are of different sizes to retrieve
+        * the egress timestamps.
+        */
+       int ptpegr_ts_bytes;
        const struct sja1105_dynamic_table_ops *dyn_ops;
        const struct sja1105_table_ops *static_ops;
        const struct sja1105_regs *regs;
index f897fdb12930068f8b402493a4f01ec1888cb3b0..121ceccd8107b991d5bdf461fb7b8f4dbca6329a 100644 (file)
@@ -1565,7 +1565,7 @@ static int sja1105_setup(struct dsa_switch *ds)
 }
 
 static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
-                            struct sk_buff *skb)
+                            struct sk_buff *skb, bool takets)
 {
        struct sja1105_mgmt_entry mgmt_route = {0};
        struct sja1105_private *priv = ds->priv;
@@ -1578,6 +1578,8 @@ static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
        mgmt_route.macaddr = ether_addr_to_u64(hdr->h_dest);
        mgmt_route.destports = BIT(port);
        mgmt_route.enfport = 1;
+       mgmt_route.tsreg = 0;
+       mgmt_route.takets = takets;
 
        rc = sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE,
                                          slot, &mgmt_route, true);
@@ -1629,7 +1631,11 @@ static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port,
 {
        struct sja1105_private *priv = ds->priv;
        struct sja1105_port *sp = &priv->ports[port];
+       struct skb_shared_hwtstamps shwt = {0};
        int slot = sp->mgmt_slot;
+       struct sk_buff *clone;
+       u64 now, ts;
+       int rc;
 
        /* The tragic fact about the switch having 4x2 slots for installing
         * management routes is that all of them except one are actually
@@ -1647,8 +1653,36 @@ static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port,
         */
        mutex_lock(&priv->mgmt_lock);
 
-       sja1105_mgmt_xmit(ds, port, slot, skb);
+       /* The clone, if there, was made by dsa_skb_tx_timestamp */
+       clone = DSA_SKB_CB(skb)->clone;
+
+       sja1105_mgmt_xmit(ds, port, slot, skb, !!clone);
+
+       if (!clone)
+               goto out;
+
+       skb_shinfo(clone)->tx_flags |= SKBTX_IN_PROGRESS;
+
+       mutex_lock(&priv->ptp_lock);
+
+       now = priv->tstamp_cc.read(&priv->tstamp_cc);
+
+       rc = sja1105_ptpegr_ts_poll(priv, slot, &ts);
+       if (rc < 0) {
+               dev_err(ds->dev, "xmit: timed out polling for tstamp\n");
+               kfree_skb(clone);
+               goto out_unlock_ptp;
+       }
+
+       ts = sja1105_tstamp_reconstruct(priv, now, ts);
+       ts = timecounter_cyc2time(&priv->tstamp_tc, ts);
 
+       shwt.hwtstamp = ns_to_ktime(ts);
+       skb_complete_tx_timestamp(clone, &shwt);
+
+out_unlock_ptp:
+       mutex_unlock(&priv->ptp_lock);
+out:
        mutex_unlock(&priv->mgmt_lock);
        return NETDEV_TX_OK;
 }
@@ -1677,6 +1711,22 @@ static int sja1105_set_ageing_time(struct dsa_switch *ds,
        return sja1105_static_config_reload(priv);
 }
 
+/* Called from dsa_skb_tx_timestamp. This callback is just to make DSA clone
+ * the skb and have it available in DSA_SKB_CB in the .port_deferred_xmit
+ * callback, where we will timestamp it synchronously.
+ */
+bool sja1105_port_txtstamp(struct dsa_switch *ds, int port,
+                          struct sk_buff *skb, unsigned int type)
+{
+       struct sja1105_private *priv = ds->priv;
+       struct sja1105_port *sp = &priv->ports[port];
+
+       if (!sp->hwts_tx_en)
+               return false;
+
+       return true;
+}
+
 static const struct dsa_switch_ops sja1105_switch_ops = {
        .get_tag_protocol       = sja1105_get_tag_protocol,
        .setup                  = sja1105_setup,
@@ -1701,6 +1751,7 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
        .port_mdb_add           = sja1105_mdb_add,
        .port_mdb_del           = sja1105_mdb_del,
        .port_deferred_xmit     = sja1105_port_deferred_xmit,
+       .port_txtstamp          = sja1105_port_txtstamp,
 };
 
 static int sja1105_check_device_id(struct sja1105_private *priv)
index 47313a6ec932d9e9255b84be5060792bacac1bce..01ecc8fb1b30b85410eff3c03d1200af6e12c0ca 100644 (file)
@@ -113,6 +113,112 @@ int sja1105pqrs_ptp_cmd(const void *ctx, const void *data)
 }
 EXPORT_SYMBOL_GPL(sja1105pqrs_ptp_cmd);
 
+/* The switch returns partial timestamps (24 bits for SJA1105 E/T, which wrap
+ * around in 0.135 seconds, and 32 bits for P/Q/R/S, wrapping around in 34.35
+ * seconds).
+ *
+ * This receives the RX or TX MAC timestamps, provided by hardware as
+ * the lower bits of the cycle counter, sampled at the time the timestamp was
+ * collected.
+ *
+ * To reconstruct into a full 64-bit-wide timestamp, the cycle counter is
+ * read and the high-order bits are filled in.
+ *
+ * Must be called within one wraparound period of the partial timestamp since
+ * it was generated by the MAC.
+ */
+u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv, u64 now,
+                              u64 ts_partial)
+{
+       u64 partial_tstamp_mask = CYCLECOUNTER_MASK(priv->info->ptp_ts_bits);
+       u64 ts_reconstructed;
+
+       ts_reconstructed = (now & ~partial_tstamp_mask) | ts_partial;
+
+       /* Check lower bits of current cycle counter against the timestamp.
+        * If the current cycle counter is lower than the partial timestamp,
+        * then wraparound surely occurred and must be accounted for.
+        */
+       if ((now & partial_tstamp_mask) <= ts_partial)
+               ts_reconstructed -= (partial_tstamp_mask + 1);
+
+       return ts_reconstructed;
+}
+EXPORT_SYMBOL_GPL(sja1105_tstamp_reconstruct);
+
+/* Reads the SPI interface for an egress timestamp generated by the switch
+ * for frames sent using management routes.
+ *
+ * SJA1105 E/T layout of the 4-byte SPI payload:
+ *
+ * 31    23    15    7     0
+ * |     |     |     |     |
+ * +-----+-----+-----+     ^
+ *          ^              |
+ *          |              |
+ *  24-bit timestamp   Update bit
+ *
+ *
+ * SJA1105 P/Q/R/S layout of the 8-byte SPI payload:
+ *
+ * 31    23    15    7     0     63    55    47    39    32
+ * |     |     |     |     |     |     |     |     |     |
+ *                         ^     +-----+-----+-----+-----+
+ *                         |                 ^
+ *                         |                 |
+ *                    Update bit    32-bit timestamp
+ *
+ * Notice that the update bit is in the same place.
+ * To have common code for E/T and P/Q/R/S for reading the timestamp,
+ * we need to juggle with the offset and the bit indices.
+ */
+int sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts)
+{
+       const struct sja1105_regs *regs = priv->info->regs;
+       int tstamp_bit_start, tstamp_bit_end;
+       int timeout = 10;
+       u8 packed_buf[8];
+       u64 update;
+       int rc;
+
+       do {
+               rc = sja1105_spi_send_packed_buf(priv, SPI_READ,
+                                                regs->ptpegr_ts[port],
+                                                packed_buf,
+                                                priv->info->ptpegr_ts_bytes);
+               if (rc < 0)
+                       return rc;
+
+               sja1105_unpack(packed_buf, &update, 0, 0,
+                              priv->info->ptpegr_ts_bytes);
+               if (update)
+                       break;
+
+               usleep_range(10, 50);
+       } while (--timeout);
+
+       if (!timeout)
+               return -ETIMEDOUT;
+
+       /* Point the end bit to the second 32-bit word on P/Q/R/S,
+        * no-op on E/T.
+        */
+       tstamp_bit_end = (priv->info->ptpegr_ts_bytes - 4) * 8;
+       /* Shift the 24-bit timestamp on E/T to be collected from 31:8.
+        * No-op on P/Q/R/S.
+        */
+       tstamp_bit_end += 32 - priv->info->ptp_ts_bits;
+       tstamp_bit_start = tstamp_bit_end + priv->info->ptp_ts_bits - 1;
+
+       *ts = 0;
+
+       sja1105_unpack(packed_buf, ts, tstamp_bit_start, tstamp_bit_end,
+                      priv->info->ptpegr_ts_bytes);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(sja1105_ptpegr_ts_poll);
+
 int sja1105_ptp_reset(struct sja1105_private *priv)
 {
        struct dsa_switch *ds = priv->ds;
index 137ffbb0a233a3132e12f2454ec88c8d6bfe7d30..af456b0a4d27696746586b5fdd16654d17c2323e 100644 (file)
@@ -10,6 +10,8 @@ int sja1105_ptp_clock_register(struct sja1105_private *priv);
 
 void sja1105_ptp_clock_unregister(struct sja1105_private *priv);
 
+int sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts);
+
 int sja1105et_ptp_cmd(const void *ctx, const void *data);
 
 int sja1105pqrs_ptp_cmd(const void *ctx, const void *data);
@@ -17,6 +19,9 @@ int sja1105pqrs_ptp_cmd(const void *ctx, const void *data);
 int sja1105_get_ts_info(struct dsa_switch *ds, int port,
                        struct ethtool_ts_info *ts);
 
+u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv, u64 now,
+                              u64 ts_partial);
+
 int sja1105_ptp_reset(struct sja1105_private *priv);
 
 #else
@@ -31,6 +36,18 @@ static inline void sja1105_ptp_clock_unregister(struct sja1105_private *priv)
        return;
 }
 
+static inline int
+sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts)
+{
+       return 0;
+}
+
+static inline u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv,
+                                            u64 now, u64 ts_partial)
+{
+       return 0;
+}
+
 static inline int sja1105_ptp_reset(struct sja1105_private *priv)
 {
        return 0;
index a0d08e6c22ff4f4e251f00f5beedfb5d1ec8c4a9..d729a0f0b28effb0897a1af810d870a80ece3a3a 100644 (file)
@@ -514,6 +514,7 @@ static struct sja1105_regs sja1105et_regs = {
        .rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032},
        .rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031},
        .rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034},
+       .ptpegr_ts = {0xC0, 0xC2, 0xC4, 0xC6, 0xC8},
        .ptp_control = 0x17,
        .ptpclk = 0x18, /* Spans 0x18 to 0x19 */
        .ptpclkrate = 0x1A,
@@ -544,6 +545,7 @@ static struct sja1105_regs sja1105pqrs_regs = {
        .rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D},
        .rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F},
        .qlevel = {0x604, 0x614, 0x624, 0x634, 0x644},
+       .ptpegr_ts = {0xC0, 0xC4, 0xC8, 0xCC, 0xD0},
        .ptp_control = 0x18,
        .ptpclk = 0x19,
        .ptpclkrate = 0x1B,
@@ -555,6 +557,8 @@ struct sja1105_info sja1105e_info = {
        .part_no                = SJA1105ET_PART_NO,
        .static_ops             = sja1105e_table_ops,
        .dyn_ops                = sja1105et_dyn_ops,
+       .ptp_ts_bits            = 24,
+       .ptpegr_ts_bytes        = 4,
        .reset_cmd              = sja1105et_reset_cmd,
        .fdb_add_cmd            = sja1105et_fdb_add,
        .fdb_del_cmd            = sja1105et_fdb_del,
@@ -567,6 +571,8 @@ struct sja1105_info sja1105t_info = {
        .part_no                = SJA1105ET_PART_NO,
        .static_ops             = sja1105t_table_ops,
        .dyn_ops                = sja1105et_dyn_ops,
+       .ptp_ts_bits            = 24,
+       .ptpegr_ts_bytes        = 4,
        .reset_cmd              = sja1105et_reset_cmd,
        .fdb_add_cmd            = sja1105et_fdb_add,
        .fdb_del_cmd            = sja1105et_fdb_del,
@@ -579,6 +585,8 @@ struct sja1105_info sja1105p_info = {
        .part_no                = SJA1105P_PART_NO,
        .static_ops             = sja1105p_table_ops,
        .dyn_ops                = sja1105pqrs_dyn_ops,
+       .ptp_ts_bits            = 32,
+       .ptpegr_ts_bytes        = 8,
        .reset_cmd              = sja1105pqrs_reset_cmd,
        .fdb_add_cmd            = sja1105pqrs_fdb_add,
        .fdb_del_cmd            = sja1105pqrs_fdb_del,
@@ -591,6 +599,8 @@ struct sja1105_info sja1105q_info = {
        .part_no                = SJA1105Q_PART_NO,
        .static_ops             = sja1105q_table_ops,
        .dyn_ops                = sja1105pqrs_dyn_ops,
+       .ptp_ts_bits            = 32,
+       .ptpegr_ts_bytes        = 8,
        .reset_cmd              = sja1105pqrs_reset_cmd,
        .fdb_add_cmd            = sja1105pqrs_fdb_add,
        .fdb_del_cmd            = sja1105pqrs_fdb_del,
@@ -603,6 +613,8 @@ struct sja1105_info sja1105r_info = {
        .part_no                = SJA1105R_PART_NO,
        .static_ops             = sja1105r_table_ops,
        .dyn_ops                = sja1105pqrs_dyn_ops,
+       .ptp_ts_bits            = 32,
+       .ptpegr_ts_bytes        = 8,
        .reset_cmd              = sja1105pqrs_reset_cmd,
        .fdb_add_cmd            = sja1105pqrs_fdb_add,
        .fdb_del_cmd            = sja1105pqrs_fdb_del,
@@ -616,6 +628,8 @@ struct sja1105_info sja1105s_info = {
        .static_ops             = sja1105s_table_ops,
        .dyn_ops                = sja1105pqrs_dyn_ops,
        .regs                   = &sja1105pqrs_regs,
+       .ptp_ts_bits            = 32,
+       .ptpegr_ts_bytes        = 8,
        .reset_cmd              = sja1105pqrs_reset_cmd,
        .fdb_add_cmd            = sja1105pqrs_fdb_add,
        .fdb_del_cmd            = sja1105pqrs_fdb_del,
index e46e18c47d41c221bf9ca75a965e8f3c6a72e852..5a956f335022aabcf7e86504bc0db01170c41267 100644 (file)
@@ -22,6 +22,7 @@
 
 struct sja1105_port {
        struct dsa_port *dp;
+       bool hwts_tx_en;
        int mgmt_slot;
 };