]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
net: ethtool: plumb PHY stats to PHY drivers
authorJakub Kicinski <kuba@kernel.org>
Fri, 10 Jan 2025 06:05:12 +0000 (07:05 +0100)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 14 Jan 2025 10:44:19 +0000 (11:44 +0100)
Introduce support for standardized PHY statistics reporting in ethtool
by extending the PHYLIB framework. Add the functions
phy_ethtool_get_phy_stats() and phy_ethtool_get_link_ext_stats() to
provide a consistent interface for retrieving PHY-level and
link-specific statistics. These functions are used within the ethtool
implementation to avoid direct access to the phy_device structure
outside of the PHYLIB framework.

A new structure, ethtool_phy_stats, is introduced to standardize PHY
statistics such as packet counts, byte counts, and error counters.
Drivers are updated to include callbacks for retrieving PHY and
link-specific statistics, ensuring values are explicitly set only for
supported fields, initialized with ETHTOOL_STAT_NOT_SET to avoid
ambiguity.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/phy/phy.c
drivers/net/phy/phy_device.c
include/linux/ethtool.h
include/linux/phy.h
include/linux/phylib_stubs.h
net/ethtool/linkstate.c
net/ethtool/stats.c

index a4b9fcc2503aef3ccd993d003bd158edf3f8dd53..508f3ae3240f0f975fa0b0f71616a2d88989442b 100644 (file)
@@ -615,6 +615,49 @@ int phy_ethtool_get_stats(struct phy_device *phydev,
 }
 EXPORT_SYMBOL(phy_ethtool_get_stats);
 
+/**
+ * __phy_ethtool_get_phy_stats - Retrieve standardized PHY statistics
+ * @phydev: Pointer to the PHY device
+ * @phy_stats: Pointer to ethtool_eth_phy_stats structure
+ * @phydev_stats: Pointer to ethtool_phy_stats structure
+ *
+ * Fetches PHY statistics using a kernel-defined interface for consistent
+ * diagnostics. Unlike phy_ethtool_get_stats(), which allows custom stats,
+ * this function enforces a standardized format for better interoperability.
+ */
+void __phy_ethtool_get_phy_stats(struct phy_device *phydev,
+                                struct ethtool_eth_phy_stats *phy_stats,
+                                struct ethtool_phy_stats *phydev_stats)
+{
+       if (!phydev->drv || !phydev->drv->get_phy_stats)
+               return;
+
+       mutex_lock(&phydev->lock);
+       phydev->drv->get_phy_stats(phydev, phy_stats, phydev_stats);
+       mutex_unlock(&phydev->lock);
+}
+
+/**
+ * __phy_ethtool_get_link_ext_stats - Retrieve extended link statistics for a PHY
+ * @phydev: Pointer to the PHY device
+ * @link_stats: Pointer to the structure to store extended link statistics
+ *
+ * Populates the ethtool_link_ext_stats structure with link down event counts
+ * and additional driver-specific link statistics, if available.
+ */
+void __phy_ethtool_get_link_ext_stats(struct phy_device *phydev,
+                                     struct ethtool_link_ext_stats *link_stats)
+{
+       link_stats->link_down_events = READ_ONCE(phydev->link_down_events);
+
+       if (!phydev->drv || !phydev->drv->get_link_stats)
+               return;
+
+       mutex_lock(&phydev->lock);
+       phydev->drv->get_link_stats(phydev, link_stats);
+       mutex_unlock(&phydev->lock);
+}
+
 /**
  * phy_ethtool_get_plca_cfg - Get PLCA RS configuration
  * @phydev: the phy_device struct
index bdc997f5977902c829e41cd1523d453b22c75f76..5b34d39d1d52a6d3bffba569661cbf9569209b3d 100644 (file)
@@ -3800,6 +3800,8 @@ static const struct ethtool_phy_ops phy_ethtool_phy_ops = {
 static const struct phylib_stubs __phylib_stubs = {
        .hwtstamp_get = __phy_hwtstamp_get,
        .hwtstamp_set = __phy_hwtstamp_set,
+       .get_phy_stats = __phy_ethtool_get_phy_stats,
+       .get_link_ext_stats = __phy_ethtool_get_link_ext_stats,
 };
 
 static void phylib_register_stubs(void)
index f711bfd75c4dcf017d9532e4e495de16cef58d65..4bf70cfec8268e86a792b853dc5b7023756a0fbc 100644 (file)
@@ -412,6 +412,29 @@ struct ethtool_eth_phy_stats {
        );
 };
 
+/**
+ * struct ethtool_phy_stats - PHY-level statistics counters
+ * @rx_packets: Total successfully received frames
+ * @rx_bytes: Total successfully received bytes
+ * @rx_errors: Total received frames with errors (e.g., CRC errors)
+ * @tx_packets: Total successfully transmitted frames
+ * @tx_bytes: Total successfully transmitted bytes
+ * @tx_errors: Total transmitted frames with errors
+ *
+ * This structure provides a standardized interface for reporting
+ * PHY-level statistics counters. It is designed to expose statistics
+ * commonly provided by PHYs but not explicitly defined in the IEEE
+ * 802.3 standard.
+ */
+struct ethtool_phy_stats {
+       u64 rx_packets;
+       u64 rx_bytes;
+       u64 rx_errors;
+       u64 tx_packets;
+       u64 tx_bytes;
+       u64 tx_errors;
+};
+
 /* Basic IEEE 802.3 MAC Ctrl statistics (30.3.3.*), not otherwise exposed
  * via a more targeted API.
  */
index 4875465653ca34274dc3dd59868bc576212b385d..81d1612e7d35b2a7877defe571c2d384c57e13c7 100644 (file)
@@ -1144,6 +1144,35 @@ struct phy_driver {
        int (*cable_test_get_status)(struct phy_device *dev, bool *finished);
 
        /* Get statistics from the PHY using ethtool */
+       /**
+        * @get_phy_stats: Retrieve PHY statistics.
+        * @dev: The PHY device for which the statistics are retrieved.
+        * @eth_stats: structure where Ethernet PHY stats will be stored.
+        * @stats: structure where additional PHY-specific stats will be stored.
+        *
+        * Retrieves the supported PHY statistics and populates the provided
+        * structures. The input structures are pre-initialized with
+        * `ETHTOOL_STAT_NOT_SET`, and the driver must only modify members
+        * corresponding to supported statistics. Unmodified members will remain
+        * set to `ETHTOOL_STAT_NOT_SET` and will not be returned to userspace.
+        */
+       void (*get_phy_stats)(struct phy_device *dev,
+                             struct ethtool_eth_phy_stats *eth_stats,
+                             struct ethtool_phy_stats *stats);
+
+       /**
+        * @get_link_stats: Retrieve link statistics.
+        * @dev: The PHY device for which the statistics are retrieved.
+        * @link_stats: structure where link-specific stats will be stored.
+        *
+        * Retrieves link-related statistics for the given PHY device. The input
+        * structure is pre-initialized with `ETHTOOL_STAT_NOT_SET`, and the
+        * driver must only modify members corresponding to supported
+        * statistics. Unmodified members will remain set to
+        * `ETHTOOL_STAT_NOT_SET` and will not be returned to userspace.
+        */
+       void (*get_link_stats)(struct phy_device *dev,
+                              struct ethtool_link_ext_stats *link_stats);
        /** @get_sset_count: Number of statistic counters */
        int (*get_sset_count)(struct phy_device *dev);
        /** @get_strings: Names of the statistic counters */
@@ -2124,6 +2153,13 @@ int phy_ethtool_get_strings(struct phy_device *phydev, u8 *data);
 int phy_ethtool_get_sset_count(struct phy_device *phydev);
 int phy_ethtool_get_stats(struct phy_device *phydev,
                          struct ethtool_stats *stats, u64 *data);
+
+void __phy_ethtool_get_phy_stats(struct phy_device *phydev,
+                        struct ethtool_eth_phy_stats *phy_stats,
+                        struct ethtool_phy_stats *phydev_stats);
+void __phy_ethtool_get_link_ext_stats(struct phy_device *phydev,
+                                     struct ethtool_link_ext_stats *link_stats);
+
 int phy_ethtool_get_plca_cfg(struct phy_device *phydev,
                             struct phy_plca_cfg *plca_cfg);
 int phy_ethtool_set_plca_cfg(struct phy_device *phydev,
index 1279f48c8a7077ae495f861e5471146b636a2b37..9d2d6090c86d1221a792987c7c7b45fa7d85872f 100644 (file)
@@ -5,6 +5,9 @@
 
 #include <linux/rtnetlink.h>
 
+struct ethtool_eth_phy_stats;
+struct ethtool_link_ext_stats;
+struct ethtool_phy_stats;
 struct kernel_hwtstamp_config;
 struct netlink_ext_ack;
 struct phy_device;
@@ -19,6 +22,11 @@ struct phylib_stubs {
        int (*hwtstamp_set)(struct phy_device *phydev,
                            struct kernel_hwtstamp_config *config,
                            struct netlink_ext_ack *extack);
+       void (*get_phy_stats)(struct phy_device *phydev,
+                             struct ethtool_eth_phy_stats *phy_stats,
+                             struct ethtool_phy_stats *phydev_stats);
+       void (*get_link_ext_stats)(struct phy_device *phydev,
+                                  struct ethtool_link_ext_stats *link_stats);
 };
 
 static inline int phy_hwtstamp_get(struct phy_device *phydev,
@@ -50,6 +58,29 @@ static inline int phy_hwtstamp_set(struct phy_device *phydev,
        return phylib_stubs->hwtstamp_set(phydev, config, extack);
 }
 
+static inline void phy_ethtool_get_phy_stats(struct phy_device *phydev,
+                                       struct ethtool_eth_phy_stats *phy_stats,
+                                       struct ethtool_phy_stats *phydev_stats)
+{
+       ASSERT_RTNL();
+
+       if (!phylib_stubs)
+               return;
+
+       phylib_stubs->get_phy_stats(phydev, phy_stats, phydev_stats);
+}
+
+static inline void phy_ethtool_get_link_ext_stats(struct phy_device *phydev,
+                                   struct ethtool_link_ext_stats *link_stats)
+{
+       ASSERT_RTNL();
+
+       if (!phylib_stubs)
+               return;
+
+       phylib_stubs->get_link_ext_stats(phydev, link_stats);
+}
+
 #else
 
 static inline int phy_hwtstamp_get(struct phy_device *phydev,
@@ -65,4 +96,15 @@ static inline int phy_hwtstamp_set(struct phy_device *phydev,
        return -EOPNOTSUPP;
 }
 
+static inline void phy_ethtool_get_phy_stats(struct phy_device *phydev,
+                                       struct ethtool_eth_phy_stats *phy_stats,
+                                       struct ethtool_phy_stats *phydev_stats)
+{
+}
+
+static inline void phy_ethtool_get_link_ext_stats(struct phy_device *phydev,
+                                   struct ethtool_link_ext_stats *link_stats)
+{
+}
+
 #endif
index 459cfea7652d44dbaccb10cdeb660a250dc08d90..af19e1bed303f1484c51f7e085b848280f1b2324 100644 (file)
@@ -3,6 +3,7 @@
 #include "netlink.h"
 #include "common.h"
 #include <linux/phy.h>
+#include <linux/phylib_stubs.h>
 
 struct linkstate_req_info {
        struct ethnl_req_info           base;
@@ -135,8 +136,8 @@ static int linkstate_prepare_data(const struct ethnl_req_info *req_base,
 
        if (req_base->flags & ETHTOOL_FLAG_STATS) {
                if (phydev)
-                       data->link_stats.link_down_events =
-                               READ_ONCE(phydev->link_down_events);
+                       phy_ethtool_get_link_ext_stats(phydev,
+                                                      &data->link_stats);
 
                if (dev->ethtool_ops->get_link_ext_stats)
                        dev->ethtool_ops->get_link_ext_stats(dev,
index 912f0c4fff2fb984a80f20d5cbffd29a491b1f69..f4d822c225db6a5a2d44baf89f4ab725c83d8e45 100644 (file)
@@ -1,5 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
+#include <linux/phy.h>
+#include <linux/phylib_stubs.h>
+
 #include "netlink.h"
 #include "common.h"
 #include "bitset.h"
@@ -20,6 +23,7 @@ struct stats_reply_data {
                struct ethtool_eth_mac_stats    mac_stats;
                struct ethtool_eth_ctrl_stats   ctrl_stats;
                struct ethtool_rmon_stats       rmon_stats;
+               struct ethtool_phy_stats        phydev_stats;
        );
        const struct ethtool_rmon_hist_range    *rmon_ranges;
 };
@@ -120,8 +124,15 @@ static int stats_prepare_data(const struct ethnl_req_info *req_base,
        struct stats_reply_data *data = STATS_REPDATA(reply_base);
        enum ethtool_mac_stats_src src = req_info->src;
        struct net_device *dev = reply_base->dev;
+       struct nlattr **tb = info->attrs;
+       struct phy_device *phydev;
        int ret;
 
+       phydev = ethnl_req_get_phydev(req_base, tb[ETHTOOL_A_STATS_HEADER],
+                                     info->extack);
+       if (IS_ERR(phydev))
+               return PTR_ERR(phydev);
+
        ret = ethnl_ops_begin(dev);
        if (ret < 0)
                return ret;
@@ -145,6 +156,13 @@ static int stats_prepare_data(const struct ethnl_req_info *req_base,
        data->ctrl_stats.src = src;
        data->rmon_stats.src = src;
 
+       if (test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask) &&
+           src == ETHTOOL_MAC_STATS_SRC_AGGREGATE) {
+               if (phydev)
+                       phy_ethtool_get_phy_stats(phydev, &data->phy_stats,
+                                                 &data->phydev_stats);
+       }
+
        if (test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask) &&
            dev->ethtool_ops->get_eth_phy_stats)
                dev->ethtool_ops->get_eth_phy_stats(dev, &data->phy_stats);