Commit b7a2c1fe authored by Jakub Kicinski's avatar Jakub Kicinski Committed by Paolo Abeni
Browse files

net: ethtool: plumb PHY stats to PHY drivers



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: default avatarJakub Kicinski <kuba@kernel.org>
Signed-off-by: default avatarOleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent fe55b1d4
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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)
+23 −0
Original line number Diff line number Diff line
@@ -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.
 */
+36 −0
Original line number Diff line number Diff line
@@ -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,
+42 −0
Original line number Diff line number Diff line
@@ -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
Loading