* [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads
@ 2026-11-10 22:06 Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 1/6] eth: mpnic: Tx checksum offload Daniel Zahka
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
Add checksum and segmentation offload support. Tx checksum offload
follows the generic csum_start/csum_offset model. Rx checksum also
follows the normal unnecessary/complete model. The device can validate l4
checksums for tcp or udp, when the l3 type is one of: ipv4, ipv6,
ipv4-in-ipv6, or ipv6-in-ipv6. The full l2 checksum of a frame is always
passed to driver via a Rx completion descriptor field, which can be used
for checksum complete on other packet types.
Tx segmentation supports tcp or udp in ipv4, ipv6, 4-in-6, or 6-in-6
natively, and can support most other tunnels with GSO_PARTIAL.
Counters for CSO/LSO are exported via the netdev qstats API.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
Changes in v2:
- eth: mpnic: Tx checksum offload
- use skb_network_offset() for the L2 header length in
mpnic_features_check(), since the mac header is not yet set when
called from the IP GSO slow path
- also enable Tx checksum offload for encapsulated packets via
hw_enc_features (moved from the IPv4 and IPv6 in IPv6 patch)
- eth: mpnic: Rx checksum offload
- convert l2_csum with cpu_to_le16() before csum_unfold(), since the
device reports it in network byte order and skb->csum was wrong on
big-endian hosts
- eth: mpnic: TCP segmentation offload
- count discarded skbs rather than segments in twq.dropped, matching
the xmit error path and the core's tx_dropped
- report only hw_gso_wire_packets; drop the super-packet counter
- eth: mpnic: UDP segmentation offload
- fall back to SW GSO for skbs shorter than MPNIC_MIN_FRAME_LEN, since
HW LSO would segment the zero padding as payload
- eth: mpnic: IPv4 and IPv6 in IPv6 tunnel segmentation
- derive inner L3 header length from csum_start rather than
inner_transport_header, which can be stale for DODGY skbs
- check the outer header is IPv6 in mpnic_ipxip6_gso_ok(), since
SKB_GSO_IPXIP6 can be set by a nested tunnel under an IPv4 outer
- move hw_enc_features setup to the Tx checksum offload patch
- eth: mpnic: add GSO_PARTIAL for other tunnels
- derive the L3 header length from csum_start rather than
inner_transport_header, matching mpnic_features_check() and
mpnic_tx_lso()
- Link to v1: https://lore.kernel.org/r/20261106-mpnic-lso-v1-0-e943ff1493e4@gmail.com
---
Daniel Zahka (6):
eth: mpnic: Tx checksum offload
eth: mpnic: Rx checksum offload
eth: mpnic: TCP segmentation offload
eth: mpnic: UDP segmentation offload
eth: mpnic: IPv4 and IPv6 in IPv6 tunnel segmentation
eth: mpnic: add GSO_PARTIAL for other tunnels
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 41 +++++
drivers/net/ethernet/meta/mpnic/mpnic_init.c | 14 ++
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 40 +++-
drivers/net/ethernet/meta/mpnic/mpnic_netdev.h | 5 +
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 242 ++++++++++++++++++++++++-
drivers/net/ethernet/meta/mpnic/mpnic_txrx.h | 8 +
6 files changed, 346 insertions(+), 4 deletions(-)
---
base-commit: 97ced2c090bfc2156db31fca9f52cf253e4bc186
change-id: 20261102-mpnic-lso-19aeb38e5648
Best regards,
--
Daniel Zahka <daniel.zahka@gmail.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 1/6] eth: mpnic: Tx checksum offload
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
@ 2026-11-10 22:06 ` Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 2/6] eth: mpnic: Rx " Daniel Zahka
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
Support generic Tx checksum offload per NETIF_F_HW_CSUM. Propagate all
netdev features to hw_enc_features, so CSO also works on encapsulated
skbs.
Add mpnic_features_check() to ensure that skb metadata can be mapped
into Tx descriptor fields. The headers and offsets used have units of
2B. The device also requires any headers modified to be in the first
256B of the packet.
Zero checksums are inverted by HW, matching the behavior of
skb_checksum_help().
Count CSO frames and report them via netdev qstats API.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
v2:
- use skb_network_offset() for the L2 header length in
mpnic_features_check(), since the mac header is not yet set when
called from the IP GSO slow path
- also enable Tx checksum offload for encapsulated packets via
hw_enc_features (moved from the IPv4 and IPv6 in IPv6 patch)
---
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 6 +++
drivers/net/ethernet/meta/mpnic/mpnic_init.c | 2 +
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 11 ++++-
drivers/net/ethernet/meta/mpnic/mpnic_netdev.h | 1 +
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 60 ++++++++++++++++++++++++++
drivers/net/ethernet/meta/mpnic/mpnic_txrx.h | 4 ++
6 files changed, 83 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
index d6ab9df58168..62304be203e6 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
@@ -13,6 +13,10 @@
#define DESC_GENMASK(h, l) GENMASK_ULL(h, l)
/* Transmit Work Descriptor Format */
+#define MPNIC_TWD_L2_HLEN DESC_GENMASK(5, 0)
+#define MPNIC_TWD_L3_IHLEN DESC_GENMASK(23, 16)
+#define MPNIC_TWD_CSUM_OFFSET DESC_GENMASK(27, 24)
+#define MPNIC_TWD_FLAG_REQ_CSO DESC_BIT(36)
#define MPNIC_TWD_FLAG_REQ_COMPLETION DESC_BIT(37)
#define MPNIC_TWD_FLAG_DEST_MAC DESC_BIT(43)
#define MPNIC_TWD_TYPE DESC_GENMASK(47, 46)
@@ -305,6 +309,8 @@ enum {
/* NIC_CORE_TOF */
#define MPNIC_TOF_TCAM_DEST_REMAP 0x7e3022 /* 0x1f8c088 */
+#define MPNIC_TOF_MODE 0x7e302e /* 0x1f8c0b8 */
+#define MPNIC_TOF_MODE_L4_ZERO_CSUM CSR_BIT(4)
/* PEMO_WRAPPER */
#define MPNIC_OB_ATTR_RO CSR_BIT(1)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_init.c b/drivers/net/ethernet/meta/mpnic/mpnic_init.c
index ace4124158fb..71d13bec7ec4 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_init.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_init.c
@@ -298,6 +298,8 @@ static void mpnic_txglb_init(struct mpnic_dev *mpd)
* programmed with its addresses.
*/
mpnic_wr64(mpd, MPNIC_TOF_TCAM_DEST_REMAP, 0);
+ mpnic_wr64(mpd, MPNIC_TOF_MODE,
+ FIELD_PREP(MPNIC_TOF_MODE_L4_ZERO_CSUM, 0));
mpnic_wr64(mpd, MPNIC_TCM_MEM_INIT_REQ, MPNIC_MEM_INIT_TCM_VAL);
mpnic_wr64(mpd, MPNIC_TDE_MEM_INIT_REQ, MPNIC_MEM_INIT_TDE_VAL);
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
index 7fdf57f70151..d386672738fc 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
@@ -144,6 +144,7 @@ static const struct net_device_ops mpnic_netdev_ops = {
.ndo_stop = mpnic_stop,
.ndo_validate_addr = eth_validate_addr,
.ndo_start_xmit = mpnic_xmit_frame,
+ .ndo_features_check = mpnic_features_check,
.ndo_get_stats64 = mpnic_get_stats64,
};
@@ -200,6 +201,7 @@ static void mpnic_get_queue_stats_tx(struct net_device *dev, int idx,
u64 bytes, packets, stop, wake;
struct mpnic_q_triad *qt;
unsigned int start;
+ u64 csum_partial;
if (!txr)
return;
@@ -217,11 +219,13 @@ static void mpnic_get_queue_stats_tx(struct net_device *dev, int idx,
stats = &txr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
+ csum_partial = u64_stats_read(&stats->twq.csum_partial);
stop = u64_stats_read(&stats->twq.stop);
} while (u64_stats_fetch_retry(&stats->syncp, start));
tx->bytes = bytes;
tx->packets = packets;
+ tx->needs_csum = csum_partial;
tx->stop = stop;
tx->wake = wake;
}
@@ -237,6 +241,7 @@ static void mpnic_get_base_stats(struct net_device *dev,
tx->bytes = base->tx.bytes;
tx->packets = base->tx.packets;
+ tx->needs_csum = base->tx.csum_partial;
tx->stop = base->tx.stop;
tx->wake = base->tx.wake;
@@ -303,9 +308,13 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
mpn->num_rx_queues = queues;
mpn->num_napi = queues;
- netdev->features |= NETIF_F_SG;
+ netdev->features |=
+ NETIF_F_SG |
+ NETIF_F_HW_CSUM;
+
netdev->hw_features |= netdev->features;
netdev->vlan_features |= netdev->features;
+ netdev->hw_enc_features |= netdev->features;
netdev->min_mtu = IPV6_MIN_MTU;
netdev->max_mtu = MPNIC_MAX_JUMBO_FRAME_SIZE - ETH_HLEN;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
index 6711766ebc3c..94f7a3355c5c 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
@@ -16,6 +16,7 @@ struct mpnic_base_stats {
u64 packets;
u64 bytes;
u64 dropped;
+ u64 csum_partial;
u64 stop;
u64 wake;
} tx;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
index 93abdec44642..65de6d999d54 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
@@ -8,6 +8,7 @@
#include <linux/slab.h>
#include <net/netdev_lock.h>
#include <net/page_pool/helpers.h>
+#include <net/tcp.h>
#include "mpnic.h"
#include "mpnic_netdev.h"
@@ -18,6 +19,10 @@ struct mpnic_xmit_cb {
u8 desc_count;
};
+struct mpnic_tx_hdrs {
+ unsigned int i3len;
+};
+
#define MPNIC_XMIT_CB(__skb) ((struct mpnic_xmit_cb *)((__skb)->cb))
#define MPNIC_TWD_TYPE_MASK(_type) \
cpu_to_le64(FIELD_PREP(MPNIC_TWD_TYPE, MPNIC_TWD_TYPE_##_type))
@@ -208,6 +213,34 @@ mpnic_tx_map(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
return true;
}
+static void mpnic_tx_fill_hdrs(struct sk_buff *skb, struct mpnic_tx_hdrs *hdrs)
+{
+ hdrs->i3len = skb_checksum_start(skb) - skb_network_header(skb);
+}
+
+static void
+mpnic_tx_offloads(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
+{
+ struct mpnic_tx_hdrs hdrs = {};
+
+ if (skb->ip_summed != CHECKSUM_PARTIAL)
+ return;
+
+ mpnic_tx_fill_hdrs(skb, &hdrs);
+
+ *meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_CSUM_OFFSET,
+ skb->csum_offset / 2) |
+ MPNIC_TWD_FLAG_REQ_CSO);
+
+ u64_stats_update_begin(&ring->stats.syncp);
+ u64_stats_inc(&ring->stats.twq.csum_partial);
+ u64_stats_update_end(&ring->stats.syncp);
+
+ *meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_L2_HLEN,
+ skb_mac_header_len(skb) / 2) |
+ FIELD_PREP(MPNIC_TWD_L3_IHLEN, hdrs.i3len / 2));
+}
+
#define MPNIC_MIN_FRAME_LEN 60
static netdev_tx_t mpnic_xmit_frame_ring(struct sk_buff *skb,
@@ -230,6 +263,8 @@ static netdev_tx_t mpnic_xmit_frame_ring(struct sk_buff *skb,
MPNIC_XMIT_CB(skb)->bytecount = skb->len;
MPNIC_XMIT_CB(skb)->desc_count = 0;
+ mpnic_tx_offloads(ring, skb, meta);
+
if (mpnic_tx_map(ring, skb, meta))
goto err_free;
@@ -256,6 +291,30 @@ netdev_tx_t mpnic_xmit_frame(struct sk_buff *skb, struct net_device *dev)
return mpnic_xmit_frame_ring(skb, mpn->tx[skb_get_queue_mapping(skb)]);
}
+netdev_features_t mpnic_features_check(struct sk_buff *skb,
+ struct net_device *dev,
+ netdev_features_t features)
+{
+ unsigned int l2len, i3len;
+
+ if (skb->ip_summed != CHECKSUM_PARTIAL)
+ return features;
+
+ l2len = skb_network_offset(skb);
+ if (skb_vlan_tag_present(skb))
+ l2len += VLAN_HLEN;
+ i3len = skb_checksum_start(skb) - skb_network_header(skb);
+
+ if ((l2len | i3len | skb->csum_offset) % 2 ||
+ !FIELD_FIT(MPNIC_TWD_L2_HLEN, l2len / 2) ||
+ !FIELD_FIT(MPNIC_TWD_L3_IHLEN, i3len / 2) ||
+ !FIELD_FIT(MPNIC_TWD_CSUM_OFFSET, skb->csum_offset / 2) ||
+ l2len + i3len + sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE > 256)
+ return features & ~(NETIF_F_CSUM_MASK | NETIF_F_GSO_MASK);
+
+ return features;
+}
+
static void mpnic_clean_twq0(struct mpnic_napi_vector *nv, int napi_budget,
struct mpnic_q_triad *qt, bool discard,
unsigned int hw_head)
@@ -721,6 +780,7 @@ static void mpnic_aggregate_ring_twq_counters(struct mpnic_base_stats *base,
struct mpnic_ring *twq)
{
base->tx.dropped += u64_stats_read(&twq->stats.twq.dropped);
+ base->tx.csum_partial += u64_stats_read(&twq->stats.twq.csum_partial);
base->tx.stop += u64_stats_read(&twq->stats.twq.stop);
}
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
index 52492e3f484f..a6dff9dc32d6 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
@@ -89,6 +89,7 @@ struct mpnic_queue_stats {
union {
struct {
u64_stats_t dropped;
+ u64_stats_t csum_partial;
u64_stats_t stop;
} twq;
struct {
@@ -168,6 +169,9 @@ struct mpnic_napi_vector {
};
netdev_tx_t mpnic_xmit_frame(struct sk_buff *skb, struct net_device *dev);
+netdev_features_t mpnic_features_check(struct sk_buff *skb,
+ struct net_device *dev,
+ netdev_features_t features);
int mpnic_alloc_napi_vectors(struct mpnic_net *mpn);
void mpnic_free_napi_vectors(struct mpnic_net *mpn);
void mpnic_stats_attach_rings(struct mpnic_net *mpn);
--
2.52.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 2/6] eth: mpnic: Rx checksum offload
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 1/6] eth: mpnic: Tx checksum offload Daniel Zahka
@ 2026-11-10 22:06 ` Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 3/6] eth: mpnic: TCP segmentation offload Daniel Zahka
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
The device reports two Rx completion fields to the driver related to
checksums: l2_csum and l4_csum_unnecessary. l2_csum is the ones'
complement sum of the frame starting from the end of the ethernet
header. l4_csum_unnecessary means the device validated the l4 checksum of
the packet. This is only supported for tcp or udp in
ipv4/ipv6/4-in-6/6-in-6.
Report checksum none/complete/unnecessary counts via netdev qstats API.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
v2:
- convert l2_csum with cpu_to_le16() before csum_unfold(), since the
device reports it in network byte order and skb->csum was wrong on
big-endian hosts
---
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 2 ++
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 14 ++++++++++-
drivers/net/ethernet/meta/mpnic/mpnic_netdev.h | 3 +++
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 34 ++++++++++++++++++++++++++
drivers/net/ethernet/meta/mpnic/mpnic_txrx.h | 3 +++
5 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
index 62304be203e6..1c86f114ab0c 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
@@ -60,6 +60,8 @@ enum {
#define MPNIC_RCD_AL_PAGE_FIN DESC_BIT(53)
/* Metadata Completion Descriptors */
+#define MPNIC_RCD_META_L2_CSUM DESC_GENMASK(47, 32)
+#define MPNIC_RCD_META_L4_CSUM_UNNECESSARY DESC_BIT(52)
#define MPNIC_RCD_META_ERR_MAC_EOP DESC_BIT(53)
#define MPNIC_RCD_META_ERR_TRUNCATED_FRAME DESC_BIT(54)
#define MPNIC_RCD_META_UNCORRECTABLE_ERR_MASK \
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
index d386672738fc..35173f8999cb 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
@@ -167,10 +167,12 @@ static void mpnic_get_queue_stats_rx(struct net_device *dev, int idx,
{
struct mpnic_net *mpn = netdev_priv(dev);
struct mpnic_ring *rxr = mpn->rx[idx];
+ u64 csum_unnecessary, csum_complete;
u64 bytes, packets, alloc_failed;
struct mpnic_queue_stats *stats;
struct mpnic_q_triad *qt;
unsigned int start;
+ u64 csum_none;
if (!rxr)
return;
@@ -181,6 +183,9 @@ static void mpnic_get_queue_stats_rx(struct net_device *dev, int idx,
bytes = u64_stats_read(&stats->rcq.bytes);
packets = u64_stats_read(&stats->rcq.packets);
alloc_failed = u64_stats_read(&stats->rcq.alloc_failed);
+ csum_complete = u64_stats_read(&stats->rcq.csum_complete);
+ csum_unnecessary = u64_stats_read(&stats->rcq.csum_unnecessary);
+ csum_none = u64_stats_read(&stats->rcq.csum_none);
} while (u64_stats_fetch_retry(&stats->syncp, start));
qt = container_of(rxr, struct mpnic_q_triad, cmpl);
@@ -190,6 +195,9 @@ static void mpnic_get_queue_stats_rx(struct net_device *dev, int idx,
rx->bytes = bytes;
rx->packets = packets;
rx->alloc_fail = alloc_failed;
+ rx->csum_complete = csum_complete;
+ rx->csum_unnecessary = csum_unnecessary;
+ rx->csum_none = csum_none;
}
static void mpnic_get_queue_stats_tx(struct net_device *dev, int idx,
@@ -248,6 +256,9 @@ static void mpnic_get_base_stats(struct net_device *dev,
rx->bytes = base->rx.bytes;
rx->packets = base->rx.packets;
rx->alloc_fail = base->rx.alloc_failed;
+ rx->csum_complete = base->rx.csum_complete;
+ rx->csum_unnecessary = base->rx.csum_unnecessary;
+ rx->csum_none = base->rx.csum_none;
}
static const struct netdev_stat_ops mpnic_stat_ops = {
@@ -310,7 +321,8 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
netdev->features |=
NETIF_F_SG |
- NETIF_F_HW_CSUM;
+ NETIF_F_HW_CSUM |
+ NETIF_F_RXCSUM;
netdev->hw_features |= netdev->features;
netdev->vlan_features |= netdev->features;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
index 94f7a3355c5c..76ec9811e79e 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
@@ -26,6 +26,9 @@ struct mpnic_base_stats {
u64 dropped;
u64 alloc_failed;
u64 errors;
+ u64 csum_complete;
+ u64 csum_unnecessary;
+ u64 csum_none;
} rx;
unsigned int num_tx_queues;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
index 65de6d999d54..a35ce8998409 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
@@ -655,9 +655,34 @@ static void mpnic_put_pkt_buff(struct mpnic_pkt_ctxt *ctxt, bool napi)
page_pool_put_full_page(page->pp, page, napi);
}
+static void mpnic_rx_csum(u64 rcd, struct sk_buff *skb,
+ unsigned int *csum_complete,
+ unsigned int *csum_unnecessary,
+ unsigned int *csum_none)
+{
+ skb_checksum_none_assert(skb);
+
+ if (!(skb->dev->features & NETIF_F_RXCSUM)) {
+ (*csum_none)++;
+ return;
+ }
+
+ if (FIELD_GET(MPNIC_RCD_META_L4_CSUM_UNNECESSARY, rcd)) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ (*csum_unnecessary)++;
+ } else {
+ u16 csum = FIELD_GET(MPNIC_RCD_META_L2_CSUM, rcd);
+
+ skb->ip_summed = CHECKSUM_COMPLETE;
+ skb->csum = csum_unfold((__force __sum16)cpu_to_le16(csum));
+ (*csum_complete)++;
+ }
+}
+
static int mpnic_clean_rcq(struct mpnic_napi_vector *nv,
struct mpnic_q_triad *qt, int budget)
{
+ unsigned int csum_complete = 0, csum_unnecessary = 0, csum_none = 0;
unsigned int packets = 0, bytes = 0, dropped = 0, errors = 0;
struct mpnic_ring *rcq = &qt->cmpl;
struct mpnic_rcq_state *state;
@@ -700,6 +725,8 @@ static int mpnic_clean_rcq(struct mpnic_napi_vector *nv,
}
if (likely(skb)) {
+ mpnic_rx_csum(rcd, skb, &csum_complete,
+ &csum_unnecessary, &csum_none);
napi_gro_receive(&nv->napi, skb);
} else {
mpnic_put_pkt_buff(&state->pkt, true);
@@ -732,6 +759,9 @@ static int mpnic_clean_rcq(struct mpnic_napi_vector *nv,
u64_stats_add(&rcq->stats.rcq.dropped, dropped);
u64_stats_add(&rcq->stats.rcq.alloc_failed, alloc_failed);
u64_stats_add(&rcq->stats.rcq.errors, errors);
+ u64_stats_add(&rcq->stats.rcq.csum_complete, csum_complete);
+ u64_stats_add(&rcq->stats.rcq.csum_unnecessary, csum_unnecessary);
+ u64_stats_add(&rcq->stats.rcq.csum_none, csum_none);
u64_stats_update_end(&rcq->stats.syncp);
/* Allocate buffers, force dma_wmb(), and then start writing tails */
@@ -806,6 +836,10 @@ static void mpnic_aggregate_ring_rcq_counters(struct mpnic_base_stats *base,
base->rx.dropped += u64_stats_read(&rcq->stats.rcq.dropped);
base->rx.alloc_failed += u64_stats_read(&rcq->stats.rcq.alloc_failed);
base->rx.errors += u64_stats_read(&rcq->stats.rcq.errors);
+ base->rx.csum_complete += u64_stats_read(&rcq->stats.rcq.csum_complete);
+ base->rx.csum_unnecessary +=
+ u64_stats_read(&rcq->stats.rcq.csum_unnecessary);
+ base->rx.csum_none += u64_stats_read(&rcq->stats.rcq.csum_none);
}
static struct mpnic_base_stats *mpnic_base_stats_prepare(struct mpnic_net *mpn)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
index a6dff9dc32d6..714f09f4160e 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
@@ -103,6 +103,9 @@ struct mpnic_queue_stats {
u64_stats_t dropped;
u64_stats_t alloc_failed;
u64_stats_t errors;
+ u64_stats_t csum_complete;
+ u64_stats_t csum_unnecessary;
+ u64_stats_t csum_none;
} rcq;
struct {
u64_stats_t alloc_failed;
--
2.52.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 3/6] eth: mpnic: TCP segmentation offload
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 1/6] eth: mpnic: Tx checksum offload Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 2/6] eth: mpnic: Rx " Daniel Zahka
@ 2026-11-10 22:06 ` Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 4/6] eth: mpnic: UDP " Daniel Zahka
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
Support segmentation of non-encapsulated tcp4/tcp6 packets. When doing
segmentation the device expects the l4 checksum to be seeded with 0
length.
IPv4 header and TCP checksums that compute to zero are sent as 0 rather
than 0xffff when the HW segments packets. IPv4 IDs are incrementing
starting from the value that is in the skb's IPv4 header.
Count gso wire packets and report via the netdev qstats API.
Completions are counted in wire packets, but dropped TSO skbs are still
counted once per super packet, matching the core's per skb tx_dropped
accounting that gets summed into the same counter.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
v2:
- count discarded skbs rather than segments in twq.dropped, matching
the xmit error path and the core's tx_dropped
- report only hw_gso_wire_packets; drop the super-packet counter
---
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 27 ++++++++
drivers/net/ethernet/meta/mpnic/mpnic_init.c | 11 ++++
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 14 ++--
drivers/net/ethernet/meta/mpnic/mpnic_netdev.h | 1 +
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 88 ++++++++++++++++++++++----
drivers/net/ethernet/meta/mpnic/mpnic_txrx.h | 1 +
6 files changed, 125 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
index 1c86f114ab0c..672ce0a98c22 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
@@ -16,8 +16,21 @@
#define MPNIC_TWD_L2_HLEN DESC_GENMASK(5, 0)
#define MPNIC_TWD_L3_IHLEN DESC_GENMASK(23, 16)
#define MPNIC_TWD_CSUM_OFFSET DESC_GENMASK(27, 24)
+#define MPNIC_TWD_L4_HLEN DESC_GENMASK(31, 28)
+#define MPNIC_TWD_L4_TYPE DESC_GENMASK(33, 32)
+enum {
+ MPNIC_TWD_L4_TYPE_TCP = 1,
+};
+
+#define MPNIC_TWD_FLAG_REQ_LSO DESC_BIT(35)
#define MPNIC_TWD_FLAG_REQ_CSO DESC_BIT(36)
#define MPNIC_TWD_FLAG_REQ_COMPLETION DESC_BIT(37)
+#define MPNIC_TWD_L3_TYPE DESC_GENMASK(42, 40)
+enum {
+ MPNIC_TWD_L3_TYPE_IPV4 = 1,
+ MPNIC_TWD_L3_TYPE_IPV6 = 2,
+};
+
#define MPNIC_TWD_FLAG_DEST_MAC DESC_BIT(43)
#define MPNIC_TWD_TYPE DESC_GENMASK(47, 46)
enum {
@@ -26,6 +39,8 @@ enum {
MPNIC_TWD_TYPE_LAST_AL = 3,
};
+#define MPNIC_TWD_MSS DESC_GENMASK(61, 48)
+
#define MPNIC_TWD_ADDR DESC_GENMASK(45, 0)
#define MPNIC_TWD_LEN DESC_GENMASK(63, 48)
@@ -312,7 +327,19 @@ enum {
/* NIC_CORE_TOF */
#define MPNIC_TOF_TCAM_DEST_REMAP 0x7e3022 /* 0x1f8c088 */
#define MPNIC_TOF_MODE 0x7e302e /* 0x1f8c0b8 */
+#define MPNIC_TOF_MODE_IPV4_ID_MODE CSR_GENMASK(0, 0)
+enum {
+ MPNIC_IPV4_ID_MODE_FIXED = 0,
+ MPNIC_IPV4_ID_MODE_INCREMENT = 1,
+};
+
+#define MPNIC_TOF_MODE_IPV4_ZERO_CSUM CSR_BIT(1)
+#define MPNIC_TOF_MODE_TCP_ZERO_CSUM CSR_BIT(2)
#define MPNIC_TOF_MODE_L4_ZERO_CSUM CSR_BIT(4)
+#define MPNIC_TOF_TCP_FLAG_CLR 0x7e3030 /* 0x1f8c0c0 */
+#define MPNIC_TOF_TCP_FLAG_CLR_FIRST_SEGMENT CSR_GENMASK(8, 0)
+#define MPNIC_TOF_TCP_FLAG_CLR_MIDDLE_SEGMENT CSR_GENMASK(24, 16)
+#define MPNIC_TOF_TCP_FLAG_CLR_LAST_SEGMENT CSR_GENMASK(40, 32)
/* PEMO_WRAPPER */
#define MPNIC_OB_ATTR_RO CSR_BIT(1)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_init.c b/drivers/net/ethernet/meta/mpnic/mpnic_init.c
index 71d13bec7ec4..b9a41656f8f7 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_init.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_init.c
@@ -8,6 +8,7 @@
#include <linux/iopoll.h>
#include <linux/log2.h>
#include <linux/sizes.h>
+#include <net/tcp.h>
#include "mpnic.h"
#include "mpnic_txrx.h"
@@ -299,7 +300,17 @@ static void mpnic_txglb_init(struct mpnic_dev *mpd)
*/
mpnic_wr64(mpd, MPNIC_TOF_TCAM_DEST_REMAP, 0);
mpnic_wr64(mpd, MPNIC_TOF_MODE,
+ FIELD_PREP(MPNIC_TOF_MODE_IPV4_ID_MODE,
+ MPNIC_IPV4_ID_MODE_INCREMENT) |
+ FIELD_PREP(MPNIC_TOF_MODE_IPV4_ZERO_CSUM, 1) |
+ FIELD_PREP(MPNIC_TOF_MODE_TCP_ZERO_CSUM, 1) |
FIELD_PREP(MPNIC_TOF_MODE_L4_ZERO_CSUM, 0));
+ mpnic_wr64(mpd, MPNIC_TOF_TCP_FLAG_CLR,
+ FIELD_PREP(MPNIC_TOF_TCP_FLAG_CLR_FIRST_SEGMENT,
+ TCPHDR_PSH | TCPHDR_FIN) |
+ FIELD_PREP(MPNIC_TOF_TCP_FLAG_CLR_MIDDLE_SEGMENT,
+ TCPHDR_PSH | TCPHDR_CWR | TCPHDR_FIN) |
+ FIELD_PREP(MPNIC_TOF_TCP_FLAG_CLR_LAST_SEGMENT, TCPHDR_CWR));
mpnic_wr64(mpd, MPNIC_TCM_MEM_INIT_REQ, MPNIC_MEM_INIT_TCM_VAL);
mpnic_wr64(mpd, MPNIC_TDE_MEM_INIT_REQ, MPNIC_MEM_INIT_TDE_VAL);
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
index 35173f8999cb..bde327ad3154 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
@@ -207,9 +207,9 @@ static void mpnic_get_queue_stats_tx(struct net_device *dev, int idx,
struct mpnic_ring *txr = mpn->tx[idx];
struct mpnic_queue_stats *stats;
u64 bytes, packets, stop, wake;
+ u64 csum_partial, hw_gso_wire;
struct mpnic_q_triad *qt;
unsigned int start;
- u64 csum_partial;
if (!txr)
return;
@@ -228,12 +228,14 @@ static void mpnic_get_queue_stats_tx(struct net_device *dev, int idx,
do {
start = u64_stats_fetch_begin(&stats->syncp);
csum_partial = u64_stats_read(&stats->twq.csum_partial);
+ hw_gso_wire = u64_stats_read(&stats->twq.hw_gso_wire);
stop = u64_stats_read(&stats->twq.stop);
} while (u64_stats_fetch_retry(&stats->syncp, start));
tx->bytes = bytes;
tx->packets = packets;
- tx->needs_csum = csum_partial;
+ tx->needs_csum = csum_partial + hw_gso_wire;
+ tx->hw_gso_wire_packets = hw_gso_wire;
tx->stop = stop;
tx->wake = wake;
}
@@ -249,7 +251,8 @@ static void mpnic_get_base_stats(struct net_device *dev,
tx->bytes = base->tx.bytes;
tx->packets = base->tx.packets;
- tx->needs_csum = base->tx.csum_partial;
+ tx->needs_csum = base->tx.csum_partial + base->tx.hw_gso_wire;
+ tx->hw_gso_wire_packets = base->tx.hw_gso_wire;
tx->stop = base->tx.stop;
tx->wake = base->tx.wake;
@@ -322,7 +325,10 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
netdev->features |=
NETIF_F_SG |
NETIF_F_HW_CSUM |
- NETIF_F_RXCSUM;
+ NETIF_F_RXCSUM |
+ NETIF_F_TSO |
+ NETIF_F_TSO_ECN |
+ NETIF_F_TSO6;
netdev->hw_features |= netdev->features;
netdev->vlan_features |= netdev->features;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
index 76ec9811e79e..697b3ebb74b9 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.h
@@ -17,6 +17,7 @@ struct mpnic_base_stats {
u64 bytes;
u64 dropped;
u64 csum_partial;
+ u64 hw_gso_wire;
u64 stop;
u64 wake;
} tx;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
index a35ce8998409..b78f8599bbe8 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
@@ -16,11 +16,14 @@
struct mpnic_xmit_cb {
u32 bytecount;
+ u16 gso_segs;
u8 desc_count;
};
struct mpnic_tx_hdrs {
unsigned int i3len;
+ unsigned int l3_type;
+ unsigned int l4_type;
};
#define MPNIC_XMIT_CB(__skb) ((struct mpnic_xmit_cb *)((__skb)->cb))
@@ -215,30 +218,81 @@ mpnic_tx_map(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
static void mpnic_tx_fill_hdrs(struct sk_buff *skb, struct mpnic_tx_hdrs *hdrs)
{
- hdrs->i3len = skb_checksum_start(skb) - skb_network_header(skb);
+ if (!skb_is_gso(skb)) {
+ hdrs->i3len = skb_checksum_start(skb) - skb_network_header(skb);
+ return;
+ }
+
+ hdrs->l4_type = MPNIC_TWD_L4_TYPE_TCP;
+ hdrs->i3len = skb_transport_offset(skb) - skb_network_offset(skb);
+ hdrs->l3_type = ip_hdr(skb)->version == 4 ?
+ MPNIC_TWD_L3_TYPE_IPV4 : MPNIC_TWD_L3_TYPE_IPV6;
}
-static void
+static bool
+mpnic_tx_lso(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
+{
+ struct skb_shared_info *shinfo;
+ unsigned int l4len, hdrlen;
+ struct tcphdr *tcph;
+ __be16 payload_len;
+
+ if (skb_cow_head(skb, 0))
+ return true;
+
+ shinfo = skb_shinfo(skb);
+
+ tcph = (struct tcphdr *)skb_checksum_start(skb);
+ l4len = __tcp_hdrlen(tcph);
+ payload_len = cpu_to_be16(skb->len - skb_checksum_start_offset(skb));
+ csum_replace_by_diff(&tcph->check, (__force __wsum)payload_len);
+
+ *meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_L4_HLEN, l4len / 4) |
+ FIELD_PREP(MPNIC_TWD_MSS, shinfo->gso_size) |
+ MPNIC_TWD_FLAG_REQ_LSO);
+
+ hdrlen = skb_checksum_start_offset(skb) + l4len;
+ MPNIC_XMIT_CB(skb)->bytecount += (shinfo->gso_segs - 1) * hdrlen;
+ MPNIC_XMIT_CB(skb)->gso_segs = shinfo->gso_segs;
+
+ u64_stats_update_begin(&ring->stats.syncp);
+ u64_stats_add(&ring->stats.twq.hw_gso_wire, shinfo->gso_segs);
+ u64_stats_update_end(&ring->stats.syncp);
+
+ return false;
+}
+
+static bool
mpnic_tx_offloads(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
{
struct mpnic_tx_hdrs hdrs = {};
if (skb->ip_summed != CHECKSUM_PARTIAL)
- return;
+ return false;
mpnic_tx_fill_hdrs(skb, &hdrs);
*meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_CSUM_OFFSET,
- skb->csum_offset / 2) |
- MPNIC_TWD_FLAG_REQ_CSO);
+ skb->csum_offset / 2));
- u64_stats_update_begin(&ring->stats.syncp);
- u64_stats_inc(&ring->stats.twq.csum_partial);
- u64_stats_update_end(&ring->stats.syncp);
+ if (skb_is_gso(skb)) {
+ if (mpnic_tx_lso(ring, skb, meta))
+ return true;
+ } else {
+ *meta |= cpu_to_le64(MPNIC_TWD_FLAG_REQ_CSO);
+
+ u64_stats_update_begin(&ring->stats.syncp);
+ u64_stats_inc(&ring->stats.twq.csum_partial);
+ u64_stats_update_end(&ring->stats.syncp);
+ }
*meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_L2_HLEN,
skb_mac_header_len(skb) / 2) |
- FIELD_PREP(MPNIC_TWD_L3_IHLEN, hdrs.i3len / 2));
+ FIELD_PREP(MPNIC_TWD_L3_IHLEN, hdrs.i3len / 2) |
+ FIELD_PREP(MPNIC_TWD_L3_TYPE, hdrs.l3_type) |
+ FIELD_PREP(MPNIC_TWD_L4_TYPE, hdrs.l4_type));
+
+ return false;
}
#define MPNIC_MIN_FRAME_LEN 60
@@ -261,9 +315,11 @@ static netdev_tx_t mpnic_xmit_frame_ring(struct sk_buff *skb,
*meta = cpu_to_le64(MPNIC_TWD_FLAG_DEST_MAC);
MPNIC_XMIT_CB(skb)->bytecount = skb->len;
+ MPNIC_XMIT_CB(skb)->gso_segs = 1;
MPNIC_XMIT_CB(skb)->desc_count = 0;
- mpnic_tx_offloads(ring, skb, meta);
+ if (mpnic_tx_offloads(ring, skb, meta))
+ goto err_free;
if (mpnic_tx_map(ring, skb, meta))
goto err_free;
@@ -312,6 +368,10 @@ netdev_features_t mpnic_features_check(struct sk_buff *skb,
l2len + i3len + sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE > 256)
return features & ~(NETIF_F_CSUM_MASK | NETIF_F_GSO_MASK);
+ if (skb_is_gso(skb) &&
+ !FIELD_FIT(MPNIC_TWD_MSS, skb_shinfo(skb)->gso_size))
+ return features & ~NETIF_F_GSO_MASK;
+
return features;
}
@@ -319,7 +379,7 @@ static void mpnic_clean_twq0(struct mpnic_napi_vector *nv, int napi_budget,
struct mpnic_q_triad *qt, bool discard,
unsigned int hw_head)
{
- u64 total_bytes = 0, total_packets = 0;
+ u64 total_bytes = 0, total_packets = 0, total_skbs = 0;
struct mpnic_ring *ring = &qt->sub0;
struct mpnic_ring *cmpl = &qt->cmpl;
unsigned int head = ring->head;
@@ -357,7 +417,8 @@ static void mpnic_clean_twq0(struct mpnic_napi_vector *nv, int napi_budget,
}
total_bytes += MPNIC_XMIT_CB(skb)->bytecount;
- total_packets++;
+ total_packets += MPNIC_XMIT_CB(skb)->gso_segs;
+ total_skbs++;
napi_consume_skb(skb, napi_budget);
}
@@ -370,7 +431,7 @@ static void mpnic_clean_twq0(struct mpnic_napi_vector *nv, int napi_budget,
if (discard) {
local_bh_disable();
u64_stats_update_begin(&ring->stats.syncp);
- u64_stats_add(&ring->stats.twq.dropped, total_packets);
+ u64_stats_add(&ring->stats.twq.dropped, total_skbs);
u64_stats_update_end(&ring->stats.syncp);
local_bh_enable();
return;
@@ -811,6 +872,7 @@ static void mpnic_aggregate_ring_twq_counters(struct mpnic_base_stats *base,
{
base->tx.dropped += u64_stats_read(&twq->stats.twq.dropped);
base->tx.csum_partial += u64_stats_read(&twq->stats.twq.csum_partial);
+ base->tx.hw_gso_wire += u64_stats_read(&twq->stats.twq.hw_gso_wire);
base->tx.stop += u64_stats_read(&twq->stats.twq.stop);
}
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
index 714f09f4160e..0d732ac4dde9 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.h
@@ -90,6 +90,7 @@ struct mpnic_queue_stats {
struct {
u64_stats_t dropped;
u64_stats_t csum_partial;
+ u64_stats_t hw_gso_wire;
u64_stats_t stop;
} twq;
struct {
--
2.52.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 4/6] eth: mpnic: UDP segmentation offload
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
` (2 preceding siblings ...)
2026-11-10 22:06 ` [PATCH net-next v2 3/6] eth: mpnic: TCP segmentation offload Daniel Zahka
@ 2026-11-10 22:06 ` Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 5/6] eth: mpnic: IPv4 and IPv6 in IPv6 tunnel segmentation Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 6/6] eth: mpnic: add GSO_PARTIAL for other tunnels Daniel Zahka
5 siblings, 0 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
Support segmentation of non-encapsulated udp4/udp6 packets. Like for
tcp, the segmentation engine expects the udp csum to be seeded with 0
length.
The HW inverts UDP checksums computed as 0 during segmentation.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
v2:
- fall back to SW GSO for skbs shorter than MPNIC_MIN_FRAME_LEN, since
HW LSO would segment the zero padding as payload
---
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 2 ++
drivers/net/ethernet/meta/mpnic/mpnic_init.c | 1 +
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 3 ++-
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 36 +++++++++++++++++++-------
4 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
index 672ce0a98c22..2cd627d62bf5 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
@@ -20,6 +20,7 @@
#define MPNIC_TWD_L4_TYPE DESC_GENMASK(33, 32)
enum {
MPNIC_TWD_L4_TYPE_TCP = 1,
+ MPNIC_TWD_L4_TYPE_UDP = 2,
};
#define MPNIC_TWD_FLAG_REQ_LSO DESC_BIT(35)
@@ -335,6 +336,7 @@ enum {
#define MPNIC_TOF_MODE_IPV4_ZERO_CSUM CSR_BIT(1)
#define MPNIC_TOF_MODE_TCP_ZERO_CSUM CSR_BIT(2)
+#define MPNIC_TOF_MODE_UDP_ZERO_CSUM CSR_BIT(3)
#define MPNIC_TOF_MODE_L4_ZERO_CSUM CSR_BIT(4)
#define MPNIC_TOF_TCP_FLAG_CLR 0x7e3030 /* 0x1f8c0c0 */
#define MPNIC_TOF_TCP_FLAG_CLR_FIRST_SEGMENT CSR_GENMASK(8, 0)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_init.c b/drivers/net/ethernet/meta/mpnic/mpnic_init.c
index b9a41656f8f7..ab832ec5de0e 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_init.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_init.c
@@ -304,6 +304,7 @@ static void mpnic_txglb_init(struct mpnic_dev *mpd)
MPNIC_IPV4_ID_MODE_INCREMENT) |
FIELD_PREP(MPNIC_TOF_MODE_IPV4_ZERO_CSUM, 1) |
FIELD_PREP(MPNIC_TOF_MODE_TCP_ZERO_CSUM, 1) |
+ FIELD_PREP(MPNIC_TOF_MODE_UDP_ZERO_CSUM, 0) |
FIELD_PREP(MPNIC_TOF_MODE_L4_ZERO_CSUM, 0));
mpnic_wr64(mpd, MPNIC_TOF_TCP_FLAG_CLR,
FIELD_PREP(MPNIC_TOF_TCP_FLAG_CLR_FIRST_SEGMENT,
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
index bde327ad3154..bf3b459ba5be 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
@@ -328,7 +328,8 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
NETIF_F_RXCSUM |
NETIF_F_TSO |
NETIF_F_TSO_ECN |
- NETIF_F_TSO6;
+ NETIF_F_TSO6 |
+ NETIF_F_GSO_UDP_L4;
netdev->hw_features |= netdev->features;
netdev->vlan_features |= netdev->features;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
index b78f8599bbe8..2a0560b7dd19 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
@@ -223,29 +223,44 @@ static void mpnic_tx_fill_hdrs(struct sk_buff *skb, struct mpnic_tx_hdrs *hdrs)
return;
}
- hdrs->l4_type = MPNIC_TWD_L4_TYPE_TCP;
+ if (skb_shinfo(skb)->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))
+ hdrs->l4_type = MPNIC_TWD_L4_TYPE_TCP;
+ else
+ hdrs->l4_type = MPNIC_TWD_L4_TYPE_UDP;
+
hdrs->i3len = skb_transport_offset(skb) - skb_network_offset(skb);
hdrs->l3_type = ip_hdr(skb)->version == 4 ?
MPNIC_TWD_L3_TYPE_IPV4 : MPNIC_TWD_L3_TYPE_IPV6;
}
static bool
-mpnic_tx_lso(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
+mpnic_tx_lso(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta,
+ const struct mpnic_tx_hdrs *hdrs)
{
struct skb_shared_info *shinfo;
unsigned int l4len, hdrlen;
- struct tcphdr *tcph;
- __be16 payload_len;
+ unsigned char *l4hdr;
if (skb_cow_head(skb, 0))
return true;
shinfo = skb_shinfo(skb);
+ l4hdr = skb_checksum_start(skb);
+
+ if (hdrs->l4_type == MPNIC_TWD_L4_TYPE_TCP) {
+ struct tcphdr *tcph = (struct tcphdr *)l4hdr;
+ __be16 payload_len;
- tcph = (struct tcphdr *)skb_checksum_start(skb);
- l4len = __tcp_hdrlen(tcph);
- payload_len = cpu_to_be16(skb->len - skb_checksum_start_offset(skb));
- csum_replace_by_diff(&tcph->check, (__force __wsum)payload_len);
+ l4len = __tcp_hdrlen(tcph);
+ payload_len = cpu_to_be16(skb->len -
+ skb_checksum_start_offset(skb));
+ csum_replace_by_diff(&tcph->check, (__force __wsum)payload_len);
+ } else {
+ struct udphdr *udph = (struct udphdr *)l4hdr;
+
+ l4len = sizeof(*udph);
+ csum_replace_by_diff(&udph->check, (__force __wsum)udph->len);
+ }
*meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_L4_HLEN, l4len / 4) |
FIELD_PREP(MPNIC_TWD_MSS, shinfo->gso_size) |
@@ -276,7 +291,7 @@ mpnic_tx_offloads(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
skb->csum_offset / 2));
if (skb_is_gso(skb)) {
- if (mpnic_tx_lso(ring, skb, meta))
+ if (mpnic_tx_lso(ring, skb, meta, &hdrs))
return true;
} else {
*meta |= cpu_to_le64(MPNIC_TWD_FLAG_REQ_CSO);
@@ -369,7 +384,8 @@ netdev_features_t mpnic_features_check(struct sk_buff *skb,
return features & ~(NETIF_F_CSUM_MASK | NETIF_F_GSO_MASK);
if (skb_is_gso(skb) &&
- !FIELD_FIT(MPNIC_TWD_MSS, skb_shinfo(skb)->gso_size))
+ (skb->len < MPNIC_MIN_FRAME_LEN ||
+ !FIELD_FIT(MPNIC_TWD_MSS, skb_shinfo(skb)->gso_size)))
return features & ~NETIF_F_GSO_MASK;
return features;
--
2.52.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 5/6] eth: mpnic: IPv4 and IPv6 in IPv6 tunnel segmentation
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
` (3 preceding siblings ...)
2026-11-10 22:06 ` [PATCH net-next v2 4/6] eth: mpnic: UDP " Daniel Zahka
@ 2026-11-10 22:06 ` Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 6/6] eth: mpnic: add GSO_PARTIAL for other tunnels Daniel Zahka
5 siblings, 0 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
The device can fully segment 4-in-6 and 6-in-6 packet types. Add
mpnic_features_check_encap_gso() to check the skb for some additional
constraints, such as ip6 non-atomic frag ext header.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
v2:
- derive inner L3 header length from csum_start rather than
inner_transport_header, which can be stale for DODGY skbs
- check the outer header is IPv6 in mpnic_ipxip6_gso_ok(), since
SKB_GSO_IPXIP6 can be set by a nested tunnel under an IPv4 outer
- move hw_enc_features setup to the Tx checksum offload patch
---
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 3 ++
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 1 +
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 53 ++++++++++++++++++++++++--
3 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
index 2cd627d62bf5..63282916eb13 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
@@ -14,6 +14,7 @@
/* Transmit Work Descriptor Format */
#define MPNIC_TWD_L2_HLEN DESC_GENMASK(5, 0)
+#define MPNIC_TWD_L3_OHLEN DESC_GENMASK(15, 8)
#define MPNIC_TWD_L3_IHLEN DESC_GENMASK(23, 16)
#define MPNIC_TWD_CSUM_OFFSET DESC_GENMASK(27, 24)
#define MPNIC_TWD_L4_HLEN DESC_GENMASK(31, 28)
@@ -30,6 +31,8 @@ enum {
enum {
MPNIC_TWD_L3_TYPE_IPV4 = 1,
MPNIC_TWD_L3_TYPE_IPV6 = 2,
+ MPNIC_TWD_L3_TYPE_V6V6 = 3,
+ MPNIC_TWD_L3_TYPE_V6V4 = 4,
};
#define MPNIC_TWD_FLAG_DEST_MAC DESC_BIT(43)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
index bf3b459ba5be..032a5ee92a08 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
@@ -323,6 +323,7 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
mpn->num_napi = queues;
netdev->features |=
+ NETIF_F_GSO_IPXIP6 |
NETIF_F_SG |
NETIF_F_HW_CSUM |
NETIF_F_RXCSUM |
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
index 2a0560b7dd19..839feb4716b6 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
@@ -22,6 +22,7 @@ struct mpnic_xmit_cb {
struct mpnic_tx_hdrs {
unsigned int i3len;
+ unsigned int o3len;
unsigned int l3_type;
unsigned int l4_type;
};
@@ -218,6 +219,9 @@ mpnic_tx_map(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
static void mpnic_tx_fill_hdrs(struct sk_buff *skb, struct mpnic_tx_hdrs *hdrs)
{
+ unsigned int net_off = skb_network_offset(skb);
+ unsigned int inner_off;
+
if (!skb_is_gso(skb)) {
hdrs->i3len = skb_checksum_start(skb) - skb_network_header(skb);
return;
@@ -228,9 +232,17 @@ static void mpnic_tx_fill_hdrs(struct sk_buff *skb, struct mpnic_tx_hdrs *hdrs)
else
hdrs->l4_type = MPNIC_TWD_L4_TYPE_UDP;
- hdrs->i3len = skb_transport_offset(skb) - skb_network_offset(skb);
- hdrs->l3_type = ip_hdr(skb)->version == 4 ?
- MPNIC_TWD_L3_TYPE_IPV4 : MPNIC_TWD_L3_TYPE_IPV6;
+ if (!skb->encapsulation) {
+ hdrs->i3len = skb_transport_offset(skb) - net_off;
+ hdrs->l3_type = ip_hdr(skb)->version == 4 ?
+ MPNIC_TWD_L3_TYPE_IPV4 : MPNIC_TWD_L3_TYPE_IPV6;
+ } else {
+ inner_off = skb_inner_network_offset(skb);
+ hdrs->o3len = inner_off - net_off;
+ hdrs->i3len = skb_checksum_start_offset(skb) - inner_off;
+ hdrs->l3_type = inner_ip_hdr(skb)->version == 4 ?
+ MPNIC_TWD_L3_TYPE_V6V4 : MPNIC_TWD_L3_TYPE_V6V6;
+ }
}
static bool
@@ -304,6 +316,7 @@ mpnic_tx_offloads(struct mpnic_ring *ring, struct sk_buff *skb, __le64 *meta)
*meta |= cpu_to_le64(FIELD_PREP(MPNIC_TWD_L2_HLEN,
skb_mac_header_len(skb) / 2) |
FIELD_PREP(MPNIC_TWD_L3_IHLEN, hdrs.i3len / 2) |
+ FIELD_PREP(MPNIC_TWD_L3_OHLEN, hdrs.o3len / 2) |
FIELD_PREP(MPNIC_TWD_L3_TYPE, hdrs.l3_type) |
FIELD_PREP(MPNIC_TWD_L4_TYPE, hdrs.l4_type));
@@ -362,6 +375,35 @@ netdev_tx_t mpnic_xmit_frame(struct sk_buff *skb, struct net_device *dev)
return mpnic_xmit_frame_ring(skb, mpn->tx[skb_get_queue_mapping(skb)]);
}
+static bool mpnic_ipxip6_gso_ok(struct sk_buff *skb)
+{
+ struct ipv6hdr *ip6h;
+ __be16 frag_off;
+ u8 nexthdr;
+ int start;
+
+ ip6h = ipv6_hdr(skb);
+ if (ip6h->version != 6)
+ return false;
+
+ nexthdr = ip6h->nexthdr;
+ start = skb_network_offset(skb) + sizeof(*ip6h);
+ start = ipv6_skip_exthdr(skb, start, &nexthdr, &frag_off);
+
+ return !frag_off && start == skb_inner_network_offset(skb) &&
+ (nexthdr == IPPROTO_IPV6 || nexthdr == IPPROTO_IPIP);
+}
+
+static netdev_features_t
+mpnic_features_check_encap_gso(struct sk_buff *skb, netdev_features_t features)
+{
+ if (skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP6 &&
+ mpnic_ipxip6_gso_ok(skb))
+ return features;
+
+ return features & ~NETIF_F_GSO_IPXIP6;
+}
+
netdev_features_t mpnic_features_check(struct sk_buff *skb,
struct net_device *dev,
netdev_features_t features)
@@ -388,7 +430,10 @@ netdev_features_t mpnic_features_check(struct sk_buff *skb,
!FIELD_FIT(MPNIC_TWD_MSS, skb_shinfo(skb)->gso_size)))
return features & ~NETIF_F_GSO_MASK;
- return features;
+ if (!skb->encapsulation || !skb_is_gso(skb))
+ return features;
+
+ return mpnic_features_check_encap_gso(skb, features);
}
static void mpnic_clean_twq0(struct mpnic_napi_vector *nv, int napi_budget,
--
2.52.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net-next v2 6/6] eth: mpnic: add GSO_PARTIAL for other tunnels
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
` (4 preceding siblings ...)
2026-11-10 22:06 ` [PATCH net-next v2 5/6] eth: mpnic: IPv4 and IPv6 in IPv6 tunnel segmentation Daniel Zahka
@ 2026-11-10 22:06 ` Daniel Zahka
5 siblings, 0 replies; 7+ messages in thread
From: Daniel Zahka @ 2026-11-10 22:06 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Dimitri Daskalakis,
Mohsin Bashir
Cc: netdev, linux-kernel
Tunnels that are not natively supported for segmentation can be
supported with GSO_PARTIAL. For GSO_PARTIAL, the driver uses L3 type
OTHER, which tells the device to suppress any updates to the l3 headers
of the packet. As a consequence of this, MANGLEID is required when the
inner l3 is ipv4, and for tunnels that have an outer l3 of ipv4, we need
to check if DF is set in mpnic_features_check_encap_gso(), because the
device will not edit the IDs of the wire packets.
Signed-off-by: Daniel Zahka <daniel.zahka@gmail.com>
---
v2:
- derive the L3 header length from csum_start rather than
inner_transport_header, matching mpnic_features_check() and
mpnic_tx_lso()
---
drivers/net/ethernet/meta/mpnic/mpnic_csr.h | 1 +
drivers/net/ethernet/meta/mpnic/mpnic_netdev.c | 9 +++++++++
drivers/net/ethernet/meta/mpnic/mpnic_txrx.c | 21 ++++++++++++++++++++-
3 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
index 63282916eb13..03304e22d6ce 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_csr.h
@@ -29,6 +29,7 @@ enum {
#define MPNIC_TWD_FLAG_REQ_COMPLETION DESC_BIT(37)
#define MPNIC_TWD_L3_TYPE DESC_GENMASK(42, 40)
enum {
+ MPNIC_TWD_L3_TYPE_OTHER = 0,
MPNIC_TWD_L3_TYPE_IPV4 = 1,
MPNIC_TWD_L3_TYPE_IPV6 = 2,
MPNIC_TWD_L3_TYPE_V6V6 = 3,
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
index 032a5ee92a08..198aad631c65 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_netdev.c
@@ -322,7 +322,15 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
mpn->num_rx_queues = queues;
mpn->num_napi = queues;
+ netdev->gso_partial_features =
+ NETIF_F_GSO_GRE |
+ NETIF_F_GSO_GRE_CSUM |
+ NETIF_F_GSO_IPXIP4 |
+ NETIF_F_GSO_UDP_TUNNEL |
+ NETIF_F_GSO_UDP_TUNNEL_CSUM;
+
netdev->features |=
+ netdev->gso_partial_features |
NETIF_F_GSO_IPXIP6 |
NETIF_F_SG |
NETIF_F_HW_CSUM |
@@ -330,6 +338,7 @@ struct net_device *mpnic_netdev_alloc(struct mpnic_dev *mpd)
NETIF_F_TSO |
NETIF_F_TSO_ECN |
NETIF_F_TSO6 |
+ NETIF_F_GSO_PARTIAL |
NETIF_F_GSO_UDP_L4;
netdev->hw_features |= netdev->features;
diff --git a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
index 839feb4716b6..af5e490c112d 100644
--- a/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
+++ b/drivers/net/ethernet/meta/mpnic/mpnic_txrx.c
@@ -236,6 +236,9 @@ static void mpnic_tx_fill_hdrs(struct sk_buff *skb, struct mpnic_tx_hdrs *hdrs)
hdrs->i3len = skb_transport_offset(skb) - net_off;
hdrs->l3_type = ip_hdr(skb)->version == 4 ?
MPNIC_TWD_L3_TYPE_IPV4 : MPNIC_TWD_L3_TYPE_IPV6;
+ } else if (skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) {
+ hdrs->i3len = skb_checksum_start_offset(skb) - net_off;
+ hdrs->l3_type = MPNIC_TWD_L3_TYPE_OTHER;
} else {
inner_off = skb_inner_network_offset(skb);
hdrs->o3len = inner_off - net_off;
@@ -397,11 +400,27 @@ static bool mpnic_ipxip6_gso_ok(struct sk_buff *skb)
static netdev_features_t
mpnic_features_check_encap_gso(struct sk_buff *skb, netdev_features_t features)
{
+ struct iphdr *iph;
+
if (skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP6 &&
mpnic_ipxip6_gso_ok(skb))
return features;
- return features & ~NETIF_F_GSO_IPXIP6;
+ features &= ~NETIF_F_GSO_IPXIP6;
+
+ iph = ip_hdr(skb);
+ if (iph->version == 4 && !(iph->frag_off & htons(IP_DF)))
+ return features & ~NETIF_F_GSO_MASK;
+
+ /* HW can segment inner udp4 with fixed IDs, but there is no UDP
+ * analog for MANGLEID, so defer to SW GSO.
+ */
+ if (inner_ip_hdr(skb)->version == 4)
+ features &= ~NETIF_F_GSO_UDP_L4;
+ if (!(features & NETIF_F_TSO_MANGLEID))
+ features &= ~NETIF_F_TSO;
+
+ return features;
}
netdev_features_t mpnic_features_check(struct sk_buff *skb,
--
2.52.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-10 22:06 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 22:06 [PATCH net-next v2 0/6] eth: mpnic: checksum and segmentation offloads Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 1/6] eth: mpnic: Tx checksum offload Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 2/6] eth: mpnic: Rx " Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 3/6] eth: mpnic: TCP segmentation offload Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 4/6] eth: mpnic: UDP " Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 5/6] eth: mpnic: IPv4 and IPv6 in IPv6 tunnel segmentation Daniel Zahka
2026-11-10 22:06 ` [PATCH net-next v2 6/6] eth: mpnic: add GSO_PARTIAL for other tunnels Daniel Zahka
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®