net: ethtool: plumb PHY stats to PHY drivers
[ Upstream commit b7a2c1fe6b55364e61b4b54b991eb43a47bb1104 ] 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> Stable-dep-of: 637399bf7e77 ("net: ethtool: netlink: Allow NULL nlattrs when getting a phy_device") Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
b71cd95764
commit
9c1d09cdbc
@@ -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
|
||||
|
||||
@@ -3776,6 +3776,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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -1090,6 +1090,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 */
|
||||
@@ -2055,6 +2084,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user