Linux Documentation
 help / color / mirror / Atom feed
From: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, andrew+netdev@lunn.ch, corbet@lwn.net
Cc: horms@kernel.org, skhan@linuxfoundation.org,
	rdunlap@infradead.org, netdev@vger.kernel.org,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	Subash Abhinov Kasiviswanathan
	<subash.a.kasiviswanathan@oss.qualcomm.com>,
	Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
Subject: [PATCH net-next 6/7] net: qualcomm: rmnet: Add ethtool stats for DL coalescing
Date: Tue, 29 Sep 2026 22:13:44 -0700	[thread overview]
Message-ID: <20260930051345.857443-7-subash.a.kasiviswanathan@oss.qualcomm.com> (raw)
In-Reply-To: <20260930051345.857443-1-subash.a.kasiviswanathan@oss.qualcomm.com>

Track coalescing decode activity with per-device counters and expose
them via ethtool. The counters include the coalescing related packet
counts, coalescing errors and reasons for coalescing completion.

Co-developed-by: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
Signed-off-by: Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>
Signed-off-by: Subash Abhinov Kasiviswanathan <subash.a.kasiviswanathan@oss.qualcomm.com>
---
 .../ethernet/qualcomm/rmnet/rmnet_config.h    |  20 ++++
 .../ethernet/qualcomm/rmnet/rmnet_map_data.c  | 110 ++++++++++++++++--
 .../net/ethernet/qualcomm/rmnet/rmnet_vnd.c   |  20 ++++
 3 files changed, 138 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
index 5adda0323dda..2a34f8a7b527 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
@@ -80,6 +80,26 @@ struct rmnet_priv_stats {
 	u64 csum_skipped;
 	u64 csum_sw;
 	u64 csum_hw;
+	/* DL coalescing */
+	u64 coal_rx;
+	u64 coal_pkts;
+	u64 coal_hdr_nlo_err;
+	u64 coal_hdr_pkt_err;
+	u64 coal_csum_err;
+	u64 coal_csum_drop;
+	u64 coal_reconstruct;
+	u64 coal_ip_invalid;
+	u64 coal_trans_invalid;
+	/* close-reason sub-counters */
+	u64 coal_close_non_coal;
+	u64 coal_close_ip_miss;
+	u64 coal_close_trans_miss;
+	u64 coal_close_hw_nl;
+	u64 coal_close_hw_pkt;
+	u64 coal_close_hw_byte;
+	u64 coal_close_hw_time;
+	u64 coal_close_hw_evict;
+	u64 coal_close_coal;
 };
 
 struct rmnet_priv {
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
index 1f9e592e24b6..2e76bf5a5a90 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_map_data.c
@@ -658,6 +658,7 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
 			     bool csum_valid)
 {
 	u32 dlen = coal_meta->data_len * coal_meta->pkt_count;
+	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
 	u32 hlen = coal_meta->ip_len + coal_meta->trans_len;
 	struct sk_buff *skbn;
 
@@ -668,8 +669,10 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
 	if (!csum_valid && coal_meta->zero_csum)
 		csum_valid = true;
 
-	if (!csum_valid)
+	if (!csum_valid) {
+		priv->stats.coal_csum_drop++;
 		goto next_pkt;
+	}
 
 	skbn = alloc_skb(hlen + dlen + RMNET_MAP_DEAGGR_HEADROOM, GFP_ATOMIC);
 	if (!skbn)
@@ -722,6 +725,7 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
 	rmnet_map_partial_csum(skbn, coal_meta);
 
 	skbn->dev = coal_skb->dev;
+	priv->stats.coal_reconstruct++;
 
 	if (coal_meta->pkt_count > 1)
 		rmnet_map_gso_stamp(skbn, coal_meta);
@@ -740,14 +744,17 @@ static bool rmnet_map_coal_parse_ip_hdr(struct sk_buff *coal_skb,
 					struct rmnet_map_coal_metadata *meta,
 					bool *gro)
 {
+	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
 	struct ipv6hdr *ip6h;
 	struct iphdr *iph;
 	__be16 frag_off;
 	u8 protocol;
 	int ret;
 
-	if (coal_skb->len < sizeof(*iph))
+	if (coal_skb->len < sizeof(*iph)) {
+		priv->stats.coal_ip_invalid++;
 		return false;
+	}
 
 	iph = (struct iphdr *)coal_skb->data;
 
@@ -756,25 +763,33 @@ static bool rmnet_map_coal_parse_ip_hdr(struct sk_buff *coal_skb,
 		meta->ip_len = iph->ihl * 4;
 		meta->trans_proto = iph->protocol;
 		meta->ip_header = iph;
-		if (meta->ip_len < sizeof(*iph) || coal_skb->len < meta->ip_len)
+		if (meta->ip_len < sizeof(*iph) || coal_skb->len < meta->ip_len) {
+			priv->stats.coal_ip_invalid++;
 			return false;
+		}
 
-		if (ip_is_fragment(iph))
+		if (ip_is_fragment(iph)) {
+			priv->stats.coal_ip_invalid++;
 			return false;
+		}
 
 		if (iph->ihl != 5)
 			*gro = false;
 	} else if (iph->version == 6) {
-		if (coal_skb->len < sizeof(*ip6h))
+		if (coal_skb->len < sizeof(*ip6h)) {
+			priv->stats.coal_ip_invalid++;
 			return false;
+		}
 
 		ip6h = (struct ipv6hdr *)iph;
 		protocol = ip6h->nexthdr;
 		meta->ip_proto = 6;
 		ret = ipv6_skip_exthdr(coal_skb, sizeof(*ip6h), &protocol,
 				       &frag_off);
-		if (ret < 0 || frag_off)
+		if (ret < 0 || frag_off) {
+			priv->stats.coal_ip_invalid++;
 			return false;
+		}
 
 		meta->ip_len = (u16)ret;
 		meta->trans_proto = protocol;
@@ -782,6 +797,7 @@ static bool rmnet_map_coal_parse_ip_hdr(struct sk_buff *coal_skb,
 		if (meta->ip_len > sizeof(*ip6h))
 			*gro = false;
 	} else {
+		priv->stats.coal_ip_invalid++;
 		return false;
 	}
 
@@ -794,6 +810,7 @@ static bool rmnet_map_coal_parse_ip_hdr(struct sk_buff *coal_skb,
 static bool rmnet_map_coal_parse_trans_hdr(struct sk_buff *coal_skb,
 					   struct rmnet_map_coal_metadata *meta)
 {
+	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
 	struct udphdr *uh;
 	struct tcphdr *th;
 	u32 avail;
@@ -803,17 +820,23 @@ static bool rmnet_map_coal_parse_trans_hdr(struct sk_buff *coal_skb,
 	avail = coal_skb->len - meta->ip_len;
 
 	if (meta->trans_proto == IPPROTO_TCP) {
-		if (avail < sizeof(*th))
+		if (avail < sizeof(*th)) {
+			priv->stats.coal_trans_invalid++;
 			return false;
+		}
 
 		th = (struct tcphdr *)base;
 		meta->trans_len = th->doff * 4;
 		meta->trans_header = th;
-		if (meta->trans_len < sizeof(*th) || avail < meta->trans_len)
+		if (meta->trans_len < sizeof(*th) || avail < meta->trans_len) {
+			priv->stats.coal_trans_invalid++;
 			return false;
+		}
 	} else if (meta->trans_proto == IPPROTO_UDP) {
-		if (avail < sizeof(*uh))
+		if (avail < sizeof(*uh)) {
+			priv->stats.coal_trans_invalid++;
 			return false;
+		}
 
 		uh = (struct udphdr *)base;
 		meta->trans_len = sizeof(*uh);
@@ -821,6 +844,7 @@ static bool rmnet_map_coal_parse_trans_hdr(struct sk_buff *coal_skb,
 		if (meta->ip_proto == 4 && !uh->check)
 			meta->zero_csum = true;
 	} else {
+		priv->stats.coal_trans_invalid++;
 		return false;
 	}
 
@@ -896,6 +920,7 @@ static void rmnet_map_coal_segment_loop(struct sk_buff *coal_skb,
 					struct sk_buff_head *list,
 					u64 nlo_err_mask, bool gro, u8 num_nlos)
 {
+	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
 	u32 hlen = coal_meta->ip_len + coal_meta->trans_len;
 	u8 pkt, total_pkt = 0;
 	bool csum_err;
@@ -916,6 +941,9 @@ static void rmnet_map_coal_segment_loop(struct sk_buff *coal_skb,
 		     pkt++, total_pkt++, nlo_err_mask >>= 1) {
 			csum_err = nlo_err_mask & 1;
 
+			if (csum_err)
+				priv->stats.coal_csum_err++;
+
 			if (!gro) {
 				coal_meta->pkt_count = 1;
 				__rmnet_map_segment_coal_skb(coal_skb, coal_meta,
@@ -999,6 +1027,49 @@ static int rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
 	return 0;
 }
 
+/* Log the hardware close-reason counter for a coalescing header. */
+static void rmnet_map_data_log_close_stats(struct rmnet_priv *priv,
+					   u8 type, u8 code)
+{
+	switch (type) {
+	case RMNET_MAP_COAL_CLOSE_NON_COAL:
+		priv->stats.coal_close_non_coal++;
+		break;
+	case RMNET_MAP_COAL_CLOSE_IP_MISS:
+		priv->stats.coal_close_ip_miss++;
+		break;
+	case RMNET_MAP_COAL_CLOSE_TRANS_MISS:
+		priv->stats.coal_close_trans_miss++;
+		break;
+	case RMNET_MAP_COAL_CLOSE_HW:
+		switch (code) {
+		case RMNET_MAP_COAL_CLOSE_HW_NL:
+			priv->stats.coal_close_hw_nl++;
+			break;
+		case RMNET_MAP_COAL_CLOSE_HW_PKT:
+			priv->stats.coal_close_hw_pkt++;
+			break;
+		case RMNET_MAP_COAL_CLOSE_HW_BYTE:
+			priv->stats.coal_close_hw_byte++;
+			break;
+		case RMNET_MAP_COAL_CLOSE_HW_TIME:
+			priv->stats.coal_close_hw_time++;
+			break;
+		case RMNET_MAP_COAL_CLOSE_HW_EVICT:
+			priv->stats.coal_close_hw_evict++;
+			break;
+		default:
+			break;
+		}
+		break;
+	case RMNET_MAP_COAL_CLOSE_COAL:
+		priv->stats.coal_close_coal++;
+		break;
+	default:
+		break;
+	}
+}
+
 /* Validate the coalescing header and build the checksum error mask.
  *
  * Checks performed:
@@ -1023,20 +1094,25 @@ static int rmnet_map_data_check_coal_header(struct sk_buff *skb,
 					    u64 *nlo_err_mask)
 {
 	struct rmnet_map_header *maph = (struct rmnet_map_header *)skb->data;
+	struct rmnet_priv *priv = netdev_priv(skb->dev);
 	struct rmnet_map_v5_coal_header *coal_hdr;
 	u8 num_nlos, pkts = 0;
 	u64 mask = 0;
 	int i;
 
 	/* coal header is counted in pkt_len */
-	if (ntohs(maph->pkt_len) < sizeof(*coal_hdr))
+	if (ntohs(maph->pkt_len) < sizeof(*coal_hdr)) {
+		priv->stats.coal_hdr_nlo_err++;
 		return -EINVAL;
+	}
 
 	coal_hdr = (struct rmnet_map_v5_coal_header *)(skb->data + sizeof(*maph));
 	num_nlos = u8_get_bits(coal_hdr->coal_info, MAPV5_COALINFO_NUM_NLOS_FMASK);
 
-	if (num_nlos == 0 || num_nlos > RMNET_MAP_V5_MAX_NLOS)
+	if (num_nlos == 0 || num_nlos > RMNET_MAP_V5_MAX_NLOS) {
+		priv->stats.coal_hdr_nlo_err++;
 		return -EINVAL;
+	}
 
 	for (i = 0; i < RMNET_MAP_V5_MAX_NLOS; i++) {
 		u8 err = coal_hdr->nl_pairs[i].csum_error_bitmap;
@@ -1044,10 +1120,19 @@ static int rmnet_map_data_check_coal_header(struct sk_buff *skb,
 
 		mask |= ((u64)err) << (8 * i);
 		pkts += pkt;
-		if (pkts > RMNET_MAP_V5_MAX_PACKETS)
+		if (pkts > RMNET_MAP_V5_MAX_PACKETS) {
+			priv->stats.coal_hdr_pkt_err++;
 			return -EINVAL;
+		}
 	}
 
+	priv->stats.coal_pkts += pkts;
+	rmnet_map_data_log_close_stats(priv,
+				       u8_get_bits(coal_hdr->close_info,
+						   MAPV5_CLOSEINFO_CLOSE_TYPE_FMASK),
+				       u8_get_bits(coal_hdr->close_info,
+						   MAPV5_CLOSEINFO_CLOSE_VALUE_FMASK));
+
 	*nlo_err_mask = mask;
 	return 0;
 }
@@ -1065,6 +1150,7 @@ int rmnet_map_process_next_hdr_packet(struct sk_buff *skb,
 		if (!(data_format & RMNET_FLAGS_INGRESS_COALESCE))
 			return -EINVAL;
 
+		priv->stats.coal_rx++;
 		rc = rmnet_map_data_check_coal_header(skb, &nlo_err_mask);
 		if (rc)
 			return rc;
diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
index e1e319683a55..a21d6543c014 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
@@ -183,6 +183,26 @@ static const char rmnet_gstrings_stats[][ETH_GSTRING_LEN] = {
 	"Checksum skipped",
 	"Checksum computed in software",
 	"Checksum computed in hardware",
+	/* DL coalescing */
+	"Coal frames received",
+	"Packets in coal frames",
+	"Coal hdr NLO errors",
+	"Coal hdr pkt count errors",
+	"Coal checksum errors",
+	"Coal packets dropped on csum err",
+	"Coal segments reconstructed",
+	"Coal invalid IP version",
+	"Coal invalid transport",
+	/* close reasons */
+	"Coal closed: non-coal",
+	"Coal closed: IP miss",
+	"Coal closed: transport miss",
+	"Coal closed: hw NL limit",
+	"Coal closed: hw pkt limit",
+	"Coal closed: hw byte limit",
+	"Coal closed: hw time limit",
+	"Coal closed: hw evict",
+	"Coal closed: FIN/PSH",
 };
 
 static void rmnet_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
-- 
2.34.1


  parent reply	other threads:[~2026-09-30  5:14 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30  5:13 [PATCH net-next 0/7] Add HW GRO handling in rmnet Subash Abhinov Kasiviswanathan
2026-09-30  5:13 ` [PATCH net-next 1/7] uapi: if_link: Add RMNET_FLAGS_INGRESS_COALESCE Subash Abhinov Kasiviswanathan
2026-10-04  6:05   ` netdev-bot+sashiko
2026-09-30  5:13 ` [PATCH net-next 2/7] net: qualcomm: rmnet: Process MAPv5 frames as a list Subash Abhinov Kasiviswanathan
2026-09-30  5:13 ` [PATCH net-next 3/7] net: qualcomm: rmnet: Restrict supported MAP checksum configurations Subash Abhinov Kasiviswanathan
2026-10-04  6:05   ` netdev-bot+sashiko
2026-09-30  5:13 ` [PATCH net-next 4/7] net: qualcomm: rmnet: Add DL packet coalescing support Subash Abhinov Kasiviswanathan
2026-10-04  6:05   ` netdev-bot+sashiko
2026-09-30  5:13 ` [PATCH net-next 5/7] net: qualcomm: rmnet: Work around coalescing hardware quirks Subash Abhinov Kasiviswanathan
2026-10-04  6:05   ` netdev-bot+sashiko
2026-09-30  5:13 ` Subash Abhinov Kasiviswanathan [this message]
2026-10-04  6:05   ` [PATCH net-next 6/7] net: qualcomm: rmnet: Add ethtool stats for DL coalescing netdev-bot+sashiko
2026-09-30  5:13 ` [PATCH net-next 7/7] docs: networking: Add documentation for the coalescing support in rmnet Subash Abhinov Kasiviswanathan
2026-10-04  6:05   ` netdev-bot+sashiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260930051345.857443-7-subash.a.kasiviswanathan@oss.qualcomm.com \
    --to=subash.a.kasiviswanathan@oss.qualcomm.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=corbet@lwn.net \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=rdunlap@infradead.org \
    --cc=sean.tranchetti@oss.qualcomm.com \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox