From: MD Danish Anwar <danishanwar@ti.com>
Add driver support for viewing / changing the MAC Merge sublayer
parameters and dump the Mac Merge stats via ethtool ops: .set_mm(),
.get_mm() and .get_mm_stats().
The minimum size of non-final mPacket fragments supported by the firmware
without leading errors is 64 Bytes (in octets). Add a check to ensure
user passed tx_min_frag_size argument via ethtool, honors this .
Add pa stats registers to check statistics for preemption, which can be
dumped using ethtool ops.
Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
Signed-off-by: Meghana Malladi <m-malladi@ti.com>
---
v4-v3:
- Added pa-stats availability check inside emac_get_mm_stats() as flagged
by AI-generated review.
- Return -EOPNOTSUPP for SR1 devices inside ethtool_*_mm() functions
drivers/net/ethernet/ti/icssg/icssg_ethtool.c | 98 ++++++++++++++++++-
drivers/net/ethernet/ti/icssg/icssg_prueth.h | 5 +-
drivers/net/ethernet/ti/icssg/icssg_qos.h | 20 ++++
drivers/net/ethernet/ti/icssg/icssg_stats.c | 1 -
drivers/net/ethernet/ti/icssg/icssg_stats.h | 5 +
.../net/ethernet/ti/icssg/icssg_switch_map.h | 5 +
6 files changed, 130 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
index b715af21d23a..2176536a0989 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
@@ -6,7 +6,6 @@
*/
#include "icssg_prueth.h"
-#include "icssg_stats.h"
static void emac_get_drvinfo(struct net_device *ndev,
struct ethtool_drvinfo *info)
@@ -294,6 +293,100 @@ static int emac_set_per_queue_coalesce(struct net_device *ndev, u32 queue,
return 0;
}
+static int emac_get_mm(struct net_device *ndev, struct ethtool_mm_state *state)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth_qos_iet *iet = &emac->qos.iet;
+
+ if (emac->is_sr1)
+ return -EOPNOTSUPP;
+
+ state->tx_enabled = iet->fpe_enabled;
+ state->pmac_enabled = true;
+ state->tx_min_frag_size = iet->tx_min_frag_size;
+ /* 64Bytes is the minimum fragment size supported
+ * by the firmware. <64B leads to min frame errors
+ */
+ state->rx_min_frag_size = 64;
+ state->tx_active = iet->fpe_active;
+ state->verify_enabled = iet->mac_verify_configure;
+ state->verify_time = iet->verify_time_ms;
+
+ switch (iet->verify_status) {
+ case ICSSG_IETFPE_STATE_DISABLED:
+ state->verify_status = ETHTOOL_MM_VERIFY_STATUS_DISABLED;
+ break;
+ case ICSSG_IETFPE_STATE_SUCCEEDED:
+ state->verify_status = ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED;
+ break;
+ case ICSSG_IETFPE_STATE_FAILED:
+ state->verify_status = ETHTOOL_MM_VERIFY_STATUS_FAILED;
+ break;
+ default:
+ state->verify_status = ETHTOOL_MM_VERIFY_STATUS_UNKNOWN;
+ break;
+ }
+
+ /* 802.3-2018 clause 30.14.1.6, says that the aMACMergeVerifyTime
+ * variable has a range between 1 and 128 ms inclusive. Limit to that.
+ */
+ state->max_verify_time = ETHTOOL_MM_MAX_VERIFY_TIME_MS;
+
+ return 0;
+}
+
+static int emac_set_mm(struct net_device *ndev, struct ethtool_mm_cfg *cfg,
+ struct netlink_ext_ack *extack)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+ struct prueth_qos_iet *iet = &emac->qos.iet;
+ int err;
+
+ if (emac->is_sr1)
+ return -EOPNOTSUPP;
+
+ if (!cfg->pmac_enabled)
+ NL_SET_ERR_MSG_MOD(extack, "preemptible MAC is always enabled");
+
+ err = icssg_qos_frag_size_min_to_add(cfg->tx_min_frag_size, extack);
+ if (err)
+ return err;
+
+ iet->verify_time_ms = cfg->verify_time;
+ iet->tx_min_frag_size = cfg->tx_min_frag_size;
+
+ iet->fpe_enabled = cfg->tx_enabled;
+ iet->mac_verify_configure = cfg->verify_enabled;
+
+ /* Re-trigger the state machine to incorporate the updated configuration */
+ if (iet->fpe_enabled)
+ atomic_set(&iet->enable_fpe_config, 1);
+ else
+ atomic_set(&iet->enable_fpe_config, 0);
+
+ schedule_work(&iet->fpe_config_task);
+
+ return 0;
+}
+
+static void emac_get_mm_stats(struct net_device *ndev,
+ struct ethtool_mm_stats *s)
+{
+ struct prueth_emac *emac = netdev_priv(ndev);
+
+ if (emac->is_sr1)
+ return;
+
+ if (!emac->prueth->pa_stats)
+ return;
+
+ s->MACMergeFrameAssOkCount = emac_get_stat_by_name(emac, "FW_PREEMPT_ASSEMBLY_OK");
+ s->MACMergeFrameAssErrorCount = emac_get_stat_by_name(emac, "FW_PREEMPT_ASSEMBLY_ERR");
+ s->MACMergeFragCountRx = emac_get_stat_by_name(emac, "FW_PREEMPT_FRAG_CNT_RX");
+ s->MACMergeFragCountTx = emac_get_stat_by_name(emac, "FW_PREEMPT_FRAG_CNT_TX");
+ s->MACMergeFrameSmdErrorCount = emac_get_stat_by_name(emac, "FW_PREEMPT_BAD_FRAG");
+}
+
const struct ethtool_ops icssg_ethtool_ops = {
.get_drvinfo = emac_get_drvinfo,
.get_msglevel = emac_get_msglevel,
@@ -317,5 +410,8 @@ const struct ethtool_ops icssg_ethtool_ops = {
.set_eee = emac_set_eee,
.nway_reset = emac_nway_reset,
.get_rmon_stats = emac_get_rmon_stats,
+ .get_mm = emac_get_mm,
+ .set_mm = emac_set_mm,
+ .get_mm_stats = emac_get_mm_stats,
};
EXPORT_SYMBOL_GPL(icssg_ethtool_ops);
diff --git a/drivers/net/ethernet/ti/icssg/icssg_prueth.h b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
index 37de534e4d43..d9f95d6edc8d 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_prueth.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_prueth.h
@@ -45,6 +45,7 @@
#include "icss_iep.h"
#include "icssg_switch_map.h"
#include "icssg_qos.h"
+#include "icssg_stats.h"
#define PRUETH_MAX_MTU (2000 - ETH_HLEN - ETH_FCS_LEN)
#define PRUETH_MIN_PKT_SIZE (VLAN_ETH_ZLEN)
@@ -58,8 +59,8 @@
#define ICSSG_MAX_RFLOWS 8 /* per slice */
-#define ICSSG_NUM_PA_STATS 32
-#define ICSSG_NUM_MIIG_STATS 60
+#define ICSSG_NUM_PA_STATS ARRAY_SIZE(icssg_all_pa_stats)
+#define ICSSG_NUM_MIIG_STATS ARRAY_SIZE(icssg_all_miig_stats)
/* Number of ICSSG related stats */
#define ICSSG_NUM_STATS (ICSSG_NUM_MIIG_STATS + ICSSG_NUM_PA_STATS)
#define ICSSG_NUM_STANDARD_STATS 31
diff --git a/drivers/net/ethernet/ti/icssg/icssg_qos.h b/drivers/net/ethernet/ti/icssg/icssg_qos.h
index 653dbb57791d..bf84cc1b8282 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_qos.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_qos.h
@@ -57,4 +57,24 @@ void icssg_qos_link_up(struct net_device *ndev);
void icssg_qos_link_down(struct net_device *ndev);
int icssg_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
void *type_data);
+static inline int icssg_qos_frag_size_min_to_add(u32 min_frag_size,
+ struct netlink_ext_ack *extack)
+{
+ /* The minimum size of the non-final mPacket supported
+ * by the firmware is 64B and multiples of 64B.
+ */
+ if (min_frag_size < 64) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "tx_min_frag_size must be at least 64 bytes");
+ return -EINVAL;
+ }
+
+ if (min_frag_size % (ETH_ZLEN + ETH_FCS_LEN)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "tx_min_frag_size must be a multiple of 64 bytes");
+ return -EINVAL;
+ }
+
+ return 0;
+}
#endif /* __NET_TI_ICSSG_QOS_H */
diff --git a/drivers/net/ethernet/ti/icssg/icssg_stats.c b/drivers/net/ethernet/ti/icssg/icssg_stats.c
index 7159baa0155c..d27e1c48976f 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_stats.c
+++ b/drivers/net/ethernet/ti/icssg/icssg_stats.c
@@ -6,7 +6,6 @@
*/
#include "icssg_prueth.h"
-#include "icssg_stats.h"
#include <linux/regmap.h>
#define ICSSG_TX_PACKET_OFFSET 0xA0
diff --git a/drivers/net/ethernet/ti/icssg/icssg_stats.h b/drivers/net/ethernet/ti/icssg/icssg_stats.h
index 5ec0b38e0c67..f35ae1b4f846 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_stats.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_stats.h
@@ -189,6 +189,11 @@ static const struct icssg_pa_stats icssg_all_pa_stats[] = {
ICSSG_PA_STATS(FW_INF_DROP_PRIOTAGGED),
ICSSG_PA_STATS(FW_INF_DROP_NOTAG),
ICSSG_PA_STATS(FW_INF_DROP_NOTMEMBER),
+ ICSSG_PA_STATS(FW_PREEMPT_BAD_FRAG),
+ ICSSG_PA_STATS(FW_PREEMPT_ASSEMBLY_ERR),
+ ICSSG_PA_STATS(FW_PREEMPT_FRAG_CNT_TX),
+ ICSSG_PA_STATS(FW_PREEMPT_ASSEMBLY_OK),
+ ICSSG_PA_STATS(FW_PREEMPT_FRAG_CNT_RX),
ICSSG_PA_STATS(FW_RX_EOF_SHORT_FRMERR),
ICSSG_PA_STATS(FW_RX_B0_DROP_EARLY_EOF),
ICSSG_PA_STATS(FW_TX_JUMBO_FRM_CUTOFF),
diff --git a/drivers/net/ethernet/ti/icssg/icssg_switch_map.h b/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
index 7e053b8af3ec..855fd4ed0b3f 100644
--- a/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
+++ b/drivers/net/ethernet/ti/icssg/icssg_switch_map.h
@@ -256,6 +256,11 @@
#define FW_INF_DROP_PRIOTAGGED 0x0148
#define FW_INF_DROP_NOTAG 0x0150
#define FW_INF_DROP_NOTMEMBER 0x0158
+#define FW_PREEMPT_BAD_FRAG 0x0160
+#define FW_PREEMPT_ASSEMBLY_ERR 0x0168
+#define FW_PREEMPT_FRAG_CNT_TX 0x0170
+#define FW_PREEMPT_ASSEMBLY_OK 0x0178
+#define FW_PREEMPT_FRAG_CNT_RX 0x0180
#define FW_RX_EOF_SHORT_FRMERR 0x0188
#define FW_RX_B0_DROP_EARLY_EOF 0x0190
#define FW_TX_JUMBO_FRM_CUTOFF 0x0198
--
2.43.0
On Tue, Feb 24, 2026 at 06:18:03PM +0530, Meghana Malladi wrote:
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_qos.h b/drivers/net/ethernet/ti/icssg/icssg_qos.h
> index 653dbb57791d..bf84cc1b8282 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_qos.h
> +++ b/drivers/net/ethernet/ti/icssg/icssg_qos.h
> @@ -57,4 +57,24 @@ void icssg_qos_link_up(struct net_device *ndev);
> void icssg_qos_link_down(struct net_device *ndev);
> int icssg_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
> void *type_data);
> +static inline int icssg_qos_frag_size_min_to_add(u32 min_frag_size,
> + struct netlink_ext_ack *extack)
This function is poorly named.
It draws obvious inspiration from ethtool_mm_frag_size_min_to_add(),
which had the following meaning: "convert min_frag_size (input) to
addFragSize (output)". That's where the "min *to* add" comes from.
Your function does _not_ do that (nor is it used like that). It returns
negative failure, or 0. But that 0 is confusingly not an addFragSize
value, just "success".
> +{
> + /* The minimum size of the non-final mPacket supported
> + * by the firmware is 64B and multiples of 64B.
> + */
> + if (min_frag_size < 64) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "tx_min_frag_size must be at least 64 bytes");
> + return -EINVAL;
> + }
> +
> + if (min_frag_size % (ETH_ZLEN + ETH_FCS_LEN)) {
> + NL_SET_ERR_MSG_MOD(extack,
> + "tx_min_frag_size must be a multiple of 64 bytes");
The min_frag_size as passed by user space represents a value that
doesn't include the FCS length (this is what the "4" in the addFragSize
formula excludes: "64 x (1 + addFragSize) - 4").
If your firmware takes a value including FCS, make the adjustment
privately to the driver, but accept values from user space that are 4
octets larger than what is programmed to the firmware. This allows the
same user space commands to have the same meaning on your hardware.
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: ti: icssg-prueth: Add ethtool ops for Frame Preemption MAC Merge
This commit adds driver support for viewing and changing the MAC Merge
sublayer parameters and dumping MAC Merge statistics via ethtool
operations.
> diff --git a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
> index b715af21d23a..2176536a0989 100644
> --- a/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
> +++ b/drivers/net/ethernet/ti/icssg/icssg_ethtool.c
[ ... ]
> @@ -294,6 +293,100 @@ static int emac_set_per_queue_coalesce(struct net_device *ndev, u32 queue,
> return 0;
> }
>
> +static int emac_get_mm(struct net_device *ndev, struct ethtool_mm_state *state)
> +{
> + struct prueth_emac *emac = netdev_priv(ndev);
> + struct prueth_qos_iet *iet = &emac->qos.iet;
> +
> + if (emac->is_sr1)
> + return -EOPNOTSUPP;
> +
> + state->tx_enabled = iet->fpe_enabled;
> + state->pmac_enabled = true;
> + state->tx_min_frag_size = iet->tx_min_frag_size;
> + /* 64Bytes is the minimum fragment size supported
> + * by the firmware. <64B leads to min frame errors
> + */
> + state->rx_min_frag_size = 64;
> + state->tx_active = iet->fpe_active;
> + state->verify_enabled = iet->mac_verify_configure;
> + state->verify_time = iet->verify_time_ms;
> +
> + switch (iet->verify_status) {
> + case ICSSG_IETFPE_STATE_DISABLED:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_DISABLED;
> + break;
> + case ICSSG_IETFPE_STATE_SUCCEEDED:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED;
> + break;
> + case ICSSG_IETFPE_STATE_FAILED:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_FAILED;
> + break;
> + default:
> + state->verify_status = ETHTOOL_MM_VERIFY_STATUS_UNKNOWN;
> + break;
> + }
Does this switch statement handle all valid verify_status values?
The switch only explicitly handles ICSSG_IETFPE_STATE_DISABLED,
ICSSG_IETFPE_STATE_SUCCEEDED, and ICSSG_IETFPE_STATE_FAILED, falling
through to ETHTOOL_MM_VERIFY_STATUS_UNKNOWN for any other states.
Looking at the icssg_ietfpe_verify_states enum, there are also
ICSSG_IETFPE_STATE_INITIAL and ICSSG_IETFPE_STATE_VERIFYING states. The
ethtool UAPI defines ETHTOOL_MM_VERIFY_STATUS_INITIAL and
ETHTOOL_MM_VERIFY_STATUS_VERIFYING which directly correspond to these
states.
When the MAC Verify process is actively running, userspace will see
'unknown' instead of the more accurate 'verifying' status. This means
information about the ongoing state machine transition is lost.
Should the switch include explicit cases for ICSSG_IETFPE_STATE_INITIAL
and ICSSG_IETFPE_STATE_VERIFYING to provide more accurate status
reporting to userspace?
> +
> + /* 802.3-2018 clause 30.14.1.6, says that the aMACMergeVerifyTime
> + * variable has a range between 1 and 128 ms inclusive. Limit to that.
> + */
> + state->max_verify_time = ETHTOOL_MM_MAX_VERIFY_TIME_MS;
> +
> + return 0;
> +}
[ ... ]
© 2016 - 2026 Red Hat, Inc.