Add timestamping information as provided by ETHTOOL_GET_TS_INFO ioctl
command in GET_INFO reply if ETH_INFO_IM_TSINFO flag is set in the request.

Add constants for counts of HWTSTAMP_TX_* and HWTSTAM_FILTER_* constants
and provide symbolic names for timestamping related values so that they can
be retrieved in GET_STRSET and GET_INFO requests.

Signed-off-by: Michal Kubecek <mkube...@suse.cz>
---
 Documentation/networking/ethtool-netlink.txt |  10 +-
 include/uapi/linux/ethtool.h                 |   6 +
 include/uapi/linux/ethtool_netlink.h         |  14 +-
 include/uapi/linux/net_tstamp.h              |  13 ++
 net/ethtool/common.c                         |  24 ++++
 net/ethtool/common.h                         |   2 +
 net/ethtool/info.c                           | 138 +++++++++++++++++++
 net/ethtool/ioctl.c                          |  20 +--
 net/ethtool/netlink.h                        |   9 ++
 net/ethtool/strset.c                         |  18 +++
 10 files changed, 234 insertions(+), 20 deletions(-)

diff --git a/Documentation/networking/ethtool-netlink.txt 
b/Documentation/networking/ethtool-netlink.txt
index 1e615e111262..c6c7475340e2 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -242,6 +242,11 @@ Kernel response contents:
     ETHA_INFO_PERMADDR         (nested)
         ETHA_PERMADDR_ADDRESS          (binary)        permanent HW address
         ETHA_PERMADDR_TYPE             (u16)           dev->type
+    ETHA_INFO_TSINFO           (nested)        timestamping information
+        ETHA_TSINFO_TIMESTAMPING       (bitset)        supported flags
+        ETHA_TSINFO_PHC_INDEX          (u32)           associated PHC
+        ETHA_TSINFO_TX_TYPES           (bitset)        Tx types
+        ETHA_TSINFO_RX_FILTERS         (bitset)        Rx filters
 
 The meaning of DRVINFO attributes follows the corresponding fields of
 ETHTOOL_GDRVINFO response. Second part with various counts and sizes is
@@ -253,6 +258,9 @@ There is no separate attribute for permanent address length 
as the length can
 be determined from attribute length (nla_len()). ETHA_PERMADDR_TYPE provides
 net_device::type value to give a hint about what kind of address device has.
 
+ETHA_TSINFO_PHC_INDEX can be unsigned as there is no need for special value;
+if no PHC is associated, the attribute is not present.
+
 GET_INFO requests allow dumps.
 
 
@@ -328,7 +336,7 @@ ETHTOOL_SCHANNELS           n/a
 ETHTOOL_SET_DUMP               n/a
 ETHTOOL_GET_DUMP_FLAG          n/a
 ETHTOOL_GET_DUMP_DATA          n/a
-ETHTOOL_GET_TS_INFO            n/a
+ETHTOOL_GET_TS_INFO            ETHNL_CMD_GET_INFO
 ETHTOOL_GMODULEINFO            n/a
 ETHTOOL_GMODULEEEPROM          n/a
 ETHTOOL_GEEE                   n/a
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index 9ea278961d80..1b58637d3a4d 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -563,6 +563,9 @@ struct ethtool_pauseparam {
  * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
  * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
  * @ETH_SS_PHY_TUNABLES: PHY tunable names
+ * @ETH_SS_TSTAMP_SOF: timestamping flag names
+ * @ETH_SS_TSTAMP_TX_TYPE: timestamping Tx type names
+ * @ETH_SS_TSTAMP_RX_FILTER: timestamping Rx filter names
  */
 enum ethtool_stringset {
        ETH_SS_TEST             = 0,
@@ -574,6 +577,9 @@ enum ethtool_stringset {
        ETH_SS_TUNABLES,
        ETH_SS_PHY_STATS,
        ETH_SS_PHY_TUNABLES,
+       ETH_SS_TSTAMP_SOF,
+       ETH_SS_TSTAMP_TX_TYPE,
+       ETH_SS_TSTAMP_RX_FILTER,
 
        ETH_SS_COUNT
 };
diff --git a/include/uapi/linux/ethtool_netlink.h 
b/include/uapi/linux/ethtool_netlink.h
index fb756b6a8592..8ab2b7454e81 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -154,8 +154,9 @@ enum {
 
 #define ETH_INFO_IM_DRVINFO                    0x01
 #define ETH_INFO_IM_PERMADDR                   0x02
+#define ETH_INFO_IM_TSINFO                     0x04
 
-#define ETH_INFO_IM_ALL                                0x03
+#define ETH_INFO_IM_ALL                                0x07
 
 enum {
        ETHA_DRVINFO_UNSPEC,
@@ -177,6 +178,17 @@ enum {
        ETHA_PERMADDR_MAX = (__ETHA_PERMADDR_CNT - 1)
 };
 
+enum {
+       ETHA_TSINFO_UNSPEC,
+       ETHA_TSINFO_TIMESTAMPING,               /* bitset */
+       ETHA_TSINFO_PHC_INDEX,                  /* u32 */
+       ETHA_TSINFO_TX_TYPES,                   /* bitset */
+       ETHA_TSINFO_RX_FILTERS,                 /* bitset */
+
+       __ETHA_TSINFO_CNT,
+       ETHA_TSINFO_MAX = (__ETHA_TSINFO_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/include/uapi/linux/net_tstamp.h b/include/uapi/linux/net_tstamp.h
index e5b39721c6e4..e5b0472c4416 100644
--- a/include/uapi/linux/net_tstamp.h
+++ b/include/uapi/linux/net_tstamp.h
@@ -30,6 +30,9 @@ enum {
        SOF_TIMESTAMPING_OPT_STATS = (1<<12),
        SOF_TIMESTAMPING_OPT_PKTINFO = (1<<13),
        SOF_TIMESTAMPING_OPT_TX_SWHW = (1<<14),
+       /* when adding a flag, please update so_timestamping_labels array
+        * in net/ethtool/info.c
+        */
 
        SOF_TIMESTAMPING_LAST = SOF_TIMESTAMPING_OPT_TX_SWHW,
        SOF_TIMESTAMPING_MASK = (SOF_TIMESTAMPING_LAST - 1) |
@@ -90,6 +93,11 @@ enum hwtstamp_tx_types {
         * queue.
         */
        HWTSTAMP_TX_ONESTEP_SYNC,
+       /* when adding a value, please update tstamp_tx_type_labels array
+        * in net/ethtool/info.c
+        */
+
+       HWTSTAMP_TX_LAST = HWTSTAMP_TX_ONESTEP_SYNC
 };
 
 /* possible values for hwtstamp_config->rx_filter */
@@ -132,6 +140,11 @@ enum hwtstamp_rx_filters {
 
        /* NTP, UDP, all versions and packet modes */
        HWTSTAMP_FILTER_NTP_ALL,
+       /* when adding a value, please update tstamp_rx_filter_labels array
+        * in net/ethtool/info.c
+        */
+
+       HWTSTAMP_FILTER_LAST = HWTSTAMP_FILTER_NTP_ALL
 };
 
 /* SCM_TIMESTAMPING_PKTINFO control message */
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index e0bd7c6c5874..4616816861cc 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
 #include <linux/rtnetlink.h>
+#include <linux/phy.h>
+#include <linux/net_tstamp.h>
 #include <net/devlink.h>
 #include "common.h"
 
@@ -135,3 +137,25 @@ int __ethtool_get_drvinfo(struct net_device *dev, struct 
ethtool_drvinfo *info)
 
        return 0;
 }
+
+int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info)
+{
+       const struct ethtool_ops *ops = dev->ethtool_ops;
+       struct phy_device *phydev = dev->phydev;
+       int err = 0;
+
+       memset(info, 0, sizeof(*info));
+       info->cmd = ETHTOOL_GET_TS_INFO;
+
+       if (phydev && phydev->drv && phydev->drv->ts_info) {
+               err = phydev->drv->ts_info(phydev, info);
+       } else if (ops->get_ts_info) {
+               err = ops->get_ts_info(dev, info);
+       } else {
+               info->so_timestamping = SOF_TIMESTAMPING_RX_SOFTWARE |
+                                       SOF_TIMESTAMPING_SOFTWARE;
+               info->phc_index = -1;
+       }
+
+       return err;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index e87e58b3a274..02cbee79da35 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -16,4 +16,6 @@ extern const char
 phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
 int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo 
*info);
+int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info 
*info);
+
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/info.c b/net/ethtool/info.c
index 05dbd87ebc41..838257db1d31 100644
--- a/net/ethtool/info.c
+++ b/net/ethtool/info.c
@@ -1,15 +1,61 @@
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
+#include <linux/net_tstamp.h>
+
 #include "netlink.h"
 #include "common.h"
 #include "bitset.h"
 
+const char *const so_timestamping_labels[] = {
+       "hardware-transmit",            /* SOF_TIMESTAMPING_TX_HARDWARE */
+       "software-transmit",            /* SOF_TIMESTAMPING_TX_SOFTWARE */
+       "hardware-receive",             /* SOF_TIMESTAMPING_RX_HARDWARE */
+       "software-receive",             /* SOF_TIMESTAMPING_RX_SOFTWARE */
+       "software-system-clock",        /* SOF_TIMESTAMPING_SOFTWARE */
+       "hardware-legacy-clock",        /* SOF_TIMESTAMPING_SYS_HARDWARE */
+       "hardware-raw-clock",           /* SOF_TIMESTAMPING_RAW_HARDWARE */
+       "option-id",                    /* SOF_TIMESTAMPING_OPT_ID */
+       "sched-transmit",               /* SOF_TIMESTAMPING_TX_SCHED */
+       "ack-transmit",                 /* SOF_TIMESTAMPING_TX_ACK */
+       "option-cmsg",                  /* SOF_TIMESTAMPING_OPT_CMSG */
+       "option-tsonly",                /* SOF_TIMESTAMPING_OPT_TSONLY */
+       "option-stats",                 /* SOF_TIMESTAMPING_OPT_STATS */
+       "option-pktinfo",               /* SOF_TIMESTAMPING_OPT_PKTINFO */
+       "option-tx-swhw",               /* SOF_TIMESTAMPING_OPT_TX_SWHW */
+};
+
+const char *const tstamp_tx_type_labels[] = {
+       [HWTSTAMP_TX_OFF]               = "off",
+       [HWTSTAMP_TX_ON]                = "on",
+       [HWTSTAMP_TX_ONESTEP_SYNC]      = "one-step-sync",
+};
+
+const char *const tstamp_rx_filter_labels[] = {
+       [HWTSTAMP_FILTER_NONE]                  = "none",
+       [HWTSTAMP_FILTER_ALL]                   = "all",
+       [HWTSTAMP_FILTER_SOME]                  = "some",
+       [HWTSTAMP_FILTER_PTP_V1_L4_EVENT]       = "ptpv1-l4-event",
+       [HWTSTAMP_FILTER_PTP_V1_L4_SYNC]        = "ptpv1-l4-sync",
+       [HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ]   = "ptpv1-l4-delay-req",
+       [HWTSTAMP_FILTER_PTP_V2_L4_EVENT]       = "ptpv2-l4-event",
+       [HWTSTAMP_FILTER_PTP_V2_L4_SYNC]        = "ptpv2-l4-sync",
+       [HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ]   = "ptpv2-l4-delay-req",
+       [HWTSTAMP_FILTER_PTP_V2_L2_EVENT]       = "ptpv2-l2-event",
+       [HWTSTAMP_FILTER_PTP_V2_L2_SYNC]        = "ptpv2-l2-sync",
+       [HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ]   = "ptpv2-l2-delay-req",
+       [HWTSTAMP_FILTER_PTP_V2_EVENT]          = "ptpv2-event",
+       [HWTSTAMP_FILTER_PTP_V2_SYNC]           = "ptpv2-sync",
+       [HWTSTAMP_FILTER_PTP_V2_DELAY_REQ]      = "ptpv2-delay-req",
+       [HWTSTAMP_FILTER_NTP_ALL]               = "ntp-all",
+};
+
 struct info_data {
        struct common_req_info          reqinfo_base;
 
        /* everything below here will be reset for each device in dumps */
        struct common_reply_data        repdata_base;
        struct ethtool_drvinfo          drvinfo;
+       struct ethtool_ts_info          tsinfo;
 };
 
 static const struct nla_policy get_info_policy[ETHA_INFO_MAX + 1] = {
@@ -19,6 +65,7 @@ static const struct nla_policy get_info_policy[ETHA_INFO_MAX 
+ 1] = {
        [ETHA_INFO_COMPACT]             = { .type = NLA_FLAG },
        [ETHA_INFO_DRVINFO]             = { .type = NLA_REJECT },
        [ETHA_INFO_PERMADDR]            = { .type = NLA_REJECT },
+       [ETHA_INFO_TSINFO]              = { .type = NLA_REJECT },
 };
 
 static int parse_info(struct common_req_info *req_info, struct sk_buff *skb,
@@ -67,6 +114,11 @@ static int prepare_info(struct common_req_info *req_info,
                if (ret < 0)
                        req_mask &= ~ETH_INFO_IM_DRVINFO;
        }
+       if (req_mask & ETH_INFO_IM_TSINFO) {
+               ret = __ethtool_get_ts_info(dev, &data->tsinfo);
+               if (ret < 0)
+                       req_mask &= ~ETH_INFO_IM_TSINFO;
+       }
        ethnl_after_ops(dev);
 
        data->repdata_base.info_mask = req_mask;
@@ -97,6 +149,42 @@ static int permaddr_size(const struct net_device *dev)
        return nla_total_size(len);
 }
 
+static int tsinfo_size(const struct ethtool_ts_info *tsinfo, bool compact)
+{
+       const unsigned int flags = compact ? ETHNL_BITSET_COMPACT : 0;
+       int len = 0;
+       int ret;
+
+       /* if any of these exceeds 32, we need a different interface to talk to
+        * NIC drivers anyway
+        */
+       BUILD_BUG_ON(__SOF_TIMESTAMPING_COUNT > 32);
+       BUILD_BUG_ON(__HWTSTAMP_TX_COUNT > 32);
+       BUILD_BUG_ON(__HWTSTAMP_FILTER_COUNT > 32);
+
+       ret = ethnl_bitset32_size(__SOF_TIMESTAMPING_COUNT,
+                                 &tsinfo->so_timestamping, NULL,
+                                 so_timestamping_labels, flags);
+       if (ret < 0)
+               return ret;
+       len += ret;
+       ret = ethnl_bitset32_size(__HWTSTAMP_TX_COUNT,
+                                 &tsinfo->tx_types, NULL,
+                                 tstamp_tx_type_labels, flags);
+       if (ret < 0)
+               return ret;
+       len += ret;
+       ret = ethnl_bitset32_size(__HWTSTAMP_FILTER_COUNT,
+                                 &tsinfo->rx_filters, NULL,
+                                 tstamp_rx_filter_labels, flags);
+       if (ret < 0)
+               return ret;
+       len += ret;
+       len += nla_total_size(sizeof(u32));
+
+       return nla_total_size(len);
+}
+
 static int info_size(const struct common_req_info *req_info)
 {
        const struct info_data *data =
@@ -110,6 +198,13 @@ static int info_size(const struct common_req_info 
*req_info)
                len += drvinfo_size(&data->drvinfo);
        if (info_mask & ETH_INFO_IM_PERMADDR)
                len += permaddr_size(dev);
+       if (info_mask & ETH_INFO_IM_TSINFO) {
+               int ret = tsinfo_size(&data->tsinfo, req_info->compact);
+
+               if (ret < 0)
+                       return ret;
+               len += ret;
+       }
 
        return len;
 }
@@ -158,6 +253,44 @@ static int fill_permaddr(struct sk_buff *skb, const struct 
net_device *dev)
        return ret;
 }
 
+static int fill_tsinfo(struct sk_buff *skb,
+                      const struct ethtool_ts_info *tsinfo, bool compact)
+{
+       const unsigned int flags = compact ? ETHNL_BITSET_COMPACT : 0;
+       struct nlattr *nest = ethnl_nest_start(skb, ETHA_INFO_TSINFO);
+       int ret;
+
+       if (!nest)
+               return -EMSGSIZE;
+       ret = ethnl_put_bitset32(skb, ETHA_TSINFO_TIMESTAMPING,
+                                __SOF_TIMESTAMPING_COUNT,
+                                &tsinfo->so_timestamping, NULL,
+                                so_timestamping_labels, flags);
+       if (ret < 0)
+               goto err;
+       ret = -EMSGSIZE;
+       if (tsinfo->phc_index >= 0 &&
+           nla_put_u32(skb, ETHA_TSINFO_PHC_INDEX, tsinfo->phc_index))
+               goto err;
+
+       ret = ethnl_put_bitset32(skb, ETHA_TSINFO_TX_TYPES, __HWTSTAMP_TX_COUNT,
+                                &tsinfo->tx_types, NULL, tstamp_tx_type_labels,
+                                flags);
+       if (ret < 0)
+               goto err;
+       ret = ethnl_put_bitset32(skb, ETHA_TSINFO_RX_FILTERS,
+                                __HWTSTAMP_FILTER_COUNT, &tsinfo->rx_filters,
+                                NULL, tstamp_rx_filter_labels, flags);
+       if (ret < 0)
+               goto err;
+
+       nla_nest_end(skb, nest);
+       return 0;
+err:
+       nla_nest_cancel(skb, nest);
+       return ret;
+}
+
 static int fill_info(struct sk_buff *skb,
                     const struct common_req_info *req_info)
 {
@@ -176,6 +309,11 @@ static int fill_info(struct sk_buff *skb,
                if (ret < 0)
                        return ret;
        }
+       if (info_mask & ETH_INFO_IM_TSINFO) {
+               ret = fill_tsinfo(skb, &data->tsinfo, req_info->compact);
+               if (ret < 0)
+                       return ret;
+       }
 
        return 0;
 }
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index c883239001a4..0837849156d3 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -2034,28 +2034,12 @@ static int ethtool_get_dump_data(struct net_device *dev,
 
 static int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr)
 {
-       int err = 0;
+       int err;
        struct ethtool_ts_info info;
-       const struct ethtool_ops *ops = dev->ethtool_ops;
-       struct phy_device *phydev = dev->phydev;
-
-       memset(&info, 0, sizeof(info));
-       info.cmd = ETHTOOL_GET_TS_INFO;
-
-       if (phydev && phydev->drv && phydev->drv->ts_info) {
-               err = phydev->drv->ts_info(phydev, &info);
-       } else if (ops->get_ts_info) {
-               err = ops->get_ts_info(dev, &info);
-       } else {
-               info.so_timestamping =
-                       SOF_TIMESTAMPING_RX_SOFTWARE |
-                       SOF_TIMESTAMPING_SOFTWARE;
-               info.phc_index = -1;
-       }
 
+       err = __ethtool_get_ts_info(dev, &info);
        if (err)
                return err;
-
        if (copy_to_user(useraddr, &info, sizeof(info)))
                err = -EFAULT;
 
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 7141ec71a6d3..82a4c1f398d8 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -7,14 +7,23 @@
 #include <linux/netdevice.h>
 #include <net/genetlink.h>
 #include <net/sock.h>
+#include <linux/net_tstamp.h>
 
 #define ETHNL_SET_ERRMSG(info, msg) \
        do { if (info) GENL_SET_ERR_MSG(info, msg); } while (0)
 
+#define __SOF_TIMESTAMPING_COUNT (const_ilog2(SOF_TIMESTAMPING_LAST) + 1)
+#define __HWTSTAMP_TX_COUNT (HWTSTAMP_TX_LAST + 1)
+#define __HWTSTAMP_FILTER_COUNT (HWTSTAMP_FILTER_LAST + 1)
+
 extern u32 ethnl_bcast_seq;
 
 extern struct genl_family ethtool_genl_family;
 
+extern const char *const so_timestamping_labels[];
+extern const char *const tstamp_tx_type_labels[];
+extern const char *const tstamp_rx_filter_labels[];
+
 struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest);
 int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype);
 
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
index a7d0ec2865fb..5c74498d9c72 100644
--- a/net/ethtool/strset.c
+++ b/net/ethtool/strset.c
@@ -67,6 +67,24 @@ static const struct strset_info info_template[] = {
                .count          = ARRAY_SIZE(phy_tunable_strings),
                .data           = { .legacy = phy_tunable_strings },
        },
+       [ETH_SS_TSTAMP_SOF] = {
+               .type           = ETH_SS_TYPE_SIMPLE,
+               .per_dev        = false,
+               .count          = __SOF_TIMESTAMPING_COUNT,
+               .data           = { .simple = so_timestamping_labels },
+       },
+       [ETH_SS_TSTAMP_TX_TYPE] = {
+               .type           = ETH_SS_TYPE_SIMPLE,
+               .per_dev        = false,
+               .count          = __HWTSTAMP_TX_COUNT,
+               .data           = { .simple = tstamp_tx_type_labels },
+       },
+       [ETH_SS_TSTAMP_RX_FILTER] = {
+               .type           = ETH_SS_TYPE_SIMPLE,
+               .per_dev        = false,
+               .count          = __HWTSTAMP_FILTER_COUNT,
+               .data           = { .simple = tstamp_rx_filter_labels },
+       },
 };
 
 struct strset_data {
-- 
2.20.1

Reply via email to