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
next prev 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