* [PATCH net] net: wan: fsl_ucc_hdlc: free tx_skbuff in uhdlc_memclean
From: Holger Brunck @ 2026-05-04 16:11 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, andrew+netdev, chleroy, qiang.zhao, horms,
Holger Brunck
When cleaning up the resources we need to iterate over the
tx_skbuf array to free pending TX messages.
Fixes: c19b6d246a35 ("drivers/net: support hdlc function for QE-UCC")
Signed-off-by: Holger Brunck <holger.brunck@hitachienergy.com>
---
drivers/net/wan/fsl_ucc_hdlc.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/wan/fsl_ucc_hdlc.c b/drivers/net/wan/fsl_ucc_hdlc.c
index adf3863463f5..68f78aeabdc3 100644
--- a/drivers/net/wan/fsl_ucc_hdlc.c
+++ b/drivers/net/wan/fsl_ucc_hdlc.c
@@ -739,6 +739,8 @@ static int uhdlc_open(struct net_device *dev)
static void uhdlc_memclean(struct ucc_hdlc_private *priv)
{
+ int i;
+
qe_muram_free(ioread16be(&priv->ucc_pram->riptr));
qe_muram_free(ioread16be(&priv->ucc_pram->tiptr));
@@ -769,6 +771,11 @@ static void uhdlc_memclean(struct ucc_hdlc_private *priv)
kfree(priv->rx_skbuff);
priv->rx_skbuff = NULL;
+ for (i = 0; i < TX_BD_RING_LEN) {
+ kfree(priv->tx_skbuff[i]);
+ priv->tx_skbuff[i] = NULL;
+ }
+
kfree(priv->tx_skbuff);
priv->tx_skbuff = NULL;
--
2.47.3
^ permalink raw reply related
* [PATCH iproute2-next v2 6/6] seg6: add support for the H.M.GTP4.D behavior
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the H.M.GTP4.D headend behavior, which translates
IPv4/GTP-U traffic into an SRv6 SR Policy. H.M.GTP4.D is installed
on an IPv4 route and reuses the existing src, v4_mask_len,
sr_prefix_len, and v6_src_prefix_len keywords.
The kernel validates that sr_prefix_len + v4_mask_len does not
exceed 88 bits and returns EINVAL via netlink extack otherwise,
since the locator, the embedded IPv4 destination, and the 40-bit
Args.Mob.Session field together must fit inside the 128-bit egress
SID.
Example:
ip -4 r a 10.0.0.0/24 encap seg6local action H.M.GTP4.D \
nh6 2001:db8:f:: src 2001:db8::1 \
v4_mask_len 32 sr_prefix_len 56 v6_src_prefix_len 64 dev sr0
Link: https://datatracker.ietf.org/doc/html/rfc9433
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/uapi/linux/seg6_local.h | 2 ++
ip/iproute.c | 2 +-
ip/iproute_lwtunnel.c | 1 +
man/man8/ip-route.8.in | 45 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 0ca8405df2f2..69a875fcad73 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -82,6 +82,8 @@ enum {
SEG6_LOCAL_ACTION_END_M_GTP6_D = 20,
/* IPv6/GTP-U decap into SRv6, drop-in mode (RFC 9433 Section 6.4) */
SEG6_LOCAL_ACTION_END_M_GTP6_D_DI = 21,
+ /* SR headend: IPv4/GTP-U decap, encap in SRv6 (RFC 9433 Section 6.7) */
+ SEG6_LOCAL_ACTION_H_M_GTP4_D = 22,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 40494ccf8eaa..d956b39a8ef8 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -107,7 +107,7 @@ static void usage(void)
" End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
" End.BM | End.S | End.AS | End.AM | End.BPF |\n"
" End.MAP | End.M.GTP4.E | End.M.GTP6.E |\n"
- " End.M.GTP6.D | End.M.GTP6.D.Di }\n"
+ " End.M.GTP6.D | End.M.GTP6.D.Di | H.M.GTP4.D }\n"
"OPTIONS := OPTION [ OPTIONS ]\n"
"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
" table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 570d95780ae4..0bb29e69dc52 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -410,6 +410,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_M_GTP6_E] = "End.M.GTP6.E",
[SEG6_LOCAL_ACTION_END_M_GTP6_D] = "End.M.GTP6.D",
[SEG6_LOCAL_ACTION_END_M_GTP6_D_DI] = "End.M.GTP6.D.Di",
+ [SEG6_LOCAL_ACTION_H_M_GTP4_D] = "H.M.GTP4.D",
};
static const char *format_action_type(int action)
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 0487338707c6..7badfcc1e8c3 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1133,6 +1133,51 @@ is rejected for this action: the original outer destination is
preserved verbatim instead of being repacked with an Args.Mob.Session
field, so no locator length needs to be carried.
+.B H.M.GTP4.D nh6
+.IR ADDRESS
+.B src
+.IR ADDRESS
+.B v4_mask_len
+.IR BITS
+.B sr_prefix_len
+.IR BITS
+.RB [ "v6_src_prefix_len"
+.IR BITS ]
+- SRv6 Mobile User Plane H.M.GTP4.D headend behavior (RFC 9433 Section
+6.7). Match an IPv4/UDP/GTP-U packet, strip the GTP-U envelope, and
+re-encapsulate the inner T-PDU in a new IPv6 header whose addresses
+encode the per-session identifiers expected by an
+.B End.M.GTP4.E
+SID at the egress SR gateway. The destination UPF prefix template is
+specified by
+.BR nh6 ,
+the source UPF prefix template by
+.BR src ;
+.B v4_mask_len
+is the bit length reserved for the original IPv4 destination/source
+address, and
+.B sr_prefix_len
+is the locator length of the egress End.M.GTP4.E SID (1..88).
+The 40-bit Args.Mob.Session field defined
+by RFC 9433 Section 6.1 follows the embedded IPv4 destination at the
+offset implied by
+.BR sr_prefix_len " + " v4_mask_len ;
+its width is fixed by the RFC and is not exposed as a knob.
+.BR sr_prefix_len " + " v4_mask_len
+must therefore be at most 88 bits so the resulting 128-bit SID can
+hold all three fields.
+.B v6_src_prefix_len
+controls the IPv6 SA layout per RFC 9433 Section 6.6 Figure 10
+(\fIP\fR | IPv4 SA | padding) in the same way as for End.M.GTP4.E:
+1..127, and
+.BR v6_src_prefix_len " + " v4_mask_len " <= 128" ;
+defaults to 64 when omitted.
+.PP
+.B Note:
+because H.M.GTP4.D matches IPv4/GTP-U packets, the route must be
+installed on the IPv4 FIB (\fBip -4 route add ...\fR); installing it
+under \fBip -6 route\fR is rejected by the kernel.
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
^ permalink raw reply related
* [PATCH iproute2-next v2 5/6] seg6: add support for the End.M.GTP6.D.Di behavior
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the End.M.GTP6.D.Di drop-in interconnect behavior,
which translates IPv6/GTP-U traffic into an SRv6 SR Policy while
preserving the original outer IPv6 destination as the final SRH
segment. Unlike End.M.GTP6.D, this behavior does not take
sr_prefix_len.
Example:
ip -6 r a 2001:db8:f::/64 encap seg6local action End.M.GTP6.D.Di \
srh segs 2001:db8:2::1,2001:db8:3::e \
src 2001:db8::1 dev sr0
Link: https://datatracker.ietf.org/doc/html/rfc9433
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/uapi/linux/seg6_local.h | 2 ++
ip/iproute.c | 2 +-
ip/iproute_lwtunnel.c | 2 ++
man/man8/ip-route.8.in | 16 ++++++++++++++++
4 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index ed44fb858600..0ca8405df2f2 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -80,6 +80,8 @@ enum {
SEG6_LOCAL_ACTION_END_M_GTP6_E = 19,
/* IPv6/GTP-U decap into SRv6 (RFC 9433 Section 6.3) */
SEG6_LOCAL_ACTION_END_M_GTP6_D = 20,
+ /* IPv6/GTP-U decap into SRv6, drop-in mode (RFC 9433 Section 6.4) */
+ SEG6_LOCAL_ACTION_END_M_GTP6_D_DI = 21,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 6cd19c2c2b00..40494ccf8eaa 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -107,7 +107,7 @@ static void usage(void)
" End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
" End.BM | End.S | End.AS | End.AM | End.BPF |\n"
" End.MAP | End.M.GTP4.E | End.M.GTP6.E |\n"
- " End.M.GTP6.D }\n"
+ " End.M.GTP6.D | End.M.GTP6.D.Di }\n"
"OPTIONS := OPTION [ OPTIONS ]\n"
"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
" table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index a63dfe379a89..570d95780ae4 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -409,6 +409,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_M_GTP4_E] = "End.M.GTP4.E",
[SEG6_LOCAL_ACTION_END_M_GTP6_E] = "End.M.GTP6.E",
[SEG6_LOCAL_ACTION_END_M_GTP6_D] = "End.M.GTP6.D",
+ [SEG6_LOCAL_ACTION_END_M_GTP6_D_DI] = "End.M.GTP6.D.Di",
};
static const char *format_action_type(int action)
@@ -632,6 +633,7 @@ static bool seg6local_action_excludes_final_seg(int action)
switch (action) {
case SEG6_LOCAL_ACTION_END_B6_ENCAP:
case SEG6_LOCAL_ACTION_END_M_GTP6_D:
+ case SEG6_LOCAL_ACTION_END_M_GTP6_D_DI:
return true;
default:
return false;
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 35e6e2080a1f..0487338707c6 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1117,6 +1117,22 @@ The action requires either no SRH or an SRH with
equal to zero on the inbound packet; other matching packets are
dropped.
+.B End.M.GTP6.D.Di srh segs
+.IR SEGMENTS
+.B src
+.IR ADDRESS
+- SRv6 Mobile User Plane End.M.GTP6.D.Di drop-in interconnect behavior
+(RFC 9433 Section 6.4). Identical to
+.B End.M.GTP6.D
+except that the original outer IPv6 destination address of the
+incoming GTP-U packet is preserved as the final segment of the new
+SRH, allowing existing SRv6 networks to be inserted into a legacy
+mobile path without changing the destination semantics.
+.B sr_prefix_len
+is rejected for this action: the original outer destination is
+preserved verbatim instead of being repacked with an Args.Mob.Session
+field, so no locator length needs to be carried.
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
^ permalink raw reply related
* [PATCH iproute2-next v2 4/6] seg6: add support for the End.M.GTP6.D behavior
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the End.M.GTP6.D behavior, which translates IPv6/GTP-U
traffic into an SRv6 SR Policy. The SR Policy is supplied through the
existing srh segs syntax, and a new sr_prefix_len keyword specifies
the locator length of the egress End.M.GTP6.E SID (1..88, leaving
40 bits for the Args.Mob.Session field).
Example:
ip -6 r a 2001:db8:f::/64 encap seg6local action End.M.GTP6.D \
srh segs 2001:db8:2::1,2001:db8:3::e \
src 2001:db8::1 sr_prefix_len 88 dev sr0
Link: https://datatracker.ietf.org/doc/html/rfc9433
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/uapi/linux/seg6_local.h | 3 +++
ip/iproute.c | 6 ++++--
ip/iproute_lwtunnel.c | 45 ++++++++++++++++++++++++++++++++++++++---
man/man8/ip-route.8.in | 29 ++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 5 deletions(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 6af145259ffb..ed44fb858600 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -33,6 +33,7 @@ enum {
SEG6_LOCAL_MOBILE_V4_MASK_LEN,
SEG6_LOCAL_MOBILE_PDU_TYPE,
SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN,
+ SEG6_LOCAL_MOBILE_SR_PREFIX_LEN,
__SEG6_LOCAL_MAX,
};
#define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
@@ -77,6 +78,8 @@ enum {
SEG6_LOCAL_ACTION_END_M_GTP4_E = 18,
/* SRv6 to IPv6/GTP-U encap (RFC 9433 Section 6.5) */
SEG6_LOCAL_ACTION_END_M_GTP6_E = 19,
+ /* IPv6/GTP-U decap into SRv6 (RFC 9433 Section 6.3) */
+ SEG6_LOCAL_ACTION_END_M_GTP6_D = 20,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index e009e7480e76..6cd19c2c2b00 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -106,11 +106,13 @@ static void usage(void)
"ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
" End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
" End.BM | End.S | End.AS | End.AM | End.BPF |\n"
- " End.MAP | End.M.GTP4.E | End.M.GTP6.E }\n"
+ " End.MAP | End.M.GTP4.E | End.M.GTP6.E |\n"
+ " End.M.GTP6.D }\n"
"OPTIONS := OPTION [ OPTIONS ]\n"
"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
" table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
- "MOBILE_OPTION := { src ADDR | v4_mask_len BITS | v6_src_prefix_len BITS |\n"
+ "MOBILE_OPTION := { src ADDR | v4_mask_len BITS | sr_prefix_len BITS |\n"
+ " v6_src_prefix_len BITS |\n"
" pdu_type { downlink | dl | uplink | ul | 0..15 } }\n"
"FLAVORS := { FLAVOR[,FLAVOR] }\n"
"FLAVOR := { psp | usp | usd | next-csid }\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 38e806b053c5..a63dfe379a89 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -408,6 +408,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_MAP] = "End.MAP",
[SEG6_LOCAL_ACTION_END_M_GTP4_E] = "End.M.GTP4.E",
[SEG6_LOCAL_ACTION_END_M_GTP6_E] = "End.M.GTP6.E",
+ [SEG6_LOCAL_ACTION_END_M_GTP6_D] = "End.M.GTP6.D",
};
static const char *format_action_type(int action)
@@ -589,6 +590,11 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
print_uint(PRINT_ANY, "v4_mask_len", "v4_mask_len %u ",
rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_V4_MASK_LEN]));
+ if (tb[SEG6_LOCAL_MOBILE_SR_PREFIX_LEN])
+ print_uint(PRINT_ANY, "sr_prefix_len",
+ "sr_prefix_len %u ",
+ rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_SR_PREFIX_LEN]));
+
if (tb[SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN])
print_uint(PRINT_ANY, "v6_src_prefix_len",
"v6_src_prefix_len %u ",
@@ -616,6 +622,22 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
}
}
+/*
+ * SRH-supplying actions (the seg6local equivalents of seg6 inline mode)
+ * pass the entire segment list explicitly; parse_srh() must not append the
+ * implicit terminating SID it adds for inline-style callers.
+ */
+static bool seg6local_action_excludes_final_seg(int action)
+{
+ switch (action) {
+ case SEG6_LOCAL_ACTION_END_B6_ENCAP:
+ case SEG6_LOCAL_ACTION_END_M_GTP6_D:
+ return true;
+ default:
+ return false;
+ }
+}
+
static void print_encap_mpls(FILE *fp, struct rtattr *encap)
{
struct rtattr *tb[MPLS_IPTUNNEL_MAX+1];
@@ -1489,7 +1511,7 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
int segs_ok = 0, hmac_ok = 0, table_ok = 0, vrftable_ok = 0;
int action_ok = 0, srh_ok = 0, bpf_ok = 0, counters_ok = 0;
int mobile_src_ok = 0, mobile_v4mask_ok = 0, mobile_pdusess_ok = 0;
- int mobile_v6src_plen_ok = 0;
+ int mobile_sr_plen_ok = 0, mobile_v6src_plen_ok = 0;
__u32 action = 0, table, vrftable, iif, oif;
struct ipv6_sr_hdr *srh;
char **argv = *argvp;
@@ -1497,7 +1519,7 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
char segbuf[1024];
inet_prefix addr;
__u32 hmac = 0;
- __u8 v4_mask_len = 0, v6_src_prefix_len = 0;
+ __u8 v4_mask_len = 0, sr_prefix_len = 0, v6_src_prefix_len = 0;
int ret = 0;
while (argc > 0) {
@@ -1621,6 +1643,23 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
*argv);
ret = rta_addattr8(rta, len, SEG6_LOCAL_MOBILE_V4_MASK_LEN,
v4_mask_len);
+ } else if (strcmp(*argv, "sr_prefix_len") == 0) {
+ NEXT_ARG();
+ if (mobile_sr_plen_ok++)
+ duparg2("sr_prefix_len", *argv);
+ /*
+ * The egress SID must leave room for the 40-bit
+ * Args.Mob.Session field, so the locator can be at
+ * most (128 - 40) = 88 bits.
+ */
+ if (get_u8(&sr_prefix_len, *argv, 0) ||
+ sr_prefix_len == 0 ||
+ sr_prefix_len > 88)
+ invarg("\"sr_prefix_len\" must be in the range 1..88\n",
+ *argv);
+ ret = rta_addattr8(rta, len,
+ SEG6_LOCAL_MOBILE_SR_PREFIX_LEN,
+ sr_prefix_len);
} else if (strcmp(*argv, "v6_src_prefix_len") == 0) {
NEXT_ARG();
if (mobile_v6src_plen_ok++)
@@ -1680,7 +1719,7 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
int srhlen;
srh = parse_srh(segbuf, hmac,
- action == SEG6_LOCAL_ACTION_END_B6_ENCAP);
+ seg6local_action_excludes_final_seg(action));
srhlen = (srh->hdrlen + 1) << 3;
ret = rta_addattr_l(rta, len, SEG6_LOCAL_SRH, srh, srhlen);
free(srh);
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 7cf97924d699..35e6e2080a1f 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1088,6 +1088,35 @@ takes the same syntax and semantics as in
.B End.M.GTP4.E
above (no PDU Session Container is inserted unless explicitly set).
+.B End.M.GTP6.D srh segs
+.IR SEGMENTS
+.B src
+.IR ADDRESS
+.B sr_prefix_len
+.IR BITS
+- SRv6 Mobile User Plane End.M.GTP6.D behavior (RFC 9433 Section 6.3).
+At the SR ingress gateway, the matching IPv6/UDP/GTP-U packet has its
+GTP-U envelope removed and the inner T-PDU is re-encapsulated in SRv6
+using the supplied SR Policy
+.RI ( srh
+\fBsegs\fR\~\fISEGMENTS\fR). The TEID is folded into the 40-bit
+Args.Mob.Session field placed immediately after the egress
+End.M.GTP6.E SID's locator (RFC 9433 Section 6.5),
+.B sr_prefix_len
+specifying the locator length in bits (1..88 -- the upper bound
+leaves room for the 40-bit Args.Mob.Session field within the
+128-bit egress SID). The egress SID's locator
+length cannot be inferred from local state at the SR Gateway. The
+egress
+.B End.M.GTP6.E
+SID can then recover the per-session identifier from the same offset.
+The new outer IPv6 source address is taken from
+.BR src .
+The action requires either no SRH or an SRH with
+.B Segments Left
+equal to zero on the inbound packet; other matching packets are
+dropped.
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
^ permalink raw reply related
* [PATCH iproute2-next v2 3/6] seg6: add support for the End.M.GTP6.E behavior
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the End.M.GTP6.E behavior, which translates SRv6
traffic into IPv6/GTP-U. The behavior reuses the src and pdu_type
keywords introduced for End.M.GTP4.E; v4_mask_len is not meaningful
for an IPv6/GTP-U tunnel and is rejected.
Example:
ip -6 r a 2001:db8:1::/64 encap seg6local action End.M.GTP6.E \
src 2001:db8::1 pdu_type ul dev sr0
Link: https://datatracker.ietf.org/doc/html/rfc9433
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/uapi/linux/seg6_local.h | 2 ++
ip/iproute.c | 2 +-
ip/iproute_lwtunnel.c | 1 +
man/man8/ip-route.8.in | 21 +++++++++++++++++++++
4 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 8bb3cdc3a649..6af145259ffb 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -75,6 +75,8 @@ enum {
SEG6_LOCAL_ACTION_END_MAP = 17,
/* SRv6 to IPv4/GTP-U encap (RFC 9433 Section 6.6) */
SEG6_LOCAL_ACTION_END_M_GTP4_E = 18,
+ /* SRv6 to IPv6/GTP-U encap (RFC 9433 Section 6.5) */
+ SEG6_LOCAL_ACTION_END_M_GTP6_E = 19,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index f9ebba6541af..e009e7480e76 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -106,7 +106,7 @@ static void usage(void)
"ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
" End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
" End.BM | End.S | End.AS | End.AM | End.BPF |\n"
- " End.MAP | End.M.GTP4.E }\n"
+ " End.MAP | End.M.GTP4.E | End.M.GTP6.E }\n"
"OPTIONS := OPTION [ OPTIONS ]\n"
"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
" table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 49fe563d9b86..38e806b053c5 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -407,6 +407,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_DT46] = "End.DT46",
[SEG6_LOCAL_ACTION_END_MAP] = "End.MAP",
[SEG6_LOCAL_ACTION_END_M_GTP4_E] = "End.M.GTP4.E",
+ [SEG6_LOCAL_ACTION_END_M_GTP6_E] = "End.M.GTP6.E",
};
static const char *format_action_type(int action)
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index a878d4375f03..7cf97924d699 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1067,6 +1067,27 @@ PDU Type; when omitted no Container is inserted, so 5G N3 deployments
must set it explicitly.
The action only accepts packets with Segments Left = 0 or no SRH.
+.B End.M.GTP6.E src
+.IR ADDRESS
+.RB [ "pdu_type"
+.IR DIR ]
+- SRv6 Mobile User Plane End.M.GTP6.E behavior (RFC 9433 Section 6.5).
+At the SR egress gateway, an SRv6 packet whose current SID is an
+End.M.GTP6.E SID is converted into an IPv6/UDP/GTP-U packet directed at
+the next segment held in
+.IR SRH[0] .
+The 40-bit Args.Mob.Session field defined in RFC 9433 Section 6.1 is
+read from the right-aligned tail of the matching SID, and its 32-bit
+PDU Session ID portion is used as the GTP-U TEID. The IPv6 source
+address of the new tunnel is set to the user-provided template
+.BR src .
+The action requires Segments Left to equal 1; other matching packets are
+dropped. The optional
+.B pdu_type
+takes the same syntax and semantics as in
+.B End.M.GTP4.E
+above (no PDU Session Container is inserted unless explicitly set).
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
^ permalink raw reply related
* [PATCH iproute2-next v2 2/6] seg6: add support for the End.M.GTP4.E behavior
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the End.M.GTP4.E behavior, which translates SRv6
traffic into IPv4/GTP-U. Four new keywords are introduced:
src IPv6 source-address template
v4_mask_len IPv4 DA portion of the SID, in bits (1..32)
v6_src_prefix_len Source UPF Prefix length P in the IPv6 SA
template (1..127, defaults to 64); requires
P + v4_mask_len <= 128
pdu_type GTP-U PDU Session Container PDU Type
(downlink|dl|uplink|ul or 0..15)
Example:
ip -6 r a 2001:db8:1::/56 encap seg6local action End.M.GTP4.E \
src 2001:db8::1 v4_mask_len 32 v6_src_prefix_len 64 \
pdu_type ul dev sr0
Link: https://datatracker.ietf.org/doc/html/rfc9433
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/uapi/linux/seg6_local.h | 6 +++
ip/iproute.c | 6 ++-
ip/iproute_lwtunnel.c | 103 ++++++++++++++++++++++++++++++++++++++++
man/man8/ip-route.8.in | 34 +++++++++++++
4 files changed, 147 insertions(+), 2 deletions(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 1678db71e8e7..8bb3cdc3a649 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -29,6 +29,10 @@ enum {
SEG6_LOCAL_VRFTABLE,
SEG6_LOCAL_COUNTERS,
SEG6_LOCAL_FLAVORS,
+ SEG6_LOCAL_MOBILE_SRC_ADDR,
+ SEG6_LOCAL_MOBILE_V4_MASK_LEN,
+ SEG6_LOCAL_MOBILE_PDU_TYPE,
+ SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN,
__SEG6_LOCAL_MAX,
};
#define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
@@ -69,6 +73,8 @@ enum {
SEG6_LOCAL_ACTION_END_DT46 = 16,
/* swap DA with new SID, leave SRH untouched (RFC 9433 Section 6.2) */
SEG6_LOCAL_ACTION_END_MAP = 17,
+ /* SRv6 to IPv4/GTP-U encap (RFC 9433 Section 6.6) */
+ SEG6_LOCAL_ACTION_END_M_GTP4_E = 18,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 61394847018f..f9ebba6541af 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -106,10 +106,12 @@ static void usage(void)
"ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
" End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
" End.BM | End.S | End.AS | End.AM | End.BPF |\n"
- " End.MAP }\n"
+ " End.MAP | End.M.GTP4.E }\n"
"OPTIONS := OPTION [ OPTIONS ]\n"
"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
- " table TABLEID | vrftable TABLEID | endpoint PROGNAME }\n"
+ " table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
+ "MOBILE_OPTION := { src ADDR | v4_mask_len BITS | v6_src_prefix_len BITS |\n"
+ " pdu_type { downlink | dl | uplink | ul | 0..15 } }\n"
"FLAVORS := { FLAVOR[,FLAVOR] }\n"
"FLAVOR := { psp | usp | usd | next-csid }\n"
"IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 3a25835662d1..49fe563d9b86 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -406,6 +406,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_BPF] = "End.BPF",
[SEG6_LOCAL_ACTION_END_DT46] = "End.DT46",
[SEG6_LOCAL_ACTION_END_MAP] = "End.MAP",
+ [SEG6_LOCAL_ACTION_END_M_GTP4_E] = "End.M.GTP4.E",
};
static const char *format_action_type(int action)
@@ -577,6 +578,41 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
if (tb[SEG6_LOCAL_FLAVORS])
print_seg6_local_flavors(fp, tb[SEG6_LOCAL_FLAVORS]);
+
+ if (tb[SEG6_LOCAL_MOBILE_SRC_ADDR])
+ print_string(PRINT_ANY, "src", "src %s ",
+ rt_addr_n2a_rta(AF_INET6,
+ tb[SEG6_LOCAL_MOBILE_SRC_ADDR]));
+
+ if (tb[SEG6_LOCAL_MOBILE_V4_MASK_LEN])
+ print_uint(PRINT_ANY, "v4_mask_len", "v4_mask_len %u ",
+ rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_V4_MASK_LEN]));
+
+ if (tb[SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN])
+ print_uint(PRINT_ANY, "v6_src_prefix_len",
+ "v6_src_prefix_len %u ",
+ rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN]));
+
+ if (tb[SEG6_LOCAL_MOBILE_PDU_TYPE]) {
+ __u8 t = rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_PDU_TYPE]);
+ const char *name = NULL;
+
+ switch (t) {
+ case 0:
+ name = "downlink";
+ break;
+ case 1:
+ name = "uplink";
+ break;
+ }
+
+ if (name)
+ print_string(PRINT_ANY, "pdu_type",
+ "pdu_type %s ", name);
+ else
+ print_uint(PRINT_ANY, "pdu_type",
+ "pdu_type %u ", t);
+ }
}
static void print_encap_mpls(FILE *fp, struct rtattr *encap)
@@ -1451,6 +1487,8 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
int nh4_ok = 0, nh6_ok = 0, iif_ok = 0, oif_ok = 0, flavors_ok = 0;
int segs_ok = 0, hmac_ok = 0, table_ok = 0, vrftable_ok = 0;
int action_ok = 0, srh_ok = 0, bpf_ok = 0, counters_ok = 0;
+ int mobile_src_ok = 0, mobile_v4mask_ok = 0, mobile_pdusess_ok = 0;
+ int mobile_v6src_plen_ok = 0;
__u32 action = 0, table, vrftable, iif, oif;
struct ipv6_sr_hdr *srh;
char **argv = *argvp;
@@ -1458,6 +1496,7 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
char segbuf[1024];
inet_prefix addr;
__u32 hmac = 0;
+ __u8 v4_mask_len = 0, v6_src_prefix_len = 0;
int ret = 0;
while (argc > 0) {
@@ -1559,6 +1598,70 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
if (lwt_parse_bpf(rta, len, &argc, &argv, SEG6_LOCAL_BPF,
BPF_PROG_TYPE_LWT_SEG6LOCAL) < 0)
exit(-1);
+ } else if (strcmp(*argv, "src") == 0) {
+ /*
+ * Mobile User Plane "src" template; scoped to the
+ * seg6local block and unrelated to the top-level
+ * "src" prefsrc keyword.
+ */
+ NEXT_ARG();
+ if (mobile_src_ok++)
+ duparg2("src", *argv);
+ get_addr(&addr, *argv, AF_INET6);
+ ret = rta_addattr_l(rta, len, SEG6_LOCAL_MOBILE_SRC_ADDR,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "v4_mask_len") == 0) {
+ NEXT_ARG();
+ if (mobile_v4mask_ok++)
+ duparg2("v4_mask_len", *argv);
+ if (get_u8(&v4_mask_len, *argv, 0) ||
+ v4_mask_len == 0 || v4_mask_len > 32)
+ invarg("\"v4_mask_len\" must be in the range 1..32\n",
+ *argv);
+ ret = rta_addattr8(rta, len, SEG6_LOCAL_MOBILE_V4_MASK_LEN,
+ v4_mask_len);
+ } else if (strcmp(*argv, "v6_src_prefix_len") == 0) {
+ NEXT_ARG();
+ if (mobile_v6src_plen_ok++)
+ duparg2("v6_src_prefix_len", *argv);
+ /*
+ * Per RFC 9433 Section 6.6 Figure 10, the IPv6 SA is
+ * "Source UPF Prefix (P bits) | IPv4 SA (b bits) |
+ * padding (128 - P - b)". P is validated as 1..127
+ * here; the kernel enforces P + b <= 128 via netlink
+ * extack.
+ */
+ if (get_u8(&v6_src_prefix_len, *argv, 0) ||
+ v6_src_prefix_len == 0 ||
+ v6_src_prefix_len > 127)
+ invarg("\"v6_src_prefix_len\" must be in the range 1..127\n",
+ *argv);
+ ret = rta_addattr8(rta, len,
+ SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN,
+ v6_src_prefix_len);
+ } else if (strcmp(*argv, "pdu_type") == 0) {
+ __u8 psc_type;
+
+ NEXT_ARG();
+ if (mobile_pdusess_ok++)
+ duparg2("pdu_type", *argv);
+ /*
+ * 3GPP TS 38.415 PDU Session Type is a 4-bit field; the
+ * kernel mirrors that range (0..15). 0 = DL, 1 = UL.
+ */
+ if (strcmp(*argv, "downlink") == 0 ||
+ strcmp(*argv, "dl") == 0) {
+ psc_type = 0;
+ } else if (strcmp(*argv, "uplink") == 0 ||
+ strcmp(*argv, "ul") == 0) {
+ psc_type = 1;
+ } else if (get_u8(&psc_type, *argv, 0) ||
+ psc_type > 15) {
+ invarg("invalid \"pdu_type\" value (must be downlink|dl|uplink|ul or 0..15)\n", *argv);
+ }
+ ret = rta_addattr8(rta, len,
+ SEG6_LOCAL_MOBILE_PDU_TYPE,
+ psc_type);
} else {
break;
}
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index c0b1e87ad022..a878d4375f03 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1033,6 +1033,40 @@ with the configured next SID
and forward via the IPv6 FIB. The Segment Routing Header is left
untouched.
+.B End.M.GTP4.E src
+.IR ADDRESS
+.B v4_mask_len
+.IR BITS
+.RB [ "v6_src_prefix_len"
+.IR BITS ]
+.RB [ "pdu_type"
+.IR DIR ]
+- SRv6 Mobile User Plane End.M.GTP4.E behavior (RFC 9433 Section 6.6).
+At the SR egress gateway, the matching SRv6 packet is converted into
+an IPv4/UDP/GTP-U packet for delivery to a legacy IPv4-attached gNB or
+eNB. The IPv6 destination address of the matching SID encodes
+.IR Locator " | " "IPv4 DA" " (\fBv4_mask_len\fR bits) | "
+.IR "Args.Mob.Session" " (40 bits, RFC 9433 Section 6.1)" ,
+and the IPv6 source address is built from
+.B src
+as
+.IR "Source UPF Prefix" " (\fBv6_src_prefix_len\fR bits) | "
+.IR "IPv4 SA" " (\fBv4_mask_len\fR bits) | padding" .
+.B v4_mask_len
+must be in 1..32 and
+.B v6_src_prefix_len
+in 1..127 (default 64); the route prefix length plus
+.BR v4_mask_len " + 40"
+and
+.BR v6_src_prefix_len " + " v4_mask_len
+must each fit in 128 bits.
+.B pdu_type
+.RB ( downlink | dl | uplink | ul " or " 0..15 )
+forces a GTP-U PDU Session Container (3GPP TS 38.415) with the given
+PDU Type; when omitted no Container is inserted, so 5G N3 deployments
+must set it explicitly.
+The action only accepts packets with Segments Left = 0 or no SRH.
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
^ permalink raw reply related
* [PATCH iproute2-next v2 1/6] seg6: add support for the End.MAP behavior
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the End.MAP behavior, which swaps the IPv6 destination
address with the next SID without consuming the SRH. The new SID is
specified using the existing nh6 attribute.
Example:
ip -6 r a 2001:db8:f::/64 encap seg6local action End.MAP \
nh6 2001:db8:1::beef dev sr0
Link: https://datatracker.ietf.org/doc/html/rfc9433
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/uapi/linux/seg6_local.h | 2 ++
ip/iproute.c | 3 ++-
ip/iproute_lwtunnel.c | 1 +
man/man8/ip-route.8.in | 9 +++++++++
4 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 6e71d97f6f44..1678db71e8e7 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -67,6 +67,8 @@ enum {
SEG6_LOCAL_ACTION_END_BPF = 15,
/* decap and lookup of DA in v4 or v6 table */
SEG6_LOCAL_ACTION_END_DT46 = 16,
+ /* swap DA with new SID, leave SRH untouched (RFC 9433 Section 6.2) */
+ SEG6_LOCAL_ACTION_END_MAP = 17,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 5b9e7ac1134a..61394847018f 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -105,7 +105,8 @@ static void usage(void)
"SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"
"ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
" End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
- " End.BM | End.S | End.AS | End.AM | End.BPF }\n"
+ " End.BM | End.S | End.AS | End.AM | End.BPF |\n"
+ " End.MAP }\n"
"OPTIONS := OPTION [ OPTIONS ]\n"
"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
" table TABLEID | vrftable TABLEID | endpoint PROGNAME }\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 00b4f7565be6..3a25835662d1 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -405,6 +405,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_AM] = "End.AM",
[SEG6_LOCAL_ACTION_END_BPF] = "End.BPF",
[SEG6_LOCAL_ACTION_END_DT46] = "End.DT46",
+ [SEG6_LOCAL_ACTION_END_MAP] = "End.MAP",
};
static const char *format_action_type(int action)
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 9f29fd436f59..c0b1e87ad022 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1024,6 +1024,15 @@ followed by the specified SRH. The destination address of the outer IPv6
header is set to the first segment of the new SRH. The source
address is set as described in \fBip-sr\fR(8).
+.B End.MAP nh6
+.IR ADDRESS
+- SRv6 Mobile User Plane End.MAP behavior (RFC 9433 Section 6.2).
+Decrement the IPv6 Hop Limit, replace the IPv6 destination address
+with the configured next SID
+.RI ( nh6 ),
+and forward via the IPv6 FIB. The Segment Routing Header is left
+untouched.
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
^ permalink raw reply related
* [PATCH iproute2-next v2 0/6] seg6: SRv6 Mobile User Plane (RFC 9433)
From: Yuya Kusakabe @ 2026-05-04 16:10 UTC (permalink / raw)
To: dsahern; +Cc: Yuya Kusakabe, netdev
This series adds the iproute2 frontend for the SRv6 Mobile User Plane
(MUP) endpoint behaviors of RFC 9433. It is sent in parallel with the
matching kernel net-next series; each commit here is self-contained
and brings in the seg6local UAPI bits it needs from the in-progress
kernel UAPI header (include/uapi/linux/seg6_local.h):
Section 6.2 End.MAP
Section 6.3 End.M.GTP6.D
Section 6.4 End.M.GTP6.D.Di
Section 6.5 End.M.GTP6.E
Section 6.6 End.M.GTP4.E
Section 6.7 H.M.GTP4.D
The series adds these seg6local CLI keywords:
src IPv6 source-address template
v4_mask_len length of the IPv4 DA portion of the SID, in
bits (1..32)
sr_prefix_len locator length of the egress End.M.GTP*.E SID,
in bits (1..88, leaving 40 bits for the
Args.Mob.Session field)
v6_src_prefix_len Source UPF Prefix length P in the IPv6 SA
template (1..127, defaults to 64); requires
P + v4_mask_len <= 128
pdu_type GTP-U PDU Session Container PDU Type (3GPP
TS 38.415 Section 5.5.2): downlink|dl|uplink|ul
or 0..15. When omitted, the egress emits a
short GTPv1-U header (no PDU Session Container)
regardless of the QFI in the SID; 5G N3
deployments must set pdu_type explicitly.
The matching kernel series has been posted to net-next:
https://lore.kernel.org/r/20260504-srv6-mup-v1-v1-0-e0a6791575cb@gmail.com
Link: https://datatracker.ietf.org/doc/html/rfc9433
Changes since v1:
- Drop the per-action userspace attribute validator entirely per
Stephen Hemminger's review. invarg() expects the offending value
as its second argument, but the validator runs after parsing and
has no argv to pass; the kernel already returns a clean EINVAL via
netlink extack for the same conditions, matching how every other
seg6local action is structured.
- Link to v1: https://lore.kernel.org/netdev/20260503154510.912576-1-yuya.kusakabe@gmail.com/
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
Yuya Kusakabe (6):
seg6: add support for the End.MAP behavior
seg6: add support for the End.M.GTP4.E behavior
seg6: add support for the End.M.GTP6.E behavior
seg6: add support for the End.M.GTP6.D behavior
seg6: add support for the End.M.GTP6.D.Di behavior
seg6: add support for the H.M.GTP4.D behavior
include/uapi/linux/seg6_local.h | 17 +++++
ip/iproute.c | 9 ++-
ip/iproute_lwtunnel.c | 149 +++++++++++++++++++++++++++++++++++++-
man/man8/ip-route.8.in | 154 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 326 insertions(+), 3 deletions(-)
---
base-commit: 4f5de57e2ff11a5925dacdf3deeeabee7ba9502a
change-id: 20260504-seg6-mobile-5345055f6608
Best regards,
--
Yuya Kusakabe <yuya.kusakabe@gmail.com>
^ permalink raw reply
* Re: [PATCH net v2 1/2] tcp: protect locked SO_RCVBUF from Silly Window Syndrome
From: Eric Dumazet @ 2026-05-04 16:09 UTC (permalink / raw)
To: Ankit Jain
Cc: kuba, netdev, davem, pabeni, ncardwell, kuniyu, horms, shuah,
quic_subashab, quic_stranche, linux-kselftest, linux-kernel,
karen.badiryan, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu
In-Reply-To: <20260504144945.13477-2-ankit-aj.jain@broadcom.com>
On Mon, May 4, 2026 at 7:53 AM Ankit Jain <ankit-aj.jain@broadcom.com> wrote:
>
> When an application locks SO_RCVBUF, it expects strict memory bounds and
> disables TCP window auto-tuning. However, recent TCP memory fragmentation
> optimizations still apply dynamic truesize penalties to the `scaling_ratio`
> of these locked sockets.
>
> For workloads processing small, fragmented packets (like Java's Tomcat),
> this penalty drops the scaling_ratio to 1. This shrinks the dynamically
> calculated advertised window, leading to Silly Window Syndrome (SWS)
> deadlocks and 504 Gateway Timeouts.
>
> This patch fixes the issue by bypassing the truesize penalty for sockets
> with `SOCK_RCVBUF_LOCK` set. To ensure the kernel still defends against
> memory exhaustion from large aggregate payloads (e.g., GRO), the penalty
> is still applied if `skb->len` exceeds the advertised MSS.
>
> Fixes: a2cbb1603943 ("tcp: Update window clamping condition")
> Reported-by: Karen Badiryan <karen.badiryan@broadcom.com>
> Signed-off-by: Ankit Jain <ankit-aj.jain@broadcom.com>
> ---
> net/ipv4/tcp_input.c | 8 +++++++-
> 1 file changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
> index d5c9e65d9760..569299dafa88 100644
> --- a/net/ipv4/tcp_input.c
> +++ b/net/ipv4/tcp_input.c
> @@ -240,8 +240,14 @@ static void tcp_measure_rcv_mss(struct sock *sk, const struct sk_buff *skb)
> /* Note: divides are still a bit expensive.
> * For the moment, only adjust scaling_ratio
> * when we update icsk_ack.rcv_mss.
> + *
> + * Protect locked SO_RCVBUF from Silly Window Syndrome
> + * due to truesize penalties on small packets. Allow
> + * penalty if aggregate payload (e.g., GRO) exceeds MSS.
> */
> - if (unlikely(len != icsk->icsk_ack.rcv_mss)) {
> + if (unlikely(len != icsk->icsk_ack.rcv_mss &&
> + (!(sk->sk_userlocks & SOCK_RCVBUF_LOCK) ||
> + skb->len > tcp_sk(sk)->advmss))) {
Testing tp->advmss is not doing what you want I think.
A remote peer can send GRO packets with tiny segments, regardless of tp->advmss
If GRO is what you are looking for, why not testing (skb->len > len) ?
> u64 val = (u64)skb->len << TCP_RMEM_TO_WIN_SCALE;
> u8 old_ratio = tcp_sk(sk)->scaling_ratio;
>
> --
> 2.53.0
>
^ permalink raw reply
* [PATCH net] net: wan: fsl_ucc_hdlc: fix indentation error
From: Holger Brunck @ 2026-05-04 16:07 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, andrew+netdev, chleroy, qiang.zhao, horms,
Holger Brunck
Remove the whitespace to fix the indentation.
Fixes: c19b6d246a35 ("drivers/net: support hdlc function for QE-UCC")
Signed-off-by: Holger Brunck <holger.brunck@hitachienergy.com>
---
drivers/net/wan/fsl_ucc_hdlc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wan/fsl_ucc_hdlc.c b/drivers/net/wan/fsl_ucc_hdlc.c
index 09081f128a98..adf3863463f5 100644
--- a/drivers/net/wan/fsl_ucc_hdlc.c
+++ b/drivers/net/wan/fsl_ucc_hdlc.c
@@ -764,7 +764,7 @@ static void uhdlc_memclean(struct ucc_hdlc_private *priv)
qe_muram_free(priv->ucc_pram_offset);
priv->ucc_pram = NULL;
priv->ucc_pram_offset = 0;
- }
+ }
kfree(priv->rx_skbuff);
priv->rx_skbuff = NULL;
--
2.47.3
^ permalink raw reply related
* Re: [PATCH net 2/4] net: sparx5: fix sleep in atomic context in MAC table access
From: Sebastian Andrzej Siewior @ 2026-05-04 16:06 UTC (permalink / raw)
To: Daniel Machon
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Steen Hegelund, UNGLinuxDriver, Clark Williams,
Steven Rostedt, Bjarni Jonasson, Lars Povlsen, Philipp Zabel,
linux-kernel, netdev, linux-arm-kernel, linux-rt-devel
In-Reply-To: <20260504-misc-fixes-sparx5-lan969x-v1-2-6604306b5743@microchip.com>
On 2026-05-04 16:43:43 [+0200], Daniel Machon wrote:
> index 2bf9c5f64151..0797cfa32916 100644
> --- a/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c
> +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c
> @@ -50,7 +50,7 @@ static int sparx5_mact_wait_for_completion(struct sparx5 *sparx5)
> {
> u32 val;
>
> - return readx_poll_timeout(sparx5_mact_get_status,
> + return readx_poll_timeout_atomic(sparx5_mact_get_status,
If you do _atomic, it becomes atomic. That means it does not sleep as in
TABLE_UPDATE_SLEEP_US for 10 us but spins via udelay(). The
TABLE_UPDATE_TIMEOUT_US is set to 100ms which _might_ be high.
This is probably just nitpicking (given that there are other drivers
doing the same with a greater timeout (READL_TIMEOUT_US)) so feel free
to ignore it.
> sparx5, val,
> LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_GET(val) == 0,
> TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
Sebastian
^ permalink raw reply
* RE: [Intel-wired-lan] [PATCH iwl-net] idpf: fix read_dev_clk_lock spinlock init in idpf_ptp_init()
From: Salin, Samuel @ 2026-05-04 16:04 UTC (permalink / raw)
To: Simon Horman, Tantilov, Emil S
Cc: intel-wired-lan@lists.osuosl.org, netdev@vger.kernel.org,
Nguyen, Anthony L, Loktionov, Aleksandr, Kitszel, Przemyslaw,
andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, richardcochran@gmail.com,
Olech, Milena, Keller, Jacob E, Ilichev, Konstantin
In-Reply-To: <20260408162749.GD469338@kernel.org>
> -----Original Message-----
> From: Intel-wired-lan <intel-wired-lan-bounces@osuosl.org> On Behalf Of
> Simon Horman
> Sent: Wednesday, April 8, 2026 9:28 AM
> To: Tantilov, Emil S <emil.s.tantilov@intel.com>
> Cc: intel-wired-lan@lists.osuosl.org; netdev@vger.kernel.org; Nguyen,
> Anthony L <anthony.l.nguyen@intel.com>; Loktionov, Aleksandr
> <aleksandr.loktionov@intel.com>; Kitszel, Przemyslaw
> <przemyslaw.kitszel@intel.com>; andrew+netdev@lunn.ch;
> davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
> pabeni@redhat.com; richardcochran@gmail.com; Olech, Milena
> <milena.olech@intel.com>; Keller, Jacob E <jacob.e.keller@intel.com>; Ilichev,
> Konstantin <konstantin.ilichev@intel.com>
> Subject: Re: [Intel-wired-lan] [PATCH iwl-net] idpf: fix read_dev_clk_lock
> spinlock init in idpf_ptp_init()
>
> On Tue, Apr 07, 2026 at 03:00:22PM -0700, Tantilov, Emil S wrote:
> >
> >
> > On 4/7/2026 9:02 AM, Simon Horman wrote:
> > > From: 'Simon Horman' <horms@kernel.org>
> > >
> > > This is an AI-generated review of your patch. The human sending this
> > > email has considered the AI review valid, or at least plausible.
> > > Full review at: https://sashiko.dev
> > >
> > > Simon says: I don't agree with the regression characterisation made
> > > by the AI review - I think this patch is good. But I do think the
> > > issues flagged by the AI review warrant investigation.
> >
> > The point of the change is to resolve the use of uninitialized
> > spinlock. The questions below appear to be generated around that code,
> > which would be out of scope for this patch, but I will address them anyway
> ...
>
> Right, I agree with that general statement on the review: it muddles up
> potential problems in nearby code, with problems introduced by your patch
> (none seen).
>
> I do thank you for analysing the problems raised. And I'll leave it up to you to
> provide follow-up patches as you see fit.
>
> For this patch, I think we are good.
>
> Reviewed-by: Simon Horman <horms@kernel.org>
Tested-by: Samuel Salin <Samuel.salin@intel.com>
^ permalink raw reply
* Re: [PATCH net-next v9 0/5] TLS read_sock performance scalability
From: Chuck Lever @ 2026-05-04 15:59 UTC (permalink / raw)
To: Sabrina Dubroca
Cc: Jakub Kicinski, John Fastabend, Eric Dumazet, Simon Horman,
Paolo Abeni, netdev, kernel-tls-handshake, Chuck Lever,
Hannes Reinecke, Alistair Francis
In-Reply-To: <afigKenambAyKkhu@krikkit>
On Mon, May 4, 2026, at 3:33 PM, Sabrina Dubroca wrote:
> 2026-05-03, 21:34:01 +0200, Chuck Lever wrote:
>> On 5/3/26 3:04 AM, Jakub Kicinski wrote:
>> > On Wed, 29 Apr 2026 17:48:07 -0400 Chuck Lever wrote:
>> >> I'd like to encourage in-kernel kTLS consumers (i.e., NFS and
>> >> NVMe/TCP) to coalesce on the use of read_sock. When I suggested
>> >> this to Hannes, he reported a few performance scalability issues
>> >> with read_sock.
>> >
>> > Meaning, this series achieves.. what right now?
>> > I mean - the headline is "performance scalability" and there's no
>> > performance testing result in any of the messages :S
>> > Patch 5 for instance "seems logical" but how much difference does
>> > it make?
>>
>> The cover Subject: line has not been changed so all the revisions of
>> this series can be located easily.
>
> (not to bikeshed, links to lore also do that)
>
>> The cover letter makes it clear that the series is now only a clean-up
>> series. Since async_capable is set to false for TLSv1.3, there is no
>> performance benefit to these changes, so I don't intend to post a
>> motivation for it based on performance.
>
> Maybe I misunderstood, but I thought there was a somewhat noticeable
> benefit to the "suppress spurious wakeups" patch (not +20%, but at
> least improved behavior for some users of kTLS), and maybe the "flush
> backlog" one.
>
> Patch 2 may still be beneficial (though it's now mixing 2 separate
> changes), and patch 1 is a very reasonable code cleanup.
>
> Patch 4 does feel like a pretty large amount of churn if it has no
> observable benefit.
There is potential benefit to eliminating spurious wake-ups,
but nothing I've found to be observable at the application
level.
>> We'd really like
>> to get TLS KeyUpdate working for in-kernel TLS consumers, so anything
>> that can move this process forward is welcome.
>
> But net/tls doesn't need any changes for that, right?
>> 1. The in-kernel TLS consumers need to reliably and securely handle TLS
>> Alerts. That is coming in the next series I plan to post.
This series will make changes to net/tls/.
--
Chuck Lever
^ permalink raw reply
* Re: [PATCH net v2 1/2] openvswitch: vport: fix self-deadlock on release of tunnel ports
From: Aaron Conole @ 2026-05-04 15:57 UTC (permalink / raw)
To: Ilya Maximets
Cc: netdev, Eelco Chaudron, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan, Yuan Tan,
Yang Yang, dev, linux-kernel, linux-kselftest, stable
In-Reply-To: <20260430233848.440994-2-i.maximets@ovn.org>
Ilya Maximets <i.maximets@ovn.org> writes:
> vports are used concurrently and protected by RCU, so netdev_put()
> must happen after the RCU grace period. So, either in an RCU call or
> after the synchronize_net(). The rtnl_delete_link() must happen under
> RTNL and so can't be executed in RCU context. Calling synchronize_net()
> while holding RTNL is not a good idea for performance and system
> stability under load in general, so calling netdev_put() in RCU call
> is the right solution here.
>
> However,
> when the device is deleted, rtnl_unlock() will call netdev_run_todo()
> and block until all the references are gone. In the current code this
> means that we never reach the call_rcu() and the vport is never freed
> and the reference is never released, causing a self-deadlock on device
> removal.
>
> Fix that by moving the rcu_call() before the rtnl_unlock(), so the
> scheduled RCU callback will be executed when synchronize_net() is
> called from the rtnl_unlock()->netdev_run_todo() while the RTNL itself
> is already released.
>
> Fixes: 6931d21f87bc ("openvswitch: defer tunnel netdev_put to RCU release")
> Cc: stable@vger.kernel.org
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
> ---
Acked-by: Aaron Conole <aconole@redhat.com>
^ permalink raw reply
* Re: [PATCH net v2 2/2] selftests: openvswitch: add tests for tunnel vport refcounting
From: Aaron Conole @ 2026-05-04 15:57 UTC (permalink / raw)
To: Ilya Maximets
Cc: netdev, Eelco Chaudron, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan, Yuan Tan,
Yang Yang, dev, linux-kernel, linux-kselftest
In-Reply-To: <20260430233848.440994-3-i.maximets@ovn.org>
Ilya Maximets <i.maximets@ovn.org> writes:
> There were a few issues found with the tunnel vport types around the
> vport destruction code. Add some basic tests, so at least we know that
> they can be properly added and removed without obvious issues.
>
> The test creates OVS datapath, adds a non-LWT tunnel port, makes sure
> they are created, and then removes the datapath and waits for all the
> ports to be gone.
>
> The dpctl script had a few bugs in the none-lwt tunnel creation code,
> so fixing them as well to make the testing possible:
> - The type of the --lwt option changed in order to properly disable it.
> - Removed byte order conversion for the port numbers, as the value
> supposed to be in the host order.
> - Added missing 'gre' choice for the tunnel type.
>
> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
> ---
Looks good to me. Thanks for the test.
Acked-by: Aaron Conole <aconole@redhat.com>
^ permalink raw reply
* [PATCH net] net: wan: fsl_uhdlc_hdlc: fix dma_rmb usage in hdlc_rx_done
From: Holger Brunck @ 2026-05-04 15:56 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, andrew+netdev, chleroy, qiang.zhao, horms,
Holger Brunck
If dma_rmb is used it has to be done after reading bd_status and checking
if R_E_S is zero. Therefore we need to move it into the while loop.
Fixes: c19b6d246a35 ("drivers/net: support hdlc function for QE-UCC")
Signed-off-by: Holger Brunck <holger.brunck@hitachienergy.com>
---
drivers/net/wan/fsl_ucc_hdlc.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/wan/fsl_ucc_hdlc.c b/drivers/net/wan/fsl_ucc_hdlc.c
index 15bfb78381d4..09081f128a98 100644
--- a/drivers/net/wan/fsl_ucc_hdlc.c
+++ b/drivers/net/wan/fsl_ucc_hdlc.c
@@ -523,12 +523,12 @@ static int hdlc_rx_done(struct ucc_hdlc_private *priv, int rx_work_limit)
u16 length, howmany = 0;
u8 *bdbuffer;
- dma_rmb();
bd = priv->currx_bd;
bd_status = be16_to_cpu(bd->status);
/* while there are received buffers and BD is full (~R_E) */
while (!((bd_status & (R_E_S)) || (--rx_work_limit < 0))) {
+ dma_rmb();
if (bd_status & (RX_BD_ERRORS)) {
dev->stats.rx_errors++;
@@ -610,7 +610,6 @@ static int hdlc_rx_done(struct ucc_hdlc_private *priv, int rx_work_limit)
bd_status = be16_to_cpu(bd->status);
}
- dma_rmb();
priv->currx_bd = bd;
return howmany;
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v3 2/2] dpll: zl3073x: report FFO as DPLL vs input reference offset
From: Ivan Vecera @ 2026-05-04 15:53 UTC (permalink / raw)
To: netdev, Jiri Pirko
Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
linux-rdma
In-Reply-To: <20260504155340.411063-1-ivecera@redhat.com>
Replace the per-reference frequency offset measurement (which was
redundant with measured-frequency) with a direct read of the DPLL's
delta frequency offset vs its tracked input reference.
The new implementation uses the dpll_df_offset_x register with
ref_ofst=1 via the dpll_df_read_x semaphore mechanism. This
provides 2^-48 resolution (~3.5 fE) and reports the actual
frequency difference between the DPLL and its active input.
FFO is now reported only for the active input pin in the nested
(pin vs parent DPLL) context. Top-level FFO returns -ENODATA.
Rewrite ffo_check to compare the cached df_offset converted to PPT
instead of using the old per-reference measurement. Remove the
ref_ffo_update periodic measurement and the ref ffo field since
they are no longer needed.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/zl3073x/chan.c | 31 +++++++++++++++++++++++--
drivers/dpll/zl3073x/chan.h | 14 ++++++++++++
drivers/dpll/zl3073x/core.c | 45 -------------------------------------
drivers/dpll/zl3073x/dpll.c | 34 ++++++++++++----------------
drivers/dpll/zl3073x/ref.h | 14 ------------
drivers/dpll/zl3073x/regs.h | 15 +++++++++++++
6 files changed, 72 insertions(+), 81 deletions(-)
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2f48ca2391494..2fe3c3da84bb5 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -18,6 +18,7 @@
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
+ u64 val;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
@@ -25,8 +26,34 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
- return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
- &chan->refsel_status);
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+ &chan->refsel_status);
+ if (rc)
+ return rc;
+
+ /* Read df_offset vs tracked reference */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
+ if (rc)
+ return rc;
+
+ chan->df_offset = sign_extend64(val, 47);
+
+ return 0;
}
/**
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 481da2133202b..4353809c69122 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -17,6 +17,7 @@ struct zl3073x_dev;
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
* @refsel_status: reference selection status register value
+ * @df_offset: frequency offset vs tracked reference in 2^-48 steps
*/
struct zl3073x_chan {
struct_group(cfg,
@@ -26,6 +27,7 @@ struct zl3073x_chan {
struct_group(stat,
u8 mon_status;
u8 refsel_status;
+ s64 df_offset;
);
};
@@ -37,6 +39,18 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
+/**
+ * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
+ * @chan: pointer to channel state
+ *
+ * Return: frequency offset in 2^-48 steps
+ */
+static inline s64
+zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
+{
+ return chan->df_offset;
+}
+
/**
* zl3073x_chan_mode_get - get DPLL channel operating mode
* @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 5f1e70f3e40a0..b3345060490db 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
return 0;
}
-/**
- * zl3073x_ref_ffo_update - update reference fractional frequency offsets
- * @zldev: pointer to zl3073x_dev structure
- *
- * The function asks device to latch the latest measured fractional
- * frequency offset values, reads and stores them into the ref state.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
-{
- int i, rc;
-
- rc = zl3073x_ref_freq_meas_latch(zldev,
- ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
- if (rc)
- return rc;
-
- /* Read DPLL-to-REFx frequency offset measurements */
- for (i = 0; i < ZL3073X_NUM_REFS; i++) {
- s32 value;
-
- /* Read value stored in units of 2^-32 signed */
- rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
- if (rc)
- return rc;
-
- /* Convert to ppt
- * ffo = (10^12 * value) / 2^32
- * ffo = ( 5^12 * value) / 2^20
- */
- zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
- }
-
- return 0;
-}
-
static void
zl3073x_dev_periodic_work(struct kthread_work *work)
{
@@ -776,13 +738,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
}
}
- /* Update references' fractional frequency offsets */
- rc = zl3073x_ref_ffo_update(zldev);
- if (rc)
- dev_warn(zldev->dev,
- "Failed to update fractional frequency offsets: %pe\n",
- ERR_PTR(rc));
-
list_for_each_entry(zldpll, &zldev->dplls, list)
zl3073x_dpll_changes_check(zldpll);
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index f2d430d1a8e7b..af50cd6200001 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -299,8 +299,12 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
{
struct zl3073x_dpll_pin *pin = pin_priv;
- /* Only rx vs tx symbol rate FFO is supported */
- if (dpll)
+ /* Only nested FFO (pin vs parent DPLL) is supported */
+ if (!dpll)
+ return -ENODATA;
+
+ /* Report FFO only for the active pin */
+ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return -ENODATA;
*ffo = pin->freq_offset;
@@ -1733,37 +1737,27 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
}
/**
- * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change
+ * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin
* @pin: pin to check
*
- * Check for the given pin's fractional frequency change.
- *
- * Return: true on fractional frequency offset change, false otherwise
+ * Return: true on change, false otherwise
*/
static bool
zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
{
struct zl3073x_dpll *zldpll = pin->dpll;
- struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_ref *ref;
- u8 ref_id;
+ const struct zl3073x_chan *chan;
s64 ffo;
- /* Get reference monitor status */
- ref_id = zl3073x_input_pin_ref_get(pin->id);
- ref = zl3073x_ref_state_get(zldev, ref_id);
-
- /* Do not report ffo changes if the reference monitor report errors */
- if (!zl3073x_ref_is_status_ok(ref))
+ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return false;
- /* Compare with previous value */
- ffo = zl3073x_ref_ffo_get(ref);
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
+ 244140625, 36);
+
if (pin->freq_offset != ffo) {
- dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
- pin->label, pin->freq_offset, ffo);
pin->freq_offset = ffo;
-
return true;
}
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 55e80e4f08734..e140ca3ea17dc 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -22,7 +22,6 @@ struct zl3073x_dev;
* @freq_ratio_n: FEC mode divisor
* @sync_ctrl: reference sync control
* @config: reference config
- * @ffo: current fractional frequency offset
* @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status
*/
@@ -40,7 +39,6 @@ struct zl3073x_ref {
u8 config;
);
struct_group(stat, /* Status */
- s64 ffo;
u32 meas_freq;
u8 mon_status;
);
@@ -58,18 +56,6 @@ int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
-/**
- * zl3073x_ref_ffo_get - get current fractional frequency offset
- * @ref: pointer to ref state
- *
- * Return: the latest measured fractional frequency offset
- */
-static inline s64
-zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
-{
- return ref->ffo;
-}
-
/**
* zl3073x_ref_meas_freq_get - get measured input frequency
* @ref: pointer to ref state
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 8015808bdf548..9578f00095282 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -164,6 +164,11 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+#define ZL_REG_DPLL_DF_READ(_idx) \
+ ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
+#define ZL_DPLL_DF_READ_SEM BIT(4)
+#define ZL_DPLL_DF_READ_REF_OFST BIT(3)
+
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
@@ -176,6 +181,16 @@
#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \
ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
+/*******************************
+ * Register Pages 6-7, DPLL Data
+ *******************************/
+
+#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
+ ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
+#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6)
+#define ZL_REG_DPLL_DF_OFFSET(_idx) \
+ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
+
/***********************************
* Register Page 9, Synth and Output
***********************************/
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v3 1/2] dpll: add fractional frequency offset to pin-parent-device
From: Ivan Vecera @ 2026-05-04 15:53 UTC (permalink / raw)
To: netdev, Jiri Pirko
Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
linux-rdma
In-Reply-To: <20260504155340.411063-1-ivecera@redhat.com>
Add both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes to the
pin-parent-device nested attribute set, alongside the existing
top-level pin attributes. Both carry the same measurement at
different precisions.
Distinguish the two contexts in the ffo_get callback by passing
dpll=NULL for the top-level call and a valid dpll pointer for the
nested per-parent call. This allows drivers to report a different
value per parent DPLL if needed. Update mlx5 and zl3073x drivers
to return -ENODATA for the context they do not yet support.
Add documentation for both FFO attributes to dpll.rst.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
Documentation/driver-api/dpll.rst | 15 ++++++++++
Documentation/netlink/specs/dpll.yaml | 28 ++++++++++++-------
drivers/dpll/dpll_netlink.c | 23 +++++++--------
drivers/dpll/dpll_nl.c | 2 ++
drivers/dpll/zl3073x/dpll.c | 4 +++
.../net/ethernet/mellanox/mlx5/core/dpll.c | 4 +++
6 files changed, 55 insertions(+), 21 deletions(-)
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 37eaef785e304..c21aea6b52f6b 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -258,6 +258,21 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute.
``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature
=============================== ========================
+Fractional frequency offset
+===========================
+
+The fractional frequency offset (FFO) is reported through two attributes
+that carry the same measurement at different precisions:
+
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET`` in PPM (parts per million)
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT`` in PPT (parts per trillion)
+
+Both attributes appear at the top level of a pin and inside each
+``pin-parent-device`` nest. The driver's ``ffo_get`` callback receives
+a NULL ``dpll`` pointer for the top-level context and a valid pointer
+for the per-parent context, allowing it to distinguish the two if
+needed (e.g. report a different measurement per parent DPLL).
+
Frequency monitor
=================
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index c45de70a47ce6..91a172617b3a9 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -448,12 +448,14 @@ attribute-sets:
name: fractional-frequency-offset
type: sint
doc: |
- The FFO (Fractional Frequency Offset) between the RX and TX
- symbol rate on the media associated with the pin:
- (rx_frequency-tx_frequency)/rx_frequency
+ The FFO (Fractional Frequency Offset) of the pin.
+ At top level this represents the RX vs TX symbol rate
+ offset on the media associated with the pin. Inside
+ the pin-parent-device nest it represents the frequency
+ offset between the pin and its parent DPLL device.
Value is in PPM (parts per million).
- This may be implemented for example for pin of type
- PIN_TYPE_SYNCE_ETH_PORT.
+ This is a lower-precision version of
+ fractional-frequency-offset-ppt.
-
name: esync-frequency
type: u64
@@ -492,12 +494,14 @@ attribute-sets:
name: fractional-frequency-offset-ppt
type: sint
doc: |
- The FFO (Fractional Frequency Offset) of the pin with respect to
- the nominal frequency.
- Value = (frequency_measured - frequency_nominal) / frequency_nominal
+ The FFO (Fractional Frequency Offset) of the pin.
+ At top level this represents the RX vs TX symbol rate
+ offset on the media associated with the pin. Inside
+ the pin-parent-device nest it represents the frequency
+ offset between the pin and its parent DPLL device.
Value is in PPT (parts per trillion, 10^-12).
- Note: This attribute provides higher resolution than the standard
- fractional-frequency-offset (which is in PPM).
+ This is a higher-precision version of
+ fractional-frequency-offset.
-
name: measured-frequency
type: u64
@@ -534,6 +538,10 @@ attribute-sets:
name: operstate
-
name: phase-offset
+ -
+ name: fractional-frequency-offset
+ -
+ name: fractional-frequency-offset-ppt
-
name: pin-parent-pin
subset-of: pin
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 05cf946b4be5e..e1158033ba0a1 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -417,30 +417,27 @@ dpll_msg_add_phase_offset(struct sk_buff *msg, struct dpll_pin *pin,
static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref,
+ const struct dpll_device *dpll, void *dpll_priv,
struct netlink_ext_ack *extack)
{
const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
- struct dpll_device *dpll = ref->dpll;
s64 ffo;
int ret;
if (!ops->ffo_get)
return 0;
- ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(dpll, pin),
- dpll, dpll_priv(dpll), &ffo, extack);
+ ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(ref->dpll, pin),
+ dpll, dpll_priv, &ffo, extack);
if (ret) {
if (ret == -ENODATA)
return 0;
return ret;
}
- /* Put the FFO value in PPM to preserve compatibility with older
- * programs.
- */
- ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
- div_s64(ffo, 1000000));
- if (ret)
+ if (nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
+ div_s64(ffo, 1000000)))
return -EMSGSIZE;
- return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+ return nla_put_sint(msg,
+ DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
ffo);
}
@@ -686,6 +683,10 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
goto nest_cancel;
ret = dpll_msg_add_phase_offset(msg, pin, ref, extack);
+ if (ret)
+ goto nest_cancel;
+ ret = dpll_msg_add_ffo(msg, pin, ref, ref->dpll,
+ dpll_priv(ref->dpll), extack);
if (ret)
goto nest_cancel;
nla_nest_end(msg, attr);
@@ -748,7 +749,7 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
ret = dpll_msg_add_pin_phase_adjust(msg, pin, ref, extack);
if (ret)
return ret;
- ret = dpll_msg_add_ffo(msg, pin, ref, extack);
+ ret = dpll_msg_add_ffo(msg, pin, ref, NULL, NULL, extack);
if (ret)
return ret;
ret = dpll_msg_add_measured_freq(msg, pin, ref, extack);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index 58235845fa3d5..b1d9182c7802f 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -19,6 +19,8 @@ const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE +
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
[DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4),
[DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, },
+ [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET] = { .type = NLA_SINT, },
+ [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT] = { .type = NLA_SINT, },
};
const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = {
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 6fd718696de0d..f2d430d1a8e7b 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -299,6 +299,10 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
{
struct zl3073x_dpll_pin *pin = pin_priv;
+ /* Only rx vs tx symbol rate FFO is supported */
+ if (dpll)
+ return -ENODATA;
+
*ffo = pin->freq_offset;
return 0;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
index bce72e8d1bc31..ef2c58c390efa 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
@@ -306,6 +306,10 @@ static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
struct mlx5_dpll *mdpll = pin_priv;
int err;
+ /* Only rx vs tx symbol rate FFO is supported */
+ if (dpll)
+ return -ENODATA;
+
err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
if (err)
return err;
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v3 0/2] dpll: rework fractional frequency offset reporting
From: Ivan Vecera @ 2026-05-04 15:53 UTC (permalink / raw)
To: netdev, Jiri Pirko
Cc: Andrew Lunn, Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jonathan Corbet, Leon Romanovsky,
Mark Bloch, Michal Schmidt, Paolo Abeni, Pasi Vaananen, Petr Oros,
Prathosh Satish, Saeed Mahameed, Shuah Khan, Simon Horman,
Tariq Toukan, Vadim Fedorenko, linux-doc, linux-kernel,
linux-rdma
Rework how the fractional frequency offset (FFO) is reported in
the DPLL subsystem.
Both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes are now present at
the top level of a pin and inside each pin-parent-device nest. They
carry the same measurement at different precisions.
The ffo_get callback distinguishes the two contexts: dpll=NULL for
the top-level call (RX vs TX symbol rate offset) and a valid dpll
pointer for the nested per-parent call (pin vs DPLL offset). This
allows drivers to report a different value per parent DPLL if needed.
Patch 1 adds both attributes to the pin-parent-device subset, updates
the DPLL netlink handling to emit both at each level, updates the YAML
spec and driver-api documentation, and adds NULL guards to mlx5 and
zl3073x drivers.
Patch 2 implements the nested FFO for zl3073x using the
dpll_df_offset_x register with ref_ofst=1, providing 2^-48
resolution. The old per-reference frequency measurement is removed
as it was redundant with measured-frequency.
Changes v2 -> v3:
- Keep both FFO attributes (PPM and PPT) at both levels instead of
moving PPT under pin-parent-device only (Jiri Pirko)
- Unify attribute documentation to describe semantics at each level
Changes v1 -> v2:
- Minor commit message fixes
Ivan Vecera (2):
dpll: add fractional frequency offset to pin-parent-device
dpll: zl3073x: report FFO as DPLL vs input reference offset
Documentation/driver-api/dpll.rst | 15 +++++++
Documentation/netlink/specs/dpll.yaml | 28 +++++++-----
drivers/dpll/dpll_netlink.c | 23 +++++-----
drivers/dpll/dpll_nl.c | 2 +
drivers/dpll/zl3073x/chan.c | 31 ++++++++++++-
drivers/dpll/zl3073x/chan.h | 14 ++++++
drivers/dpll/zl3073x/core.c | 45 -------------------
drivers/dpll/zl3073x/dpll.c | 34 +++++++-------
drivers/dpll/zl3073x/ref.h | 14 ------
drivers/dpll/zl3073x/regs.h | 15 +++++++
.../net/ethernet/mellanox/mlx5/core/dpll.c | 4 ++
11 files changed, 125 insertions(+), 100 deletions(-)
--
2.53.0
^ permalink raw reply
* Re: [PATCH net-next v2 1/2] selftests: openvswitch: add vlan() and encap() flow string parsing
From: 侯敏熙 @ 2026-05-04 15:52 UTC (permalink / raw)
To: Aaron Conole
Cc: netdev, linux-kselftest, dev, Eelco Chaudron, Ilya Maximets,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kernel
In-Reply-To: <f7tpl3bxjs9.fsf@redhat.com>
Hi Aaron,
Thanks for the review!
A v4 is already posted at:
https://lore.kernel.org/netdev/20260504123713.555461-1-houminxi@gmail.com/
It addresses several issues found during review (see changelog in the
cover letter). Specifically for your comments:
> ^ That's kindof a fix, BUT it also seems like we never actually used it
> anywhere, so I hope it is okay to not treat it that way.
Agreed! Since it was never used, treating it as a new feature is fine.
In v4 the ENCAP type is changed to "encap_ovskey" (a new subclass)
instead of "nested", which restricts the nla_map to L2-L4 attributes
only. This avoids pyroute2 trying to decode metadata attributes
(SKB_MARK, DP_HASH, etc.) that never appear inside ENCAP.
> It would be more useful if we could set the MAX_ENCAP_DEPTH separately,
> so that we have the option to break assumptions. Maybe just skip a
> depth check here completely.
Makes sense! The kernel already enforces its own nesting limits, so
duplicating that check in the test tool is unnecessary. I'll drop the
depth check in v5.
Best,
Minxi
Aaron Conole <aconole@redhat.com> 于2026年5月4日周一 23:43写道:
>
> Minxi Hou <houminxi@gmail.com> writes:
>
> > Extend the ovs-dpctl.py flow parser to support vlan() and encap()
> > match strings. vlan() accepts tci=, vid=, pcp=, and cfi=
> > parameters and generates the OVS_KEY_ATTR_VLAN attribute with a TCI
> > value in network byte order. encap() parses nested flow strings
> > and returns OVS_KEY_ATTR_ENCAP with inner key attributes as a
> > recursive NLA container.
> >
> > The encap nla_map type is changed from "none" to "nested" so that
> > pyroute2 recursively encodes the inner flow key attributes. The
> > VLAN nla_map type is changed from "uint16" to "be16" to match the
> > kernel's big-endian wire format.
>
> ^ That's kindof a fix, BUT it also seems like we never actually used it
> anywhere, so I hope it is okay to not treat it that way.
>
> > Signed-off-by: Minxi Hou <houminxi@gmail.com>
> > ---
> > v1 -> v2: rebase to latest net-next/main, drop --base=auto
> >
> > .../selftests/net/openvswitch/ovs-dpctl.py | 190 +++++++++++++++++-
> > 1 file changed, 188 insertions(+), 2 deletions(-)
> >
> > diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> > index 848f61fdcee0..317be7878937 100644
> > --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> > +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> > @@ -901,11 +901,11 @@ class ovskey(nla):
> > nla_flags = NLA_F_NESTED
> > nla_map = (
> > ("OVS_KEY_ATTR_UNSPEC", "none"),
> > - ("OVS_KEY_ATTR_ENCAP", "none"),
> > + ("OVS_KEY_ATTR_ENCAP", "nested"),
> > ("OVS_KEY_ATTR_PRIORITY", "uint32"),
> > ("OVS_KEY_ATTR_IN_PORT", "uint32"),
> > ("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
> > - ("OVS_KEY_ATTR_VLAN", "uint16"),
> > + ("OVS_KEY_ATTR_VLAN", "be16"),
> > ("OVS_KEY_ATTR_ETHERTYPE", "be16"),
> > ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
> > ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
> > @@ -1636,6 +1636,180 @@ class ovskey(nla):
> > class ovs_key_mpls(nla):
> > fields = (("lse", ">I"),)
> >
> > + # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
> > + _VLAN_CFI_MASK = 0x1000
> > + _MAX_ENCAP_DEPTH = 4
>
> It would be more useful if we could set the MAX_ENCAP_DEPTH separately,
> so that we have the option to break assumptions. Maybe just skip a
> depth check here completely.
>
> > + _encap_depth = 0 # single-threaded usage assumed
> > +
> > + @staticmethod
> > + def _parse_vlan_from_flowstr(flowstr):
> > + """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
> > +
> > + Returns (remaining_flowstr, key_tci, mask_tci).
> > + TCI values use standard bit layout (VID bits 0-11,
> > + CFI bit 12, PCP bits 13-15); byte order conversion to
> > + big-endian happens in pyroute2 be16 NLA serialization.
> > + The mask covers only the fields the caller specified:
> > + vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
> > +
> > + The tci= key sets the raw TCI bitfield (no CFI validation) to allow
> > + non-Ethernet use cases. Use cfi=1 for standard Ethernet VLAN matching.
> > + """
> > + tci = 0
> > + mask = 0
> > + has_tci = False
> > + has_vid = has_pcp = has_cfi = False
> > + _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
> > + "with 'vid'/'pcp'/'cfi'"
> > + first = True
> > + while True:
> > + flowstr = flowstr.lstrip()
> > + if not flowstr:
> > + raise ValueError("vlan(): missing ')'")
> > + if flowstr[0] == ')':
> > + break
> > + if not first:
> > + flowstr = flowstr[1:] # skip ','
> > + if not flowstr:
> > + raise ValueError("vlan(): missing ')' after trailing comma")
> > + flowstr = flowstr.lstrip()
> > + if flowstr and flowstr[0] == ')':
> > + break
> > + if flowstr and flowstr[0] == ',':
> > + raise ValueError(
> > + "vlan(): empty or extra comma in field list")
> > + first = False
> > +
> > + eq = flowstr.find('=')
> > + if eq == -1:
> > + raise ValueError("vlan(): expected key=value, got '%s'" % flowstr)
> > + key = flowstr[:eq].strip()
> > + flowstr = flowstr[eq + 1:]
> > +
> > + end = flowstr.find(',')
> > + end2 = flowstr.find(')')
> > + if end == -1 or (end2 != -1 and end2 < end):
> > + end = end2
> > + val = flowstr[:end].strip()
> > + flowstr = flowstr[end:]
> > +
> > + if not val:
> > + raise ValueError("vlan(): empty value for key '%s'" % key)
> > + try:
> > + v = int(val, 16) if val.startswith(('0x', '0X')) else int(val)
> > + except ValueError:
> > + raise ValueError("vlan(): invalid value '%s' for key '%s'" %
> > + (val, key))
> > +
> > + if key == 'tci':
> > + if has_tci:
> > + raise ValueError("vlan(): duplicate 'tci'")
> > + if has_vid or has_pcp or has_cfi:
> > + raise ValueError(_tci_mix_err)
> > + if v > 0xFFFF or v < 0:
> > + raise ValueError("vlan(): tci=0x%x out of range" % v)
> > + tci = v
> > + mask = 0xFFFF
> > + has_tci = True
> > + elif key == 'vid':
> > + if has_tci:
> > + raise ValueError(_tci_mix_err)
> > + if has_vid:
> > + raise ValueError("vlan(): duplicate 'vid'")
> > + if v < 0 or v > 0xFFF:
> > + raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
> > + tci |= v
> > + mask |= 0x0FFF
> > + has_vid = True
> > + elif key == 'pcp':
> > + if has_tci:
> > + raise ValueError(_tci_mix_err)
> > + if has_pcp:
> > + raise ValueError("vlan(): duplicate 'pcp'")
> > + if v < 0 or v > 7:
> > + raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
> > + tci |= (v & 0x7) << 13
> > + mask |= 0xE000
> > + has_pcp = True
> > + elif key == 'cfi':
> > + if has_tci:
> > + raise ValueError(_tci_mix_err)
> > + if has_cfi:
> > + raise ValueError("vlan(): duplicate 'cfi'")
> > + if v != 1:
> > + raise ValueError("vlan(): cfi must be 1 for Ethernet")
> > + tci |= ovskey._VLAN_CFI_MASK
> > + mask |= ovskey._VLAN_CFI_MASK
> > + has_cfi = True
> > + else:
> > + raise ValueError("vlan(): unknown key '%s'" % key)
> > +
> > + flowstr = flowstr[1:] # skip ')'
> > + # Catch immediate '))' (user error). A ')' after ',' is consumed
> > + # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
> > + if flowstr.lstrip().startswith(')'):
> > + raise ValueError("vlan(): unmatched ')'")
> > + # parse() strips trailing ',', ')', ' ' as inter-field separators,
> > + # so we do not need to call strspn here.
> > +
> > + if mask == 0:
> > + raise ValueError("vlan(): no fields specified, "
> > + "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
> > + if not has_tci:
> > + tci |= ovskey._VLAN_CFI_MASK
> > + mask |= ovskey._VLAN_CFI_MASK
> > + return flowstr, tci, mask
> > +
> > + @staticmethod
> > + def _parse_encap_from_flowstr(flowstr):
> > + """Parse encap(inner_flow) from flowstr.
> > +
> > + Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
> > + where each dict has an 'attrs' key for recursive NLA encoding.
> > + Parenthesis-depth tracking handles nested encap() calls but not
> > + quoted strings containing literal parentheses.
> > + """
> > + if ovskey._encap_depth >= ovskey._MAX_ENCAP_DEPTH:
> > + raise ValueError("encap(): max nesting depth %d exceeded" %
> > + ovskey._MAX_ENCAP_DEPTH)
> > + try:
> > + ovskey._encap_depth += 1
> > + depth = 1
> > + end = -1
> > + for i, c in enumerate(flowstr):
> > + if c == '(':
> > + depth += 1
> > + elif c == ')':
> > + depth -= 1
> > + if depth < 0:
> > + raise ValueError("encap(): unmatched ')' at position %d" % i)
> > + if depth == 0:
> > + end = i
> > + break
> > +
> > + if end == -1:
> > + if depth > 1:
> > + raise ValueError("encap(): missing ')' at end")
> > + raise ValueError("encap(): missing closing ')'")
> > +
> > + inner_str = flowstr[:end].strip()
> > + if not inner_str:
> > + raise ValueError("encap(): empty inner flow")
> > +
> > + flowstr = flowstr[end + 1:]
> > + if flowstr.lstrip().startswith(')'):
> > + raise ValueError("encap(): unmatched ')' after encap()")
> > + # parse() strips trailing ',', ')', ' ' as inter-field separators,
> > + # so we do not need to call strspn here.
> > +
> > + inner_key = ovskey()
> > + inner_mask = ovskey()
> > + inner_key.parse(inner_str, inner_mask)
> > +
> > + return flowstr, inner_key, inner_mask
> > + finally:
> > + ovskey._encap_depth -= 1
> > +
> > def parse(self, flowstr, mask=None):
> > for field in (
> > ("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
> > @@ -1657,6 +1831,16 @@ class ovskey(nla):
> > "eth_type",
> > lambda x: intparse(x, "0xffff"),
> > ),
> > + (
> > + "OVS_KEY_ATTR_VLAN",
> > + "vlan",
> > + ovskey._parse_vlan_from_flowstr,
> > + ),
> > + (
> > + "OVS_KEY_ATTR_ENCAP",
> > + "encap",
> > + ovskey._parse_encap_from_flowstr,
> > + ),
> > (
> > "OVS_KEY_ATTR_IPV4",
> > "ipv4",
> > @@ -1794,6 +1978,8 @@ class ovskey(nla):
> > True,
> > ),
> > ("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
> > + ("OVS_KEY_ATTR_VLAN", "vlan", "0x%04x", lambda x: False, True),
> > + ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
> > (
> > "OVS_KEY_ATTR_ETHERTYPE",
> > "eth_type",
>
^ permalink raw reply
* Re: [PATCH net-next v2 2/2] selftests: openvswitch: add pop_vlan test
From: Aaron Conole @ 2026-05-04 15:51 UTC (permalink / raw)
To: Minxi Hou
Cc: netdev, linux-kselftest, dev, Eelco Chaudron, Ilya Maximets,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kernel
In-Reply-To: <20260501133924.3100680-3-houminxi@gmail.com>
Hi Minxi,
Thanks for the test (and the support infra in 1/2). Questions below.
Minxi Hou <houminxi@gmail.com> writes:
> Add a test for the OVS datapath POP_VLAN action. The test uses
> the VLAN sub-interface of a veth pair to generate 802.1Q tagged
> frames and verifies that pop_vlan correctly strips the VLAN tag
> before delivery via pcap frame capture.
>
> The test has two verifications:
> - Negative: forward tagged frames without pop_vlan, verify the
> VLAN tag is still present on the egress interface.
> - Positive: apply pop_vlan, verify the tag is removed and an
> untagged ICMP echo request arrives at the egress interface.
>
> Static ARP entries avoid the complexity of VLAN-tagged ARP
> resolution (the egress side has no VLAN sub-interface and cannot
> process tagged ARP).
>
> Signed-off-by: Minxi Hou <houminxi@gmail.com>
> ---
> v1 -> v2: rebase to latest net-next/main, drop --base=auto
>
> .../selftests/net/openvswitch/openvswitch.sh | 132 ++++++++++++++++++
> 1 file changed, 132 insertions(+)
>
> diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
> index b327d3061ed5..cd614ff7b740 100755
> --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
> +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
> @@ -27,6 +27,7 @@ tests="
> upcall_interfaces ovs: test the upcall interfaces
> tunnel_metadata ovs: test extraction of tunnel metadata
> drop_reason drop: test drop reasons are emitted
> + pop_vlan vlan-pop: POP_VLAN action strips 802.1Q tag
nit: The whitespace alignment above is slightly off.
> psample psample: Sampling packets with psample"
>
> info() {
> @@ -830,6 +831,137 @@ test_tunnel_metadata() {
> return 0
> }
>
> +test_pop_vlan() {
> + modprobe -q openvswitch 2>/dev/null || true
> + [ -d /sys/module/openvswitch ] || return $ksft_skip
> + ip netns add __test_pop_vlan_netns__ 2>/dev/null || \
> + { info "CONFIG_NET_NS missing"; return $ksft_skip; }
> + ip netns del __test_pop_vlan_netns__ 2>/dev/null
Weird that this is here. All other tests would also not work if there
were no netns or OVS module. Why were these added?
> + modprobe -q 8021q 2>/dev/null || true
> + [ -d /sys/module/8021q ] || { info "CONFIG_VLAN_8021Q missing"; return $ksft_skip; }
> +
> + local sbx="test_pop_vlan"
> + sbx_add "$sbx" || return $ksft_skip
> + ovs_add_dp "$sbx" vlandp || return 1
> +
> + # --- baseline: untagged forwarding ---
> + ovs_add_netns_and_veths "$sbx" vlandp ns1 veth1 ns1veth 192.0.2.1/24 || return 1
> + ovs_add_netns_and_veths "$sbx" vlandp ns2 veth2 ns2veth 192.0.2.2/24 || return 1
> +
> + # ARP + IPv4 bidirectional (all untagged)
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(1),eth(),eth_type(0x0800),ipv4()' '2' || return 1
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
> + ip netns exec ns1 ping -c 3 -W 2 192.0.2.2 || return 1
> +
> + # --- POP_VLAN test ---
> + # Register cleanup before creating resources (safe on failure paths)
> + on_exit "ip -n ns1 link del ns1veth.10 2>/dev/null || true
> + ip -n ns2 addr del 198.51.100.2/24 dev ns2veth 2>/dev/null || true"
> +
> + # ns1: VLAN sub-interface generates tagged frames
> + ip -n ns1 link add link ns1veth name ns1veth.10 type vlan id 10
> + ip -n ns1 addr add 198.51.100.1/24 dev ns1veth.10
> + ip -n ns1 link set ns1veth.10 up
> +
> + # ns2: no VLAN sub-interface. POP delivers untagged frames to ns2veth
> + ip -n ns2 addr add 198.51.100.2/24 dev ns2veth
> +
> + # veth disable VLAN offload + GRO (ensure kernel software tag processing)
> + if command -v ethtool >/dev/null 2>&1; then
> + ip netns exec ns1 ethtool -k ns1veth 2>/dev/null | grep -q vlan-offload && \
> + ip netns exec ns1 ethtool -K ns1veth rx-vlan-offload off \
> + tx-vlan-offload off gro off 2>/dev/null || true
> + ip netns exec ns2 ethtool -k ns2veth 2>/dev/null | grep -q vlan-offload && \
> + ip netns exec ns2 ethtool -K ns2veth rx-vlan-offload off \
> + tx-vlan-offload off gro off 2>/dev/null || true
> + fi
> +
> + ovs_del_flows "$sbx" vlandp
> +
> + # Static ARP avoids VLAN-tagged ARP complexity (ns2 has no VLAN
> + # sub-interface, so tagged ARP would be invisible to ns2).
> + local ns1veth10mac ns2mac
> + ns1veth10mac=$(ip -n ns1 link show ns1veth.10 | \
> + awk '/link\/ether/ {print $2}')
> + ns2mac=$(ip -n ns2 link show ns2veth | \
> + awk '/link\/ether/ {print $2}')
> + [ -n "$ns1veth10mac" ] && echo "$ns1veth10mac" | \
> + grep -qE "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" || return 1
> + [ -n "$ns2mac" ] && echo "$ns2mac" | \
> + grep -qE "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" || return 1
> + ip -n ns1 neigh replace 198.51.100.2 lladdr "$ns2mac" \
> + dev ns1veth.10 nud permanent || return 1
> + ip -n ns2 neigh replace 198.51.100.1 lladdr "$ns1veth10mac" \
> + dev ns2veth nud permanent || return 1
> +
> + # --- Negative check: fwd without pop_vlan, VLAN tag stays ---
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(1),eth(),eth_type(0x8100),vlan(vid=10),encap(eth_type(0x0800),ipv4(src=198.51.100.1,proto=1),icmp())' \
> + '2' || return 1
> +
> + local pcap_no_pop
> + pcap_no_pop=$(mktemp --suffix=.pcap)
> + on_exit "rm -f $pcap_no_pop"
> + ip netns exec ns2 tcpdump -nei ns2veth -w "$pcap_no_pop" -U &
> + local tpid_no_pop=$!
> + on_exit "kill $tpid_no_pop 2>/dev/null || true"
> + sleep $((WAIT_TIMEOUT / 5 < 2 ? 2 : WAIT_TIMEOUT / 5))
> +
We don't like to make extra files when not requested. For example, this
is making some pcap file but do we need it? We also have a specific
spawn function `ovs_netns_spawn_daemon` that should take care of
cleaning up at least the PID.
> + ip netns exec ns1 ping -I ns1veth.10 -c 3 -W 1 198.51.100.2 \
> + >/dev/null 2>&1 || true
> + kill $tpid_no_pop 2>/dev/null || true; wait $tpid_no_pop 2>/dev/null || true
> +
> + # assert: VLAN tag still present (no pop_vlan in action)
> + tcpdump -nr "$pcap_no_pop" 'vlan' 2>/dev/null | grep -q . || {
> + info "FAIL: negative check: no VLAN tag (expected tag present)"; return 1
> + }
> +
> + ovs_del_flows "$sbx" vlandp
> +
> + # --- Positive: pop_vlan strips tag ---
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(1),eth(),eth_type(0x8100),vlan(vid=10),encap(eth_type(0x0800),ipv4(src=198.51.100.1,proto=1),icmp())' \
> + 'pop_vlan,2' || return 1
> + ovs_add_flow "$sbx" vlandp \
> + 'in_port(2),eth(),eth_type(0x0800),ipv4()' '1' || return 1
> +
> + local pcap
> + pcap=$(mktemp --suffix=.pcap)
> + on_exit "rm -f $pcap"
> + ip netns exec ns2 tcpdump -nei ns2veth -w "$pcap" -U &
> + local tpid=$!
> + on_exit "kill $tpid 2>/dev/null || true"
> + sleep $((WAIT_TIMEOUT / 5 < 2 ? 2 : WAIT_TIMEOUT / 5))
> +
> + # ping reply unreachable: ns1veth.10 only accepts tagged frames,
> + # ns2 sends untagged reply -> dropped by ns1veth.10.
> + local ping_rc=0
> + ip netns exec ns1 ping -I ns1veth.10 -c 3 -W 1 198.51.100.2 \
> + >/dev/null 2>&1 || ping_rc=$?
> + kill $tpid 2>/dev/null || true; wait $tpid 2>/dev/null || true
> +
> + # ping failure is expected (reply path asymmetric)
> + [ "$ping_rc" -ne 0 ] || \
> + info "NOTE: ping succeeded unexpectedly (reply reached ns1veth.10)"
> +
> + # assert: no VLAN tag (POP succeeded), untagged ICMP echo request arrived
> + tcpdump -nr "$pcap" 'vlan' 2>/dev/null | grep -q . && {
> + info "FAIL: POP_VLAN: VLAN tag still present"; return 1
> + }
> + tcpdump -nr "$pcap" 'icmp and icmp[icmptype]=8' \
> + 2>/dev/null | grep -q . || {
> + info "FAIL: POP_VLAN: no untagged ICMP echo request"; return 1
> + }
Do we really need these tcpdump checks? The ping commands above should
already capture when we are expecting tagged / untagged traffic.
> + return 0
> +}
> +
> run_test() {
> (
> tname="$1"
^ permalink raw reply
* Re: [PATCH net-next v2 1/2] selftests: openvswitch: add vlan() and encap() flow string parsing
From: Aaron Conole @ 2026-05-04 15:43 UTC (permalink / raw)
To: Minxi Hou
Cc: netdev, linux-kselftest, dev, Eelco Chaudron, Ilya Maximets,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kernel
In-Reply-To: <20260501133924.3100680-2-houminxi@gmail.com>
Minxi Hou <houminxi@gmail.com> writes:
> Extend the ovs-dpctl.py flow parser to support vlan() and encap()
> match strings. vlan() accepts tci=, vid=, pcp=, and cfi=
> parameters and generates the OVS_KEY_ATTR_VLAN attribute with a TCI
> value in network byte order. encap() parses nested flow strings
> and returns OVS_KEY_ATTR_ENCAP with inner key attributes as a
> recursive NLA container.
>
> The encap nla_map type is changed from "none" to "nested" so that
> pyroute2 recursively encodes the inner flow key attributes. The
> VLAN nla_map type is changed from "uint16" to "be16" to match the
> kernel's big-endian wire format.
^ That's kindof a fix, BUT it also seems like we never actually used it
anywhere, so I hope it is okay to not treat it that way.
> Signed-off-by: Minxi Hou <houminxi@gmail.com>
> ---
> v1 -> v2: rebase to latest net-next/main, drop --base=auto
>
> .../selftests/net/openvswitch/ovs-dpctl.py | 190 +++++++++++++++++-
> 1 file changed, 188 insertions(+), 2 deletions(-)
>
> diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> index 848f61fdcee0..317be7878937 100644
> --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> @@ -901,11 +901,11 @@ class ovskey(nla):
> nla_flags = NLA_F_NESTED
> nla_map = (
> ("OVS_KEY_ATTR_UNSPEC", "none"),
> - ("OVS_KEY_ATTR_ENCAP", "none"),
> + ("OVS_KEY_ATTR_ENCAP", "nested"),
> ("OVS_KEY_ATTR_PRIORITY", "uint32"),
> ("OVS_KEY_ATTR_IN_PORT", "uint32"),
> ("OVS_KEY_ATTR_ETHERNET", "ethaddr"),
> - ("OVS_KEY_ATTR_VLAN", "uint16"),
> + ("OVS_KEY_ATTR_VLAN", "be16"),
> ("OVS_KEY_ATTR_ETHERTYPE", "be16"),
> ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"),
> ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"),
> @@ -1636,6 +1636,180 @@ class ovskey(nla):
> class ovs_key_mpls(nla):
> fields = (("lse", ">I"),)
>
> + # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet
> + _VLAN_CFI_MASK = 0x1000
> + _MAX_ENCAP_DEPTH = 4
It would be more useful if we could set the MAX_ENCAP_DEPTH separately,
so that we have the option to break assumptions. Maybe just skip a
depth check here completely.
> + _encap_depth = 0 # single-threaded usage assumed
> +
> + @staticmethod
> + def _parse_vlan_from_flowstr(flowstr):
> + """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr.
> +
> + Returns (remaining_flowstr, key_tci, mask_tci).
> + TCI values use standard bit layout (VID bits 0-11,
> + CFI bit 12, PCP bits 13-15); byte order conversion to
> + big-endian happens in pyroute2 be16 NLA serialization.
> + The mask covers only the fields the caller specified:
> + vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF.
> +
> + The tci= key sets the raw TCI bitfield (no CFI validation) to allow
> + non-Ethernet use cases. Use cfi=1 for standard Ethernet VLAN matching.
> + """
> + tci = 0
> + mask = 0
> + has_tci = False
> + has_vid = has_pcp = has_cfi = False
> + _tci_mix_err = "vlan(): 'tci' cannot be mixed " \
> + "with 'vid'/'pcp'/'cfi'"
> + first = True
> + while True:
> + flowstr = flowstr.lstrip()
> + if not flowstr:
> + raise ValueError("vlan(): missing ')'")
> + if flowstr[0] == ')':
> + break
> + if not first:
> + flowstr = flowstr[1:] # skip ','
> + if not flowstr:
> + raise ValueError("vlan(): missing ')' after trailing comma")
> + flowstr = flowstr.lstrip()
> + if flowstr and flowstr[0] == ')':
> + break
> + if flowstr and flowstr[0] == ',':
> + raise ValueError(
> + "vlan(): empty or extra comma in field list")
> + first = False
> +
> + eq = flowstr.find('=')
> + if eq == -1:
> + raise ValueError("vlan(): expected key=value, got '%s'" % flowstr)
> + key = flowstr[:eq].strip()
> + flowstr = flowstr[eq + 1:]
> +
> + end = flowstr.find(',')
> + end2 = flowstr.find(')')
> + if end == -1 or (end2 != -1 and end2 < end):
> + end = end2
> + val = flowstr[:end].strip()
> + flowstr = flowstr[end:]
> +
> + if not val:
> + raise ValueError("vlan(): empty value for key '%s'" % key)
> + try:
> + v = int(val, 16) if val.startswith(('0x', '0X')) else int(val)
> + except ValueError:
> + raise ValueError("vlan(): invalid value '%s' for key '%s'" %
> + (val, key))
> +
> + if key == 'tci':
> + if has_tci:
> + raise ValueError("vlan(): duplicate 'tci'")
> + if has_vid or has_pcp or has_cfi:
> + raise ValueError(_tci_mix_err)
> + if v > 0xFFFF or v < 0:
> + raise ValueError("vlan(): tci=0x%x out of range" % v)
> + tci = v
> + mask = 0xFFFF
> + has_tci = True
> + elif key == 'vid':
> + if has_tci:
> + raise ValueError(_tci_mix_err)
> + if has_vid:
> + raise ValueError("vlan(): duplicate 'vid'")
> + if v < 0 or v > 0xFFF:
> + raise ValueError("vlan(): vid=%d out of range (0-4095)" % v)
> + tci |= v
> + mask |= 0x0FFF
> + has_vid = True
> + elif key == 'pcp':
> + if has_tci:
> + raise ValueError(_tci_mix_err)
> + if has_pcp:
> + raise ValueError("vlan(): duplicate 'pcp'")
> + if v < 0 or v > 7:
> + raise ValueError("vlan(): pcp=%d out of range (0-7)" % v)
> + tci |= (v & 0x7) << 13
> + mask |= 0xE000
> + has_pcp = True
> + elif key == 'cfi':
> + if has_tci:
> + raise ValueError(_tci_mix_err)
> + if has_cfi:
> + raise ValueError("vlan(): duplicate 'cfi'")
> + if v != 1:
> + raise ValueError("vlan(): cfi must be 1 for Ethernet")
> + tci |= ovskey._VLAN_CFI_MASK
> + mask |= ovskey._VLAN_CFI_MASK
> + has_cfi = True
> + else:
> + raise ValueError("vlan(): unknown key '%s'" % key)
> +
> + flowstr = flowstr[1:] # skip ')'
> + # Catch immediate '))' (user error). A ')' after ',' is consumed
> + # by parse()'s strspn(flowstr, "), ") inter-field separator stripping.
> + if flowstr.lstrip().startswith(')'):
> + raise ValueError("vlan(): unmatched ')'")
> + # parse() strips trailing ',', ')', ' ' as inter-field separators,
> + # so we do not need to call strspn here.
> +
> + if mask == 0:
> + raise ValueError("vlan(): no fields specified, "
> + "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)")
> + if not has_tci:
> + tci |= ovskey._VLAN_CFI_MASK
> + mask |= ovskey._VLAN_CFI_MASK
> + return flowstr, tci, mask
> +
> + @staticmethod
> + def _parse_encap_from_flowstr(flowstr):
> + """Parse encap(inner_flow) from flowstr.
> +
> + Returns (remaining_flowstr, inner_key_dict, inner_mask_dict)
> + where each dict has an 'attrs' key for recursive NLA encoding.
> + Parenthesis-depth tracking handles nested encap() calls but not
> + quoted strings containing literal parentheses.
> + """
> + if ovskey._encap_depth >= ovskey._MAX_ENCAP_DEPTH:
> + raise ValueError("encap(): max nesting depth %d exceeded" %
> + ovskey._MAX_ENCAP_DEPTH)
> + try:
> + ovskey._encap_depth += 1
> + depth = 1
> + end = -1
> + for i, c in enumerate(flowstr):
> + if c == '(':
> + depth += 1
> + elif c == ')':
> + depth -= 1
> + if depth < 0:
> + raise ValueError("encap(): unmatched ')' at position %d" % i)
> + if depth == 0:
> + end = i
> + break
> +
> + if end == -1:
> + if depth > 1:
> + raise ValueError("encap(): missing ')' at end")
> + raise ValueError("encap(): missing closing ')'")
> +
> + inner_str = flowstr[:end].strip()
> + if not inner_str:
> + raise ValueError("encap(): empty inner flow")
> +
> + flowstr = flowstr[end + 1:]
> + if flowstr.lstrip().startswith(')'):
> + raise ValueError("encap(): unmatched ')' after encap()")
> + # parse() strips trailing ',', ')', ' ' as inter-field separators,
> + # so we do not need to call strspn here.
> +
> + inner_key = ovskey()
> + inner_mask = ovskey()
> + inner_key.parse(inner_str, inner_mask)
> +
> + return flowstr, inner_key, inner_mask
> + finally:
> + ovskey._encap_depth -= 1
> +
> def parse(self, flowstr, mask=None):
> for field in (
> ("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse),
> @@ -1657,6 +1831,16 @@ class ovskey(nla):
> "eth_type",
> lambda x: intparse(x, "0xffff"),
> ),
> + (
> + "OVS_KEY_ATTR_VLAN",
> + "vlan",
> + ovskey._parse_vlan_from_flowstr,
> + ),
> + (
> + "OVS_KEY_ATTR_ENCAP",
> + "encap",
> + ovskey._parse_encap_from_flowstr,
> + ),
> (
> "OVS_KEY_ATTR_IPV4",
> "ipv4",
> @@ -1794,6 +1978,8 @@ class ovskey(nla):
> True,
> ),
> ("OVS_KEY_ATTR_ETHERNET", None, None, False, False),
> + ("OVS_KEY_ATTR_VLAN", "vlan", "0x%04x", lambda x: False, True),
> + ("OVS_KEY_ATTR_ENCAP", None, None, False, False),
> (
> "OVS_KEY_ATTR_ETHERTYPE",
> "eth_type",
^ permalink raw reply
* [PATCH net-next 5/5] net: dsa: mv88e6xxx: enable devlink ATU hash param for 6320 family
From: Marek Behún @ 2026-05-04 15:32 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, Russell King (Oracle),
Vivien Didelot, Tobias Waldekranz, netdev, Fidan Aliyeva
Cc: Lev Olshvang, Marek Behún
In-Reply-To: <20260504153227.1390546-1-kabel@kernel.org>
Commit 23e8b470c7788 ("net: dsa: mv88e6xxx: Add devlink param for ATU
hash algorithm.") introduced ATU hash algorithm access via devlink, but
did not enable it for the 6320 family. Do it now.
Signed-off-by: Marek Behún <kabel@kernel.org>
---
drivers/net/dsa/mv88e6xxx/chip.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 09da154f80d7..4234755100ea 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5196,6 +5196,8 @@ static const struct mv88e6xxx_ops mv88e6320_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6352_g1_rmu_disable,
+ .atu_get_hash = mv88e6165_g1_atu_get_hash,
+ .atu_set_hash = mv88e6165_g1_atu_set_hash,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.stu_getnext = mv88e6352_g1_stu_getnext,
@@ -5250,6 +5252,8 @@ static const struct mv88e6xxx_ops mv88e6321_ops = {
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_g1_reset,
.rmu_disable = mv88e6352_g1_rmu_disable,
+ .atu_get_hash = mv88e6165_g1_atu_get_hash,
+ .atu_set_hash = mv88e6165_g1_atu_set_hash,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.stu_getnext = mv88e6352_g1_stu_getnext,
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 4/5] net: dsa: mv88e6xxx: enable .rmu_disable() for 6320 family
From: Marek Behún @ 2026-05-04 15:32 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, Russell King (Oracle),
Vivien Didelot, Tobias Waldekranz, netdev, Fidan Aliyeva
Cc: Lev Olshvang, Marek Behún
In-Reply-To: <20260504153227.1390546-1-kabel@kernel.org>
Commit 9e5baf9b3636 ("net: dsa: mv88e6xxx: add RMU disable op") did not
add the .rmu_disable() method for the 6320 family. Add it now.
Signed-off-by: Marek Behún <kabel@kernel.org>
---
drivers/net/dsa/mv88e6xxx/chip.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index b55a9d4a1ee1..09da154f80d7 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5195,6 +5195,7 @@ static const struct mv88e6xxx_ops mv88e6320_ops = {
.hardware_reset_pre = mv88e6xxx_g2_eeprom_wait,
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_g1_reset,
+ .rmu_disable = mv88e6352_g1_rmu_disable,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.stu_getnext = mv88e6352_g1_stu_getnext,
@@ -5248,6 +5249,7 @@ static const struct mv88e6xxx_ops mv88e6321_ops = {
.hardware_reset_pre = mv88e6xxx_g2_eeprom_wait,
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_g1_reset,
+ .rmu_disable = mv88e6352_g1_rmu_disable,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.stu_getnext = mv88e6352_g1_stu_getnext,
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 3/5] net: dsa: mv88e6xxx: define .pot_clear() for 6321
From: Marek Behún @ 2026-05-04 15:32 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, Russell King (Oracle),
Vivien Didelot, Tobias Waldekranz, netdev, Fidan Aliyeva
Cc: Lev Olshvang, Marek Behún
In-Reply-To: <20260504153227.1390546-1-kabel@kernel.org>
Commit 9e907d739cc3 ("net: dsa: mv88e6xxx: add POT operation") did not
add the .pot_clear() method to the 6321 switch operations structure.
Add them now.
Signed-off-by: Marek Behún <kabel@kernel.org>
---
drivers/net/dsa/mv88e6xxx/chip.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 78799666e6b7..b55a9d4a1ee1 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5244,6 +5244,7 @@ static const struct mv88e6xxx_ops mv88e6321_ops = {
.set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6352_g2_mgmt_rsvd2cpu,
+ .pot_clear = mv88e6xxx_g2_pot_clear,
.hardware_reset_pre = mv88e6xxx_g2_eeprom_wait,
.hardware_reset_post = mv88e6xxx_g2_eeprom_wait,
.reset = mv88e6352_g1_reset,
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox