ethtool: add standard pause stats
authorJakub Kicinski <kuba@kernel.org>
Tue, 15 Sep 2020 00:11:52 +0000 (17:11 -0700)
committerDavid S. Miller <davem@davemloft.net>
Tue, 15 Sep 2020 20:26:28 +0000 (13:26 -0700)
Currently drivers have to report their pause frames statistics
via ethtool -S, and there is a wide variety of names used for
these statistics.

Add the two statistics defined in IEEE 802.3x to the standard
API. Create a new ethtool request header flag for including
statistics in the response to GET commands.

Always create the ETHTOOL_A_PAUSE_STATS nest in replies when
flag is set. Testing if driver declares the op is not a reliable
way of checking if any stats will actually be included and therefore
we don't want to give the impression that presence of
ETHTOOL_A_PAUSE_STATS indicates driver support.

Note that this patch does not include PFC counters, which may fit
better in dcbnl? But mostly I don't need them/have a setup to test
them so I haven't looked deeply into exposing them :)

v3:
 - add a helper for "uninitializing" stats, rather than a cryptic
   memset() (Andrew)

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Saeed Mahameed <saeedm@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Documentation/networking/ethtool-netlink.rst
include/linux/ethtool.h
include/uapi/linux/ethtool_netlink.h
net/ethtool/pause.c

index d53bcb31645a4707ed9fd0bacaf912481097f7d6..2c8e0ddf548ed633f7d0222664d044d59c94d98e 100644 (file)
@@ -68,6 +68,7 @@ the flags may not apply to requests. Recognized flags are:
   =================================  ===================================
   ``ETHTOOL_FLAG_COMPACT_BITSETS``   use compact format bitsets in reply
   ``ETHTOOL_FLAG_OMIT_REPLY``        omit optional reply (_SET and _ACT)
+  ``ETHTOOL_FLAG_STATS``             include optional device statistics
   =================================  ===================================
 
 New request flags should follow the general idea that if the flag is not set,
@@ -989,8 +990,18 @@ Kernel response contents:
   ``ETHTOOL_A_PAUSE_AUTONEG``            bool    pause autonegotiation
   ``ETHTOOL_A_PAUSE_RX``                 bool    receive pause frames
   ``ETHTOOL_A_PAUSE_TX``                 bool    transmit pause frames
+  ``ETHTOOL_A_PAUSE_STATS``              nested  pause statistics
   =====================================  ======  ==========================
 
+``ETHTOOL_A_PAUSE_STATS`` are reported if ``ETHTOOL_FLAG_STATS`` was set
+in ``ETHTOOL_A_HEADER_FLAGS``.
+It will be empty if driver did not report any statistics. Drivers fill in
+the statistics in the following structure:
+
+.. kernel-doc:: include/linux/ethtool.h
+    :identifiers: ethtool_pause_stats
+
+Each member has a corresponding attribute defined.
 
 PAUSE_SET
 ============
index 969a80211df67f39e05ed6f0d50ae1a5126d98b7..060b20f0b20f29f47a8f83432bf3adfc69077ee1 100644 (file)
@@ -241,6 +241,27 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
         ETHTOOL_COALESCE_PKT_RATE_LOW | ETHTOOL_COALESCE_PKT_RATE_HIGH | \
         ETHTOOL_COALESCE_RATE_SAMPLE_INTERVAL)
 
+#define ETHTOOL_STAT_NOT_SET   (~0ULL)
+
+/**
+ * struct ethtool_pause_stats - statistics for IEEE 802.3x pause frames
+ * @tx_pause_frames: transmitted pause frame count. Reported to user space
+ *     as %ETHTOOL_A_PAUSE_STAT_TX_FRAMES.
+ *
+ *     Equivalent to `30.3.4.2 aPAUSEMACCtrlFramesTransmitted`
+ *     from the standard.
+ *
+ * @rx_pause_frames: received pause frame count. Reported to user space
+ *     as %ETHTOOL_A_PAUSE_STAT_RX_FRAMES. Equivalent to:
+ *
+ *     Equivalent to `30.3.4.3 aPAUSEMACCtrlFramesReceived`
+ *     from the standard.
+ */
+struct ethtool_pause_stats {
+       u64 tx_pause_frames;
+       u64 rx_pause_frames;
+};
+
 /**
  * struct ethtool_ops - optional netdev operations
  * @supported_coalesce_params: supported types of interrupt coalescing.
@@ -282,6 +303,9 @@ bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
  *     Returns a negative error code or zero.
  * @get_ringparam: Report ring sizes
  * @set_ringparam: Set ring sizes.  Returns a negative error code or zero.
+ * @get_pause_stats: Report pause frame statistics. Drivers must not zero
+ *     statistics which they don't report. The stats structure is initialized
+ *     to ETHTOOL_STAT_NOT_SET indicating driver does not report statistics.
  * @get_pauseparam: Report pause parameters
  * @set_pauseparam: Set pause parameters.  Returns a negative error code
  *     or zero.
@@ -418,6 +442,8 @@ struct ethtool_ops {
                                 struct ethtool_ringparam *);
        int     (*set_ringparam)(struct net_device *,
                                 struct ethtool_ringparam *);
+       void    (*get_pause_stats)(struct net_device *dev,
+                                  struct ethtool_pause_stats *pause_stats);
        void    (*get_pauseparam)(struct net_device *,
                                  struct ethtool_pauseparam*);
        int     (*set_pauseparam)(struct net_device *,
index 5dcd24cb33ea2d6ea69829e6c7ca22d320b1fc77..9cee6df01a104827f6ba613a6df281ab29106041 100644 (file)
@@ -91,9 +91,12 @@ enum {
 #define ETHTOOL_FLAG_COMPACT_BITSETS   (1 << 0)
 /* provide optional reply for SET or ACT requests */
 #define ETHTOOL_FLAG_OMIT_REPLY        (1 << 1)
+/* request statistics, if supported by the driver */
+#define ETHTOOL_FLAG_STATS             (1 << 2)
 
 #define ETHTOOL_FLAG_ALL (ETHTOOL_FLAG_COMPACT_BITSETS | \
-                         ETHTOOL_FLAG_OMIT_REPLY)
+                         ETHTOOL_FLAG_OMIT_REPLY | \
+                         ETHTOOL_FLAG_STATS)
 
 enum {
        ETHTOOL_A_HEADER_UNSPEC,
@@ -376,12 +379,25 @@ enum {
        ETHTOOL_A_PAUSE_AUTONEG,                        /* u8 */
        ETHTOOL_A_PAUSE_RX,                             /* u8 */
        ETHTOOL_A_PAUSE_TX,                             /* u8 */
+       ETHTOOL_A_PAUSE_STATS,                          /* nest - _PAUSE_STAT_* */
 
        /* add new constants above here */
        __ETHTOOL_A_PAUSE_CNT,
        ETHTOOL_A_PAUSE_MAX = (__ETHTOOL_A_PAUSE_CNT - 1)
 };
 
+enum {
+       ETHTOOL_A_PAUSE_STAT_UNSPEC,
+       ETHTOOL_A_PAUSE_STAT_PAD,
+
+       ETHTOOL_A_PAUSE_STAT_TX_FRAMES,
+       ETHTOOL_A_PAUSE_STAT_RX_FRAMES,
+
+       /* add new constants above here */
+       __ETHTOOL_A_PAUSE_STAT_CNT,
+       ETHTOOL_A_PAUSE_STAT_MAX = (__ETHTOOL_A_PAUSE_STAT_CNT - 1)
+};
+
 /* EEE */
 
 enum {
index 7aea35d1e8a5fd7b4564d0f9bd7e88c0f00c5532..1980aa7eb2b6a16af5efcd9c6a8cb542f7def7c5 100644 (file)
@@ -10,6 +10,7 @@ struct pause_req_info {
 struct pause_reply_data {
        struct ethnl_reply_data         base;
        struct ethtool_pauseparam       pauseparam;
+       struct ethtool_pause_stats      pausestat;
 };
 
 #define PAUSE_REPDATA(__reply_base) \
@@ -22,8 +23,15 @@ pause_get_policy[ETHTOOL_A_PAUSE_MAX + 1] = {
        [ETHTOOL_A_PAUSE_AUTONEG]               = { .type = NLA_REJECT },
        [ETHTOOL_A_PAUSE_RX]                    = { .type = NLA_REJECT },
        [ETHTOOL_A_PAUSE_TX]                    = { .type = NLA_REJECT },
+       [ETHTOOL_A_PAUSE_STATS]                 = { .type = NLA_REJECT },
 };
 
+static void ethtool_stats_init(u64 *stats, unsigned int n)
+{
+       while (n--)
+               stats[n] = ETHTOOL_STAT_NOT_SET;
+}
+
 static int pause_prepare_data(const struct ethnl_req_info *req_base,
                              struct ethnl_reply_data *reply_base,
                              struct genl_info *info)
@@ -34,10 +42,17 @@ static int pause_prepare_data(const struct ethnl_req_info *req_base,
 
        if (!dev->ethtool_ops->get_pauseparam)
                return -EOPNOTSUPP;
+
        ret = ethnl_ops_begin(dev);
        if (ret < 0)
                return ret;
        dev->ethtool_ops->get_pauseparam(dev, &data->pauseparam);
+       if (req_base->flags & ETHTOOL_FLAG_STATS &&
+           dev->ethtool_ops->get_pause_stats) {
+               ethtool_stats_init((u64 *)&data->pausestat,
+                                  sizeof(data->pausestat) / 8);
+               dev->ethtool_ops->get_pause_stats(dev, &data->pausestat);
+       }
        ethnl_ops_complete(dev);
 
        return 0;
@@ -46,9 +61,50 @@ static int pause_prepare_data(const struct ethnl_req_info *req_base,
 static int pause_reply_size(const struct ethnl_req_info *req_base,
                            const struct ethnl_reply_data *reply_base)
 {
-       return nla_total_size(sizeof(u8)) +     /* _PAUSE_AUTONEG */
+       int n = nla_total_size(sizeof(u8)) +    /* _PAUSE_AUTONEG */
                nla_total_size(sizeof(u8)) +    /* _PAUSE_RX */
                nla_total_size(sizeof(u8));     /* _PAUSE_TX */
+
+       if (req_base->flags & ETHTOOL_FLAG_STATS)
+               n += nla_total_size(0) +        /* _PAUSE_STATS */
+                       nla_total_size_64bit(sizeof(u64)) *
+                               (ETHTOOL_A_PAUSE_STAT_MAX - 2);
+       return n;
+}
+
+static int ethtool_put_stat(struct sk_buff *skb, u64 val, u16 attrtype,
+                           u16 padtype)
+{
+       if (val == ETHTOOL_STAT_NOT_SET)
+               return 0;
+       if (nla_put_u64_64bit(skb, attrtype, val, padtype))
+               return -EMSGSIZE;
+
+       return 0;
+}
+
+static int pause_put_stats(struct sk_buff *skb,
+                          const struct ethtool_pause_stats *pause_stats)
+{
+       const u16 pad = ETHTOOL_A_PAUSE_STAT_PAD;
+       struct nlattr *nest;
+
+       nest = nla_nest_start(skb, ETHTOOL_A_PAUSE_STATS);
+       if (!nest)
+               return -EMSGSIZE;
+
+       if (ethtool_put_stat(skb, pause_stats->tx_pause_frames,
+                            ETHTOOL_A_PAUSE_STAT_TX_FRAMES, pad) ||
+           ethtool_put_stat(skb, pause_stats->rx_pause_frames,
+                            ETHTOOL_A_PAUSE_STAT_RX_FRAMES, pad))
+               goto err_cancel;
+
+       nla_nest_end(skb, nest);
+       return 0;
+
+err_cancel:
+       nla_nest_cancel(skb, nest);
+       return -EMSGSIZE;
 }
 
 static int pause_fill_reply(struct sk_buff *skb,
@@ -63,6 +119,10 @@ static int pause_fill_reply(struct sk_buff *skb,
            nla_put_u8(skb, ETHTOOL_A_PAUSE_TX, !!pauseparam->tx_pause))
                return -EMSGSIZE;
 
+       if (req_base->flags & ETHTOOL_FLAG_STATS &&
+           pause_put_stats(skb, &data->pausestat))
+               return -EMSGSIZE;
+
        return 0;
 }
 
@@ -89,6 +149,7 @@ pause_set_policy[ETHTOOL_A_PAUSE_MAX + 1] = {
        [ETHTOOL_A_PAUSE_AUTONEG]               = { .type = NLA_U8 },
        [ETHTOOL_A_PAUSE_RX]                    = { .type = NLA_U8 },
        [ETHTOOL_A_PAUSE_TX]                    = { .type = NLA_U8 },
+       [ETHTOOL_A_PAUSE_STATS]                 = { .type = NLA_REJECT },
 };
 
 int ethnl_set_pause(struct sk_buff *skb, struct genl_info *info)