diff mbox series

[net-next,v2,3/3] phy: dp83tg720: Add statistics support

Message ID 20240820122914.1958664-4-o.rempel@pengutronix.de (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series Add Statistics Support for DP83TG720 PHY | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 16 this patch: 16
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang success Errors and warnings before: 16 this patch: 16
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 16 this patch: 16
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-08-20--15-00 (tests: 712)

Commit Message

Oleksij Rempel Aug. 20, 2024, 12:29 p.m. UTC
Introduce statistics support for the DP83TG720 PHY driver, enabling
detailed monitoring and reporting of link quality and packet-related
metrics.

To avoid double reading of certain registers, the implementation caches
all relevant register values in a single operation. This approach
ensures accurate and consistent data retrieval, particularly for
registers that clear upon reading or require special handling.

Some of the statistics, such as link training times, do not increment
and therefore require special handling during the extraction process.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v2:
- use ethtool_puts()
- use defines for statistic names
---
 drivers/net/phy/dp83tg720.c | 327 ++++++++++++++++++++++++++++++++++++
 1 file changed, 327 insertions(+)

Comments

Maxime Chevallier Aug. 21, 2024, 8:16 a.m. UTC | #1
Hello Oleksij,

On Tue, 20 Aug 2024 14:29:14 +0200
Oleksij Rempel <o.rempel@pengutronix.de> wrote:

> Introduce statistics support for the DP83TG720 PHY driver, enabling
> detailed monitoring and reporting of link quality and packet-related
> metrics.
> 
> To avoid double reading of certain registers, the implementation caches
> all relevant register values in a single operation. This approach
> ensures accurate and consistent data retrieval, particularly for
> registers that clear upon reading or require special handling.
> 
> Some of the statistics, such as link training times, do not increment
> and therefore require special handling during the extraction process.

This all looks good to me, I do have one small nit bellow :

> +/**
> + * dp83tg720_get_stats - Get the statistics values.
> + * @phydev: Pointer to the phy_device structure.
> + * @stats: Pointer to the ethtool_stats structure.
> + * @data: Pointer to the buffer where the statistics values will be stored.
> + *
> + * Fills the buffer with the statistics values, filtering out those that are
> + * not applicable based on the PHY's operating mode (e.g., RGMII).

I don't see how this filtering is actually implemented, is this comment
correct ?

> + */
> +static void dp83tg720_get_stats(struct phy_device *phydev,
> +				struct ethtool_stats *stats, u64 *data)
> +{
> +	int i, j = 0;
> +
> +	dp83tg720_cache_reg_values(phydev);
> +
> +	for (i = 0; i < ARRAY_SIZE(dp83tg720_hw_stats); i++) {
> +		data[j] = dp83tg720_extract_stat_value(phydev, i);
> +		j++;
> +	}
> +}

Thanks,

Maxime
Oleksij Rempel Aug. 21, 2024, 9:01 a.m. UTC | #2
Hi Maxime,

On Wed, Aug 21, 2024 at 10:16:22AM +0200, Maxime Chevallier wrote:
> Hello Oleksij,
> 
> On Tue, 20 Aug 2024 14:29:14 +0200
> Oleksij Rempel <o.rempel@pengutronix.de> wrote:
> 
> > Introduce statistics support for the DP83TG720 PHY driver, enabling
> > detailed monitoring and reporting of link quality and packet-related
> > metrics.
> > 
> > To avoid double reading of certain registers, the implementation caches
> > all relevant register values in a single operation. This approach
> > ensures accurate and consistent data retrieval, particularly for
> > registers that clear upon reading or require special handling.
> > 
> > Some of the statistics, such as link training times, do not increment
> > and therefore require special handling during the extraction process.
> 
> This all looks good to me, I do have one small nit bellow :
> 
> > +/**
> > + * dp83tg720_get_stats - Get the statistics values.
> > + * @phydev: Pointer to the phy_device structure.
> > + * @stats: Pointer to the ethtool_stats structure.
> > + * @data: Pointer to the buffer where the statistics values will be stored.
> > + *
> > + * Fills the buffer with the statistics values, filtering out those that are
> > + * not applicable based on the PHY's operating mode (e.g., RGMII).
> 
> I don't see how this filtering is actually implemented, is this comment
> correct ?

You are right, it is outdated. My previous implementation had rgmii-fail
status flags.

Regards,
Oleksij
diff mbox series

Patch

diff --git a/drivers/net/phy/dp83tg720.c b/drivers/net/phy/dp83tg720.c
index 0ef4d7dba0656..080cac71d1d0b 100644
--- a/drivers/net/phy/dp83tg720.c
+++ b/drivers/net/phy/dp83tg720.c
@@ -51,6 +51,20 @@ 
 /* Register 0x0405: Unknown Register */
 #define DP83TG720S_UNKNOWN_0405			0x405
 
+#define DP83TG720S_A2D_REG_66			0x442
+#define DP83TG720S_ESD_EVENT_COUNT_MASK		GENMASK(14, 9)
+
+#define DP83TG720S_LINK_QUAL_1			0x543
+#define DP83TG720S_LINK_TRAINING_TIME_MASK	GENMASK(7, 0)
+
+#define DP83TG720S_LINK_QUAL_2			0x544
+#define DP83TG720S_REMOTE_RECEIVER_TIME_MASK	GENMASK(15, 8)
+#define DP83TG720S_LOCAL_RECEIVER_TIME_MASK	GENMASK(7, 0)
+
+#define DP83TG720S_LINK_QUAL_3			0x547
+#define DP83TG720S_LINK_LOSS_CNT_MASK		GENMASK(15, 10)
+#define DP83TG720S_LINK_FAIL_CNT_MASK		GENMASK(9, 0)
+
 /* Register 0x0576: TDR Master Link Down Control */
 #define DP83TG720S_TDR_MASTER_LINK_DOWN		0x576
 
@@ -60,6 +74,24 @@ 
 /* In RGMII mode, Enable or disable the internal delay for TXD */
 #define DP83TG720S_RGMII_TX_CLK_SEL		BIT(0)
 
+#define DP83TG720S_PKT_STAT_1			0x639
+#define DP83TG720S_TX_PKT_CNT_15_0_MASK		GENMASK(15, 0)
+
+#define DP83TG720S_PKT_STAT_2			0x63A
+#define DP83TG720S_TX_PKT_CNT_31_16_MASK	GENMASK(15, 0)
+
+#define DP83TG720S_PKT_STAT_3			0x63B
+#define DP83TG720S_TX_ERR_PKT_CNT_MASK		GENMASK(15, 0)
+
+#define DP83TG720S_PKT_STAT_4			0x63C
+#define DP83TG720S_RX_PKT_CNT_15_0_MASK		GENMASK(15, 0)
+
+#define DP83TG720S_PKT_STAT_5			0x63D
+#define DP83TG720S_RX_PKT_CNT_31_16_MASK	GENMASK(15, 0)
+
+#define DP83TG720S_PKT_STAT_6			0x63E
+#define DP83TG720S_RX_ERR_PKT_CNT_MASK		GENMASK(15, 0)
+
 /* Register 0x083F: Unknown Register */
 #define DP83TG720S_UNKNOWN_083F			0x83f
 
@@ -69,6 +101,283 @@ 
 
 #define DP83TG720_SQI_MAX			7
 
+struct dp83tg720_cache_reg {
+	u16 mmd;
+	u16 reg;
+};
+
+#define DP83TG720_FLAG_COUNTER BIT(0)
+
+struct dp83tg720_hw_stat {
+	const char *string;
+	u8 cache_index1;
+	u8 cache_index2;  /* If a statistic spans multiple registers */
+	u32 mask1;
+	u32 mask2;        /* Mask for the second register */
+	u8 shift1;
+	u8 shift2;        /* Shift for the second register */
+	u8 flags;
+};
+
+enum dp83tg720_cache_reg_idx {
+	DP83TG720_CACHE_A2D_REG_66 = 0,
+	DP83TG720_CACHE_LINK_QUAL_1,
+	DP83TG720_CACHE_LINK_QUAL_2,
+	DP83TG720_CACHE_LINK_QUAL_3,
+	DP83TG720_CACHE_PKT_STAT_1,
+	DP83TG720_CACHE_PKT_STAT_2,
+	DP83TG720_CACHE_PKT_STAT_3,
+	DP83TG720_CACHE_PKT_STAT_4,
+	DP83TG720_CACHE_PKT_STAT_5,
+	DP83TG720_CACHE_PKT_STAT_6,
+
+	DP83TG720_CACHE_EMPTY,
+};
+
+static const struct dp83tg720_cache_reg dp83tg720_cache_regs[] = {
+	[DP83TG720_CACHE_A2D_REG_66] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_A2D_REG_66,
+	},
+	[DP83TG720_CACHE_LINK_QUAL_1] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_LINK_QUAL_1,
+	},
+	[DP83TG720_CACHE_LINK_QUAL_2] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_LINK_QUAL_2,
+	},
+	[DP83TG720_CACHE_LINK_QUAL_3] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_LINK_QUAL_3,
+	},
+	[DP83TG720_CACHE_PKT_STAT_1] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_PKT_STAT_1,
+	},
+	[DP83TG720_CACHE_PKT_STAT_2] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_PKT_STAT_2,
+	},
+	[DP83TG720_CACHE_PKT_STAT_3] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_PKT_STAT_3,
+	},
+	[DP83TG720_CACHE_PKT_STAT_4] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_PKT_STAT_4,
+	},
+	[DP83TG720_CACHE_PKT_STAT_5] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_PKT_STAT_5,
+	},
+	[DP83TG720_CACHE_PKT_STAT_6] = {
+		.mmd = MDIO_MMD_VEND2,
+		.reg = DP83TG720S_PKT_STAT_6,
+	},
+};
+
+static const struct dp83tg720_hw_stat dp83tg720_hw_stats[] = {
+	{
+		.string = OA_LQ_LFL_ESD_EVENT_COUNT,
+		.cache_index1 = DP83TG720_CACHE_A2D_REG_66,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_ESD_EVENT_COUNT_MASK,
+		.shift1 = 9,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+	{
+		.string = OA_LQ_LINK_TRAINING_TIME,
+		.cache_index1 = DP83TG720_CACHE_LINK_QUAL_1,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_LINK_TRAINING_TIME_MASK,
+	},
+	{
+		.string = OA_LQ_REMOTE_RECEIVER_TIME,
+		.cache_index1 = DP83TG720_CACHE_LINK_QUAL_2,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_REMOTE_RECEIVER_TIME_MASK,
+		.shift1 = 8,
+	},
+	{
+		.string = OA_LQ_LOCAL_RECEIVER_TIME,
+		.cache_index1 = DP83TG720_CACHE_LINK_QUAL_2,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_LOCAL_RECEIVER_TIME_MASK,
+	},
+	{
+		.string = OA_LQ_LFL_LINK_LOSS_COUNT,
+		.cache_index1 = DP83TG720_CACHE_LINK_QUAL_3,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_LINK_LOSS_CNT_MASK,
+		.shift1 = 10,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+	{
+		.string = OA_LQ_LFL_LINK_FAILURE_COUNT,
+		.cache_index1 = DP83TG720_CACHE_LINK_QUAL_3,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_LINK_FAIL_CNT_MASK,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+	{
+		.string = PHY_TX_PKT_COUNT,
+		.cache_index1 = DP83TG720_CACHE_PKT_STAT_1,
+		.cache_index2 = DP83TG720_CACHE_PKT_STAT_2,
+		.mask1 = DP83TG720S_TX_PKT_CNT_15_0_MASK,
+		.mask2 = DP83TG720S_TX_PKT_CNT_31_16_MASK,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+	{
+		.string = PHY_TX_ERR_COUNT,
+		.cache_index1 = DP83TG720_CACHE_PKT_STAT_3,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_TX_ERR_PKT_CNT_MASK,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+	{
+		.string = PHY_RX_PKT_COUNT,
+		.cache_index1 = DP83TG720_CACHE_PKT_STAT_4,
+		.cache_index2 = DP83TG720_CACHE_PKT_STAT_5,
+		.mask1 = DP83TG720S_RX_PKT_CNT_15_0_MASK,
+		.mask2 = DP83TG720S_RX_PKT_CNT_31_16_MASK,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+	{
+		.string = PHY_RX_ERR_COUNT,
+		.cache_index1 = DP83TG720_CACHE_PKT_STAT_6,
+		.cache_index2 = DP83TG720_CACHE_EMPTY,
+		.mask1 = DP83TG720S_RX_ERR_PKT_CNT_MASK,
+		.flags = DP83TG720_FLAG_COUNTER,
+	},
+};
+
+struct dp83tg720_priv {
+	u64 stats[ARRAY_SIZE(dp83tg720_hw_stats)];
+	u16 reg_cache[ARRAY_SIZE(dp83tg720_cache_regs)];
+};
+
+/**
+ * dp83tg720_cache_reg_values - Cache register values to avoid clearing counters.
+ * @phydev: Pointer to the phy_device structure.
+ *
+ * Reads and caches the values of all relevant registers.
+ *
+ * Returns: 0 on success, a negative error code on failure.
+ */
+static int dp83tg720_cache_reg_values(struct phy_device *phydev)
+{
+	struct dp83tg720_priv *priv = phydev->priv;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(dp83tg720_cache_regs); i++) {
+		const struct dp83tg720_cache_reg *cache_reg =
+			&dp83tg720_cache_regs[i];
+
+		ret = phy_read_mmd(phydev, cache_reg->mmd, cache_reg->reg);
+		if (ret < 0)
+			return ret;
+
+		priv->reg_cache[i] = (u16)ret;
+	}
+
+	return 0;
+}
+
+/**
+ * dp83tg720_extract_stat_value - Extract specific statistic value from cache.
+ * @phydev: Pointer to the phy_device structure.
+ * @i: Index of the statistic in the dp83tg720_hw_stats array.
+ *
+ * Extracts the specific statistic value from the cached register values.
+ *
+ * Returns: The extracted statistic value.
+ */
+static u64 dp83tg720_extract_stat_value(struct phy_device *phydev, int i)
+{
+	const struct dp83tg720_hw_stat *stat = &dp83tg720_hw_stats[i];
+	struct dp83tg720_priv *priv = phydev->priv;
+	u32 val1, val2 = 0;
+
+	val1 = (priv->reg_cache[stat->cache_index1] & stat->mask1);
+	val1 >>= stat->shift1;
+	if (stat->cache_index2 != DP83TG720_CACHE_EMPTY) {
+		val2 = (priv->reg_cache[stat->cache_index2] & stat->mask2);
+		val2 >>= stat->shift2;
+	}
+
+	if (stat->flags & DP83TG720_FLAG_COUNTER)
+		priv->stats[i] += val1 | (val2 << 16);
+	else
+		priv->stats[i] = val1 | (val2 << 16);
+
+	return priv->stats[i];
+}
+
+/**
+ * dp83tg720_get_sset_count - Get the number of statistics sets.
+ * @phydev: Pointer to the phy_device structure.
+ *
+ * Returns: The number of statistics sets.
+ */
+static int dp83tg720_get_sset_count(struct phy_device *phydev)
+{
+	return ARRAY_SIZE(dp83tg720_hw_stats);
+}
+
+/**
+ * dp83tg720_get_strings - Get the strings for the statistics.
+ * @phydev: Pointer to the phy_device structure.
+ * @data: Pointer to the buffer where the strings will be stored.
+ *
+ * Fills the buffer with the strings for the statistics
+ */
+static void dp83tg720_get_strings(struct phy_device *phydev, u8 *data)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dp83tg720_hw_stats); i++)
+		ethtool_puts(&data, dp83tg720_hw_stats[i].string);
+}
+
+/**
+ * dp83tg720_get_stats - Get the statistics values.
+ * @phydev: Pointer to the phy_device structure.
+ * @stats: Pointer to the ethtool_stats structure.
+ * @data: Pointer to the buffer where the statistics values will be stored.
+ *
+ * Fills the buffer with the statistics values, filtering out those that are
+ * not applicable based on the PHY's operating mode (e.g., RGMII).
+ */
+static void dp83tg720_get_stats(struct phy_device *phydev,
+				struct ethtool_stats *stats, u64 *data)
+{
+	int i, j = 0;
+
+	dp83tg720_cache_reg_values(phydev);
+
+	for (i = 0; i < ARRAY_SIZE(dp83tg720_hw_stats); i++) {
+		data[j] = dp83tg720_extract_stat_value(phydev, i);
+		j++;
+	}
+}
+
+/**
+ * dp83tg720_update_stats - Update the statistics values.
+ * @phydev: Pointer to the phy_device structure.
+ *
+ * Updates the statistics values.
+ */
+static void dp83tg720_update_stats(struct phy_device *phydev)
+{
+	int i;
+
+	dp83tg720_cache_reg_values(phydev);
+
+	for (i = 0; i < ARRAY_SIZE(dp83tg720_hw_stats); i++)
+		dp83tg720_extract_stat_value(phydev, i);
+}
+
 /**
  * dp83tg720_cable_test_start - Start the cable test for the DP83TG720 PHY.
  * @phydev: Pointer to the phy_device structure.
@@ -208,6 +517,7 @@  static int dp83tg720_read_status(struct phy_device *phydev)
 	u16 phy_sts;
 	int ret;
 
+	dp83tg720_update_stats(phydev);
 	phydev->pause = 0;
 	phydev->asym_pause = 0;
 
@@ -341,12 +651,26 @@  static int dp83tg720_config_init(struct phy_device *phydev)
 	return genphy_c45_pma_baset1_read_master_slave(phydev);
 }
 
+static int dp83tg720_probe(struct phy_device *phydev)
+{
+	struct dp83tg720_priv *priv;
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
 static struct phy_driver dp83tg720_driver[] = {
 {
 	PHY_ID_MATCH_MODEL(DP83TG720S_PHY_ID),
 	.name		= "TI DP83TG720S",
 
 	.flags          = PHY_POLL_CABLE_TEST,
+	.probe		= dp83tg720_probe,
 	.config_aneg	= dp83tg720_config_aneg,
 	.read_status	= dp83tg720_read_status,
 	.get_features	= genphy_c45_pma_read_ext_abilities,
@@ -355,6 +679,9 @@  static struct phy_driver dp83tg720_driver[] = {
 	.get_sqi_max	= dp83tg720_get_sqi_max,
 	.cable_test_start = dp83tg720_cable_test_start,
 	.cable_test_get_status = dp83tg720_cable_test_get_status,
+	.get_sset_count = dp83tg720_get_sset_count,
+	.get_strings	= dp83tg720_get_strings,
+	.get_stats	= dp83tg720_get_stats,
 
 	.suspend	= genphy_suspend,
 	.resume		= genphy_resume,