* Re: [RFC PATCH] net/iucv: Descend into net/iucv when AFIUCV is enabled
From: Alexandra Winter @ 2026-07-07 14:54 UTC (permalink / raw)
To: Pengpeng Hou, Thorsten Winkler, David Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Heiko Carstens, linux-s390, netdev, linux-kernel
In-Reply-To: <20260625061303.36326-1-pengpeng@iscas.ac.cn>
On 25.06.26 08:13, Pengpeng Hou wrote:
> AFIUCV can be enabled by the QETH_L3/HiperSockets path even when IUCV
> itself is not enabled. However, the top-level net Makefile only descends
> into net/iucv/ under CONFIG_IUCV.
>
> That creates a Kconfig/Kbuild carrier mismatch: CONFIG_AFIUCV=m can be
the same is true for CONFIG_AFIUCV=y; please mention both cases.
(Maybe also mention explicitely that CONFIG_IUCV=n is required to trigger the issue)
> selected, but af_iucv.o is never considered because the containing
> directory is skipped.
>
> This RFC uses an always-descend model for net/iucv/. The subdirectory
> Makefile already gates iucv.o and af_iucv.o on their own Kconfig symbols,
> so entering the directory does not force either provider object on.
>
> This is intentionally RFC because s390 maintainers should confirm whether
> the QETH_L3-only AF_IUCV configuration is intended to build af_iucv.o
> without the base IUCV object.
>
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> net/Makefile | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/Makefile b/net/Makefile
> --- a/net/Makefile
> +++ b/net/Makefile
> @@ -45,7 +45,7 @@
> obj-$(CONFIG_MAC80211) += mac80211/
> obj-$(CONFIG_TIPC) += tipc/
> obj-$(CONFIG_NETLABEL) += netlabel/
> -obj-$(CONFIG_IUCV) += iucv/
> +obj-y += iucv/
> obj-$(CONFIG_SMC) += smc/
> obj-$(CONFIG_RFKILL) += rfkill/
> obj-$(CONFIG_NET_9P) += 9p/
For the records: I do not see a usecase where somebody would
create configuration with AF_IUCV=y/m and IUCV=n; i.e. where
somebody would support AF_IUCV over HiperSockets, but not via
z/VM. But technically, it is possible and who knows.
So please re-send as a regular problem patch to net and add my
Reviewed-by: Alexandra Winter <wintera@linux.ibm.com>
Thanks you.
^ permalink raw reply
* [PATCH v2 net-next 3/3] geneve: make geneve_fill_info() RTNL independent
From: Eric Dumazet @ 2026-07-07 14:53 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet
In-Reply-To: <20260707145331.3717941-1-edumazet@google.com>
Now that geneve->cfg is an RCU-protected pointer, update
geneve_fill_info() to read the configuration under RCU read lock
instead of relying on RTNL.
Also add const qualifiers to the dereferenced pointers where appropriate
and fix local variable declaration ordering.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/geneve.c | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index f14e019f3f3f4d861ebd9e5e81175b8149b9acc2..17bd3543d587a9e941737109df82b83023d23711 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -2464,17 +2464,27 @@ static size_t geneve_get_size(const struct net_device *dev)
static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
- struct geneve_dev *geneve = netdev_priv(dev);
- struct geneve_config *cfg = rtnl_dereference(geneve->cfg);
- struct ip_tunnel_info *info = &cfg->info;
- bool ttl_inherit = cfg->ttl_inherit;
- bool metadata = cfg->collect_md;
- struct ifla_geneve_port_range ports = {
- .low = htons(cfg->port_min),
- .high = htons(cfg->port_max),
- };
+ const struct geneve_dev *geneve = netdev_priv(dev);
+ struct ifla_geneve_port_range ports;
+ const struct geneve_config *cfg;
+ const struct ip_tunnel_info *info;
+ bool ttl_inherit, metadata;
__u8 tmp_vni[3];
__u32 vni;
+ int err = 0;
+
+ rcu_read_lock();
+ cfg = rcu_dereference(geneve->cfg);
+ if (!cfg) {
+ err = -ENODEV;
+ goto out;
+ }
+
+ info = &cfg->info;
+ ttl_inherit = cfg->ttl_inherit;
+ metadata = cfg->collect_md;
+ ports.low = htons(cfg->port_min);
+ ports.high = htons(cfg->port_max);
tunnel_id_to_vni(info->key.tun_id, tmp_vni);
vni = (tmp_vni[0] << 16) | (tmp_vni[1] << 8) | tmp_vni[2];
@@ -2554,10 +2564,13 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
nla_put_flag(skb, IFLA_GENEVE_GRO_HINT))
goto nla_put_failure;
- return 0;
+out:
+ rcu_read_unlock();
+ return err;
nla_put_failure:
- return -EMSGSIZE;
+ err = -EMSGSIZE;
+ goto out;
}
static struct rtnl_link_ops geneve_link_ops __read_mostly = {
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH v2 net-next 2/3] geneve: convert config to RCU-protected pointer
From: Eric Dumazet @ 2026-07-07 14:53 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet
In-Reply-To: <20260707145331.3717941-1-edumazet@google.com>
geneve_changelink() currently updates configuration by copying it over
the old one using memcpy() under RTNL, forcing data path pause via
geneve_quiesce() and synchronize_net() to avoid reading torn values.
Convert geneve->cfg to an RCU-protected pointer, allowing lockless
and safe reads under RCU read lock without synchronization overhead.
Key changes:
- Introduced geneve_config_alloc/free() helpers for lifecycle.
- geneve_configure() allocates config and publishes it via RCU.
- Setting dev->priv_destructor = geneve_free_dev handles config cleanup
if register_netdevice() fails or during netdev unregistration.
- geneve_changelink() performs RCU swap; old config is freed via call_rcu_hurry().
- Allocates new dst_cache during changelink to prevent pcpu sharing.
- Removed geneve_quiesce/unquiesce() and synchronize_net() from changelink.
- Added rcu_barrier() to module exit to wait for pending callbacks.
- Updated data path to use rcu_dereference().
- Updated geneve_fill_info() to use rtnl_dereference() for now.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
drivers/net/geneve.c | 213 ++++++++++++++++++++++++-------------------
1 file changed, 118 insertions(+), 95 deletions(-)
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index cab38e7de8715f5a1ee0869e1d6c7b7ea9c693c1..f14e019f3f3f4d861ebd9e5e81175b8149b9acc2 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -82,6 +82,7 @@ struct geneve_config {
u16 port_min;
u16 port_max;
+ struct rcu_head rcu;
/* Must be last --ends in a flexible-array member. */
struct ip_tunnel_info info;
};
@@ -100,7 +101,7 @@ struct geneve_dev {
#endif
struct list_head next; /* geneve's per namespace list */
struct gro_cells gro_cells;
- struct geneve_config cfg;
+ struct geneve_config __rcu *cfg;
};
struct geneve_sock {
@@ -182,8 +183,10 @@ static struct geneve_dev *geneve_lookup(struct geneve_sock *gs,
hash = geneve_net_vni_hash(vni);
vni_list_head = &gs->vni_list[hash];
hlist_for_each_entry_rcu(node, vni_list_head, hlist) {
- if (eq_tun_id_and_vni((u8 *)&node->geneve->cfg.info.key.tun_id, vni) &&
- addr == node->geneve->cfg.info.key.u.ipv4.dst)
+ const struct geneve_config *cfg = rcu_dereference(node->geneve->cfg);
+
+ if (eq_tun_id_and_vni((u8 *)&cfg->info.key.tun_id, vni) &&
+ addr == cfg->info.key.u.ipv4.dst)
return node->geneve;
}
return NULL;
@@ -201,8 +204,10 @@ static struct geneve_dev *geneve6_lookup(struct geneve_sock *gs,
hash = geneve_net_vni_hash(vni);
vni_list_head = &gs->vni_list[hash];
hlist_for_each_entry_rcu(node, vni_list_head, hlist) {
- if (eq_tun_id_and_vni((u8 *)&node->geneve->cfg.info.key.tun_id, vni) &&
- ipv6_addr_equal(&addr6, &node->geneve->cfg.info.key.u.ipv6.dst))
+ const struct geneve_config *cfg = rcu_dereference(node->geneve->cfg);
+
+ if (eq_tun_id_and_vni((u8 *)&cfg->info.key.tun_id, vni) &&
+ ipv6_addr_equal(&addr6, &cfg->info.key.u.ipv6.dst))
return node->geneve;
}
return NULL;
@@ -386,11 +391,6 @@ static int geneve_init(struct net_device *dev)
if (err)
return err;
- err = dst_cache_init(&geneve->cfg.info.dst_cache, GFP_KERNEL);
- if (err) {
- gro_cells_destroy(&geneve->gro_cells);
- return err;
- }
netdev_lockdep_set_classes(dev);
return 0;
}
@@ -399,7 +399,6 @@ static void geneve_uninit(struct net_device *dev)
{
struct geneve_dev *geneve = netdev_priv(dev);
- dst_cache_destroy(&geneve->cfg.info.dst_cache);
gro_cells_destroy(&geneve->gro_cells);
}
@@ -650,6 +649,7 @@ static int geneve_post_decap_hint(const struct sock *sk, struct sk_buff *skb,
/* Callback from net/ipv4/udp.c to receive packets */
static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
{
+ const struct geneve_config *cfg;
struct genevehdr *geneveh;
struct geneve_dev *geneve;
struct geneve_sock *gs;
@@ -675,8 +675,9 @@ static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
inner_proto = geneveh->proto_type;
- if (unlikely((!geneve->cfg.inner_proto_inherit &&
- inner_proto != htons(ETH_P_TEB)))) {
+ cfg = rcu_dereference(geneve->cfg);
+ if (unlikely(!cfg || (!cfg->inner_proto_inherit &&
+ inner_proto != htons(ETH_P_TEB)))) {
dev_dstats_rx_dropped(geneve->dev);
goto drop;
}
@@ -1141,10 +1142,11 @@ static int geneve_sock_add(struct geneve_dev *geneve,
static int geneve_open(struct net_device *dev)
{
struct geneve_dev *geneve = netdev_priv(dev);
- const struct geneve_config *cfg = &geneve->cfg;
+ const struct geneve_config *cfg;
bool ipv4, ipv6, dualstack;
int ret = 0;
+ cfg = rtnl_dereference(geneve->cfg);
dualstack = cfg->dualstack;
ipv6 = cfg->info.mode & IP_TUNNEL_INFO_IPV6 || dualstack;
ipv4 = !ipv6 || dualstack;
@@ -1552,20 +1554,21 @@ static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
const struct geneve_config *cfg;
int err;
- cfg = &geneve->cfg;
+ rcu_read_lock();
+ cfg = rcu_dereference(geneve->cfg);
if (cfg->collect_md) {
info = skb_tunnel_info(skb);
if (unlikely(!info || !(info->mode & IP_TUNNEL_INFO_TX))) {
netdev_dbg(dev, "no tunnel metadata\n");
dev_kfree_skb(skb);
dev_dstats_tx_dropped(dev);
+ rcu_read_unlock();
return NETDEV_TX_OK;
}
} else {
info = &cfg->info;
}
- rcu_read_lock();
#if IS_ENABLED(CONFIG_IPV6)
if (info->mode & IP_TUNNEL_INFO_IPV6)
err = geneve6_xmit_skb(skb, dev, geneve, cfg, info);
@@ -1604,9 +1607,13 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
{
struct ip_tunnel_info *info = skb_tunnel_info(skb);
struct geneve_dev *geneve = netdev_priv(dev);
- const struct geneve_config *cfg = &geneve->cfg;
+ const struct geneve_config *cfg;
__be16 sport;
+ cfg = rcu_dereference(geneve->cfg);
+ if (unlikely(!cfg))
+ return -ENODEV;
+
if (ip_tunnel_info_af(info) == AF_INET) {
struct rtable *rt;
struct geneve_sock *gs4 = rcu_dereference(geneve->sock4);
@@ -1721,7 +1728,50 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push)
}
}
+static struct geneve_config *geneve_config_alloc(const struct geneve_config *src)
+{
+ struct geneve_config *cfg;
+ int err;
+
+ cfg = kmemdup(src, sizeof(*src), GFP_KERNEL);
+ if (!cfg)
+ return ERR_PTR(-ENOMEM);
+
+ cfg->info.dst_cache.cache = NULL;
+ err = dst_cache_init(&cfg->info.dst_cache, GFP_KERNEL);
+ if (err) {
+ kfree(cfg);
+ return ERR_PTR(err);
+ }
+
+ return cfg;
+}
+
+static void geneve_config_free(struct geneve_config *cfg)
+{
+ if (cfg) {
+ dst_cache_destroy(&cfg->info.dst_cache);
+ kfree(cfg);
+ }
+}
+
+static void geneve_config_free_rcu(struct rcu_head *head)
+{
+ struct geneve_config *cfg = container_of(head, struct geneve_config, rcu);
+
+ geneve_config_free(cfg);
+}
+
/* Initialize the device structure. */
+static void geneve_free_dev(struct net_device *dev)
+{
+ struct geneve_dev *geneve = netdev_priv(dev);
+ struct geneve_config *cfg = rcu_dereference_protected(geneve->cfg, 1);
+
+ geneve_config_free(cfg);
+ RCU_INIT_POINTER(geneve->cfg, NULL);
+}
+
static void geneve_setup(struct net_device *dev)
{
ether_setup(dev);
@@ -1729,6 +1779,7 @@ static void geneve_setup(struct net_device *dev)
dev->netdev_ops = &geneve_netdev_ops;
dev->ethtool_ops = &geneve_ethtool_ops;
dev->needs_free_netdev = true;
+ dev->priv_destructor = geneve_free_dev;
SET_NETDEV_DEVTYPE(dev, &geneve_type);
@@ -1890,15 +1941,17 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn,
*tun_on_same_port = false;
*tun_collect_md = false;
list_for_each_entry(geneve, &gn->geneve_list, next) {
- if (info->key.tp_dst == geneve->cfg.info.key.tp_dst &&
- (cfg->dualstack || geneve->cfg.dualstack ||
- geneve_saddr_conflict(info, &geneve->cfg.info))) {
- *tun_collect_md |= geneve->cfg.collect_md;
+ const struct geneve_config *gcfg = rtnl_dereference(geneve->cfg);
+
+ if (info->key.tp_dst == gcfg->info.key.tp_dst &&
+ (cfg->dualstack || gcfg->dualstack ||
+ geneve_saddr_conflict(info, &gcfg->info))) {
+ *tun_collect_md |= gcfg->collect_md;
*tun_on_same_port = true;
}
- if (info->key.tun_id == geneve->cfg.info.key.tun_id &&
- info->key.tp_dst == geneve->cfg.info.key.tp_dst &&
- !memcmp(&info->key.u, &geneve->cfg.info.key.u, sizeof(info->key.u)))
+ if (info->key.tun_id == gcfg->info.key.tun_id &&
+ info->key.tp_dst == gcfg->info.key.tp_dst &&
+ !memcmp(&info->key.u, &gcfg->info.key.u, sizeof(info->key.u)))
t = geneve;
}
return t;
@@ -1934,6 +1987,7 @@ static int geneve_configure(struct net *net, struct net_device *dev,
struct geneve_dev *t, *geneve = netdev_priv(dev);
const struct ip_tunnel_info *info = &cfg->info;
bool tun_collect_md, tun_on_same_port;
+ struct geneve_config *new_cfg;
int err, encap_len;
if (cfg->collect_md && !is_tnl_info_zero(info)) {
@@ -1974,10 +2028,13 @@ static int geneve_configure(struct net *net, struct net_device *dev,
}
}
- dst_cache_reset(&geneve->cfg.info.dst_cache);
- memcpy(&geneve->cfg, cfg, sizeof(*cfg));
+ new_cfg = geneve_config_alloc(cfg);
+ if (IS_ERR(new_cfg))
+ return PTR_ERR(new_cfg);
+
+ rcu_assign_pointer(geneve->cfg, new_cfg);
- if (geneve->cfg.inner_proto_inherit) {
+ if (cfg->inner_proto_inherit) {
dev->header_ops = NULL;
dev->type = ARPHRD_NONE;
dev->hard_header_len = 0;
@@ -2335,81 +2392,45 @@ static int geneve_newlink(struct net_device *dev,
return 0;
}
-/* Quiesces the geneve device data path for both TX and RX.
- *
- * On transmit geneve checks for non-NULL geneve_sock before it proceeds.
- * So, if we set that socket to NULL under RCU and wait for synchronize_net()
- * to complete for the existing set of in-flight packets to be transmitted,
- * then we would have quiesced the transmit data path. All the future packets
- * will get dropped until we unquiesce the data path.
- *
- * On receive geneve dereference the geneve_sock stashed in the socket. So,
- * if we set that to NULL under RCU and wait for synchronize_net() to
- * complete, then we would have quiesced the receive data path.
+/* Update the device configuration under RTNL.
+ * We use RCU swap to update the configuration atomically, so the data path
+ * (both TX and RX) can continue running without interruption or packet loss.
*/
-static void geneve_quiesce(struct geneve_dev *geneve, struct geneve_sock **gs4,
- struct geneve_sock **gs6)
-{
- *gs4 = rtnl_dereference(geneve->sock4);
- rcu_assign_pointer(geneve->sock4, NULL);
- if (*gs4)
- rcu_assign_sk_user_data((*gs4)->sk, NULL);
-#if IS_ENABLED(CONFIG_IPV6)
- *gs6 = rtnl_dereference(geneve->sock6);
- rcu_assign_pointer(geneve->sock6, NULL);
- if (*gs6)
- rcu_assign_sk_user_data((*gs6)->sk, NULL);
-#else
- *gs6 = NULL;
-#endif
- synchronize_net();
-}
-
-/* Resumes the geneve device data path for both TX and RX. */
-static void geneve_unquiesce(struct geneve_dev *geneve, struct geneve_sock *gs4,
- struct geneve_sock __maybe_unused *gs6)
-{
- rcu_assign_pointer(geneve->sock4, gs4);
- if (gs4)
- rcu_assign_sk_user_data(gs4->sk, gs4);
-#if IS_ENABLED(CONFIG_IPV6)
- rcu_assign_pointer(geneve->sock6, gs6);
- if (gs6)
- rcu_assign_sk_user_data(gs6->sk, gs6);
-#endif
-}
-
static int geneve_changelink(struct net_device *dev, struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack)
{
struct geneve_dev *geneve = netdev_priv(dev);
- struct geneve_sock *gs4, *gs6;
- struct geneve_config cfg;
+ struct geneve_config *old_cfg = rtnl_dereference(geneve->cfg);
+ struct geneve_config *cfg;
int err;
/* If the geneve device is configured for metadata (or externally
* controlled, for example, OVS), then nothing can be changed.
*/
- if (geneve->cfg.collect_md)
+ if (old_cfg->collect_md)
return -EOPNOTSUPP;
/* Start with the existing info. */
- memcpy(&cfg, &geneve->cfg, sizeof(cfg));
- err = geneve_nl2info(tb, data, extack, &cfg, true);
+ cfg = geneve_config_alloc(old_cfg);
+ if (IS_ERR(cfg))
+ return PTR_ERR(cfg);
+
+ err = geneve_nl2info(tb, data, extack, cfg, true);
if (err)
- return err;
+ goto err_free_cfg;
- if (!geneve_dst_addr_equal(&geneve->cfg.info, &cfg.info)) {
- dst_cache_reset(&cfg.info.dst_cache);
- geneve_link_config(dev, &cfg.info, tb);
- }
+ if (!geneve_dst_addr_equal(&old_cfg->info, &cfg->info))
+ geneve_link_config(dev, &cfg->info, tb);
- geneve_quiesce(geneve, &gs4, &gs6);
- memcpy(&geneve->cfg, &cfg, sizeof(cfg));
- geneve_unquiesce(geneve, gs4, gs6);
+ rcu_assign_pointer(geneve->cfg, cfg);
+ call_rcu_hurry(&old_cfg->rcu, geneve_config_free_rcu);
return 0;
+
+err_free_cfg:
+ geneve_config_free(cfg);
+ return err;
}
static void geneve_dellink(struct net_device *dev, struct list_head *head)
@@ -2444,12 +2465,13 @@ static size_t geneve_get_size(const struct net_device *dev)
static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
struct geneve_dev *geneve = netdev_priv(dev);
- struct ip_tunnel_info *info = &geneve->cfg.info;
- bool ttl_inherit = geneve->cfg.ttl_inherit;
- bool metadata = geneve->cfg.collect_md;
+ struct geneve_config *cfg = rtnl_dereference(geneve->cfg);
+ struct ip_tunnel_info *info = &cfg->info;
+ bool ttl_inherit = cfg->ttl_inherit;
+ bool metadata = cfg->collect_md;
struct ifla_geneve_port_range ports = {
- .low = htons(geneve->cfg.port_min),
- .high = htons(geneve->cfg.port_max),
+ .low = htons(cfg->port_min),
+ .high = htons(cfg->port_max),
};
__u8 tmp_vni[3];
__u32 vni;
@@ -2480,17 +2502,17 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
#endif
}
- if (!geneve->cfg.dualstack) {
+ if (!cfg->dualstack) {
if (ip_tunnel_info_af(info) == AF_INET) {
if ((info->key.u.ipv4.src ||
- geneve->cfg.collect_md) &&
+ metadata) &&
nla_put_in_addr(skb, IFLA_GENEVE_LOCAL,
info->key.u.ipv4.src))
goto nla_put_failure;
#if IS_ENABLED(CONFIG_IPV6)
} else {
if ((!ipv6_addr_any(&info->key.u.ipv6.src) ||
- geneve->cfg.collect_md) &&
+ metadata) &&
nla_put_in6_addr(skb, IFLA_GENEVE_LOCAL6,
&info->key.u.ipv6.src))
goto nla_put_failure;
@@ -2503,7 +2525,7 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
nla_put_be32(skb, IFLA_GENEVE_LABEL, info->key.label))
goto nla_put_failure;
- if (nla_put_u8(skb, IFLA_GENEVE_DF, geneve->cfg.df))
+ if (nla_put_u8(skb, IFLA_GENEVE_DF, cfg->df))
goto nla_put_failure;
if (nla_put_be16(skb, IFLA_GENEVE_PORT, info->key.tp_dst))
@@ -2514,21 +2536,21 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
#if IS_ENABLED(CONFIG_IPV6)
if (nla_put_u8(skb, IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
- !geneve->cfg.use_udp6_rx_checksums))
+ !cfg->use_udp6_rx_checksums))
goto nla_put_failure;
#endif
if (nla_put_u8(skb, IFLA_GENEVE_TTL_INHERIT, ttl_inherit))
goto nla_put_failure;
- if (geneve->cfg.inner_proto_inherit &&
+ if (cfg->inner_proto_inherit &&
nla_put_flag(skb, IFLA_GENEVE_INNER_PROTO_INHERIT))
goto nla_put_failure;
if (nla_put(skb, IFLA_GENEVE_PORT_RANGE, sizeof(ports), &ports))
goto nla_put_failure;
- if (geneve->cfg.gro_hint &&
+ if (cfg->gro_hint &&
nla_put_flag(skb, IFLA_GENEVE_GRO_HINT))
goto nla_put_failure;
@@ -2683,6 +2705,7 @@ static void __exit geneve_cleanup_module(void)
rtnl_link_unregister(&geneve_link_ops);
unregister_netdevice_notifier(&geneve_notifier_block);
unregister_pernet_subsys(&geneve_net_ops);
+ rcu_barrier();
}
module_exit(geneve_cleanup_module);
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH v2 net-next 1/3] geneve: pass geneve_config pointer to helper functions
From: Eric Dumazet @ 2026-07-07 14:53 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet
In-Reply-To: <20260707145331.3717941-1-edumazet@google.com>
In preparation for converting geneve->cfg to an RCU-protected pointer,
update helper functions to explicitly accept a const struct geneve_config
pointer instead of dereferencing geneve->cfg directly.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Suggested-by: Paolo Abeni <pabeni@redhat.com>
---
drivers/net/geneve.c | 140 +++++++++++++++++++++++--------------------
1 file changed, 76 insertions(+), 64 deletions(-)
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index 396e1a113cd49e9a43bfe6c7cd78f15ca8963906..cab38e7de8715f5a1ee0869e1d6c7b7ea9c693c1 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -762,9 +762,10 @@ static int geneve_udp_encap_err_lookup(struct sock *sk, struct sk_buff *skb)
}
static struct sock *geneve_create_sock(struct net *net,
- struct geneve_dev *geneve, bool ipv6)
+ struct geneve_dev *geneve,
+ const struct geneve_config *cfg, bool ipv6)
{
- struct ip_tunnel_info *info = &geneve->cfg.info;
+ const struct ip_tunnel_info *info = &cfg->info;
struct udp_port_cfg udp_conf;
struct socket *sock;
int err;
@@ -775,7 +776,7 @@ static struct sock *geneve_create_sock(struct net *net,
if (ipv6) {
udp_conf.family = AF_INET6;
udp_conf.ipv6_v6only = 1;
- udp_conf.use_udp6_rx_checksums = geneve->cfg.use_udp6_rx_checksums;
+ udp_conf.use_udp6_rx_checksums = cfg->use_udp6_rx_checksums;
udp_conf.local_ip6 = info->key.u.ipv6.src;
} else
#endif
@@ -991,7 +992,8 @@ static int geneve_gro_complete(struct sock *sk, struct sk_buff *skb,
/* Create new listen socket if needed */
static struct geneve_sock *geneve_socket_create(struct net *net,
- struct geneve_dev *geneve, bool ipv6)
+ struct geneve_dev *geneve,
+ const struct geneve_config *cfg, bool ipv6)
{
struct geneve_net *gn = net_generic(net, geneve_net_id);
struct udp_tunnel_sock_cfg tunnel_cfg;
@@ -1003,7 +1005,7 @@ static struct geneve_sock *geneve_socket_create(struct net *net,
if (!gs)
return ERR_PTR(-ENOMEM);
- sk = geneve_create_sock(net, geneve, ipv6);
+ sk = geneve_create_sock(net, geneve, cfg, ipv6);
if (IS_ERR(sk)) {
kfree(gs);
return ERR_CAST(sk);
@@ -1060,12 +1062,13 @@ static void geneve_sock_release(struct geneve_dev *geneve)
}
static struct geneve_sock *geneve_find_sock(struct net *net,
- struct geneve_dev *geneve, bool ipv6)
+ struct geneve_dev *geneve,
+ const struct geneve_config *cfg, bool ipv6)
{
struct geneve_net *gn = net_generic(net, geneve_net_id);
- struct ip_tunnel_info *info = &geneve->cfg.info;
+ const struct ip_tunnel_info *info = &cfg->info;
sa_family_t family = ipv6 ? AF_INET6 : AF_INET;
- bool gro_hint = geneve->cfg.gro_hint;
+ bool gro_hint = cfg->gro_hint;
__be16 dst_port = info->key.tp_dst;
struct geneve_sock *gs;
@@ -1095,7 +1098,8 @@ static struct geneve_sock *geneve_find_sock(struct net *net,
return NULL;
}
-static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6)
+static int geneve_sock_add(struct geneve_dev *geneve,
+ const struct geneve_config *cfg, bool ipv6)
{
struct net *net = geneve->net;
struct geneve_dev_node *node;
@@ -1103,19 +1107,19 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6)
__u8 vni[3];
__u32 hash;
- gs = geneve_find_sock(net, geneve, ipv6);
+ gs = geneve_find_sock(net, geneve, cfg, ipv6);
if (gs) {
gs->refcnt++;
goto out;
}
- gs = geneve_socket_create(net, geneve, ipv6);
+ gs = geneve_socket_create(net, geneve, cfg, ipv6);
if (IS_ERR(gs))
return PTR_ERR(gs);
out:
- gs->collect_md = geneve->cfg.collect_md;
- gs->gro_hint = geneve->cfg.gro_hint;
+ gs->collect_md = cfg->collect_md;
+ gs->gro_hint = cfg->gro_hint;
#if IS_ENABLED(CONFIG_IPV6)
if (ipv6) {
rcu_assign_pointer(geneve->sock6, gs);
@@ -1128,7 +1132,7 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6)
}
node->geneve = geneve;
- tunnel_id_to_vni(geneve->cfg.info.key.tun_id, vni);
+ tunnel_id_to_vni(cfg->info.key.tun_id, vni);
hash = geneve_net_vni_hash(vni);
hlist_add_head_rcu(&node->hlist, &gs->vni_list[hash]);
return 0;
@@ -1137,21 +1141,22 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6)
static int geneve_open(struct net_device *dev)
{
struct geneve_dev *geneve = netdev_priv(dev);
- bool dualstack = geneve->cfg.dualstack;
- bool ipv4, ipv6;
+ const struct geneve_config *cfg = &geneve->cfg;
+ bool ipv4, ipv6, dualstack;
int ret = 0;
- ipv6 = geneve->cfg.info.mode & IP_TUNNEL_INFO_IPV6 || dualstack;
+ dualstack = cfg->dualstack;
+ ipv6 = cfg->info.mode & IP_TUNNEL_INFO_IPV6 || dualstack;
ipv4 = !ipv6 || dualstack;
#if IS_ENABLED(CONFIG_IPV6)
if (ipv6) {
- ret = geneve_sock_add(geneve, true);
+ ret = geneve_sock_add(geneve, cfg, true);
if (ret < 0 && ret != -EAFNOSUPPORT)
ipv4 = false;
}
#endif
if (ipv4)
- ret = geneve_sock_add(geneve, false);
+ ret = geneve_sock_add(geneve, cfg, false);
if (ret < 0)
geneve_sock_release(geneve);
@@ -1189,6 +1194,7 @@ static void geneve_build_header(struct genevehdr *geneveh,
}
static int geneve_build_gro_hint_opt(const struct geneve_dev *geneve,
+ const struct geneve_config *cfg,
struct sk_buff *skb)
{
struct geneve_skb_cb *cb = GENEVE_SKB_CB(skb);
@@ -1201,7 +1207,7 @@ static int geneve_build_gro_hint_opt(const struct geneve_dev *geneve,
cb->gro_hint_len = 0;
/* Try to add the GRO hint only in case of double encap. */
- if (!geneve->cfg.gro_hint || !skb->encapsulation)
+ if (!cfg->gro_hint || !skb->encapsulation)
return 0;
/*
@@ -1262,10 +1268,11 @@ static void geneve_put_gro_hint_opt(struct genevehdr *gnvh, int opt_size,
static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb,
const struct ip_tunnel_info *info,
- const struct geneve_dev *geneve, int ip_hdr_len)
+ const struct geneve_dev *geneve,
+ const struct geneve_config *cfg, int ip_hdr_len)
{
bool udp_sum = test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags);
- bool inner_proto_inherit = geneve->cfg.inner_proto_inherit;
+ bool inner_proto_inherit = cfg->inner_proto_inherit;
bool xnet = !net_eq(geneve->net, dev_net(geneve->dev));
struct geneve_skb_cb *cb = GENEVE_SKB_CB(skb);
struct genevehdr *gnvh;
@@ -1306,14 +1313,14 @@ static int geneve_build_skb(struct dst_entry *dst, struct sk_buff *skb,
}
static u8 geneve_get_dsfield(struct sk_buff *skb, struct net_device *dev,
+ const struct geneve_config *cfg,
const struct ip_tunnel_info *info,
bool *use_cache)
{
- struct geneve_dev *geneve = netdev_priv(dev);
u8 dsfield;
dsfield = info->key.tos;
- if (dsfield == 1 && !geneve->cfg.collect_md) {
+ if (cfg && dsfield == 1 && !cfg->collect_md) {
dsfield = ip_tunnel_get_dsfield(ip_hdr(skb), skb);
*use_cache = false;
}
@@ -1323,6 +1330,7 @@ static u8 geneve_get_dsfield(struct sk_buff *skb, struct net_device *dev,
static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,
struct geneve_dev *geneve,
+ const struct geneve_config *cfg,
const struct ip_tunnel_info *info)
{
struct geneve_sock *gs4 = rcu_dereference(geneve->sock4);
@@ -1335,35 +1343,35 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,
__be16 sport;
int err;
- if (skb_vlan_inet_prepare(skb, geneve->cfg.inner_proto_inherit))
+ if (skb_vlan_inet_prepare(skb, cfg->inner_proto_inherit))
return -EINVAL;
if (!gs4)
return -EIO;
use_cache = ip_tunnel_dst_cache_usable(skb, info);
- tos = geneve_get_dsfield(skb, dev, info, &use_cache);
+ tos = geneve_get_dsfield(skb, dev, cfg, info, &use_cache);
sport = udp_flow_src_port(geneve->net, skb,
- geneve->cfg.port_min,
- geneve->cfg.port_max, true);
+ cfg->port_min,
+ cfg->port_max, true);
rt = udp_tunnel_dst_lookup(skb, dev, geneve->net, 0, &saddr,
&info->key,
- sport, geneve->cfg.info.key.tp_dst, tos,
+ sport, cfg->info.key.tp_dst, tos,
use_cache ?
(struct dst_cache *)&info->dst_cache : NULL);
if (IS_ERR(rt))
return PTR_ERR(rt);
- if (geneve->cfg.info.key.u.ipv4.src &&
- saddr != geneve->cfg.info.key.u.ipv4.src) {
+ if (cfg->info.key.u.ipv4.src &&
+ saddr != cfg->info.key.u.ipv4.src) {
dst_release(&rt->dst);
return -EADDRNOTAVAIL;
}
err = skb_tunnel_check_pmtu(skb, &rt->dst,
GENEVE_IPV4_HLEN + info->options_len +
- geneve_build_gro_hint_opt(geneve, skb),
+ geneve_build_gro_hint_opt(geneve, cfg, skb),
netif_is_any_bridge_port(dev));
if (err < 0) {
dst_release(&rt->dst);
@@ -1397,21 +1405,21 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,
}
tos = ip_tunnel_ecn_encap(tos, ip_hdr(skb), skb);
- if (geneve->cfg.collect_md) {
+ if (cfg->collect_md) {
ttl = key->ttl;
df = test_bit(IP_TUNNEL_DONT_FRAGMENT_BIT, key->tun_flags) ?
htons(IP_DF) : 0;
} else {
- if (geneve->cfg.ttl_inherit)
+ if (cfg->ttl_inherit)
ttl = ip_tunnel_get_ttl(ip_hdr(skb), skb);
else
ttl = key->ttl;
ttl = ttl ? : ip4_dst_hoplimit(&rt->dst);
- if (geneve->cfg.df == GENEVE_DF_SET) {
+ if (cfg->df == GENEVE_DF_SET) {
df = htons(IP_DF);
- } else if (geneve->cfg.df == GENEVE_DF_INHERIT) {
+ } else if (cfg->df == GENEVE_DF_INHERIT) {
struct ethhdr *eth = skb_eth_hdr(skb);
if (ntohs(eth->h_proto) == ETH_P_IPV6) {
@@ -1425,13 +1433,13 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,
}
}
- err = geneve_build_skb(&rt->dst, skb, info, geneve,
+ err = geneve_build_skb(&rt->dst, skb, info, geneve, cfg,
sizeof(struct iphdr));
if (unlikely(err))
return err;
udp_tunnel_xmit_skb(rt, gs4->sk, skb, saddr, info->key.u.ipv4.dst,
- tos, ttl, df, sport, geneve->cfg.info.key.tp_dst,
+ tos, ttl, df, sport, cfg->info.key.tp_dst,
!net_eq(geneve->net, dev_net(geneve->dev)),
!test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags),
0);
@@ -1441,6 +1449,7 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,
#if IS_ENABLED(CONFIG_IPV6)
static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,
struct geneve_dev *geneve,
+ const struct geneve_config *cfg,
const struct ip_tunnel_info *info)
{
struct geneve_sock *gs6 = rcu_dereference(geneve->sock6);
@@ -1452,35 +1461,35 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,
__be16 sport;
int err;
- if (skb_vlan_inet_prepare(skb, geneve->cfg.inner_proto_inherit))
+ if (skb_vlan_inet_prepare(skb, cfg->inner_proto_inherit))
return -EINVAL;
if (!gs6)
return -EIO;
use_cache = ip_tunnel_dst_cache_usable(skb, info);
- prio = geneve_get_dsfield(skb, dev, info, &use_cache);
+ prio = geneve_get_dsfield(skb, dev, cfg, info, &use_cache);
sport = udp_flow_src_port(geneve->net, skb,
- geneve->cfg.port_min,
- geneve->cfg.port_max, true);
+ cfg->port_min,
+ cfg->port_max, true);
dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sk, 0,
&saddr, key, sport,
- geneve->cfg.info.key.tp_dst, prio,
+ cfg->info.key.tp_dst, prio,
use_cache ?
(struct dst_cache *)&info->dst_cache : NULL);
if (IS_ERR(dst))
return PTR_ERR(dst);
- if (!ipv6_addr_any(&geneve->cfg.info.key.u.ipv6.src) &&
- !ipv6_addr_equal(&saddr, &geneve->cfg.info.key.u.ipv6.src)) {
+ if (!ipv6_addr_any(&cfg->info.key.u.ipv6.src) &&
+ !ipv6_addr_equal(&saddr, &cfg->info.key.u.ipv6.src)) {
dst_release(dst);
return -EADDRNOTAVAIL;
}
err = skb_tunnel_check_pmtu(skb, dst,
GENEVE_IPV6_HLEN + info->options_len +
- geneve_build_gro_hint_opt(geneve, skb),
+ geneve_build_gro_hint_opt(geneve, cfg, skb),
netif_is_any_bridge_port(dev));
if (err < 0) {
dst_release(dst);
@@ -1513,22 +1522,22 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,
}
prio = ip_tunnel_ecn_encap(prio, ip_hdr(skb), skb);
- if (geneve->cfg.collect_md) {
+ if (cfg->collect_md) {
ttl = key->ttl;
} else {
- if (geneve->cfg.ttl_inherit)
+ if (cfg->ttl_inherit)
ttl = ip_tunnel_get_ttl(ip_hdr(skb), skb);
else
ttl = key->ttl;
ttl = ttl ? : ip6_dst_hoplimit(dst);
}
- err = geneve_build_skb(dst, skb, info, geneve, sizeof(struct ipv6hdr));
+ err = geneve_build_skb(dst, skb, info, geneve, cfg, sizeof(struct ipv6hdr));
if (unlikely(err))
return err;
udp_tunnel6_xmit_skb(dst, gs6->sk, skb, dev,
&saddr, &key->u.ipv6.dst, prio, ttl,
- info->key.label, sport, geneve->cfg.info.key.tp_dst,
+ info->key.label, sport, cfg->info.key.tp_dst,
!test_bit(IP_TUNNEL_CSUM_BIT,
info->key.tun_flags),
0);
@@ -1539,10 +1548,12 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,
static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct geneve_dev *geneve = netdev_priv(dev);
- struct ip_tunnel_info *info = NULL;
+ const struct ip_tunnel_info *info = NULL;
+ const struct geneve_config *cfg;
int err;
- if (geneve->cfg.collect_md) {
+ cfg = &geneve->cfg;
+ if (cfg->collect_md) {
info = skb_tunnel_info(skb);
if (unlikely(!info || !(info->mode & IP_TUNNEL_INFO_TX))) {
netdev_dbg(dev, "no tunnel metadata\n");
@@ -1551,16 +1562,16 @@ static netdev_tx_t geneve_xmit(struct sk_buff *skb, struct net_device *dev)
return NETDEV_TX_OK;
}
} else {
- info = &geneve->cfg.info;
+ info = &cfg->info;
}
rcu_read_lock();
#if IS_ENABLED(CONFIG_IPV6)
if (info->mode & IP_TUNNEL_INFO_IPV6)
- err = geneve6_xmit_skb(skb, dev, geneve, info);
+ err = geneve6_xmit_skb(skb, dev, geneve, cfg, info);
else
#endif
- err = geneve_xmit_skb(skb, dev, geneve, info);
+ err = geneve_xmit_skb(skb, dev, geneve, cfg, info);
rcu_read_unlock();
if (likely(!err))
@@ -1593,6 +1604,7 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
{
struct ip_tunnel_info *info = skb_tunnel_info(skb);
struct geneve_dev *geneve = netdev_priv(dev);
+ const struct geneve_config *cfg = &geneve->cfg;
__be16 sport;
if (ip_tunnel_info_af(info) == AF_INET) {
@@ -1606,14 +1618,14 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
return -EIO;
use_cache = ip_tunnel_dst_cache_usable(skb, info);
- tos = geneve_get_dsfield(skb, dev, info, &use_cache);
+ tos = geneve_get_dsfield(skb, dev, cfg, info, &use_cache);
sport = udp_flow_src_port(geneve->net, skb,
- geneve->cfg.port_min,
- geneve->cfg.port_max, true);
+ cfg->port_min,
+ cfg->port_max, true);
rt = udp_tunnel_dst_lookup(skb, dev, geneve->net, 0, &saddr,
&info->key,
- sport, geneve->cfg.info.key.tp_dst,
+ sport, cfg->info.key.tp_dst,
tos,
use_cache ? &info->dst_cache : NULL);
if (IS_ERR(rt))
@@ -1633,14 +1645,14 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
return -EIO;
use_cache = ip_tunnel_dst_cache_usable(skb, info);
- prio = geneve_get_dsfield(skb, dev, info, &use_cache);
+ prio = geneve_get_dsfield(skb, dev, cfg, info, &use_cache);
sport = udp_flow_src_port(geneve->net, skb,
- geneve->cfg.port_min,
- geneve->cfg.port_max, true);
+ cfg->port_min,
+ cfg->port_max, true);
dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sk, 0,
&saddr, &info->key, sport,
- geneve->cfg.info.key.tp_dst, prio,
+ cfg->info.key.tp_dst, prio,
use_cache ? &info->dst_cache : NULL);
if (IS_ERR(dst))
return PTR_ERR(dst);
@@ -1653,7 +1665,7 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
}
info->key.tp_src = sport;
- info->key.tp_dst = geneve->cfg.info.key.tp_dst;
+ info->key.tp_dst = cfg->info.key.tp_dst;
return 0;
}
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH v2 net-next 0/3] geneve: make geneve_fill_info() RTNL-less
From: Eric Dumazet @ 2026-07-07 14:53 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
eric.dumazet, Eric Dumazet
This series makes geneve_fill_info() independent of the RTNL lock by
converting the device configuration to an RCU-protected pointer.
Historically, geneve_changelink() updated the device configuration by
copying the new configuration over the old one using memcpy() under RTNL.
To prevent the transmit/receive data paths from reading torn values during
the copy, geneve_quiesce() was used to pause the data path and wait for
a synchronize_net(), causing packet loss and latency.
By converting the configuration to an RCU-protected pointer, we can
perform atomic updates via RCU swap. This allows data path readers to
safely access the configuration locklessly under RCU read lock, and
removes the need to stop the data path during changelink.
With the RCU infrastructure in place, geneve_fill_info() is then updated
to read the configuration under RCU read lock, removing its dependency
on RTNL.
v2: addressed Kuniyuki, Paolo and sashiko feedback
v1: https://lore.kernel.org/netdev/20260701120454.3533252-1-edumazet@google.com/T/#m887804321856d9b5c7142107e81b52553e60e6ab
Eric Dumazet (3):
geneve: pass geneve_config pointer to helper functions
geneve: convert config to RCU-protected pointer
geneve: make geneve_fill_info() RTNL independent
drivers/net/geneve.c | 370 ++++++++++++++++++++++++-------------------
1 file changed, 209 insertions(+), 161 deletions(-)
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply
* Re: [PATCH net-next] gtp: annotate PDP lookups under RTNL
From: Pablo Neira Ayuso @ 2026-07-07 14:51 UTC (permalink / raw)
To: Simon Horman
Cc: Runyu Xiao, laforge, andrew+netdev, davem, edumazet, kuba, pabeni,
osmocom-net-gprs, netdev, linux-kernel, jianhao.xu
In-Reply-To: <20260707142820.GG1364329@horms.kernel.org>
Hi Simon,
On Tue, Jul 07, 2026 at 03:28:20PM +0100, Simon Horman wrote:
> On Wed, Jul 01, 2026 at 08:39:25PM +0800, Runyu Xiao wrote:
> > The GTP PDP lookup helpers are shared by RCU-protected data and report
> > paths and RTNL-protected control paths such as gtp_genl_new_pdp(). The
> > helpers walk RCU hlists, but they do not currently pass the RTNL
> > condition for the control-path lookups.
> >
> > Pass lockdep_rtnl_is_held() to the PDP hlist iterators. Existing
> > RCU-reader callers remain valid because the RCU-list macros also accept
> > an active RCU read-side section; the added condition only documents the
> > non-RCU protection already used by RTNL control paths.
> >
> > This was found by our static analysis tool and then manually reviewed
> > against the current tree. The dynamic triage evidence is a
> > target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
> > to documenting the existing protection contract.
> >
> > This is a lockdep annotation cleanup. It does not change PDP lifetime or
> > hash updates.
> >
> > Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
>
> Reviewed-by: Simon Horman <horms@kernel.org>
>
> There is an AI-generated review of this patch available on sashko.dev.
> While I don't believe that the issues raised there should impede progress
> of this patch you may want to look into them as possible follow-up.
This patch refers to the rtnl_lock, but it is the genetlink mutex that
protects updates on the PDP context list.
Then, from packet path, rcu lookups are performed.
I think this patch is not correct.
^ permalink raw reply
* Re: [PATCH 0/4] drivers/net: replace __get_free_pages() with kmalloc()
From: Simon Horman @ 2026-07-07 14:46 UTC (permalink / raw)
To: Mike Rapoport (Microsoft)
Cc: Johannes Berg, Brian Norris, Francesco Dolcini, Jakub Kicinski,
b43-dev, libertas-dev, linux-kernel, linux-mm, linux-wireless,
netdev
In-Reply-To: <20260701-b4-drivers-wireless-v1-0-60264cdf2efe@kernel.org>
On Wed, Jul 01, 2026 at 04:59:09PM +0300, Mike Rapoport (Microsoft) wrote:
> This is a (small) part of larger work of replacing page allocator calls
> with kmalloc.
>
> My initial intention a few month ago was to remove ugly casts [1], but then
> willy pointed out that Linus objected to something like this [2] and it
> looks like more than a decade old technical debt.
>
> Largely, anything that doesn't need struct page (or a memdesc in the
> future) should just use kmalloc() or kvmalloc() to allocate memory.
> kmalloc() guarantees alignment, physical contiguity and working
> virt_to_phys() and beside nicer API that returns void * on alloc and
> doesn't require to know the allocation size on free, kmalloc() provides
> better debugging capabilities than page allocator.
>
> Another thing is that touching these allocation sites gives the reviewers
> opportunity to see if a PAGE_SIZE buffer is actually needed or maybe
> another size is appropriate.
>
> For larger allocations that don't need physically contiguous memory
> kvmalloc() can be a better option that __get_free_pages() because under
> memory pressure it's is easier to allocate several order-0 pages than a
> physically contiguous chunk with the same number of pages.
>
> And last, but not least, removing needless calls to page allocator should
> help with memdesc (aka project folio) conversion. There will be way less
> places to audit to see if the user was actually using struct page.
>
> Also in git:
> https://git.kernel.org/pub/scm/linux/kernel/git/rppt/linux.git gfp-to-kmalloc/drivers-net-wireless
>
> [1] https://lore.kernel.org/all/20251018093002.3660549-1-rppt@kernel.org/
> [2] https://lore.kernel.org/all/CA+55aFwp4iy4rtX2gE2WjBGFL=NxMVnoFeHqYa2j1dYOMMGqxg@mail.gmail.com/
>
> ---
> Changes in v2:
> - split out wireless drivers from a larger set
> - use kzalloc() instead of kmalloc() + memset in b43legacy
>
> v1: https://patch.msgid.link/20260630-b4-drivers-net-v1-0-672162a91f37@kernel.org
For the series:
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply
* Re: bpf, sockmap: FIONREAD returns 0 for TCP sockets in a sockmap without a verdict program
From: John Fastabend @ 2026-07-07 14:33 UTC (permalink / raw)
To: Mattia Meleleo; +Cc: bpf, netdev, jakub@cloudflare.com, jiayuan.chen
In-Reply-To: <CAOvpEWN6xgFx4GWFnnWLGCB+_1auDcAZPYPSv1PDu3UfXkcriw@mail.gmail.com>
On Fri, Jul 03, 2026 at 06:10:16PM +0200, Mattia Meleleo wrote:
>Hi,
>
>in OpenTelemetry eBPF instrumentation we use a sockhash to track
>outgoing TCP sockets for trace context propagation. Sockets are added
>from a sock_ops program, and the map only has an sk_msg program
>attached on the egress side - there is no ingress verdict program.
This is how we use sockmap in many cases as well. I just haven't
rolled out any actual workload tests recently sadly. I expect we
would also hit this.
>
>Since commit 929e30f93125 ("bpf, sockmap: Fix FIONREAD for sockmap"),
>ioctl(FIONREAD) returns 0 for these sockets even though read() returns
>data. tcp_bpf_ioctl() answers from psock->msg_tot_len, which only
>accounts for bytes in ingress_msg. Without a verdict program, data
>never lands there: it stays in sk_receive_queue and is read through
>the tcp_bpf_recvmsg() fallback, so FIONREAD always reports 0.
>
>The commit message explains that sk_receive_queue is intentionally not
>counted because a verdict program may redirect or drop its contents.
>Without a verdict program, however, all of that data is readable, and
>applications that use FIONREAD to size their reads (nginx, Java, .NET)
>hang or truncate transfers once their sockets are in the map.
>
>Observed on mainline and on 6.12.75+, 6.6.128+, 6.18.14+.
agh because fixes tag it was backported everywhere :(
>
>Reproducer: https://github.com/mmat11/fionread-repro
>
> pre-insert FIONREAD=4096 (expect 4096) OK
> in-sockhash FIONREAD=0 (expect 4096) BROKEN
> post-delete FIONREAD=4096 (expect 4096) OK
>
>Is this intended for psocks without a verdict program? If not, would
>falling back to tcp_inq() (plus msg_tot_len) in that case be an
>acceptable fix? Happy to send a patch and test it.
Its a bug. It needs a fix. Please send a fix I'll review and
test on our dogfooding env as well. Would you also add a selftest
here so we can capture this behavior is fairly common for apps to
do this.
^ permalink raw reply
* Re: [PATCH net v2] gtp: parse extension headers before reading inner protocol
From: Pablo Neira Ayuso @ 2026-07-07 14:32 UTC (permalink / raw)
To: Zhixing Chen
Cc: Harald Welte, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, osmocom-net-gprs, netdev
In-Reply-To: <akefPJ5cCrsZcpRw@chamomile>
On Fri, Jul 03, 2026 at 01:38:39PM +0200, Pablo Neira Ayuso wrote:
> On Fri, Jul 03, 2026 at 05:37:08PM +0800, Zhixing Chen wrote:
> > GTPv1-U packets may carry a chain of extension headers before the inner
> > IP packet. The receive path already parses and skips these extension
> > headers, but it currently reads the inner protocol before doing so.
> >
> > As a result, the first extension header byte is interpreted as the inner
> > IP version. Packets with extension headers are then dropped before PDP
> > lookup.
> >
> > Parse the extension header chain before calling gtp_inner_proto(), so the
> > inner protocol is read from the actual inner IP header.
> >
> > Fixes: c75fc0b9e5be ("gtp: identify tunnel via GTP device + GTP version + TEID + family")
> > Signed-off-by: Zhixing Chen <running910@gmail.com>
>
> Reviewed-by: Pablo Neira Ayuso <pablo@netfilter.org>
Patch is not good, it needs to refetch gtp header after
pskb_may_pull().
^ permalink raw reply
* Re: [PATCH net] dibs: loopback: validate offset and size in move_data()
From: Dust Li @ 2026-07-07 14:31 UTC (permalink / raw)
To: Alexandra Winter
Cc: Wenjia Zhang, Wen Gu, Paolo Abeni, Mahanta Jambigi, D . Wythe,
Sidraya Jayagond, netdev, linux-kernel, stable,
Federico Kirschbaum
In-Reply-To: <20260707074318.1448662-1-dust.li@linux.alibaba.com>
On 2026-07-07 15:43:18, Dust Li wrote:
>The loopback move_data() performs a memcpy into the registered DMB
>without checking whether offset + size exceeds the DMB length. Unlike
>real ISM hardware, which enforces memory region bounds natively, the
>software loopback has no such protection.
>
>A peer-supplied out-of-bounds offset or oversized write would result in
>an OOB write past the allocated kernel buffer. Add an explicit bounds
>check before the memcpy to reject such requests with -EINVAL.
>
>Fixes: f7a22071dbf3("net/smc: implement DMB-related operations of loopback-ism")
>Cc: stable@vger.kernel.org
>Reported-by: Federico Kirschbaum <federico.kirschbaum@xbow.com>
Reported-by: Baul Lee <baul.lee@xbow.com>
Best regards,
Dust
>Signed-off-by: Dust Li <dust.li@linux.alibaba.com>
>---
> drivers/dibs/dibs_loopback.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
>diff --git a/drivers/dibs/dibs_loopback.c b/drivers/dibs/dibs_loopback.c
>index ec3b48cb0e87..0f2e09311152 100644
>--- a/drivers/dibs/dibs_loopback.c
>+++ b/drivers/dibs/dibs_loopback.c
>@@ -254,6 +254,11 @@ static int dibs_lo_move_data(struct dibs_dev *dibs, u64 dmb_tok,
> read_unlock_bh(&ldev->dmb_ht_lock);
> return -EINVAL;
> }
>+ if ((u64)offset + size > rmb_node->len) {
>+ read_unlock_bh(&ldev->dmb_ht_lock);
>+ return -EINVAL;
>+ }
>+
> memcpy((char *)rmb_node->cpu_addr + offset, data, size);
> sba_idx = rmb_node->sba_idx;
> read_unlock_bh(&ldev->dmb_ht_lock);
>--
>2.43.7
^ permalink raw reply
* [PATCH RFC net-next 0/3] net: dsa: mxl862xx: support firmware update
From: Daniel Golle @ 2026-07-07 14:15 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
The MaxLinear MxL862xx switches are running a ZephyrOS-based firmware
which is stored on a dedicated SPI flash chip connected to the switch IC.
When using the switch with DSA a method to allow the host to update the
firmware is needed.
DSA currently doesn't support devlink's flash_update method and
implementing the necessary teardown and subsequent re-probe is not
trivial.
This series is marked as RFC as I'd like to receive feedback regarding
the use of devlink and the teardown/re-probe implementation.
Daniel Golle (3):
net: dsa: wire flash_update devlink callback to drivers
net: dsa: mxl862xx: add SMDIO clause-22 register access
net: dsa: mxl862xx: add devlink flash_update and info_get
drivers/net/dsa/mxl862xx/Makefile | 2 +-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 434 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 15 +
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 42 +++
drivers/net/dsa/mxl862xx/mxl862xx-host.h | 2 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.h | 2 +
include/net/dsa.h | 3 +
net/dsa/devlink.c | 13 +
10 files changed, 516 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h
--
2.55.0
^ permalink raw reply
* Re: [PATCH net 1/2] net: macb: reprogram TBQP after shuffling the TX ring on link-up
From: Taedcke, Christian @ 2026-07-07 14:29 UTC (permalink / raw)
To: Kevin Hao, christian.taedcke
Cc: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
Robert Hancock, netdev, linux-kernel, linux-rt-devel, stable
In-Reply-To: <akzDQrmdYwHAMMmw@xiaowei>
On 7/7/2026 11:13 AM, Kevin Hao wrote:
> On Mon, Jul 06, 2026 at 04:02:14PM +0200, Christian Taedcke via B4 Relay wrote:
>> From: Christian Taedcke <christian.taedcke@weidmueller.com>
>>
>> gem_shuffle_tx_one_ring() rotates the software TX ring so that the
>> tail sits at index 0 and resets queue->tx_tail to 0, but it never
>> reprograms the hardware transmit buffer queue pointer (TBQP). Other
>> paths that reset tx_tail to the ring base (macb_init_buffers() and
>> macb_tx_error_task()) also reprogram TBQP to queue->tx_ring_dma; this
>> path does not, leaving TBQP pointing at a stale descriptor.
>>
>> gem_shuffle_tx_rings() runs on every link-up from
>> macb_mac_link_up(). After a few link up/down flaps that leave
>> un-completed descriptors in the ring, the stale TBQP keeps pointing at
>> a descriptor whose used bit is set. When TX is re-enabled on link-up,
>> the GEM reads that used descriptor and raises TXUBR. macb_interrupt()
>> schedules the TX NAPI, macb_tx_poll() makes no progress (work_done ==
>> 0) and macb_tx_restart() re-issues TSTART, which makes the controller
>> read the same used descriptor again and re-assert TXUBR. As the MAC
>> interrupt is level-triggered, it never deasserts and one CPU is pegged
>> at 100% in the threaded handler, eventually triggering "sched: RT
>> throttling activated" and a dead network interface.
>>
>> Fix it by reprogramming TBQP to the ring base on every path of
>> gem_shuffle_tx_one_ring() that resets tx_tail to 0, mirroring
>> macb_tx_error_task(). The early return for an already-aligned tail is
>> left untouched as TBQP is already consistent there. This is safe
>> because the shuffle runs from macb_mac_link_up() while TE is still
>> disabled, so the transmitter is halted.
>>
>> Fixes: 881a0263d502 ("net: macb: Shuffle the tx ring before enabling tx")
>> Cc: stable@vger.kernel.org
>> Assisted-by: Claude:claude-opus-4-8
>> Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
>> ---
>> drivers/net/ethernet/cadence/macb_main.c | 9 ++++++++-
>> 1 file changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
>> index fd282a1700fb..b11cb8f068b7 100644
>> --- a/drivers/net/ethernet/cadence/macb_main.c
>> +++ b/drivers/net/ethernet/cadence/macb_main.c
>> @@ -820,7 +820,7 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
>> if (!count) {
>> queue->tx_head = 0;
>> queue->tx_tail = 0;
>> - goto unlock;
>> + goto reset_hw_ptr;
>> }
>>
>> shift = tail % ring_size;
>> @@ -869,6 +869,13 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
>> /* Make descriptor updates visible to hardware */
>> wmb();
>>
>> +reset_hw_ptr:
>> + /* tx_tail was reset to the ring base, so TBQP must be reprogrammed
>> + * to match; otherwise it keeps pointing at a stale descriptor. Safe
>> + * to write directly here as TX is still disabled (called from
>> + * macb_mac_link_up() before TE is set).
>> + */
>
> Could you elaborate on why we need to reprogram the TBQP here? Based on my
> understanding, the transmit-buffer queue pointer automatically resets to the
> value of TBQP when TX is disabled. The following is quoted from the Zynq
> UltraScale TRM [1]:
> While transmit is disabled, bit [3] of the network control is
> set Low, the transmit-buffer queue pointer resets to point to the address indicated by the
> transmit-buffer queue base address register. Disabling receive does not have the same
> effect on the receive-buffer queue pointer.
>
> [1] https://docs.amd.com/v/u/en-US/ug1085-zynq-ultrascale-trm
Thanks for the review and the TRM pointer.
I agree that the TRM says the transmit pointer is reset while TE is low. My
question is whether this describes an internal pointer being reloaded from TBQP,
or whether TBQP itself is restored to the original ring base.
I will instrument this on my board and check how TBQP behaves across the link
down/up path.
>
> Thanks,
> Kevin
>
Christian
>> + queue_writel(queue, TBQP, lower_32_bits(queue->tx_ring_dma));
>> unlock:
>> spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
>> }
>>
>> --
>> 2.54.0
>>
>>
^ permalink raw reply
* Re: [PATCH net-next] gtp: annotate PDP lookups under RTNL
From: Simon Horman @ 2026-07-07 14:28 UTC (permalink / raw)
To: Runyu Xiao
Cc: pablo, laforge, andrew+netdev, davem, edumazet, kuba, pabeni,
osmocom-net-gprs, netdev, linux-kernel, jianhao.xu
In-Reply-To: <20260701123925.3193089-1-runyu.xiao@seu.edu.cn>
On Wed, Jul 01, 2026 at 08:39:25PM +0800, Runyu Xiao wrote:
> The GTP PDP lookup helpers are shared by RCU-protected data and report
> paths and RTNL-protected control paths such as gtp_genl_new_pdp(). The
> helpers walk RCU hlists, but they do not currently pass the RTNL
> condition for the control-path lookups.
>
> Pass lockdep_rtnl_is_held() to the PDP hlist iterators. Existing
> RCU-reader callers remain valid because the RCU-list macros also accept
> an active RCU read-side section; the added condition only documents the
> non-RCU protection already used by RTNL control paths.
>
> This was found by our static analysis tool and then manually reviewed
> against the current tree. The dynamic triage evidence is a
> target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
> to documenting the existing protection contract.
>
> This is a lockdep annotation cleanup. It does not change PDP lifetime or
> hash updates.
>
> Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
Reviewed-by: Simon Horman <horms@kernel.org>
There is an AI-generated review of this patch available on sashko.dev.
While I don't believe that the issues raised there should impede progress
of this patch you may want to look into them as possible follow-up.
...
^ permalink raw reply
* Re: [PATCH v19 31/40] dept: assign unique dept_key to each distinct wait_for_completion() caller
From: Gary Guo @ 2026-07-07 14:18 UTC (permalink / raw)
To: Byungchul Park, linux-kernel
Cc: max.byungchul.park, kernel_team, torvalds, damien.lemoal,
linux-ide, adilger.kernel, linux-ext4, mingo, peterz, will, tglx,
rostedt, joel, sashal, daniel.vetter, duyuyang, johannes.berg, tj,
tytso, willy, david, amir73il, gregkh, kernel-team, linux-mm,
akpm, mhocko, minchan, hannes, vdavydov.dev, sj, jglisse, dennis,
cl, penberg, rientjes, vbabka, ngupta, linux-block, josef,
linux-fsdevel, jack, jlayton, dan.j.williams, hch, djwong,
dri-devel, rodrigosiqueiramelo, melissa.srw, hamohammed.sa,
harry.yoo, chris.p.wilson, gwan-gyeong.mun, boqun.feng, longman,
yunseong.kim, ysk, yeoreum.yun, netdev, matthew.brost, her0gyugyu,
corbet, catalin.marinas, bp, x86, hpa, luto, sumit.semwal,
gustavo, christian.koenig, andi.shyti, arnd, lorenzo.stoakes,
Liam.Howlett, rppt, surenb, mcgrof, petr.pavlu, da.gomez,
samitolvanen, paulmck, frederic, neeraj.upadhyay, joelagnelf,
josh, urezki, mathieu.desnoyers, jiangshanlai, qiang.zhang,
juri.lelli, vincent.guittot, dietmar.eggemann, bsegall, mgorman,
vschneid, chuck.lever, neil, okorniev, Dai.Ngo, tom, trondmy,
anna, kees, bigeasy, clrkwllms, mark.rutland, ada.coupriediaz,
kristina.martsenko, wangkefeng.wang, broonie, kevin.brodsky, dwmw,
shakeel.butt, ast, ziy, yuzhao, baolin.wang, usamaarif642,
joel.granados, richard.weiyang, geert+renesas, tim.c.chen, linux,
alexander.shishkin, lillian, chenhuacai, francesco,
guoweikang.kernel, link, jpoimboe, masahiroy, brauner,
thomas.weissschuh, oleg, mjguzik, andrii, wangfushuai, linux-doc,
linux-arm-kernel, linux-media, linaro-mm-sig, linux-i2c,
linux-arch, linux-modules, rcu, linux-nfs, linux-rt-devel,
2407018371, dakr, miguel.ojeda.sandonis, neilb, bagasdotme,
wsa+renesas, dave.hansen, geert, ojeda, alex.gaynor, gary,
bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross, rust-for-linux
In-Reply-To: <20260706061928.66713-32-byungchul@sk.com>
On Mon Jul 6, 2026 at 7:19 AM BST, Byungchul Park wrote:
> wait_for_completion() can be used at various points in the code and it's
> very hard to distinguish wait_for_completion()s between different usages.
> Using a single dept_key for all the wait_for_completion()s could trigger
> false positive reports.
>
> Assign unique dept_key to each distinct wait_for_completion() caller to
> avoid false positive reports.
>
> While at it, add a rust helper for wait_for_completion() to avoid build
> errors.
This will cause Rust code to share the same dept_key, so it will have all the
false positives that the change is trying to avoid.
In general it is easy to create Rust bindings for static inline C functions
because it'll be just some computation, while creating bindings for C
function-like macros that define additional statics can be challenging.
Is dept_key similar to lock_class_key, where only the address matters? If so,
the approach that I use in
https://lore.kernel.org/rust-for-linux/DJP0CDOR98N5.29BK8PUFRWRUK@garyguo.net
could be used for dept_key as well, then we can keep Rust `wait_for_completion`
still a function; otherwise we have to turn it into a macro too on the Rust side
to create such statics, which isn't ideal.
Best,
Gary
>
> Signed-off-by: Byungchul Park <byungchul@sk.com>
> ---
> include/linux/completion.h | 100 +++++++++++++++++++++++++++++++------
> kernel/sched/completion.c | 60 +++++++++++-----------
> rust/helpers/completion.c | 5 ++
> 3 files changed, 120 insertions(+), 45 deletions(-)
^ permalink raw reply
* [PATCH RFC net-next 3/3] net: dsa: mxl862xx: add devlink flash_update and info_get
From: Daniel Golle @ 2026-07-07 14:16 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
Implement runtime firmware upgrade via "devlink dev flash" and version
reporting via "devlink dev info":
devlink dev info mdio_bus/<bus>/<addr>
devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
The driver sends SYS_MISC_FW_UPDATE to enter MCUboot rescue mode,
transfers the signed image over the SB PDI bulk-transfer protocol
(clause-22 SMDIO), waits for the switch to reboot, then schedules
device_reprobe() for a clean remove()+probe() cycle.
Before the transfer begins the driver closes all conduit interfaces
and marks every netdev (user and conduit) not-present via
netif_device_detach() so that userspace cannot bring ports back up
during the ~15 minute flash process. Progress is reported through
devlink status notifications. Once the FW_UPDATE command has been
sent the switch is in MCUboot mode and normal operation can only be
restored by a reprobe, so the driver always schedules one regardless
of transfer outcome.
The reprobe work item is dynamically allocated (following the iwlwifi
pattern) because device_reprobe() triggers remove() which frees the
devm-managed priv while the work is still executing.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/Makefile | 2 +-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
drivers/net/dsa/mxl862xx/mxl862xx-fw.c | 434 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-fw.h | 15 +
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 7 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.h | 2 +
7 files changed, 463 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-fw.h
diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile
index a7be0e6669df..bccac0d0f703 100644
--- a/drivers/net/dsa/mxl862xx/Makefile
+++ b/drivers/net/dsa/mxl862xx/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o mxl862xx-fw.o
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index c87a955c13c4..e2aa2934e9e1 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -70,6 +70,7 @@
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x2)
+#define SYS_MISC_FW_UPDATE (SYS_MISC_MAGIC + 0x1)
#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
#define MXL862XX_XPCS_PCS_CONFIG (MXL862XX_XPCS_MAGIC + 0x1)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.c b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
new file mode 100644
index 000000000000..7cd4a462667b
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Firmware flash and devlink support for MaxLinear MxL862xx
+ *
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ *
+ * Usage:
+ * # Query running firmware version:
+ * devlink dev info mdio_bus/<bus>/<addr>
+ *
+ * # Flash new firmware (all ports are taken down automatically):
+ * devlink dev flash mdio_bus/<bus>/<addr> file <firmware.bin>
+ *
+ * The flash process takes approximately 15 minutes. Progress is
+ * reported via devlink status notifications. After a successful (or
+ * failed) flash the driver reprobes the device automatically.
+ */
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <net/dsa.h>
+
+#include "mxl862xx.h"
+#include "mxl862xx-api.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
+#include "mxl862xx-host.h"
+
+/* SB PDI registers (clause-22 SMDIO address space) */
+#define MXL862XX_SB_PDI_CTRL 0xe100
+#define MXL862XX_SB_PDI_ADDR 0xe101
+#define MXL862XX_SB_PDI_DATA 0xe102
+#define MXL862XX_SB_PDI_STAT 0xe103
+
+/* SB PDI CTRL modes */
+#define MXL862XX_SB_PDI_CTRL_RST 0x00
+#define MXL862XX_SB_PDI_CTRL_WR 0x02
+
+/* SB PDI handshake magic */
+#define MXL862XX_SB_PDI_READY 0xc55c
+#define MXL862XX_SB_PDI_START 0xf48f
+#define MXL862XX_SB_PDI_END 0x3cc3
+
+/* Firmware transfer geometry */
+#define MXL862XX_FW_HDR_SIZE 20
+#define MXL862XX_FW_BANK_HALF 16384 /* words per half-bank */
+#define MXL862XX_FW_BANK_SLICE 32760 /* words per full slice */
+#define MXL862XX_FW_SB1_ADDR 0x7800 /* SB1 word address */
+
+/* Timeouts */
+#define MXL862XX_FW_READY_TIMEOUT_MS 30000
+#define MXL862XX_FW_ACK_TIMEOUT_MS 5000
+#define MXL862XX_FW_ERASE_TIMEOUT_MS 300000 /* flash erase is very slow */
+#define MXL862XX_FW_WRITE_TIMEOUT_MS 120000 /* per-slice program timeout */
+#define MXL862XX_FW_REBOOT_DELAY_MS 5000
+
+static void mxl862xx_sb_pdi_reset(struct mxl862xx_priv *priv)
+{
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_RST);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+ MXL862XX_SB_PDI_CTRL_RST);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA,
+ MXL862XX_SB_PDI_CTRL_RST);
+}
+
+static int mxl862xx_sb_pdi_poll_stat(struct mxl862xx_priv *priv, u16 expected,
+ unsigned long timeout_ms)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
+ int ret;
+
+ do {
+ ret = mxl862xx_smdio_read(priv, MXL862XX_SB_PDI_STAT);
+ if (ret < 0)
+ return ret;
+ if ((u16)ret == expected)
+ return 0;
+ usleep_range(10000, 11000);
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+/* Reprobe work -- dynamically allocated so it survives remove().
+ * device_reprobe() -> remove() frees priv (devm) while work is executing,
+ * so the work struct must not live in mxl862xx_priv.
+ */
+struct mxl862xx_reprobe {
+ struct device *dev;
+ struct delayed_work dwork;
+};
+
+static void mxl862xx_reprobe_work_fn(struct work_struct *work)
+{
+ struct mxl862xx_reprobe *reprobe =
+ container_of(work, struct mxl862xx_reprobe, dwork.work);
+
+ if (device_reprobe(reprobe->dev))
+ dev_err(reprobe->dev, "reprobe failed\n");
+ put_device(reprobe->dev);
+ kfree(reprobe);
+ module_put(THIS_MODULE);
+}
+
+/* MCUboot firmware image header (20 bytes) */
+struct mxl862xx_fw_hdr {
+ __le32 image_type;
+ __le32 image_size_1;
+ __le32 image_checksum_1;
+ __le32 image_size_2;
+ __le32 image_checksum_2;
+} __packed;
+
+static int mxl862xx_flash_firmware(struct mxl862xx_priv *priv,
+ const struct firmware *fw,
+ struct devlink *dl)
+{
+ const struct mxl862xx_fw_hdr *hdr;
+ u32 word_idx = 0, data_written = 0, idx = 0;
+ unsigned long next_notify = 0;
+ const u8 *payload;
+ u32 payload_size;
+ u16 word, fdata;
+ int ret, i;
+ u32 crc;
+
+ if (fw->size < MXL862XX_FW_HDR_SIZE)
+ return -EINVAL;
+
+ hdr = (const struct mxl862xx_fw_hdr *)fw->data;
+ payload = fw->data + MXL862XX_FW_HDR_SIZE;
+ payload_size = le32_to_cpu(hdr->image_size_1) +
+ le32_to_cpu(hdr->image_size_2);
+
+ if (payload_size > fw->size - MXL862XX_FW_HDR_SIZE) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: firmware file too small for declared size\n");
+ return -EINVAL;
+ }
+
+ /* Validate CRC-32 of both image slots before touching hardware */
+ if (le32_to_cpu(hdr->image_size_1)) {
+ crc = ~crc32_le(~0U, payload,
+ le32_to_cpu(hdr->image_size_1));
+ if (crc != le32_to_cpu(hdr->image_checksum_1)) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: image 1 CRC mismatch (got %08x, expected %08x)\n",
+ crc, le32_to_cpu(hdr->image_checksum_1));
+ return -EINVAL;
+ }
+ }
+
+ if (le32_to_cpu(hdr->image_size_2)) {
+ crc = ~crc32_le(~0U,
+ payload + le32_to_cpu(hdr->image_size_1),
+ le32_to_cpu(hdr->image_size_2));
+ if (crc != le32_to_cpu(hdr->image_checksum_2)) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: image 2 CRC mismatch (got %08x, expected %08x)\n",
+ crc, le32_to_cpu(hdr->image_checksum_2));
+ return -EINVAL;
+ }
+ }
+
+ /* Step 1: Tell firmware to enter MCUboot rescue mode.
+ * The FW_UPDATE command takes no payload (size 0).
+ */
+ ret = mxl862xx_api_wrap(priv, SYS_MISC_FW_UPDATE, NULL, 0,
+ false, false);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: FW_UPDATE command failed: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /* From this point on, the switch is in MCUboot rescue mode.
+ * Any failure must go through the end_magic label to tell
+ * MCUboot to reboot rather than leaving it stuck waiting.
+ */
+
+ /* Step 2: Reset PDI and wait for bootloader ready */
+ devlink_flash_update_status_notify(dl, "Waiting for bootloader",
+ NULL, 0, 0);
+ mxl862xx_sb_pdi_reset(priv);
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_READY,
+ MXL862XX_FW_READY_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: bootloader not ready: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 3: Start handshake */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_START);
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_SB_PDI_START + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: start handshake failed: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 4: Transfer 20-byte header using auto-increment write mode */
+ devlink_flash_update_status_notify(dl, "Erasing flash", NULL, 0, 0);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ for (i = 0; i < MXL862XX_FW_HDR_SIZE / 2; i++) {
+ word = fw->data[i * 2] |
+ ((u16)fw->data[i * 2 + 1] << 8);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, word);
+ }
+ mxl862xx_sb_pdi_reset(priv);
+
+ /* Write header byte count to STAT to trigger erase */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_FW_HDR_SIZE);
+
+ /* Wait for header ACK (header_size + 1) */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, MXL862XX_FW_HDR_SIZE + 1,
+ MXL862XX_FW_ACK_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: header ACK failed: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 5: Wait for erase to complete (STAT goes to 0) */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
+ MXL862XX_FW_ERASE_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: erase timeout: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ /* Step 6: Transfer payload using dual-bank auto-increment writes */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+
+ while (idx < payload_size) {
+ if (idx + 1 < payload_size) {
+ fdata = payload[idx] |
+ ((u16)payload[idx + 1] << 8);
+ idx += 2;
+ data_written += 2;
+ } else {
+ fdata = payload[idx];
+ idx++;
+ data_written++;
+ }
+
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_DATA, fdata);
+ word_idx++;
+
+ /* Last byte(s): flush final partial slice */
+ if (idx >= payload_size) {
+ mxl862xx_sb_pdi_reset(priv);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ data_written);
+ break;
+ }
+
+ /* Half-bank boundary: switch to SB1 address */
+ if (word_idx == MXL862XX_FW_BANK_HALF) {
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_RST);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_ADDR,
+ MXL862XX_FW_SB1_ADDR);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+ } else if (word_idx >= MXL862XX_FW_BANK_SLICE) {
+ /* Full slice: flush and wait for program */
+ mxl862xx_sb_pdi_reset(priv);
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ data_written);
+ word_idx = 0;
+ data_written = 0;
+
+ ret = mxl862xx_sb_pdi_poll_stat(
+ priv, 0, MXL862XX_FW_WRITE_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: write timeout at %u/%u: %pe\n",
+ idx, payload_size, ERR_PTR(ret));
+ goto end_magic;
+ }
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_CTRL,
+ MXL862XX_SB_PDI_CTRL_WR);
+
+ if (time_after(jiffies, next_notify)) {
+ devlink_flash_update_status_notify(
+ dl, "Flashing", NULL,
+ idx, payload_size);
+ next_notify = jiffies +
+ msecs_to_jiffies(500);
+ }
+ }
+ }
+
+ /* Wait for final slice to be programmed */
+ ret = mxl862xx_sb_pdi_poll_stat(priv, 0,
+ MXL862XX_FW_WRITE_TIMEOUT_MS);
+ if (ret) {
+ dev_err(&priv->mdiodev->dev,
+ "flash: final write timeout: %pe\n", ERR_PTR(ret));
+ goto end_magic;
+ }
+
+ devlink_flash_update_status_notify(dl, "Flashing", NULL,
+ payload_size, payload_size);
+
+end_magic:
+ /* Always send end magic so MCUboot reboots instead of sitting
+ * idle. The hardware reset during reprobe recovers the switch
+ * regardless of whether the transfer succeeded or failed.
+ */
+ mxl862xx_smdio_write(priv, MXL862XX_SB_PDI_STAT,
+ MXL862XX_SB_PDI_END);
+ msleep(MXL862XX_FW_REBOOT_DELAY_MS);
+
+ return ret;
+}
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ char ver_str[32];
+
+ snprintf(ver_str, sizeof(ver_str), "%u.%u.%u",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ return devlink_info_version_running_put(req, "fw", ver_str);
+}
+
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_sys_fw_image_version ver = {};
+ struct mxl862xx_reprobe *reprobe;
+ struct dsa_port *dp;
+ int ret, i;
+
+ if (params->component) {
+ NL_SET_ERR_MSG_MOD(extack, "component is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ dev_info(ds->dev, "flash: running firmware %u.%u.%u\n",
+ priv->fw_version.major, priv->fw_version.minor,
+ priv->fw_version.revision);
+
+ /* Close all user and CPU ports while the firmware is still
+ * alive. dev_close() on user ports triggers multicast group
+ * leave and host MDB/FDB removal on the CPU port through the
+ * normal DSA callbacks so the core's tracking lists are
+ * drained before we enter MCUboot. Then mark user ports
+ * not-present so userspace cannot bring them back up during
+ * the (slow) flash process. The conduit is only closed, not
+ * detached -- it is owned by the Ethernet MAC driver and
+ * dev_open() during reprobe must be able to bring it back.
+ */
+ rtnl_lock();
+ dsa_switch_for_each_user_port(dp, ds) {
+ if (dp->user) {
+ dev_close(dp->user);
+ netif_device_detach(dp->user);
+ }
+ }
+ dsa_switch_for_each_cpu_port(dp, ds)
+ dev_close(dp->conduit);
+ rtnl_unlock();
+
+ /* Block all firmware API commands while the switch is being
+ * reflashed. The conduit is intentionally kept open -- it is
+ * owned by the Ethernet MAC driver and would not recover on
+ * reprobe if we closed it here.
+ */
+ priv->block_host = true;
+
+ /* Stop stats polling and pending host-flood work */
+ cancel_delayed_work_sync(&priv->stats_work);
+ for (i = 0; i < ds->num_ports; i++)
+ cancel_work_sync(&priv->ports[i].host_flood_work);
+
+ ret = mxl862xx_flash_firmware(priv, params->fw, ds->devlink);
+ if (ret)
+ NL_SET_ERR_MSG_MOD(extack, "firmware transfer failed");
+
+ if (!ret) {
+ /* Read new firmware version (switch just rebooted).
+ * Temporarily lift the block for this single query.
+ */
+ priv->block_host = false;
+ memset(&ver, 0, sizeof(ver));
+ if (!MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver)
+ && ver.iv_major)
+ dev_info(ds->dev, "flash: new firmware %u.%u.%u\n",
+ ver.iv_major, ver.iv_minor,
+ le16_to_cpu(ver.iv_revision));
+ }
+
+ /* Silently discard all API commands during the teardown that
+ * reprobe triggers -- the switch firmware has been reset and
+ * has no knowledge of the old configuration.
+ */
+ priv->skip_teardown = true;
+
+ reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
+ if (!reprobe)
+ return ret;
+
+ if (!try_module_get(THIS_MODULE)) {
+ kfree(reprobe);
+ return ret;
+ }
+
+ reprobe->dev = get_device(ds->dev);
+ INIT_DELAYED_WORK(&reprobe->dwork, mxl862xx_reprobe_work_fn);
+ schedule_delayed_work(&reprobe->dwork, msecs_to_jiffies(500));
+
+ return ret;
+}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-fw.h b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
new file mode 100644
index 000000000000..a1b60fbacebf
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-fw.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_FW_H
+#define __MXL862XX_FW_H
+
+#include <net/dsa.h>
+
+int mxl862xx_devlink_info_get(struct dsa_switch *ds,
+ struct devlink_info_req *req,
+ struct netlink_ext_ack *extack);
+int mxl862xx_devlink_flash_update(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack);
+
+#endif /* __MXL862XX_FW_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 6e582caea1fa..7ba984e94d82 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -15,6 +15,7 @@
#include <linux/unaligned.h>
#include <net/dsa.h>
#include "mxl862xx.h"
+#include "mxl862xx-cmd.h"
#include "mxl862xx-host.h"
#define CTRL_BUSY_MASK BIT(15)
@@ -336,6 +337,12 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data,
int ret, cmd_ret;
u16 max, crc, i;
+ if (priv->skip_teardown)
+ return 0;
+
+ if (priv->block_host && cmd != SYS_MISC_FW_UPDATE)
+ return -EBUSY;
+
dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data);
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 45d237b3a40f..e643083d3938 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -21,6 +21,7 @@
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
+#include "mxl862xx-fw.h"
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
@@ -2086,6 +2087,8 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
+ .devlink_info_get = mxl862xx_devlink_info_get,
+ .devlink_flash_update = mxl862xx_devlink_flash_update,
};
static int mxl862xx_probe(struct mdio_device *mdiodev)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 432a5f3f2e08..a53fc4c37451 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -337,6 +337,8 @@ struct mxl862xx_priv {
u16 evlan_ingress_size;
u16 evlan_egress_size;
u16 vf_block_size;
+ bool block_host;
+ bool skip_teardown;
struct delayed_work stats_work;
};
--
2.55.0
^ permalink raw reply related
* [PATCH RFC net-next 2/3] net: dsa: mxl862xx: add SMDIO clause-22 register access
From: Daniel Golle @ 2026-07-07 14:16 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
SMDIO register access. MCUboot rescue mode only exposes clause-22
registers; the existing clause-45 MMD interface is unavailable during
firmware transfer. The MDIO bus lock is held per-transaction (not
across polls) so that SB PDI polling during flash erase does not
starve other MDIO users.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 35 ++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-host.h | 2 ++
2 files changed, 37 insertions(+)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
index 4acd216f7cc0..6e582caea1fa 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -495,6 +495,41 @@ int mxl862xx_reset(struct mxl862xx_priv *priv)
return ret;
}
+#define MXL862XX_SMDIO_ADDR_REG 0x1f
+#define MXL862XX_SMDIO_PAGE_MASK 0xfff0
+#define MXL862XX_SMDIO_OFF_MASK 0x000f
+
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr)
+{
+ struct mii_bus *bus = priv->mdiodev->bus;
+ int phy = priv->mdiodev->addr;
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
+ addr & MXL862XX_SMDIO_PAGE_MASK);
+ if (ret >= 0)
+ ret = __mdiobus_read(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK);
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val)
+{
+ struct mii_bus *bus = priv->mdiodev->bus;
+ int phy = priv->mdiodev->addr;
+ int ret;
+
+ mutex_lock(&bus->mdio_lock);
+ ret = __mdiobus_write(bus, phy, MXL862XX_SMDIO_ADDR_REG,
+ addr & MXL862XX_SMDIO_PAGE_MASK);
+ if (ret >= 0)
+ ret = __mdiobus_write(bus, phy, addr & MXL862XX_SMDIO_OFF_MASK,
+ val);
+ mutex_unlock(&bus->mdio_lock);
+ return ret;
+}
+
void mxl862xx_host_init(struct mxl862xx_priv *priv)
{
INIT_WORK(&priv->crc_err_work, mxl862xx_crc_err_work_fn);
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
index 66d6ae198aff..4e054c6e4c0e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
@@ -18,5 +18,7 @@ int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
int mxl862xx_reset(struct mxl862xx_priv *priv);
+int mxl862xx_smdio_read(struct mxl862xx_priv *priv, u32 addr);
+int mxl862xx_smdio_write(struct mxl862xx_priv *priv, u32 addr, u16 val);
#endif /* __MXL862XX_HOST_H */
--
2.55.0
^ permalink raw reply related
* [PATCH RFC net-next 1/3] net: dsa: wire flash_update devlink callback to drivers
From: Daniel Golle @ 2026-07-07 14:15 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev
Add a devlink_flash_update callback to dsa_switch_ops so that DSA
drivers can support devlink dev flash without open-coding the devlink
plumbing. The new trampoline in net/dsa/devlink.c follows the existing
dsa_devlink_info_get pattern exactly.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
include/net/dsa.h | 3 +++
net/dsa/devlink.c | 13 +++++++++++++
2 files changed, 16 insertions(+)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..39d1dd500e40 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1170,6 +1170,9 @@ struct dsa_switch_ops {
int (*devlink_info_get)(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack);
+ int (*devlink_flash_update)(struct dsa_switch *ds,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack);
int (*devlink_sb_pool_get)(struct dsa_switch *ds,
unsigned int sb_index, u16 pool_index,
struct devlink_sb_pool_info *pool_info);
diff --git a/net/dsa/devlink.c b/net/dsa/devlink.c
index ed342f345692..25311a87cbc5 100644
--- a/net/dsa/devlink.c
+++ b/net/dsa/devlink.c
@@ -20,6 +20,18 @@ static int dsa_devlink_info_get(struct devlink *dl,
return -EOPNOTSUPP;
}
+static int dsa_devlink_flash_update(struct devlink *dl,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+ if (!ds->ops->devlink_flash_update)
+ return -EOPNOTSUPP;
+
+ return ds->ops->devlink_flash_update(ds, params, extack);
+}
+
static int dsa_devlink_sb_pool_get(struct devlink *dl,
unsigned int sb_index, u16 pool_index,
struct devlink_sb_pool_info *pool_info)
@@ -169,6 +181,7 @@ dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
static const struct devlink_ops dsa_devlink_ops = {
.info_get = dsa_devlink_info_get,
+ .flash_update = dsa_devlink_flash_update,
.sb_pool_get = dsa_devlink_sb_pool_get,
.sb_pool_set = dsa_devlink_sb_pool_set,
.sb_port_pool_get = dsa_devlink_sb_port_pool_get,
--
2.55.0
^ permalink raw reply related
* RE: [PATCH net-next 03/15] net/mlx5e: psp: Remove unneeded ref counting for PSP steering
From: Loktionov, Aleksandr @ 2026-07-07 14:15 UTC (permalink / raw)
To: Tariq Toukan, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, netdev@vger.kernel.org, Paolo Abeni
Cc: Boris Pismenny, Chris Mi, Cosmin, Ratiu, Daniel Zahka,
Dragos Tatulea, Gal Pressman, Keller, Jacob E, Jianbo Liu,
Lama Kayal, Leon Romanovsky, linux-kernel@vger.kernel.org,
linux-rdma@vger.kernel.org, Mark Bloch, Raed Salem,
Rahul Rameshbabu, Saeed Mahameed, Stanislav Fomichev,
Stanislav Fomichev, Willem de Bruijn
In-Reply-To: <20260707130858.969928-4-tariqt@nvidia.com>
> -----Original Message-----
> From: Tariq Toukan <tariqt@nvidia.com>
> Sent: Tuesday, July 7, 2026 3:09 PM
> To: Andrew Lunn <andrew+netdev@lunn.ch>; David S. Miller
> <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub
> Kicinski <kuba@kernel.org>; netdev@vger.kernel.org; Paolo Abeni
> <pabeni@redhat.com>
> Cc: Loktionov, Aleksandr <aleksandr.loktionov@intel.com>; Boris
> Pismenny <borisp@nvidia.com>; Chris Mi <cmi@nvidia.com>; Cosmin, Ratiu
> <cratiu@nvidia.com>; Daniel Zahka <daniel.zahka@gmail.com>; Dragos
> Tatulea <dtatulea@nvidia.com>; Gal Pressman <gal@nvidia.com>; Keller,
> Jacob E <jacob.e.keller@intel.com>; Jianbo Liu <jianbol@nvidia.com>;
> Lama Kayal <lkayal@nvidia.com>; Leon Romanovsky <leon@kernel.org>;
> linux-kernel@vger.kernel.org; linux-rdma@vger.kernel.org; Mark Bloch
> <mbloch@nvidia.com>; Raed Salem <raeds@nvidia.com>; Rahul Rameshbabu
> <rrameshbabu@nvidia.com>; Saeed Mahameed <saeedm@nvidia.com>;
> Stanislav Fomichev <sdf@fomichev.me>; Stanislav Fomichev
> <sdf.kernel@gmail.com>; Tariq Toukan <tariqt@nvidia.com>; Willem de
> Bruijn <willemdebruijn.kernel@gmail.com>
> Subject: [PATCH net-next 03/15] net/mlx5e: psp: Remove unneeded ref
> counting for PSP steering
>
> From: Cosmin Ratiu <cratiu@nvidia.com>
>
> PSP steering uses reference counting for TX and RX steering tables,
> but there's only a single reference for each acquired and thus the
> reference counting is unnecessary.
>
> Remove it and consolidate functions to simplify the code.
>
> Signed-off-by: Cosmin Ratiu <cratiu@nvidia.com>
> Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
> Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
> ---
> .../mellanox/mlx5/core/en_accel/psp.c | 129 +++++------------
> -
> 1 file changed, 33 insertions(+), 96 deletions(-)
>
> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> index d4686b5af776..a69c4e2821e9 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> @@ -26,7 +26,6 @@ struct mlx5e_psp_tx {
> struct mlx5_flow_table *ft;
> struct mlx5_flow_group *fg;
> struct mlx5_flow_handle *rule;
...
> }
>
> static void mlx5e_accel_psp_fs_cleanup(struct mlx5e_psp_fs *fs)
> --
> 2.44.0
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
^ permalink raw reply
* RE: [PATCH net-next 02/15] net/mlx5e: psp: Remove PSP steering mutexes
From: Loktionov, Aleksandr @ 2026-07-07 14:14 UTC (permalink / raw)
To: Tariq Toukan, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, netdev@vger.kernel.org, Paolo Abeni
Cc: Boris Pismenny, Chris Mi, Cosmin, Ratiu, Daniel Zahka,
Dragos Tatulea, Gal Pressman, Keller, Jacob E, Jianbo Liu,
Lama Kayal, Leon Romanovsky, linux-kernel@vger.kernel.org,
linux-rdma@vger.kernel.org, Mark Bloch, Raed Salem,
Rahul Rameshbabu, Saeed Mahameed, Stanislav Fomichev,
Stanislav Fomichev, Willem de Bruijn
In-Reply-To: <20260707130858.969928-3-tariqt@nvidia.com>
> -----Original Message-----
> From: Tariq Toukan <tariqt@nvidia.com>
> Sent: Tuesday, July 7, 2026 3:09 PM
> To: Andrew Lunn <andrew+netdev@lunn.ch>; David S. Miller
> <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub
> Kicinski <kuba@kernel.org>; netdev@vger.kernel.org; Paolo Abeni
> <pabeni@redhat.com>
> Cc: Loktionov, Aleksandr <aleksandr.loktionov@intel.com>; Boris
> Pismenny <borisp@nvidia.com>; Chris Mi <cmi@nvidia.com>; Cosmin, Ratiu
> <cratiu@nvidia.com>; Daniel Zahka <daniel.zahka@gmail.com>; Dragos
> Tatulea <dtatulea@nvidia.com>; Gal Pressman <gal@nvidia.com>; Keller,
> Jacob E <jacob.e.keller@intel.com>; Jianbo Liu <jianbol@nvidia.com>;
> Lama Kayal <lkayal@nvidia.com>; Leon Romanovsky <leon@kernel.org>;
> linux-kernel@vger.kernel.org; linux-rdma@vger.kernel.org; Mark Bloch
> <mbloch@nvidia.com>; Raed Salem <raeds@nvidia.com>; Rahul Rameshbabu
> <rrameshbabu@nvidia.com>; Saeed Mahameed <saeedm@nvidia.com>;
> Stanislav Fomichev <sdf@fomichev.me>; Stanislav Fomichev
> <sdf.kernel@gmail.com>; Tariq Toukan <tariqt@nvidia.com>; Willem de
> Bruijn <willemdebruijn.kernel@gmail.com>
> Subject: [PATCH net-next 02/15] net/mlx5e: psp: Remove PSP steering
> mutexes
>
> From: Cosmin Ratiu <cratiu@nvidia.com>
>
> PSP steering uses three mutexes to serialize steering rule
> init/cleanup.
> But init/cleanup are already serialized with the higher level devlink
> lock (for both device init and esw mode changes), so there's no need
> for multiple additional mutexes.
>
> Remove them to make room for the new changes.
> Later in the series, the netdev lock will be used to serialize PSP
> steering changes from multiple sources, so don't bother adding
> assertions now only for them to be overwritten later.
>
> Signed-off-by: Cosmin Ratiu <cratiu@nvidia.com>
> Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
> Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
> ---
> .../mellanox/mlx5/core/en_accel/psp.c | 43 +++---------------
> -
> 1 file changed, 7 insertions(+), 36 deletions(-)
>
> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> index 4f2fa6756b82..d4686b5af776 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> @@ -26,7 +26,6 @@ struct mlx5e_psp_tx {
> struct mlx5_flow_table *ft;
> struct mlx5_flow_group *fg;
> struct mlx5_flow_handle *rule;
> - struct mutex mutex; /* Protect PSP TX steering */
> u32 refcnt;
> struct mlx5_fc *tx_counter;
> };
> @@ -48,7 +47,6 @@ struct mlx5e_accel_fs_psp_prot {
> struct mlx5_flow_destination default_dest;
> struct mlx5e_psp_rx_err rx_err;
> u32 refcnt;
> - struct mutex prot_mutex; /* protect ESP4/ESP6 protocol */
> struct mlx5_flow_handle *def_rule;
> };
>
> @@ -485,15 +483,14 @@ static int accel_psp_fs_rx_ft_get(struct
> mlx5e_psp_fs *fs, enum accel_fs_psp_typ
> ttc = mlx5e_fs_get_ttc(fs->fs, false);
> accel_psp = fs->rx_fs;
> fs_prot = &accel_psp->fs_prot[type];
> - mutex_lock(&fs_prot->prot_mutex);
> if (fs_prot->refcnt++)
> - goto out;
> + return 0;
>
> /* create FT */
> err = accel_psp_fs_rx_create(fs, type);
> if (err) {
> fs_prot->refcnt--;
> - goto out;
> + return err;
> }
>
> /* connect */
> @@ -501,9 +498,7 @@ static int accel_psp_fs_rx_ft_get(struct
> mlx5e_psp_fs *fs, enum accel_fs_psp_typ
> dest.ft = fs_prot->ft;
> mlx5_ttc_fwd_dest(ttc, fs_psp2tt(type), &dest);
>
> -out:
> - mutex_unlock(&fs_prot->prot_mutex);
> - return err;
> + return 0;
> }
>
> static void accel_psp_fs_rx_ft_put(struct mlx5e_psp_fs *fs, enum
> accel_fs_psp_type type) @@ -514,18 +509,14 @@ static void
> accel_psp_fs_rx_ft_put(struct mlx5e_psp_fs *fs, enum accel_fs_psp_ty
>
> accel_psp = fs->rx_fs;
> fs_prot = &accel_psp->fs_prot[type];
> - mutex_lock(&fs_prot->prot_mutex);
> if (--fs_prot->refcnt)
> - goto out;
> + return;
>
> /* disconnect */
> mlx5_ttc_fwd_default_dest(ttc, fs_psp2tt(type));
>
> /* remove FT */
> accel_psp_fs_rx_destroy(fs, type);
> -
> -out:
> - mutex_unlock(&fs_prot->prot_mutex);
> }
>
> static void accel_psp_fs_cleanup_rx(struct mlx5e_psp_fs *fs) @@ -
> 544,7 +535,6 @@ static void accel_psp_fs_cleanup_rx(struct
> mlx5e_psp_fs *fs)
> mlx5_fc_destroy(fs->mdev, accel_psp->rx_counter);
> for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) {
> fs_prot = &accel_psp->fs_prot[i];
> - mutex_destroy(&fs_prot->prot_mutex);
> WARN_ON(fs_prot->refcnt);
> }
> kfree(fs->rx_fs);
> @@ -553,22 +543,15 @@ static void accel_psp_fs_cleanup_rx(struct
> mlx5e_psp_fs *fs)
>
> static int accel_psp_fs_init_rx(struct mlx5e_psp_fs *fs) {
> - struct mlx5e_accel_fs_psp_prot *fs_prot;
> struct mlx5e_accel_fs_psp *accel_psp;
> struct mlx5_core_dev *mdev = fs->mdev;
> struct mlx5_fc *flow_counter;
> - enum accel_fs_psp_type i;
> int err;
>
> accel_psp = kzalloc_obj(*accel_psp);
> if (!accel_psp)
> return -ENOMEM;
>
> - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) {
> - fs_prot = &accel_psp->fs_prot[i];
> - mutex_init(&fs_prot->prot_mutex);
> - }
> -
> flow_counter = mlx5_fc_create(mdev, false);
> if (IS_ERR(flow_counter)) {
> mlx5_core_warn(mdev,
> @@ -623,10 +606,6 @@ static int accel_psp_fs_init_rx(struct
> mlx5e_psp_fs *fs)
> mlx5_fc_destroy(mdev, accel_psp->rx_counter);
> accel_psp->rx_counter = NULL;
> out_err:
> - for (i = 0; i < ACCEL_FS_PSP_NUM_TYPES; i++) {
> - fs_prot = &accel_psp->fs_prot[i];
> - mutex_destroy(&fs_prot->prot_mutex);
> - }
> kfree(accel_psp);
> fs->rx_fs = NULL;
>
> @@ -763,17 +742,14 @@ static void accel_psp_fs_tx_destroy(struct
> mlx5e_psp_tx *tx_fs) static int accel_psp_fs_tx_ft_get(struct
> mlx5e_psp_fs *fs) {
> struct mlx5e_psp_tx *tx_fs = fs->tx_fs;
> - int err = 0;
> + int err;
>
> - mutex_lock(&tx_fs->mutex);
> if (tx_fs->refcnt++)
> - goto out;
> + return 0;
>
> err = accel_psp_fs_tx_create_ft_table(fs);
> if (err)
> tx_fs->refcnt--;
> -out:
> - mutex_unlock(&tx_fs->mutex);
> return err;
> }
>
> @@ -781,13 +757,10 @@ static void accel_psp_fs_tx_ft_put(struct
> mlx5e_psp_fs *fs) {
> struct mlx5e_psp_tx *tx_fs = fs->tx_fs;
>
> - mutex_lock(&tx_fs->mutex);
> if (--tx_fs->refcnt)
> - goto out;
> + return;
>
> accel_psp_fs_tx_destroy(tx_fs);
> -out:
> - mutex_unlock(&tx_fs->mutex);
> }
>
> static void accel_psp_fs_cleanup_tx(struct mlx5e_psp_fs *fs) @@ -
> 798,7 +771,6 @@ static void accel_psp_fs_cleanup_tx(struct
> mlx5e_psp_fs *fs)
> return;
>
> mlx5_fc_destroy(fs->mdev, tx_fs->tx_counter);
> - mutex_destroy(&tx_fs->mutex);
> WARN_ON(tx_fs->refcnt);
> kfree(tx_fs);
> fs->tx_fs = NULL;
> @@ -828,7 +800,6 @@ static int accel_psp_fs_init_tx(struct
> mlx5e_psp_fs *fs)
> return PTR_ERR(flow_counter);
> }
> tx_fs->tx_counter = flow_counter;
> - mutex_init(&tx_fs->mutex);
> tx_fs->ns = ns;
> fs->tx_fs = tx_fs;
> return 0;
> --
> 2.44.0
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
^ permalink raw reply
* Re: [PATCH net-next v8 9/9] selftests: net: Add a test for BIG TCP in UDP tunnels
From: Matthieu Baerts @ 2026-07-07 14:12 UTC (permalink / raw)
To: Alice Mikityanska
Cc: Shuah Khan, Stanislav Fomichev, Andrew Lunn, Simon Horman,
Florian Westphal, netdev, Alice Mikityanska, Paolo Abeni,
Daniel Borkmann, David S. Miller, Eric Dumazet, Jakub Kicinski,
Xin Long, Willem de Bruijn, Willem de Bruijn, David Ahern,
Nikolay Aleksandrov
In-Reply-To: <48f70526-e4f1-472c-86f6-72c105853a21@app.fastmail.com>
Hi Alice,
On 07/07/2026 10:40, Alice Mikityanska wrote:
> On Tue, Jul 7, 2026, at 11:06, Paolo Abeni wrote:
>> On 7/6/26 8:19 PM, Alice Mikityanska wrote:
(...)
>>> +
>>> +cleanup_tunnel() {
>>> + ip -netns "$CLIENT_NS" link del tun0
>>> + ip -netns "$SERVER_NS" link del tun1
>>> +}
>>> +
>>> +cleanup() {
>>> + ip netns pids "$SERVER_NS" | xargs -r kill
>>> + ip netns pids "$CLIENT_NS" | xargs -r kill
>>> + ip netns del "$SERVER_NS"
>>> + ip netns del "$CLIENT_NS"
>>> + rm -rf "$WORKDIR"
>>> +}
>>> +
>>> +do_test() {
>>> + # When tx csum offload is off, software GSO is performed before passing the
>>> + # packet to veth. Check BIG TCP packets inside the VXLAN tunnel to verify
>>> + # the software checksum path: if the checksum code is broken, these packets
>>> + # will be dropped.
>>> + if [ "$2" = on ]; then
>>> + CAPTURE_IFACE='link'
>>> + else
>>> + CAPTURE_IFACE='tun'
>>> + fi
>>> +
>>> + ip netns exec "$SERVER_NS" tcpdump -nn -s 256 -i "${CAPTURE_IFACE}1" greater 65536 -w "$WORKDIR/server.pcap" 2> /dev/null &
>>> + TCPDUMP_SERVER_PID="$!"
>>> + ip netns exec "$CLIENT_NS" tcpdump -nn -s 256 -i "${CAPTURE_IFACE}0" greater 65536 -w "$WORKDIR/client.pcap" 2> /dev/null &
>>> + TCPDUMP_CLIENT_PID="$!"
>>> +
>>> + # This filter doesn't capture all possible variants of SACK, but it's aimed
>>> + # at the typical one where SACK follows after [nop, nop, timestamp, nop,
>>> + # nop] (14 bytes after the 20-byte TCP header). IPv6 needs a separate match,
>>> + # because man tcpdump says:
>>> + # > Arithmetic expression against transport layer headers, like tcp[0], does
>>> + # > not work against IPv6 packets. It only looks at IPv4 packets.
>>> + ip netns exec "$SERVER_NS" tcpdump -nn -s 256 -i "tun1" '(tcp[tcpflags] & (tcp-syn|tcp-ack) = tcp-ack and tcp[34:2] & 0xffc3 = 0x0502) or (ip6[6] = 0x06 and ip6[53] & 0x12 = 0x10 and ip6[74:2] & 0xffc3 = 0x0502)' -w "$WORKDIR/sack.pcap" 2> /dev/null &
The tcpdump commands might need to be used with ...
--immediate-mode --packet-buffered
... but that's maybe not needed, see below.
>>> + TCPDUMP_SACK_PID="$!"
>>> +
>>> + if [ "$1" = 4 ]; then
>>> + SERVER_IP="$SERVER_IP4_TUN"
>>> + echo "Running IPv4 traffic in the tunnel"
>>> + else
>>> + SERVER_IP="$SERVER_IP6_TUN"
>>> + echo "Running IPv6 traffic in the tunnel"
>>> + fi
>>> +
>>> + sleep 1 # Give tcpdump a second to spin up.
This is possibly not needed, see below.
>>> + ip netns exec "$CLIENT_NS" netperf -t TCP_STREAM -l 5 -H "$SERVER_IP" -- \
>>> + -m 80000 > /dev/null
>>> + sleep 1 # Give tcpdump a second to process buffered packets.
>>> + kill "$TCPDUMP_SERVER_PID" "$TCPDUMP_CLIENT_PID" "$TCPDUMP_SACK_PID"
>>> + wait "$TCPDUMP_SERVER_PID" "$TCPDUMP_CLIENT_PID" "$TCPDUMP_SACK_PID"
>>> + PACKETS_SERVER=$(tcpdump --count -r "$WORKDIR/server.pcap" 2> /dev/null | cut -d ' ' -f 1)
>>> + PACKETS_CLIENT=$(tcpdump --count -r "$WORKDIR/client.pcap" 2> /dev/null | cut -d ' ' -f 1)
>>> + PACKETS_SACK=$(tcpdump --count -r "$WORKDIR/sack.pcap" 2> /dev/null | cut -d ' ' -f 1)
>>> +
>>> + echo "Captured BIG TCP RX packets: $PACKETS_SERVER"
>>> + echo "Captured BIG TCP TX packets: $PACKETS_CLIENT"
>>> + echo "Captured TCP SACK packets: $PACKETS_SACK"
>>> + [ "$PACKETS_SERVER" -gt "$PACKETS_THRESHOLD" ] || return 1
>>> + [ "$PACKETS_CLIENT" -gt "$PACKETS_THRESHOLD" ] || return 1
>>> + [ "$PACKETS_SACK" -lt "$(( PACKETS_CLIENT / 2 ))" ] || return 1
>>
>> KCI fails here, see:
>>
>> https://netdev-ctrl.bots.linux.dev/logs/vmksft/net/results/722863/156-big-tcp-tunnels-sh/stdout
>>
>> possibly the above parsing is a bit fragile/tcpdump version's dependent?!?
>
> TL/DR: I've seen this when I ran out of space in /tmp before adding -s 256.
>
> Hmm, the changes I made in this iteration were aimed at addressing version-
> dependent behavior of tcpdump... I used to count the lines of its
> output. Newer tcpdump prints two lines per encapsulated packet. Older
> tcpdump can't parse BIG TCP in this test and prints one line per packet.
> To make it more robust, I decided to store the pcap and count the
> packets there (--count doesn't work with live capture when tcpdump is
> interrupted). The pitfall is that now it takes a few hundred megabytes
> in /tmp to store those pcaps.
Wow :)
If you need to keep tcpdump -- see below -- you can probably reduce even
more the packet size (-s 256), I don't know what's the minimum. And
purge the workdir in cleanup_tunnel().
> Now, seeing empty stdout from tcpdump could be if the pcap file is empty
> (doesn't contain the pcap header), because we ran out of space in /tmp
> (first two tcpdumps took all space, the third didn't write the header -
> I observed this behavior before I added -s 256). We don't see the
> corresponding error messages, because tcpdump starts with 2> /dev/null
> (otherwise it floods the screen with statistics). Could this be the
> reason? How much space is allocated for /tmp in the CI runners?
>
> I can probably address it by limiting the overall number of captured
> packets with -c, but then I won't be able to compare $PACKETS_SACK
> relative to $PACKETS_CLIENT.
If you only need to count some packets matching a filter, what about
using Netfilter rules with a counter, instead of using pcap files?
You can have counters not attached to rules altering packets:
https://wiki.nftables.org/wiki-nftables/index.php/Counters
It might even be easier to match the SACK packets, with the 'tcp option
sack' filter from nft [1]. And for the size, I guess you can use "meta
length > 65536" [2], or checking other fields from IP/TCP headers.
If you want to keep your cBPF filter, you can also use nfbpf_compile for
the translation, and use that with 'iptables -m bpf --bytecode <...>',
but it might not be needed.
[1] https://www.mankier.com/8/nft#Payload_Expressions-Extension_Header_Expressions
[2] https://www.mankier.com/8/nft#Primary_Expressions-Meta_Expressions
Cheers,
Matt
--
Sponsored by the NGI0 Core fund.
^ permalink raw reply
* RE: [PATCH net-next 01/15] net/mlx5e: psp: Rename the saved psp_dev to 'psd'
From: Loktionov, Aleksandr @ 2026-07-07 14:11 UTC (permalink / raw)
To: Tariq Toukan, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, netdev@vger.kernel.org, Paolo Abeni
Cc: Boris Pismenny, Chris Mi, Cosmin, Ratiu, Daniel Zahka,
Dragos Tatulea, Gal Pressman, Keller, Jacob E, Jianbo Liu,
Lama Kayal, Leon Romanovsky, linux-kernel@vger.kernel.org,
linux-rdma@vger.kernel.org, Mark Bloch, Raed Salem,
Rahul Rameshbabu, Saeed Mahameed, Stanislav Fomichev,
Stanislav Fomichev, Willem de Bruijn
In-Reply-To: <20260707130858.969928-2-tariqt@nvidia.com>
> -----Original Message-----
> From: Tariq Toukan <tariqt@nvidia.com>
> Sent: Tuesday, July 7, 2026 3:09 PM
> To: Andrew Lunn <andrew+netdev@lunn.ch>; David S. Miller
> <davem@davemloft.net>; Eric Dumazet <edumazet@google.com>; Jakub
> Kicinski <kuba@kernel.org>; netdev@vger.kernel.org; Paolo Abeni
> <pabeni@redhat.com>
> Cc: Loktionov, Aleksandr <aleksandr.loktionov@intel.com>; Boris
> Pismenny <borisp@nvidia.com>; Chris Mi <cmi@nvidia.com>; Cosmin, Ratiu
> <cratiu@nvidia.com>; Daniel Zahka <daniel.zahka@gmail.com>; Dragos
> Tatulea <dtatulea@nvidia.com>; Gal Pressman <gal@nvidia.com>; Keller,
> Jacob E <jacob.e.keller@intel.com>; Jianbo Liu <jianbol@nvidia.com>;
> Lama Kayal <lkayal@nvidia.com>; Leon Romanovsky <leon@kernel.org>;
> linux-kernel@vger.kernel.org; linux-rdma@vger.kernel.org; Mark Bloch
> <mbloch@nvidia.com>; Raed Salem <raeds@nvidia.com>; Rahul Rameshbabu
> <rrameshbabu@nvidia.com>; Saeed Mahameed <saeedm@nvidia.com>;
> Stanislav Fomichev <sdf@fomichev.me>; Stanislav Fomichev
> <sdf.kernel@gmail.com>; Tariq Toukan <tariqt@nvidia.com>; Willem de
> Bruijn <willemdebruijn.kernel@gmail.com>
> Subject: [PATCH net-next 01/15] net/mlx5e: psp: Rename the saved
> psp_dev to 'psd'
>
> From: Cosmin Ratiu <cratiu@nvidia.com>
>
> This is the canonical name used in the core, so try to be consistent.
> No-op change.
>
> Signed-off-by: Cosmin Ratiu <cratiu@nvidia.com>
> Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
> Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
> ---
> drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c | 8 ++++---
> -
> drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h | 2 +-
> .../net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c | 2 +-
> 3 files changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> index d9adb993e64d..4f2fa6756b82 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.c
> @@ -1072,11 +1072,11 @@ void mlx5e_psp_unregister(struct mlx5e_priv
> *priv) {
> struct mlx5e_psp *psp = priv->psp;
>
> - if (!psp || !psp->psp)
> + if (!psp || !psp->psd)
> return;
>
> - psp_dev_unregister(psp->psp);
> - psp->psp = NULL;
> + psp_dev_unregister(psp->psd);
> + psp->psd = NULL;
> }
>
> void mlx5e_psp_register(struct mlx5e_priv *priv) @@ -1100,7 +1100,7
> @@ void mlx5e_psp_register(struct mlx5e_priv *priv)
> psd);
> return;
> }
> - psp->psp = psd;
> + psp->psd = psd;
> }
>
> int mlx5e_psp_init(struct mlx5e_priv *priv) diff --git
> a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h
> b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h
> index 6b62fef0d9a7..a53f90f7c341 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp.h
> @@ -23,7 +23,7 @@ struct mlx5e_psp_stats { };
>
> struct mlx5e_psp {
> - struct psp_dev *psp;
> + struct psp_dev *psd;
> struct psp_dev_caps caps;
> struct mlx5e_psp_fs *fs;
> atomic_t tx_key_cnt;
> diff --git
> a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
> b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
> index ef7f5338540f..c2f9899d23a5 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
> @@ -124,7 +124,7 @@ bool mlx5e_psp_offload_handle_rx_skb(struct
> net_device *netdev, struct sk_buff * {
> u32 psp_meta_data = be32_to_cpu(cqe->ft_metadata);
> struct mlx5e_priv *priv = netdev_priv(netdev);
> - u16 dev_id = priv->psp->psp->id;
> + u16 dev_id = priv->psp->psd->id;
> bool strip_icv = true;
> u8 generation = 0;
>
> --
> 2.44.0
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
^ permalink raw reply
* Re: [PATCH net v4] net: airoha: fix MIB stats collection to be lossless
From: Lorenzo Bianconi @ 2026-07-07 14:07 UTC (permalink / raw)
To: Aniket Negi; +Cc: netdev, aniket.negi
In-Reply-To: <20260707132117.94902-1-aniket.negi03@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1418 bytes --]
> Hi Lorenzo,
> Thanks for the review.
>
> > > + for (i = AIROHA_GDM1_IDX; i <= AIROHA_GDM4_IDX; i++)
> > > + airoha_fe_set(eth, REG_FE_GDM_MIB_CLEAR(i),
> > > + FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
> >
> > This configuration is not needed since we reset FE at module load.
>
> Agreed. The SCU FE reset clears MIB counters, so the explicit loop
> is redundant. Will drop it in v5.
>
> On Mon, 2 Jul 2026 22:59:32 +0000, Sashiko <sashiko@kernel.org> wrote:
> > With this patch the driver no longer has any users of
> > REG_FE_GDM_MIB_CLEAR, FE_GDM_MIB_RX_CLEAR_MASK or
> > FE_GDM_MIB_TX_CLEAR_MASK; grep in drivers/net/ethernet/airoha/
> > matches only the definitions in airoha_regs.h. Should these macros
> > be removed in the same patch that eliminates their only caller?
>
> What is your suggestions here, I prefer that definitions should
> be retained in airoha_regs.h as register documentation for future
> reference. Only the loop will be dropped.
correct.
Regards,
Lorenzo
>
> > nit: tmp is not so meaningful, maybe better something like data?
> > nit: I would prefer "+" instead of "|"
> > nit: please drop prev and just do:
> > dev->stats.tx_ok_pkts = max(data, dev->stats.tx_ok_pkts);
> > please redo it for all the occurrences.
>
> Understood. Will rename tmp to data, and use '+' instead of '|' in v5.
>
> Thanks,
> Aniket
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH net-next v13 09/10] net: ethtool: Introduce ethtool command to list ports
From: Maxime Chevallier @ 2026-07-07 14:00 UTC (permalink / raw)
To: Paolo Abeni, davem, Andrew Lunn, Jakub Kicinski, Eric Dumazet,
Russell King, Heiner Kallweit
Cc: netdev, linux-kernel, thomas.petazzoni, Christophe Leroy,
Herve Codina, Florian Fainelli, Vladimir Oltean,
Köry Maincent, Marek Behún, Oleksij Rempel,
Nicolò Veronese, Simon Horman, mwojtas, Romain Gantois,
Daniel Golle, Dimitri Fedrau, Frank Wunderlich
In-Reply-To: <e1034a59-1ab0-4a41-b709-9da253d4901f@redhat.com>
On 7/7/26 15:46, Paolo Abeni wrote:
> On 7/7/26 12:47 PM, Maxime Chevallier wrote:
>> On 7/7/26 12:12, Paolo Abeni wrote:
>>>
>>> Both sashikos instances points to multiple races with dumps. I suspect
>>> some explicit locking is required.
>>>
>>> https://sashiko.dev/#/patchset/20260701110427.143945-1-maxime.chevallier%40bootlin.com
>>> https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260701110427.143945-1-maxime.chevallier%40bootlin.com
>>>
>>> I *think* this is hopefully the last pending issue.
>>
>> True indeed, the other issues are sashiko saying "what if we detach a PHY and attach it
>> to another netdev", which can't happen :) I agree with some of the locking points that
>> were found, I'll address that likely as a separate cleanup.
>
> I'm sorry, but I think it's better to address the above problems with
> the main series to avoid a window of opportunity that could cause a lot
> of quite randomic/drop by submissions to try to addresses them with a
> lot of noise on the ML.
I was thinking the opposite actually, send a cleanup series that gets the
locking part correct before continuing with phy_port, as it seems for now
the phy_port/topology code is avoiding races just because all the callpaths
happen to be under rtnl(), but this isn't formalised properly with asserts
and dedicated locks in the problematic parts of the code.
I otherwise agree, let's not send buggy code to be fixed later and I definitely
see what you mean about the random submissions to fix these.
Maxime
^ permalink raw reply
* [PATCH net-next v3 4/4] selftests: net: hsr: add PRP RedBox test
From: Xin Xie @ 2026-07-07 13:59 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, Sebastian Andrzej Siewior, Felix Maurer,
Luka Gejak, Fernando Fernandez Mancera, linux-kselftest,
linux-kernel, Xin Xie
In-Reply-To: <20260707135937.18-1-xiexinet@gmail.com>
Add a kselftest that builds a PRP RedBox (interlink) with a SAN behind the
interlink and a peer DANP, and checks bidirectional unicast across the
interlink, preservation of the SAN source MAC on the PRP network, and that
the proxy-announce supervision frame carries the RedBox-MAC TLV (Type 30)
terminated by an EOT marker. It reuses the hsr_common.sh / lib.sh helpers
and skips cleanly on a kernel or iproute2 without PRP interlink support.
The background ping is killed by its exact PID: ip netns exec does not
isolate the PID namespace, so a pattern-based pkill could hit unrelated
processes on the host.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
tools/testing/selftests/net/hsr/Makefile | 1 +
.../selftests/net/hsr/hsr_prp_redbox.sh | 99 +++++++++++++++++++
2 files changed, 100 insertions(+)
create mode 100755 tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326cf5..2150e487ac7 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -4,6 +4,7 @@ top_srcdir = ../../../../..
TEST_PROGS := \
hsr_ping.sh \
+ hsr_prp_redbox.sh \
hsr_redbox.sh \
link_faults.sh \
prp_ping.sh \
diff --git a/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
new file mode 100755
index 00000000000..479c892225b
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test a PRP RedBox (PRP-SAN): a SAN that sits behind the interlink port must
+# reach, and be reached by, a peer DANP on the PRP network with its own MAC
+# preserved on the wire, and the RedBox must announce the SAN with a RedBox-MAC
+# TLV (terminated by an EOT marker) in its PRP supervision frames.
+#
+# RB PRP RedBox: prp0 over rb_a/rb_b (LAN A/B) + interlink rb_il
+# PEER peer DANP : prp0 over pe_a/pe_b, 100.64.0.2
+# SAN SAN : san_il, own MAC, 100.64.0.51 (behind the interlink)
+
+ipv6=false
+
+source ./hsr_common.sh
+
+check_prerequisites
+
+if ! command -v tcpdump >/dev/null 2>&1; then
+ echo "SKIP: This test requires tcpdump"
+ exit $ksft_skip
+fi
+
+if ! ip link help hsr 2>&1 | grep -q interlink; then
+ echo "SKIP: iproute2 too old (no hsr interlink support)"
+ exit $ksft_skip
+fi
+
+setup_ns RB PEER SAN
+trap 'cleanup_ns "$RB" "$PEER" "$SAN"' EXIT
+
+ip link add rb_a netns "$RB" type veth peer name pe_a netns "$PEER"
+ip link add rb_b netns "$RB" type veth peer name pe_b netns "$PEER"
+ip link add rb_il netns "$RB" type veth peer name san_il netns "$SAN"
+
+ip -n "$RB" link set rb_a up
+ip -n "$RB" link set rb_b up
+ip -n "$RB" link set rb_il up
+ip -n "$PEER" link set pe_a up
+ip -n "$PEER" link set pe_b up
+ip -n "$SAN" link set san_il up
+ip -n "$SAN" addr add 100.64.0.51/24 dev san_il
+
+# Feature gate: PRP interlink (RedBox) creation. A kernel without PRP RedBox
+# support rejects this with -EINVAL, so SKIP rather than FAIL.
+if ! ip -n "$RB" link add name prp0 type hsr slave1 rb_a slave2 rb_b \
+ interlink rb_il proto 1 2>/dev/null; then
+ echo "SKIP: kernel without PRP RedBox (interlink) support"
+ exit $ksft_skip
+fi
+ip -n "$RB" link set prp0 up
+ip -n "$PEER" link add name prp0 type hsr slave1 pe_a slave2 pe_b proto 1
+ip -n "$PEER" link set prp0 up
+ip -n "$PEER" addr add 100.64.0.2/24 dev prp0
+sleep 1
+
+san_mac=$(ip -n "$SAN" -br link show san_il | awk '{print $3}')
+rb_mac=$(ip -n "$RB" -br link show rb_il | awk '{print $3}')
+
+# Bidirectional unicast across the interlink.
+do_ping "$PEER" 100.64.0.51
+do_ping "$SAN" 100.64.0.2
+stop_if_error "PRP RedBox bidirectional unicast failed"
+
+# The SAN source MAC must be preserved on the PRP network, not laundered to the
+# RedBox MAC: the peer resolves the SAN IP to the SAN's own MAC.
+neigh=$(ip -n "$PEER" neigh show 100.64.0.51 | awk '{print $5}')
+if [ "$neigh" != "$san_mac" ]; then
+ echo "SAN MAC preservation [ FAIL ]: peer resolved 100.64.0.51 to" \
+ "'$neigh', expected $san_mac" 1>&2
+ ret=1
+fi
+stop_if_error "SAN MAC not preserved on the PRP network"
+
+# The proxy-announce supervision frame must carry, in order, the life-check TLV
+# (type 0x14, len 6) + MacAddressA == SAN MAC + the RedBox-MAC TLV (type 0x1e,
+# len 6) + MacAddressRedBox == RedBox MAC + the EOT marker (0x0000).
+ip netns exec "$SAN" ping -i 0.2 -q 100.64.0.2 >/dev/null 2>&1 &
+ping_pid=$!
+cap=$(ip netns exec "$PEER" timeout 5 tcpdump -i pe_a -nn -x \
+ "ether proto 0x88fb and ether src $rb_mac" 2>/dev/null || true)
+kill "$ping_pid" 2>/dev/null || true
+wait "$ping_pid" 2>/dev/null || true
+
+san_hex=$(echo "$san_mac" | tr -d ':')
+rb_hex=$(echo "$rb_mac" | tr -d ':')
+# Reassemble contiguous frame hex: drop the "0x0010:" offset labels and spaces.
+frame_hex=$(echo "$cap" | awk '/^[[:space:]]*0x[0-9a-f]+:/ {
+ sub(/^[[:space:]]*0x[0-9a-f]+:[[:space:]]*/, "");
+ gsub(/ /, ""); printf "%s", $0 }')
+if ! echo "$frame_hex" | grep -q "1406${san_hex}1e06${rb_hex}0000"; then
+ echo "supervision RedBox-MAC TLV [ FAIL ]: missing SAN MAC, Type-30" \
+ "payload, or EOT" 1>&2
+ ret=1
+fi
+stop_if_error "PRP RedBox supervision RedBox-MAC TLV/EOT check failed"
+
+echo "INFO: PRP RedBox (PRP-SAN) conformance checks passed"
+exit $ret
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v3 3/4] net: hsr: allow PRP RedBox (interlink) creation
From: Xin Xie @ 2026-07-07 13:59 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, Sebastian Andrzej Siewior, Felix Maurer,
Luka Gejak, Fernando Fernandez Mancera, linux-kselftest,
linux-kernel, Xin Xie
In-Reply-To: <20260707135937.18-1-xiexinet@gmail.com>
With the PRP interlink datapath, duplicate discard and supervision support
in place, a PRP device can act as a RedBox. Remove the rtnetlink rejection
of "type hsr ... interlink <dev> proto 1"; the feature is implemented
unconditionally by the preceding patches.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_netlink.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
index 8099f2069a7..88940e8014b 100644
--- a/net/hsr/hsr_netlink.c
+++ b/net/hsr/hsr_netlink.c
@@ -121,14 +121,8 @@ static int hsr_newlink(struct net_device *dev,
}
}
- if (proto == HSR_PROTOCOL_PRP) {
+ if (proto == HSR_PROTOCOL_PRP)
proto_version = PRP_V1;
- if (interlink) {
- NL_SET_ERR_MSG_MOD(extack,
- "Interlink only works with HSR");
- return -EINVAL;
- }
- }
return hsr_dev_finalize(dev, link, interlink, multicast_spec,
proto_version, extack);
--
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