* [PATCH net-next 0/2] macvlan: broadcast delivery changes
From: Eric Dumazet @ 2026-04-01 10:38 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Andrew Lunn, netdev, eric.dumazet, Eric Dumazet
First patch adds data-race annotations.
Second patch changes macvlan_broadcast_enqueue() to return
early if the queue is full.
Eric Dumazet (2):
macvlan: annotate data-races around port->bc_queue_len_used
macvlan: avoid spinlock contention in macvlan_broadcast_enqueue()
drivers/net/macvlan.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
--
2.53.0.1118.gaef5881109-goog
^ permalink raw reply
* [PATCH net 10/10] netfilter: nf_tables: reject immediate NF_QUEUE verdict
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
nft_queue is always used from userspace nftables to deliver the NF_QUEUE
verdict. Immediately emitting an NF_QUEUE verdict is never used by the
userspace nft tools, so reject immediate NF_QUEUE verdicts.
The arp family does not provide queue support, but such an immediate
verdict is still reachable. Globally reject NF_QUEUE immediate verdicts
to address this issue.
Fixes: f342de4e2f33 ("netfilter: nf_tables: reject QUEUE/DROP verdict parameters")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_tables_api.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 3922cff1bb3d..8c42247a176c 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -11667,8 +11667,6 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
switch (data->verdict.code) {
case NF_ACCEPT:
case NF_DROP:
- case NF_QUEUE:
- break;
case NFT_CONTINUE:
case NFT_BREAK:
case NFT_RETURN:
@@ -11703,6 +11701,11 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
data->verdict.chain = chain;
break;
+ case NF_QUEUE:
+ /* The nft_queue expression is used for this purpose, an
+ * immediate NF_QUEUE verdict should not ever be seen here.
+ */
+ fallthrough;
default:
return -EINVAL;
}
--
2.47.3
^ permalink raw reply related
* [PATCH net 09/10] netfilter: x_tables: restrict xt_check_match/xt_check_target extensions for NFPROTO_ARP
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
Weiming Shi says:
xt_match and xt_target structs registered with NFPROTO_UNSPEC can be
loaded by any protocol family through nft_compat. When such a
match/target sets .hooks to restrict which hooks it may run on, the
bitmask uses NF_INET_* constants. This is only correct for families
whose hook layout matches NF_INET_*: IPv4, IPv6, INET, and bridge
all share the same five hooks (PRE_ROUTING ... POST_ROUTING).
ARP only has three hooks (IN=0, OUT=1, FORWARD=2) with different
semantics. Because NF_ARP_OUT == 1 == NF_INET_LOCAL_IN, the .hooks
validation silently passes for the wrong reasons, allowing matches to
run on ARP chains where the hook assumptions (e.g. state->in being
set on input hooks) do not hold. This leads to NULL pointer
dereferences; xt_devgroup is one concrete example:
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000044: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000220-0x0000000000000227]
RIP: 0010:devgroup_mt+0xff/0x350
Call Trace:
<TASK>
nft_match_eval (net/netfilter/nft_compat.c:407)
nft_do_chain (net/netfilter/nf_tables_core.c:285)
nft_do_chain_arp (net/netfilter/nft_chain_filter.c:61)
nf_hook_slow (net/netfilter/core.c:623)
arp_xmit (net/ipv4/arp.c:666)
</TASK>
Kernel panic - not syncing: Fatal exception in interrupt
Fix it by restricting arptables to NFPROTO_ARP extensions only.
Note that arptables-legacy only supports:
- arpt_CLASSIFY
- arpt_mangle
- arpt_MARK
that provide explicit NFPROTO_ARP match/target declarations.
Fixes: 9291747f118d ("netfilter: xtables: add device group match")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/x_tables.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c
index e594b3b7ad82..b39017c80548 100644
--- a/net/netfilter/x_tables.c
+++ b/net/netfilter/x_tables.c
@@ -501,6 +501,17 @@ int xt_check_match(struct xt_mtchk_param *par,
par->match->table, par->table);
return -EINVAL;
}
+
+ /* NFPROTO_UNSPEC implies NF_INET_* hooks which do not overlap with
+ * NF_ARP_IN,OUT,FORWARD, allow explicit extensions with NFPROTO_ARP
+ * support.
+ */
+ if (par->family == NFPROTO_ARP &&
+ par->match->family != NFPROTO_ARP) {
+ pr_info_ratelimited("%s_tables: %s match: not valid for this family\n",
+ xt_prefix[par->family], par->match->name);
+ return -EINVAL;
+ }
if (par->match->hooks && (par->hook_mask & ~par->match->hooks) != 0) {
char used[64], allow[64];
@@ -1016,6 +1027,18 @@ int xt_check_target(struct xt_tgchk_param *par,
par->target->table, par->table);
return -EINVAL;
}
+
+ /* NFPROTO_UNSPEC implies NF_INET_* hooks which do not overlap with
+ * NF_ARP_IN,OUT,FORWARD, allow explicit extensions with NFPROTO_ARP
+ * support.
+ */
+ if (par->family == NFPROTO_ARP &&
+ par->target->family != NFPROTO_ARP) {
+ pr_info_ratelimited("%s_tables: %s target: not valid for this family\n",
+ xt_prefix[par->family], par->target->name);
+ return -EINVAL;
+ }
+
if (par->target->hooks && (par->hook_mask & ~par->target->hooks) != 0) {
char used[64], allow[64];
--
2.47.3
^ permalink raw reply related
* [PATCH net 08/10] netfilter: ipset: drop logically empty buckets in mtype_del
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
From: Yifan Wu <yifanwucs@gmail.com>
mtype_del() counts empty slots below n->pos in k, but it only drops the
bucket when both n->pos and k are zero. This misses buckets whose live
entries have all been removed while n->pos still points past deleted slots.
Treat a bucket as empty when all positions below n->pos are unused and
release it directly instead of shrinking it further.
Fixes: 8af1c6fbd923 ("netfilter: ipset: Fix forceadd evaluation path")
Cc: stable@vger.kernel.org
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <dstsmallbird@foxmail.com>
Signed-off-by: Yifan Wu <yifanwucs@gmail.com>
Co-developed-by: Yuan Tan <yuantan098@gmail.com>
Signed-off-by: Yuan Tan <yuantan098@gmail.com>
Reviewed-by: Phil Sutter <phil@nwl.cc>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipset/ip_set_hash_gen.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 181daa9c2019..b79e5dd2af03 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -1098,7 +1098,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
if (!test_bit(i, n->used))
k++;
}
- if (n->pos == 0 && k == 0) {
+ if (k == n->pos) {
t->hregion[r].ext_size -= ext_size(n->size, dsize);
rcu_assign_pointer(hbucket(t, key), NULL);
kfree_rcu(n, rcu);
--
2.47.3
^ permalink raw reply related
* [PATCH net 07/10] netfilter: ctnetlink: ignore explicit helper on new expectations
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
Use the existing master conntrack helper, anything else is not really
supported and it just makes validation more complicated, so just ignore
what helper userspace suggests for this expectation.
This was uncovered when validating CTA_EXPECT_CLASS via different helper
provided by userspace than the existing master conntrack helper:
BUG: KASAN: slab-out-of-bounds in nf_ct_expect_related_report+0x2479/0x27c0
Read of size 4 at addr ffff8880043fe408 by task poc/102
Call Trace:
nf_ct_expect_related_report+0x2479/0x27c0
ctnetlink_create_expect+0x22b/0x3b0
ctnetlink_new_expect+0x4bd/0x5c0
nfnetlink_rcv_msg+0x67a/0x950
netlink_rcv_skb+0x120/0x350
Allowing to read kernel memory bytes off the expectation boundary.
CTA_EXPECT_HELP_NAME is still used to offer the helper name to userspace
via netlink dump.
Fixes: bd0779370588 ("netfilter: nfnetlink_queue: allow to attach expectations to conntracks")
Reported-by: Qi Tang <tpluszz77@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_conntrack_netlink.c | 54 +++++-----------------------
1 file changed, 9 insertions(+), 45 deletions(-)
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index 38bd7124d9f7..a20cd82446c5 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -2636,7 +2636,6 @@ static const struct nla_policy exp_nla_policy[CTA_EXPECT_MAX+1] = {
static struct nf_conntrack_expect *
ctnetlink_alloc_expect(const struct nlattr *const cda[], struct nf_conn *ct,
- struct nf_conntrack_helper *helper,
struct nf_conntrack_tuple *tuple,
struct nf_conntrack_tuple *mask);
@@ -2865,7 +2864,6 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
{
struct nlattr *cda[CTA_EXPECT_MAX+1];
struct nf_conntrack_tuple tuple, mask;
- struct nf_conntrack_helper *helper = NULL;
struct nf_conntrack_expect *exp;
int err;
@@ -2879,17 +2877,8 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
if (err < 0)
return err;
- if (cda[CTA_EXPECT_HELP_NAME]) {
- const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]);
-
- helper = __nf_conntrack_helper_find(helpname, nf_ct_l3num(ct),
- nf_ct_protonum(ct));
- if (helper == NULL)
- return -EOPNOTSUPP;
- }
-
exp = ctnetlink_alloc_expect((const struct nlattr * const *)cda, ct,
- helper, &tuple, &mask);
+ &tuple, &mask);
if (IS_ERR(exp))
return PTR_ERR(exp);
@@ -3528,11 +3517,11 @@ ctnetlink_parse_expect_nat(const struct nlattr *attr,
static struct nf_conntrack_expect *
ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
- struct nf_conntrack_helper *helper,
struct nf_conntrack_tuple *tuple,
struct nf_conntrack_tuple *mask)
{
struct net *net = read_pnet(&ct->ct_net);
+ struct nf_conntrack_helper *helper;
struct nf_conntrack_expect *exp;
struct nf_conn_help *help;
u32 class = 0;
@@ -3542,7 +3531,11 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
if (!help)
return ERR_PTR(-EOPNOTSUPP);
- if (cda[CTA_EXPECT_CLASS] && helper) {
+ helper = rcu_dereference(help->helper);
+ if (!helper)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ if (cda[CTA_EXPECT_CLASS]) {
class = ntohl(nla_get_be32(cda[CTA_EXPECT_CLASS]));
if (class > helper->expect_class_max)
return ERR_PTR(-EINVAL);
@@ -3576,8 +3569,6 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
#ifdef CONFIG_NF_CONNTRACK_ZONES
exp->zone = ct->zone;
#endif
- if (!helper)
- helper = rcu_dereference(help->helper);
rcu_assign_pointer(exp->helper, helper);
exp->tuple = *tuple;
exp->mask.src.u3 = mask->src.u3;
@@ -3609,7 +3600,6 @@ ctnetlink_create_expect(struct net *net,
{
struct nf_conntrack_tuple tuple, mask, master_tuple;
struct nf_conntrack_tuple_hash *h = NULL;
- struct nf_conntrack_helper *helper = NULL;
struct nf_conntrack_expect *exp;
struct nf_conn *ct;
int err;
@@ -3635,33 +3625,7 @@ ctnetlink_create_expect(struct net *net,
ct = nf_ct_tuplehash_to_ctrack(h);
rcu_read_lock();
- if (cda[CTA_EXPECT_HELP_NAME]) {
- const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]);
-
- helper = __nf_conntrack_helper_find(helpname, u3,
- nf_ct_protonum(ct));
- if (helper == NULL) {
- rcu_read_unlock();
-#ifdef CONFIG_MODULES
- if (request_module("nfct-helper-%s", helpname) < 0) {
- err = -EOPNOTSUPP;
- goto err_ct;
- }
- rcu_read_lock();
- helper = __nf_conntrack_helper_find(helpname, u3,
- nf_ct_protonum(ct));
- if (helper) {
- err = -EAGAIN;
- goto err_rcu;
- }
- rcu_read_unlock();
-#endif
- err = -EOPNOTSUPP;
- goto err_ct;
- }
- }
-
- exp = ctnetlink_alloc_expect(cda, ct, helper, &tuple, &mask);
+ exp = ctnetlink_alloc_expect(cda, ct, &tuple, &mask);
if (IS_ERR(exp)) {
err = PTR_ERR(exp);
goto err_rcu;
@@ -3671,8 +3635,8 @@ ctnetlink_create_expect(struct net *net,
nf_ct_expect_put(exp);
err_rcu:
rcu_read_unlock();
-err_ct:
nf_ct_put(ct);
+
return err;
}
--
2.47.3
^ permalink raw reply related
* [PATCH net 06/10] netfilter: ctnetlink: zero expect NAT fields when CTA_EXPECT_NAT absent
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
From: Qi Tang <tpluszz77@gmail.com>
ctnetlink_alloc_expect() allocates expectations from a non-zeroing
slab cache via nf_ct_expect_alloc(). When CTA_EXPECT_NAT is not
present in the netlink message, saved_addr and saved_proto are
never initialized. Stale data from a previous slab occupant can
then be dumped to userspace by ctnetlink_exp_dump_expect(), which
checks these fields to decide whether to emit CTA_EXPECT_NAT.
The safe sibling nf_ct_expect_init(), used by the packet path,
explicitly zeroes these fields.
Zero saved_addr, saved_proto and dir in the else branch, guarded
by IS_ENABLED(CONFIG_NF_NAT) since these fields only exist when
NAT is enabled.
Confirmed by priming the expect slab with NAT-bearing expectations,
freeing them, creating a new expectation without CTA_EXPECT_NAT,
and observing that the ctnetlink dump emits a spurious
CTA_EXPECT_NAT containing stale data from the prior allocation.
Fixes: 076a0ca02644 ("netfilter: ctnetlink: add NAT support for expectations")
Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Qi Tang <tpluszz77@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_conntrack_netlink.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index 3f408f3713bb..38bd7124d9f7 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -3588,6 +3588,12 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
exp, nf_ct_l3num(ct));
if (err < 0)
goto err_out;
+#if IS_ENABLED(CONFIG_NF_NAT)
+ } else {
+ memset(&exp->saved_addr, 0, sizeof(exp->saved_addr));
+ memset(&exp->saved_proto, 0, sizeof(exp->saved_proto));
+ exp->dir = 0;
+#endif
}
return exp;
err_out:
--
2.47.3
^ permalink raw reply related
* [PATCH net 05/10] netfilter: nf_conntrack_helper: pass helper to expect cleanup
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
From: Qi Tang <tpluszz77@gmail.com>
nf_conntrack_helper_unregister() calls nf_ct_expect_iterate_destroy()
to remove expectations belonging to the helper being unregistered.
However, it passes NULL instead of the helper pointer as the data
argument, so expect_iter_me() never matches any expectation and all
of them survive the cleanup.
After unregister returns, nfnl_cthelper_del() frees the helper
object immediately. Subsequent expectation dumps or packet-driven
init_conntrack() calls then dereference the freed exp->helper,
causing a use-after-free.
Pass the actual helper pointer so expectations referencing it are
properly destroyed before the helper object is freed.
BUG: KASAN: slab-use-after-free in string+0x38f/0x430
Read of size 1 at addr ffff888003b14d20 by task poc/103
Call Trace:
string+0x38f/0x430
vsnprintf+0x3cc/0x1170
seq_printf+0x17a/0x240
exp_seq_show+0x2e5/0x560
seq_read_iter+0x419/0x1280
proc_reg_read+0x1ac/0x270
vfs_read+0x179/0x930
ksys_read+0xef/0x1c0
Freed by task 103:
The buggy address is located 32 bytes inside of
freed 192-byte region [ffff888003b14d00, ffff888003b14dc0)
Fixes: ac7b84839003 ("netfilter: expect: add and use nf_ct_expect_iterate helpers")
Signed-off-by: Qi Tang <tpluszz77@gmail.com>
Reviewed-by: Phil Sutter <phil@nwl.cc>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_conntrack_helper.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c
index 1b330ba6613b..a715304a53d8 100644
--- a/net/netfilter/nf_conntrack_helper.c
+++ b/net/netfilter/nf_conntrack_helper.c
@@ -415,7 +415,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me)
*/
synchronize_rcu();
- nf_ct_expect_iterate_destroy(expect_iter_me, NULL);
+ nf_ct_expect_iterate_destroy(expect_iter_me, me);
nf_ct_iterate_destroy(unhelp, me);
/* nf_ct_iterate_destroy() does an unconditional synchronize_rcu() as
--
2.47.3
^ permalink raw reply related
* [PATCH net 04/10] netfilter: ipset: use nla_strcmp for IPSET_ATTR_NAME attr
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
From: Florian Westphal <fw@strlen.de>
IPSET_ATTR_NAME and IPSET_ATTR_NAMEREF are of NLA_STRING type, they
cannot be treated like a c-string.
They either have to be switched to NLA_NUL_STRING, or the compare
operations need to use the nla functions.
Fixes: f830837f0eed ("netfilter: ipset: list:set set type support")
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/linux/netfilter/ipset/ip_set.h | 2 +-
net/netfilter/ipset/ip_set_core.c | 4 ++--
net/netfilter/ipset/ip_set_list_set.c | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
index e9f4f845d760..b98331572ad2 100644
--- a/include/linux/netfilter/ipset/ip_set.h
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -309,7 +309,7 @@ enum {
/* register and unregister set references */
extern ip_set_id_t ip_set_get_byname(struct net *net,
- const char *name, struct ip_set **set);
+ const struct nlattr *name, struct ip_set **set);
extern void ip_set_put_byindex(struct net *net, ip_set_id_t index);
extern void ip_set_name_byindex(struct net *net, ip_set_id_t index, char *name);
extern ip_set_id_t ip_set_nfnl_get_byindex(struct net *net, ip_set_id_t index);
diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index a2fe711cb5e3..d0c9fe59c67d 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -821,7 +821,7 @@ EXPORT_SYMBOL_GPL(ip_set_del);
*
*/
ip_set_id_t
-ip_set_get_byname(struct net *net, const char *name, struct ip_set **set)
+ip_set_get_byname(struct net *net, const struct nlattr *name, struct ip_set **set)
{
ip_set_id_t i, index = IPSET_INVALID_ID;
struct ip_set *s;
@@ -830,7 +830,7 @@ ip_set_get_byname(struct net *net, const char *name, struct ip_set **set)
rcu_read_lock();
for (i = 0; i < inst->ip_set_max; i++) {
s = rcu_dereference(inst->ip_set_list)[i];
- if (s && STRNCMP(s->name, name)) {
+ if (s && nla_strcmp(name, s->name) == 0) {
__ip_set_get(s);
index = i;
*set = s;
diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c
index 98b91b34c314..1cef84f15e8c 100644
--- a/net/netfilter/ipset/ip_set_list_set.c
+++ b/net/netfilter/ipset/ip_set_list_set.c
@@ -367,7 +367,7 @@ list_set_uadt(struct ip_set *set, struct nlattr *tb[],
ret = ip_set_get_extensions(set, tb, &ext);
if (ret)
return ret;
- e.id = ip_set_get_byname(map->net, nla_data(tb[IPSET_ATTR_NAME]), &s);
+ e.id = ip_set_get_byname(map->net, tb[IPSET_ATTR_NAME], &s);
if (e.id == IPSET_INVALID_ID)
return -IPSET_ERR_NAME;
/* "Loop detection" */
@@ -389,7 +389,7 @@ list_set_uadt(struct ip_set *set, struct nlattr *tb[],
if (tb[IPSET_ATTR_NAMEREF]) {
e.refid = ip_set_get_byname(map->net,
- nla_data(tb[IPSET_ATTR_NAMEREF]),
+ tb[IPSET_ATTR_NAMEREF],
&s);
if (e.refid == IPSET_INVALID_ID) {
ret = -IPSET_ERR_NAMEREF;
--
2.47.3
^ permalink raw reply related
* [PATCH net 03/10] netfilter: x_tables: ensure names are nul-terminated
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
From: Florian Westphal <fw@strlen.de>
Reject names that lack a \0 character before feeding them
to functions that expect c-strings.
Fixes tag is the most recent commit that needs this change.
Fixes: c38c4597e4bf ("netfilter: implement xt_cgroup cgroup2 path match")
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/xt_cgroup.c | 6 ++++++
net/netfilter/xt_rateest.c | 5 +++++
2 files changed, 11 insertions(+)
diff --git a/net/netfilter/xt_cgroup.c b/net/netfilter/xt_cgroup.c
index c437fbd59ec1..43d2ae2be628 100644
--- a/net/netfilter/xt_cgroup.c
+++ b/net/netfilter/xt_cgroup.c
@@ -65,6 +65,9 @@ static int cgroup_mt_check_v1(const struct xt_mtchk_param *par)
info->priv = NULL;
if (info->has_path) {
+ if (strnlen(info->path, sizeof(info->path)) >= sizeof(info->path))
+ return -ENAMETOOLONG;
+
cgrp = cgroup_get_from_path(info->path);
if (IS_ERR(cgrp)) {
pr_info_ratelimited("invalid path, errno=%ld\n",
@@ -102,6 +105,9 @@ static int cgroup_mt_check_v2(const struct xt_mtchk_param *par)
info->priv = NULL;
if (info->has_path) {
+ if (strnlen(info->path, sizeof(info->path)) >= sizeof(info->path))
+ return -ENAMETOOLONG;
+
cgrp = cgroup_get_from_path(info->path);
if (IS_ERR(cgrp)) {
pr_info_ratelimited("invalid path, errno=%ld\n",
diff --git a/net/netfilter/xt_rateest.c b/net/netfilter/xt_rateest.c
index 72324bd976af..b1d736c15fcb 100644
--- a/net/netfilter/xt_rateest.c
+++ b/net/netfilter/xt_rateest.c
@@ -91,6 +91,11 @@ static int xt_rateest_mt_checkentry(const struct xt_mtchk_param *par)
goto err1;
}
+ if (strnlen(info->name1, sizeof(info->name1)) >= sizeof(info->name1))
+ return -ENAMETOOLONG;
+ if (strnlen(info->name2, sizeof(info->name2)) >= sizeof(info->name2))
+ return -ENAMETOOLONG;
+
ret = -ENOENT;
est1 = xt_rateest_lookup(par->net, info->name1);
if (!est1)
--
2.47.3
^ permalink raw reply related
* [PATCH net 02/10] netfilter: nfnetlink_log: account for netlink header size
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
From: Florian Westphal <fw@strlen.de>
This is a followup to an old bug fix: NLMSG_DONE needs to account
for the netlink header size, not just the attribute size.
This can result in a WARN splat + drop of the netlink message,
but other than this there are no ill effects.
Fixes: 9dfa1dfe4d5e ("netfilter: nf_log: account for size of NLMSG_DONE attribute")
Reported-by: Yiming Qian <yimingqian591@gmail.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nfnetlink_log.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c
index fcbe54940b2e..f80978c06fa0 100644
--- a/net/netfilter/nfnetlink_log.c
+++ b/net/netfilter/nfnetlink_log.c
@@ -726,7 +726,7 @@ nfulnl_log_packet(struct net *net,
+ nla_total_size(plen) /* prefix */
+ nla_total_size(sizeof(struct nfulnl_msg_packet_hw))
+ nla_total_size(sizeof(struct nfulnl_msg_packet_timestamp))
- + nla_total_size(sizeof(struct nfgenmsg)); /* NLMSG_DONE */
+ + nlmsg_total_size(sizeof(struct nfgenmsg)); /* NLMSG_DONE */
if (in && skb_mac_header_was_set(skb)) {
size += nla_total_size(skb->dev->hard_header_len)
--
2.47.3
^ permalink raw reply related
* [PATCH net 01/10] netfilter: flowtable: strictly check for maximum number of actions
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
In-Reply-To: <20260401103646.1015423-1-pablo@netfilter.org>
The maximum number of flowtable hardware offload actions in IPv6 is:
* ethernet mangling (4 payload actions, 2 for each ethernet address)
* SNAT (4 payload actions)
* DNAT (4 payload actions)
* Double VLAN (4 vlan actions, 2 for popping vlan, and 2 for pushing)
for QinQ.
* Redirect (1 action)
Which makes 17, while the maximum is 16. But act_ct supports for tunnels
actions too. Note that payload action operates at 32-bit word level, so
mangling an IPv6 address takes 4 payload actions.
Update flow_action_entry_next() calls to check for the maximum number of
supported actions.
While at it, rise the maximum number of actions per flow from 16 to 24
so this works fine with IPv6 setups.
Fixes: c29f74e0df7a ("netfilter: nf_flow_table: hardware offload support")
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_flow_table_offload.c | 196 +++++++++++++++++---------
1 file changed, 130 insertions(+), 66 deletions(-)
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index 9b677e116487..93d0aa7f8fcc 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -14,6 +14,8 @@
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_tuple.h>
+#define NF_FLOW_RULE_ACTION_MAX 24
+
static struct workqueue_struct *nf_flow_offload_add_wq;
static struct workqueue_struct *nf_flow_offload_del_wq;
static struct workqueue_struct *nf_flow_offload_stats_wq;
@@ -216,7 +218,12 @@ static void flow_offload_mangle(struct flow_action_entry *entry,
static inline struct flow_action_entry *
flow_action_entry_next(struct nf_flow_rule *flow_rule)
{
- int i = flow_rule->rule->action.num_entries++;
+ int i;
+
+ if (unlikely(flow_rule->rule->action.num_entries >= NF_FLOW_RULE_ACTION_MAX))
+ return NULL;
+
+ i = flow_rule->rule->action.num_entries++;
return &flow_rule->rule->action.entries[i];
}
@@ -234,6 +241,9 @@ static int flow_offload_eth_src(struct net *net,
u32 mask, val;
u16 val16;
+ if (!entry0 || !entry1)
+ return -E2BIG;
+
this_tuple = &flow->tuplehash[dir].tuple;
switch (this_tuple->xmit_type) {
@@ -284,6 +294,9 @@ static int flow_offload_eth_dst(struct net *net,
u8 nud_state;
u16 val16;
+ if (!entry0 || !entry1)
+ return -E2BIG;
+
this_tuple = &flow->tuplehash[dir].tuple;
switch (this_tuple->xmit_type) {
@@ -325,16 +338,19 @@ static int flow_offload_eth_dst(struct net *net,
return 0;
}
-static void flow_offload_ipv4_snat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv4_snat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask = ~htonl(0xffffffff);
__be32 addr;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v4.s_addr;
@@ -345,23 +361,27 @@ static void flow_offload_ipv4_snat(struct net *net,
offset = offsetof(struct iphdr, daddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP4, offset,
&addr, &mask);
+ return 0;
}
-static void flow_offload_ipv4_dnat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv4_dnat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask = ~htonl(0xffffffff);
__be32 addr;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v4.s_addr;
@@ -372,14 +392,15 @@ static void flow_offload_ipv4_dnat(struct net *net,
offset = offsetof(struct iphdr, saddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP4, offset,
&addr, &mask);
+ return 0;
}
-static void flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
+static int flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
unsigned int offset,
const __be32 *addr, const __be32 *mask)
{
@@ -388,15 +409,20 @@ static void flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
for (i = 0; i < sizeof(struct in6_addr) / sizeof(u32); i++) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
+
flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP6,
offset + i * sizeof(u32), &addr[i], mask);
}
+
+ return 0;
}
-static void flow_offload_ipv6_snat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv6_snat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
u32 mask = ~htonl(0xffffffff);
const __be32 *addr;
@@ -412,16 +438,16 @@ static void flow_offload_ipv6_snat(struct net *net,
offset = offsetof(struct ipv6hdr, daddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
- flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
+ return flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
}
-static void flow_offload_ipv6_dnat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv6_dnat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
u32 mask = ~htonl(0xffffffff);
const __be32 *addr;
@@ -437,10 +463,10 @@ static void flow_offload_ipv6_dnat(struct net *net,
offset = offsetof(struct ipv6hdr, saddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
- flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
+ return flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
}
static int flow_offload_l4proto(const struct flow_offload *flow)
@@ -462,15 +488,18 @@ static int flow_offload_l4proto(const struct flow_offload *flow)
return type;
}
-static void flow_offload_port_snat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_port_snat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask, port;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
port = ntohs(flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_port);
@@ -485,22 +514,26 @@ static void flow_offload_port_snat(struct net *net,
mask = ~htonl(0xffff);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, flow_offload_l4proto(flow), offset,
&port, &mask);
+ return 0;
}
-static void flow_offload_port_dnat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_port_dnat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask, port;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
port = ntohs(flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_port);
@@ -515,20 +548,24 @@ static void flow_offload_port_dnat(struct net *net,
mask = ~htonl(0xffff0000);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, flow_offload_l4proto(flow), offset,
&port, &mask);
+ return 0;
}
-static void flow_offload_ipv4_checksum(struct net *net,
- const struct flow_offload *flow,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv4_checksum(struct net *net,
+ const struct flow_offload *flow,
+ struct nf_flow_rule *flow_rule)
{
u8 protonum = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l4proto;
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
+
entry->id = FLOW_ACTION_CSUM;
entry->csum_flags = TCA_CSUM_UPDATE_FLAG_IPV4HDR;
@@ -540,12 +577,14 @@ static void flow_offload_ipv4_checksum(struct net *net,
entry->csum_flags |= TCA_CSUM_UPDATE_FLAG_UDP;
break;
}
+
+ return 0;
}
-static void flow_offload_redirect(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_redirect(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
const struct flow_offload_tuple *this_tuple, *other_tuple;
struct flow_action_entry *entry;
@@ -563,21 +602,28 @@ static void flow_offload_redirect(struct net *net,
ifindex = other_tuple->iifidx;
break;
default:
- return;
+ return -EOPNOTSUPP;
}
dev = dev_get_by_index(net, ifindex);
if (!dev)
- return;
+ return -ENODEV;
entry = flow_action_entry_next(flow_rule);
+ if (!entry) {
+ dev_put(dev);
+ return -E2BIG;
+ }
+
entry->id = FLOW_ACTION_REDIRECT;
entry->dev = dev;
+
+ return 0;
}
-static void flow_offload_encap_tunnel(const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_encap_tunnel(const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
const struct flow_offload_tuple *this_tuple;
struct flow_action_entry *entry;
@@ -585,7 +631,7 @@ static void flow_offload_encap_tunnel(const struct flow_offload *flow,
this_tuple = &flow->tuplehash[dir].tuple;
if (this_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
- return;
+ return 0;
dst = this_tuple->dst_cache;
if (dst && dst->lwtstate) {
@@ -594,15 +640,19 @@ static void flow_offload_encap_tunnel(const struct flow_offload *flow,
tun_info = lwt_tun_info(dst->lwtstate);
if (tun_info && (tun_info->mode & IP_TUNNEL_INFO_TX)) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
entry->id = FLOW_ACTION_TUNNEL_ENCAP;
entry->tunnel = tun_info;
}
}
+
+ return 0;
}
-static void flow_offload_decap_tunnel(const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_decap_tunnel(const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
const struct flow_offload_tuple *other_tuple;
struct flow_action_entry *entry;
@@ -610,7 +660,7 @@ static void flow_offload_decap_tunnel(const struct flow_offload *flow,
other_tuple = &flow->tuplehash[!dir].tuple;
if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
- return;
+ return 0;
dst = other_tuple->dst_cache;
if (dst && dst->lwtstate) {
@@ -619,9 +669,13 @@ static void flow_offload_decap_tunnel(const struct flow_offload *flow,
tun_info = lwt_tun_info(dst->lwtstate);
if (tun_info && (tun_info->mode & IP_TUNNEL_INFO_TX)) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
entry->id = FLOW_ACTION_TUNNEL_DECAP;
}
}
+
+ return 0;
}
static int
@@ -633,8 +687,9 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
const struct flow_offload_tuple *tuple;
int i;
- flow_offload_decap_tunnel(flow, dir, flow_rule);
- flow_offload_encap_tunnel(flow, dir, flow_rule);
+ if (flow_offload_decap_tunnel(flow, dir, flow_rule) < 0 ||
+ flow_offload_encap_tunnel(flow, dir, flow_rule) < 0)
+ return -1;
if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
@@ -650,6 +705,8 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
if (tuple->encap[i].proto == htons(ETH_P_8021Q)) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -1;
entry->id = FLOW_ACTION_VLAN_POP;
}
}
@@ -663,6 +720,8 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
continue;
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -1;
switch (other_tuple->encap[i].proto) {
case htons(ETH_P_PPP_SES):
@@ -688,18 +747,22 @@ int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
return -1;
if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
- flow_offload_ipv4_snat(net, flow, dir, flow_rule);
- flow_offload_port_snat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv4_snat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_snat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
if (test_bit(NF_FLOW_DNAT, &flow->flags)) {
- flow_offload_ipv4_dnat(net, flow, dir, flow_rule);
- flow_offload_port_dnat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv4_dnat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_dnat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
if (test_bit(NF_FLOW_SNAT, &flow->flags) ||
test_bit(NF_FLOW_DNAT, &flow->flags))
- flow_offload_ipv4_checksum(net, flow, flow_rule);
+ if (flow_offload_ipv4_checksum(net, flow, flow_rule) < 0)
+ return -1;
- flow_offload_redirect(net, flow, dir, flow_rule);
+ if (flow_offload_redirect(net, flow, dir, flow_rule) < 0)
+ return -1;
return 0;
}
@@ -713,22 +776,23 @@ int nf_flow_rule_route_ipv6(struct net *net, struct flow_offload *flow,
return -1;
if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
- flow_offload_ipv6_snat(net, flow, dir, flow_rule);
- flow_offload_port_snat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv6_snat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_snat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
if (test_bit(NF_FLOW_DNAT, &flow->flags)) {
- flow_offload_ipv6_dnat(net, flow, dir, flow_rule);
- flow_offload_port_dnat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv6_dnat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_dnat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
- flow_offload_redirect(net, flow, dir, flow_rule);
+ if (flow_offload_redirect(net, flow, dir, flow_rule) < 0)
+ return -1;
return 0;
}
EXPORT_SYMBOL_GPL(nf_flow_rule_route_ipv6);
-#define NF_FLOW_RULE_ACTION_MAX 16
-
static struct nf_flow_rule *
nf_flow_offload_rule_alloc(struct net *net,
const struct flow_offload_work *offload,
--
2.47.3
^ permalink raw reply related
* [PATCH net 00/10] Netfilter fixes for net
From: Pablo Neira Ayuso @ 2026-04-01 10:36 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
Hi,
The following patchset contains Netfilter fixes for net. Note that most
of the bugs fixed here are >5 years old. The large PR is not due to an
increase in regressions.
1) Flowtable hardware offload support in IPv6 can lead to out-of-bounds
when populating the rule action array when combined with double-tagged
vlan. Bump the maximum number of actions from 16 to 24 and check that
such limit is never reached, otherwise bail out. This bugs stems from
the original flowtable hardware offload support.
2) nfnetlink_log does not include the netlink header size of the trailing
NLMSG_DONE message when calculating the skb size. From Florian Westphal.
3) Reject names in xt_cgroup and xt_rateest extensions which are not
nul-terminated. Also from Florian.
4) Use nla_strcmp in ipset lookup by set name, since IPSET_ATTR_NAME and
IPSET_ATTR_NAMEREF are of NLA_STRING type. From Florian Westphal.
5) When unregistering conntrack helpers, pass the helper that is going
away so the expectation cleanup is done accordingly, otherwise UaF is
possible when accessing expectation that refer to the helper that is
gone. From Qi Tang.
6) Zero expectation NAT fields to address leaking kernel memory through
the expectation netlink dump when unset. Also from Qi Tang.
7) Use the master conntrack helper when creating expectations via
ctnetlink, ignore the suggested helper through CTA_EXPECT_HELP_NAME.
This allows to address a possible read of kernel memory off the
expectation object boundary.
8) Fix incorrect release of the hash bucket logic in ipset when the
bucket is empty, leading to shrinking the hash bucket to size 0
which deals to out-of-bound write in next element additions.
From Yifan Wu.
9) Allow the use of x_tables extensions that explicitly declare
NFPROTO_ARP support only. This is to avoid an incorrect hook number
validation due to non-overlapping arp and inet hook number
definitions.
10) Reject immediate NF_QUEUE verdict in nf_tables. The userspace
nft tool always uses the nft_queue expression for queueing.
This ensures this verdict cannot be used for the arp family,
which does supported this.
Please, pull these changes from:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-04-01
Thanks.
----------------------------------------------------------------
The following changes since commit dc9e9d61e301c087bcd990dbf2fa18ad3e2e1429:
Merge branch 'net-enetc-add-more-checks-to-enetc_set_rxfh' (2026-03-27 20:56:49 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git tags/nf-26-04-01
for you to fetch changes up to da107398cbd4bbdb6bffecb2ce86d5c9384f4cec:
netfilter: nf_tables: reject immediate NF_QUEUE verdict (2026-04-01 11:55:30 +0200)
----------------------------------------------------------------
netfilter pull request 26-04-01
----------------------------------------------------------------
Florian Westphal (3):
netfilter: nfnetlink_log: account for netlink header size
netfilter: x_tables: ensure names are nul-terminated
netfilter: ipset: use nla_strcmp for IPSET_ATTR_NAME attr
Pablo Neira Ayuso (4):
netfilter: flowtable: strictly check for maximum number of actions
netfilter: ctnetlink: ignore explicit helper on new expectations
netfilter: x_tables: restrict xt_check_match/xt_check_target extensions for NFPROTO_ARP
netfilter: nf_tables: reject immediate NF_QUEUE verdict
Qi Tang (2):
netfilter: nf_conntrack_helper: pass helper to expect cleanup
netfilter: ctnetlink: zero expect NAT fields when CTA_EXPECT_NAT absent
Yifan Wu (1):
netfilter: ipset: drop logically empty buckets in mtype_del
include/linux/netfilter/ipset/ip_set.h | 2 +-
net/netfilter/ipset/ip_set_core.c | 4 +-
net/netfilter/ipset/ip_set_hash_gen.h | 2 +-
net/netfilter/ipset/ip_set_list_set.c | 4 +-
net/netfilter/nf_conntrack_helper.c | 2 +-
net/netfilter/nf_conntrack_netlink.c | 60 +++-------
net/netfilter/nf_flow_table_offload.c | 196 ++++++++++++++++++++++-----------
net/netfilter/nf_tables_api.c | 7 +-
net/netfilter/nfnetlink_log.c | 2 +-
net/netfilter/x_tables.c | 23 ++++
net/netfilter/xt_cgroup.c | 6 +
net/netfilter/xt_rateest.c | 5 +
12 files changed, 192 insertions(+), 121 deletions(-)
^ permalink raw reply
* [PATCH V4 2/3] dt-bindings: net: Fix Tegra234 MGBE PTP clock
From: Jon Hunter @ 2026-04-01 10:29 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Thierry Reding
Cc: netdev, devicetree, linux-tegra, Jon Hunter, Krzysztof Kozlowski
In-Reply-To: <20260401102941.17466-1-jonathanh@nvidia.com>
The PTP clock for the Tegra234 MGBE device is incorrectly named
'ptp-ref' and should be 'ptp_ref'. This is causing the following
warning to be observed on Tegra234 platforms that use this device:
ERR KERN tegra-mgbe 6800000.ethernet eth0: Invalid PTP clock rate
WARNING KERN tegra-mgbe 6800000.ethernet eth0: PTP init failed
Although this constitutes an ABI breakage in the binding for this
device, PTP support has clearly never worked and so fix this now
so we can correct the device-tree for this device. Note that the
MGBE driver still supports the legacy 'ptp-ref' clock name and so
older/existing device-trees will still work, but given that this
is not the correct name, there is no point to advertise this in the
binding.
Fixes: 189c2e5c7669 ("dt-bindings: net: Add Tegra234 MGBE")
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../devicetree/bindings/net/nvidia,tegra234-mgbe.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/net/nvidia,tegra234-mgbe.yaml b/Documentation/devicetree/bindings/net/nvidia,tegra234-mgbe.yaml
index 2bd3efff2485..215f14d1897d 100644
--- a/Documentation/devicetree/bindings/net/nvidia,tegra234-mgbe.yaml
+++ b/Documentation/devicetree/bindings/net/nvidia,tegra234-mgbe.yaml
@@ -42,7 +42,7 @@ properties:
- const: mgbe
- const: mac
- const: mac-divider
- - const: ptp-ref
+ - const: ptp_ref
- const: rx-input-m
- const: rx-input
- const: tx
@@ -133,7 +133,7 @@ examples:
<&bpmp TEGRA234_CLK_MGBE0_RX_PCS_M>,
<&bpmp TEGRA234_CLK_MGBE0_RX_PCS>,
<&bpmp TEGRA234_CLK_MGBE0_TX_PCS>;
- clock-names = "mgbe", "mac", "mac-divider", "ptp-ref", "rx-input-m",
+ clock-names = "mgbe", "mac", "mac-divider", "ptp_ref", "rx-input-m",
"rx-input", "tx", "eee-pcs", "rx-pcs-input", "rx-pcs-m",
"rx-pcs", "tx-pcs";
resets = <&bpmp TEGRA234_RESET_MGBE0_MAC>,
--
2.43.0
^ permalink raw reply related
* [PATCH V4 3/3] arm64: tegra: Fix Tegra234 MGBE PTP clock
From: Jon Hunter @ 2026-04-01 10:29 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Thierry Reding
Cc: netdev, devicetree, linux-tegra, Jon Hunter, Krzysztof Kozlowski
In-Reply-To: <20260401102941.17466-1-jonathanh@nvidia.com>
The Tegra MGBE PTP clock is incorrectly named as 'ptp-ref' and not
'ptp_ref' and this causing the initialisation of the PTP clock to fail.
The device-tree binding doc for the device and the Tegra MGBE driver
have been updated to use the correct name and so update the device-tree
for Tegra234 as well.
Fixes: 610cdf3186bc ("arm64: tegra: Add MGBE nodes on Tegra234")
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
arch/arm64/boot/dts/nvidia/tegra234.dtsi | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
index 04a95b6658ca..18220cdac9f9 100644
--- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
@@ -3605,7 +3605,7 @@ ethernet@6800000 {
<&bpmp TEGRA234_CLK_MGBE0_RX_PCS_M>,
<&bpmp TEGRA234_CLK_MGBE0_RX_PCS>,
<&bpmp TEGRA234_CLK_MGBE0_TX_PCS>;
- clock-names = "mgbe", "mac", "mac-divider", "ptp-ref", "rx-input-m",
+ clock-names = "mgbe", "mac", "mac-divider", "ptp_ref", "rx-input-m",
"rx-input", "tx", "eee-pcs", "rx-pcs-input", "rx-pcs-m",
"rx-pcs", "tx-pcs";
resets = <&bpmp TEGRA234_RESET_MGBE0_MAC>,
@@ -3647,7 +3647,7 @@ ethernet@6900000 {
<&bpmp TEGRA234_CLK_MGBE1_RX_PCS_M>,
<&bpmp TEGRA234_CLK_MGBE1_RX_PCS>,
<&bpmp TEGRA234_CLK_MGBE1_TX_PCS>;
- clock-names = "mgbe", "mac", "mac-divider", "ptp-ref", "rx-input-m",
+ clock-names = "mgbe", "mac", "mac-divider", "ptp_ref", "rx-input-m",
"rx-input", "tx", "eee-pcs", "rx-pcs-input", "rx-pcs-m",
"rx-pcs", "tx-pcs";
resets = <&bpmp TEGRA234_RESET_MGBE1_MAC>,
@@ -3689,7 +3689,7 @@ ethernet@6a00000 {
<&bpmp TEGRA234_CLK_MGBE2_RX_PCS_M>,
<&bpmp TEGRA234_CLK_MGBE2_RX_PCS>,
<&bpmp TEGRA234_CLK_MGBE2_TX_PCS>;
- clock-names = "mgbe", "mac", "mac-divider", "ptp-ref", "rx-input-m",
+ clock-names = "mgbe", "mac", "mac-divider", "ptp_ref", "rx-input-m",
"rx-input", "tx", "eee-pcs", "rx-pcs-input", "rx-pcs-m",
"rx-pcs", "tx-pcs";
resets = <&bpmp TEGRA234_RESET_MGBE2_MAC>,
@@ -3731,7 +3731,7 @@ ethernet@6b00000 {
<&bpmp TEGRA234_CLK_MGBE3_RX_PCS_M>,
<&bpmp TEGRA234_CLK_MGBE3_RX_PCS>,
<&bpmp TEGRA234_CLK_MGBE3_TX_PCS>;
- clock-names = "mgbe", "mac", "mac-divider", "ptp-ref", "rx-input-m",
+ clock-names = "mgbe", "mac", "mac-divider", "ptp_ref", "rx-input-m",
"rx-input", "tx", "eee-pcs", "rx-pcs-input", "rx-pcs-m",
"rx-pcs", "tx-pcs";
resets = <&bpmp TEGRA234_RESET_MGBE3_MAC>,
--
2.43.0
^ permalink raw reply related
* [PATCH V4 0/3] net: stmmac: Fix Tegra234 MGBE clock
From: Jon Hunter @ 2026-04-01 10:29 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Thierry Reding
Cc: netdev, devicetree, linux-tegra, Jon Hunter
The name of the PTP ref clock for the Tegra234 MGBE ethernet controller
does not match the generic name in the stmmac platform driver. Despite
this basic ethernet is functional on the Tegra234 platforms that use
this driver and as far as I know, we have not tested PTP support with
this driver. Hence, the risk of breaking any functionality is low.
The previous attempt to fix this in the stmmac platform driver, by
supporting the Tegra234 PTP clock name, was rejected [0]. The preference
from the netdev maintainers is to fix this in the DT binding for
Tegra234.
This series fixes this by correcting the device-tree binding to align
with the generic name for the PTP clock. I understand that this is
breaking the ABI for this device, which we should never do, but this
is a last resort for getting this fixed. I am open to any better ideas
to fix this. Please note that we still maintain backward compatibility
in the driver to allow older device-trees to work, but we don't
advertise this via the binding, because I did not see any value in doing
so.
Changes since V3:
- Added Krzysztof's reviewed-by for device-tree changes.
- Re-worked logic in MGBE driver for detecting the legacy clock to make
it more explicit and clear.
Changes since V2:
- Corrected example in dt-binding doc.
Changes since V1:
- Moved handling of different PTP clock names into Tegra234 MGBE driver.
- Add changes to update the Tegra234 MGBE DT binding and DT source.
[0] https://lore.kernel.org/linux-tegra/20250612062032.293275-1-jonathanh@nvidia.com/
Jon Hunter (3):
net: stmmac: Fix PTP ref clock for Tegra234
dt-bindings: net: Fix Tegra234 MGBE PTP clock
arm64: tegra: Fix Tegra234 MGBE PTP clock
.../bindings/net/nvidia,tegra234-mgbe.yaml | 4 ++--
arch/arm64/boot/dts/nvidia/tegra234.dtsi | 8 ++++----
.../net/ethernet/stmicro/stmmac/dwmac-tegra.c | 20 +++++++++++++++++--
3 files changed, 24 insertions(+), 8 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH V4 1/3] net: stmmac: Fix PTP ref clock for Tegra234
From: Jon Hunter @ 2026-04-01 10:29 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Thierry Reding
Cc: netdev, devicetree, linux-tegra, Jon Hunter
In-Reply-To: <20260401102941.17466-1-jonathanh@nvidia.com>
Since commit 030ce919e114 ("net: stmmac: make sure that ptp_rate is not
0 before configuring timestamping") was added the following error is
observed on Tegra234:
ERR KERN tegra-mgbe 6800000.ethernet eth0: Invalid PTP clock rate
WARNING KERN tegra-mgbe 6800000.ethernet eth0: PTP init failed
It turns out that the Tegra234 device-tree binding defines the PTP ref
clock name as 'ptp-ref' and not 'ptp_ref' and the above commit now
exposes this and that the PTP clock is not configured correctly.
In order to update device-tree to use the correct 'ptp_ref' name, update
the Tegra MGBE driver to use 'ptp_ref' by default and fallback to using
'ptp-ref' if this clock name is present.
Fixes: d8ca113724e7 ("net: stmmac: tegra: Add MGBE support")
Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
---
.../net/ethernet/stmicro/stmmac/dwmac-tegra.c | 20 +++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
index b4b39e6a169e..3af2f001fada 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c
@@ -9,7 +9,7 @@
#include "stmmac_platform.h"
static const char *const mgbe_clks[] = {
- "rx-pcs", "tx", "tx-pcs", "mac-divider", "mac", "mgbe", "ptp-ref", "mac"
+ "rx-pcs", "tx", "tx-pcs", "mac-divider", "mac", "mgbe", "ptp_ref", "mac"
};
struct tegra_mgbe {
@@ -216,6 +216,7 @@ static int tegra_mgbe_probe(struct platform_device *pdev)
struct plat_stmmacenet_data *plat;
struct stmmac_resources res;
struct tegra_mgbe *mgbe;
+ bool use_legacy_ptp = false;
int irq, err, i;
u32 value;
@@ -257,9 +258,24 @@ static int tegra_mgbe_probe(struct platform_device *pdev)
if (!mgbe->clks)
return -ENOMEM;
- for (i = 0; i < ARRAY_SIZE(mgbe_clks); i++)
+ /*
+ * Older device-trees use 'ptp-ref' rather than 'ptp_ref'.
+ * Fall back when the legacy name is present.
+ */
+ if (of_property_match_string(pdev->dev.of_node, "clock-names",
+ "ptp-ref") >= 0)
+ use_legacy_ptp = true;
+
+ for (i = 0; i < ARRAY_SIZE(mgbe_clks); i++) {
mgbe->clks[i].id = mgbe_clks[i];
+ if (use_legacy_ptp && !strcmp(mgbe_clks[i], "ptp_ref")) {
+ dev_warn(mgbe->dev,
+ "Device-tree update needed for PTP clock!\n");
+ mgbe->clks[i].id = "ptp-ref";
+ }
+ }
+
err = devm_clk_bulk_get(mgbe->dev, ARRAY_SIZE(mgbe_clks), mgbe->clks);
if (err < 0)
return err;
--
2.43.0
^ permalink raw reply related
* [PATCH 4/4] RDMA/ionic: add completion timestamp to CQE format
From: Abhijit Gangurde @ 2026-04-01 10:25 UTC (permalink / raw)
To: jgg, leon, brett.creeley, andrew+netdev, davem, edumazet, kuba,
pabeni
Cc: allen.hubbe, nikhil.agarwal, linux-rdma, netdev, linux-kernel,
Abhijit Gangurde
In-Reply-To: <20260401102501.3395305-1-abhijit.gangurde@amd.com>
Update the CQE structure to include hardware timestamp.
When firmware supports RDMA completion timestamps,
the hardware populates the timestamp field.
Co-developed-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
---
drivers/infiniband/hw/ionic/ionic_datapath.c | 43 ++++++++++----------
drivers/infiniband/hw/ionic/ionic_fw.h | 12 ++++--
2 files changed, 31 insertions(+), 24 deletions(-)
diff --git a/drivers/infiniband/hw/ionic/ionic_datapath.c b/drivers/infiniband/hw/ionic/ionic_datapath.c
index aa2944887f23..3e2300f7ea10 100644
--- a/drivers/infiniband/hw/ionic/ionic_datapath.c
+++ b/drivers/infiniband/hw/ionic/ionic_datapath.c
@@ -32,6 +32,7 @@ static int ionic_flush_recv(struct ionic_qp *qp, struct ib_wc *wc)
{
struct ionic_rq_meta *meta;
struct ionic_v1_wqe *wqe;
+ u64 wqe_idx;
if (!qp->rq_flush)
return 0;
@@ -40,21 +41,22 @@ static int ionic_flush_recv(struct ionic_qp *qp, struct ib_wc *wc)
return 0;
wqe = ionic_queue_at_cons(&qp->rq);
+ wqe_idx = le64_to_cpu(wqe->base.wqe_idx);
- /* wqe_id must be a valid queue index */
- if (unlikely(wqe->base.wqe_id >> qp->rq.depth_log2)) {
+ /* wqe_idx must be a valid queue index */
+ if (unlikely(wqe_idx >> qp->rq.depth_log2)) {
ibdev_warn(qp->ibqp.device,
"flush qp %u recv index %llu invalid\n",
- qp->qpid, (unsigned long long)wqe->base.wqe_id);
+ qp->qpid, (unsigned long long)wqe_idx);
return -EIO;
}
- /* wqe_id must indicate a request that is outstanding */
- meta = &qp->rq_meta[wqe->base.wqe_id];
+ /* wqe_idx must indicate a request that is outstanding */
+ meta = &qp->rq_meta[wqe_idx];
if (unlikely(meta->next != IONIC_META_POSTED)) {
ibdev_warn(qp->ibqp.device,
"flush qp %u recv index %llu not posted\n",
- qp->qpid, (unsigned long long)wqe->base.wqe_id);
+ qp->qpid, (unsigned long long)wqe_idx);
return -EIO;
}
@@ -133,8 +135,8 @@ static int ionic_poll_recv(struct ionic_ibdev *dev, struct ionic_cq *cq,
{
struct ionic_qp *qp = NULL;
struct ionic_rq_meta *meta;
+ u16 vlan_tag, wqe_idx;
u32 src_qpn, st_len;
- u16 vlan_tag;
u8 op;
if (cqe_qp->rq_flush)
@@ -144,7 +146,7 @@ static int ionic_poll_recv(struct ionic_ibdev *dev, struct ionic_cq *cq,
st_len = be32_to_cpu(cqe->status_length);
- /* ignore wqe_id in case of flush error */
+ /* ignore wqe_idx in case of flush error */
if (ionic_v1_cqe_error(cqe) && st_len == IONIC_STS_WQE_FLUSHED_ERR) {
cqe_qp->rq_flush = true;
cq->flush = true;
@@ -160,20 +162,19 @@ static int ionic_poll_recv(struct ionic_ibdev *dev, struct ionic_cq *cq,
return -EIO;
}
- /* wqe_id must be a valid queue index */
- if (unlikely(cqe->recv.wqe_id >> qp->rq.depth_log2)) {
+ wqe_idx = le64_to_cpu(cqe->recv.wqe_idx_timestamp) & IONIC_V1_CQE_WQE_IDX_MASK;
+ /* wqe_idx must be a valid queue index */
+ if (unlikely(wqe_idx >> qp->rq.depth_log2)) {
ibdev_warn(&dev->ibdev,
- "qp %u recv index %llu invalid\n",
- qp->qpid, (unsigned long long)cqe->recv.wqe_id);
+ "qp %u recv index %u invalid\n", qp->qpid, wqe_idx);
return -EIO;
}
- /* wqe_id must indicate a request that is outstanding */
- meta = &qp->rq_meta[cqe->recv.wqe_id];
+ /* wqe_idx must indicate a request that is outstanding */
+ meta = &qp->rq_meta[wqe_idx];
if (unlikely(meta->next != IONIC_META_POSTED)) {
ibdev_warn(&dev->ibdev,
- "qp %u recv index %llu not posted\n",
- qp->qpid, (unsigned long long)cqe->recv.wqe_id);
+ "qp %u recv index %u not posted\n", qp->qpid, wqe_idx);
return -EIO;
}
@@ -408,7 +409,7 @@ static int ionic_comp_msn(struct ionic_qp *qp, struct ionic_v1_cqe *cqe)
static int ionic_comp_npg(struct ionic_qp *qp, struct ionic_v1_cqe *cqe)
{
struct ionic_sq_meta *meta;
- u16 cqe_idx;
+ u16 wqe_idx;
u32 st_len;
if (qp->sq_flush)
@@ -430,8 +431,8 @@ static int ionic_comp_npg(struct ionic_qp *qp, struct ionic_v1_cqe *cqe)
return 0;
}
- cqe_idx = cqe->send.npg_wqe_id & qp->sq.mask;
- meta = &qp->sq_meta[cqe_idx];
+ wqe_idx = le64_to_cpu(cqe->send.npg_wqe_idx_timestamp) & qp->sq.mask;
+ meta = &qp->sq_meta[wqe_idx];
meta->local_comp = true;
if (ionic_v1_cqe_error(cqe)) {
@@ -811,7 +812,7 @@ static void ionic_prep_base(struct ionic_qp *qp,
meta->signal = false;
meta->local_comp = false;
- wqe->base.wqe_id = qp->sq.prod;
+ wqe->base.wqe_idx = cpu_to_le64(qp->sq.prod);
if (wr->send_flags & IB_SEND_FENCE)
wqe->base.flags |= cpu_to_be16(IONIC_V1_FLAG_FENCE);
@@ -1205,7 +1206,7 @@ static int ionic_prep_recv(struct ionic_qp *qp,
meta->wrid = wr->wr_id;
- wqe->base.wqe_id = meta - qp->rq_meta;
+ wqe->base.wqe_idx = cpu_to_le64(meta - qp->rq_meta);
wqe->base.num_sge_key = wr->num_sge;
/* total length for recv goes in base imm_data_key */
diff --git a/drivers/infiniband/hw/ionic/ionic_fw.h b/drivers/infiniband/hw/ionic/ionic_fw.h
index adfbb89d856c..ee23062a1762 100644
--- a/drivers/infiniband/hw/ionic/ionic_fw.h
+++ b/drivers/infiniband/hw/ionic/ionic_fw.h
@@ -332,7 +332,7 @@ struct ionic_v1_cqe {
__le16 old_rq_cq_cindex;
} admin;
struct {
- __u64 wqe_id;
+ __le64 wqe_idx_timestamp;
__be32 src_qpn_op;
__u8 src_mac[6];
__be16 vlan_tag;
@@ -342,13 +342,19 @@ struct ionic_v1_cqe {
__u8 rsvd[4];
__be32 msg_msn;
__u8 rsvd2[8];
- __u64 npg_wqe_id;
+ __le64 npg_wqe_idx_timestamp;
} send;
};
__be32 status_length;
__be32 qid_type_flags;
};
+/* bits for cqe wqe_idx and timestamp */
+enum ionic_v1_cqe_wqe_idx_timestamp_bits {
+ IONIC_V1_CQE_WQE_IDX_MASK = 0xffff,
+ IONIC_V1_CQE_TIMESTAMP_SHIFT = 16,
+};
+
/* bits for cqe recv */
enum ionic_v1_cqe_src_qpn_bits {
IONIC_V1_CQE_RECV_QPN_MASK = 0xffffff,
@@ -423,7 +429,7 @@ static inline u32 ionic_v1_cqe_qtf_qid(u32 qtf)
/* v1 base wqe header */
struct ionic_v1_base_hdr {
- __u64 wqe_id;
+ __le64 wqe_idx;
__u8 op;
__u8 num_sge_key;
__be16 flags;
--
2.43.0
^ permalink raw reply related
* [PATCH 3/4] RDMA/ionic: map PHC state into user space
From: Abhijit Gangurde @ 2026-04-01 10:25 UTC (permalink / raw)
To: jgg, leon, brett.creeley, andrew+netdev, davem, edumazet, kuba,
pabeni
Cc: allen.hubbe, nikhil.agarwal, linux-rdma, netdev, linux-kernel,
Abhijit Gangurde
In-Reply-To: <20260401102501.3395305-1-abhijit.gangurde@amd.com>
Enable user space applications to access the PHC state page when
firmware RDMA completion timestamp is supported.
This mapping allows user space to convert RDMA completion timestamps
to system wall time without kernel transitions, minimizing latency
overhead. Applications can directly read the PHC state through mmap,
enabling efficient timestamp correlation for precision timing
applications.
Co-developed-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
---
.../infiniband/hw/ionic/ionic_controlpath.c | 36 ++++++++++++++++++-
drivers/infiniband/hw/ionic/ionic_ibdev.h | 2 ++
drivers/infiniband/hw/ionic/ionic_lif_cfg.c | 2 ++
drivers/infiniband/hw/ionic/ionic_lif_cfg.h | 1 +
include/uapi/rdma/ionic-abi.h | 1 +
5 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/drivers/infiniband/hw/ionic/ionic_controlpath.c b/drivers/infiniband/hw/ionic/ionic_controlpath.c
index a5671da3db64..5a0b189f9855 100644
--- a/drivers/infiniband/hw/ionic/ionic_controlpath.c
+++ b/drivers/infiniband/hw/ionic/ionic_controlpath.c
@@ -391,6 +391,16 @@ int ionic_alloc_ucontext(struct ib_ucontext *ibctx, struct ib_udata *udata)
goto err_mmap_dbell;
}
+ if (dev->lif_cfg.phc_state) {
+ ctx->mmap_phc = ionic_mmap_entry_insert(ctx, PAGE_SIZE, 0,
+ IONIC_MMAP_PHC,
+ &resp.phc_offset);
+ if (!ctx->mmap_phc) {
+ rc = -ENOMEM;
+ goto err_mmap_phc;
+ }
+ }
+
resp.page_shift = PAGE_SHIFT;
resp.dbell_offset = db_phys & ~PAGE_MASK;
@@ -414,13 +424,15 @@ int ionic_alloc_ucontext(struct ib_ucontext *ibctx, struct ib_udata *udata)
if (dev->lif_cfg.rq_expdb)
resp.expdb_qtypes |= IONIC_EXPDB_RQ;
- rc = ib_copy_to_udata(udata, &resp, sizeof(resp));
+ rc = ib_respond_udata(udata, resp);
if (rc)
goto err_resp;
return 0;
err_resp:
+ rdma_user_mmap_entry_remove(ctx->mmap_phc);
+err_mmap_phc:
rdma_user_mmap_entry_remove(ctx->mmap_dbell);
err_mmap_dbell:
ionic_put_dbid(dev, ctx->dbid);
@@ -433,10 +445,26 @@ void ionic_dealloc_ucontext(struct ib_ucontext *ibctx)
struct ionic_ibdev *dev = to_ionic_ibdev(ibctx->device);
struct ionic_ctx *ctx = to_ionic_ctx(ibctx);
+ rdma_user_mmap_entry_remove(ctx->mmap_phc);
rdma_user_mmap_entry_remove(ctx->mmap_dbell);
ionic_put_dbid(dev, ctx->dbid);
}
+static int ionic_mmap_phc_stage(struct ionic_ibdev *dev,
+ struct vm_area_struct *vma)
+{
+ if (!(vma->vm_flags & VM_SHARED))
+ return -EINVAL;
+
+ if (vma->vm_flags & (VM_WRITE | VM_EXEC))
+ return -EPERM;
+
+ vm_flags_clear(vma, VM_MAYWRITE);
+
+ return vm_insert_page(vma, vma->vm_start,
+ virt_to_page(dev->lif_cfg.phc_state));
+}
+
int ionic_mmap(struct ib_ucontext *ibctx, struct vm_area_struct *vma)
{
struct ionic_ibdev *dev = to_ionic_ibdev(ibctx->device);
@@ -455,6 +483,12 @@ int ionic_mmap(struct ib_ucontext *ibctx, struct vm_area_struct *vma)
ionic_entry = container_of(rdma_entry, struct ionic_mmap_entry,
rdma_entry);
+ if (ionic_entry->mmap_flags & IONIC_MMAP_PHC) {
+ rc = ionic_mmap_phc_stage(dev, vma);
+ rdma_user_mmap_entry_put(rdma_entry);
+ return rc;
+ }
+
ibdev_dbg(&dev->ibdev, "writecombine? %d\n",
ionic_entry->mmap_flags & IONIC_MMAP_WC);
if (ionic_entry->mmap_flags & IONIC_MMAP_WC)
diff --git a/drivers/infiniband/hw/ionic/ionic_ibdev.h b/drivers/infiniband/hw/ionic/ionic_ibdev.h
index 63828240d659..08655c4d8297 100644
--- a/drivers/infiniband/hw/ionic/ionic_ibdev.h
+++ b/drivers/infiniband/hw/ionic/ionic_ibdev.h
@@ -72,6 +72,7 @@ enum ionic_admin_flags {
enum ionic_mmap_flag {
IONIC_MMAP_WC = BIT(0),
+ IONIC_MMAP_PHC = BIT(1),
};
struct ionic_mmap_entry {
@@ -173,6 +174,7 @@ struct ionic_ctx {
struct ib_ucontext ibctx;
u32 dbid;
struct rdma_user_mmap_entry *mmap_dbell;
+ struct rdma_user_mmap_entry *mmap_phc;
};
struct ionic_tbl_buf {
diff --git a/drivers/infiniband/hw/ionic/ionic_lif_cfg.c b/drivers/infiniband/hw/ionic/ionic_lif_cfg.c
index f3cd281c3a2f..e3f2df1f9e6a 100644
--- a/drivers/infiniband/hw/ionic/ionic_lif_cfg.c
+++ b/drivers/infiniband/hw/ionic/ionic_lif_cfg.c
@@ -40,6 +40,8 @@ void ionic_fill_lif_cfg(struct ionic_lif *lif, struct ionic_lif_cfg *cfg)
cfg->dbid_count = le32_to_cpu(lif->ionic->ident.dev.ndbpgs_per_lif);
cfg->dbpage = lif->kern_dbpage;
cfg->intr_ctrl = lif->ionic->idev.intr_ctrl;
+ if (ident->eth.config.features & cpu_to_le64(IONIC_ETH_HW_RDMA_TIMESTAMP))
+ cfg->phc_state = lif->phc->state_page;
cfg->db_phys = lif->ionic->bars[IONIC_PCI_BAR_DBELL].bus_addr;
diff --git a/drivers/infiniband/hw/ionic/ionic_lif_cfg.h b/drivers/infiniband/hw/ionic/ionic_lif_cfg.h
index 20853429f623..2b29e646c193 100644
--- a/drivers/infiniband/hw/ionic/ionic_lif_cfg.h
+++ b/drivers/infiniband/hw/ionic/ionic_lif_cfg.h
@@ -23,6 +23,7 @@ struct ionic_lif_cfg {
u64 __iomem *dbpage;
struct ionic_intr __iomem *intr_ctrl;
phys_addr_t db_phys;
+ void *phc_state;
u64 page_size_supported;
u32 npts_per_lif;
diff --git a/include/uapi/rdma/ionic-abi.h b/include/uapi/rdma/ionic-abi.h
index 97f695510380..abd1bde0991f 100644
--- a/include/uapi/rdma/ionic-abi.h
+++ b/include/uapi/rdma/ionic-abi.h
@@ -48,6 +48,7 @@ struct ionic_ctx_resp {
__u8 expdb_qtypes;
__u8 rsvd2[3];
+ __aligned_u64 phc_offset;
};
struct ionic_qdesc {
--
2.43.0
^ permalink raw reply related
* [PATCH 2/4] net: ionic: Add PHC state page for user space access
From: Abhijit Gangurde @ 2026-04-01 10:24 UTC (permalink / raw)
To: jgg, leon, brett.creeley, andrew+netdev, davem, edumazet, kuba,
pabeni
Cc: allen.hubbe, nikhil.agarwal, linux-rdma, netdev, linux-kernel,
Abhijit Gangurde
In-Reply-To: <20260401102501.3395305-1-abhijit.gangurde@amd.com>
Add a page associated with the PHC that can be mapped to user space,
allowing applications to access hardware timestamp information.
In order to synchronize between kernel and user space, a sequence
number is incremented at the beginning and end of each update.
An odd number means the data is being updated while an even number
means the update is complete. To guarantee that the data structure
was accessed atomically, user space will:
repeat:
seq1 = <read sequence>
goto <repeat> if odd
<read PHC state>
seq2 = <read sequence>
if seq1 != seq2 goto repeat
This mechanism acts as a guard against reading invalid state during
concurrent updates.
Co-developed-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
---
.../net/ethernet/pensando/ionic/ionic_lif.h | 3 +-
.../net/ethernet/pensando/ionic/ionic_phc.c | 41 +++++++++++++++++++
include/uapi/rdma/ionic-abi.h | 11 +++++
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.h b/drivers/net/ethernet/pensando/ionic/ionic_lif.h
index 8e10f66dc50e..0b820af2b523 100644
--- a/drivers/net/ethernet/pensando/ionic/ionic_lif.h
+++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.h
@@ -249,7 +249,7 @@ struct ionic_lif {
};
struct ionic_phc {
- spinlock_t lock; /* lock for cc and tc */
+ spinlock_t lock; /* lock for state_page, cc and tc */
struct cyclecounter cc;
struct timecounter tc;
@@ -262,6 +262,7 @@ struct ionic_phc {
long aux_work_delay;
struct ptp_clock_info ptp_info;
+ struct ionic_phc_state *state_page;
struct ptp_clock *ptp;
struct ionic_lif *lif;
};
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_phc.c b/drivers/net/ethernet/pensando/ionic/ionic_phc.c
index 116408099974..61eaf3834608 100644
--- a/drivers/net/ethernet/pensando/ionic/ionic_phc.c
+++ b/drivers/net/ethernet/pensando/ionic/ionic_phc.c
@@ -3,6 +3,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
+#include <rdma/ionic-abi.h>
#include "ionic.h"
#include "ionic_bus.h"
@@ -334,6 +335,26 @@ static int ionic_setphc_cmd(struct ionic_phc *phc, struct ionic_admin_ctx *ctx)
return ionic_adminq_post(phc->lif, ctx);
}
+static void ionic_phc_state_page_update(struct ionic_phc *phc)
+{
+ struct ionic_phc_state *state = phc->state_page;
+ u32 seq;
+
+ /* read current seq */
+ seq = smp_load_acquire(&state->seq) & ~1;
+
+ /* make seq odd for updating */
+ smp_store_mb(state->seq, seq | 1);
+
+ state->tick = phc->tc.cycle_last;
+ state->nsec = phc->tc.nsec;
+ state->frac = phc->tc.frac;
+ state->mult = phc->cc.mult;
+
+ /* make seq the next even number for update completed */
+ smp_store_release(&state->seq, seq + 2);
+}
+
static int ionic_phc_adjfine(struct ptp_clock_info *info, long scaled_ppm)
{
struct ionic_phc *phc = container_of(info, struct ionic_phc, ptp_info);
@@ -361,6 +382,8 @@ static int ionic_phc_adjfine(struct ptp_clock_info *info, long scaled_ppm)
timecounter_read(&phc->tc);
phc->cc.mult = adj;
+ ionic_phc_state_page_update(phc);
+
/* Setphc commands are posted in-order, sequenced by phc->lock. We
* need to drop the lock before waiting for the command to complete.
*/
@@ -386,6 +409,8 @@ static int ionic_phc_adjtime(struct ptp_clock_info *info, s64 delta)
timecounter_adjtime(&phc->tc, delta);
+ ionic_phc_state_page_update(phc);
+
/* Setphc commands are posted in-order, sequenced by phc->lock. We
* need to drop the lock before waiting for the command to complete.
*/
@@ -415,6 +440,8 @@ static int ionic_phc_settime64(struct ptp_clock_info *info,
timecounter_init(&phc->tc, &phc->cc, ns);
+ ionic_phc_state_page_update(phc);
+
/* Setphc commands are posted in-order, sequenced by phc->lock. We
* need to drop the lock before waiting for the command to complete.
*/
@@ -472,6 +499,8 @@ static long ionic_phc_aux_work(struct ptp_clock_info *info)
/* update point-in-time basis to now */
timecounter_read(&phc->tc);
+ ionic_phc_state_page_update(phc);
+
/* Setphc commands are posted in-order, sequenced by phc->lock. We
* need to drop the lock before waiting for the command to complete.
*/
@@ -558,6 +587,12 @@ void ionic_lif_alloc_phc(struct ionic_lif *lif)
if (!phc)
return;
+ phc->state_page = (void *)get_zeroed_page(GFP_KERNEL);
+ if (!phc->state_page) {
+ devm_kfree(ionic->dev, phc);
+ return;
+ }
+
phc->lif = lif;
phc->cc.read = ionic_cc_read;
@@ -569,6 +604,7 @@ void ionic_lif_alloc_phc(struct ionic_lif *lif)
dev_err(lif->ionic->dev,
"Invalid device PHC mask multiplier %u, disabling HW timestamp support\n",
phc->cc.mult);
+ free_page((unsigned long)phc->state_page);
devm_kfree(lif->ionic->dev, phc);
lif->phc = NULL;
return;
@@ -652,6 +688,10 @@ void ionic_lif_alloc_phc(struct ionic_lif *lif)
*/
phc->ptp_info.max_adj = NORMAL_PPB;
+ phc->state_page->mask = phc->cc.mask;
+ phc->state_page->shift = phc->cc.shift;
+ ionic_phc_state_page_update(phc);
+
lif->phc = phc;
}
@@ -662,6 +702,7 @@ void ionic_lif_free_phc(struct ionic_lif *lif)
mutex_destroy(&lif->phc->config_lock);
+ free_page((unsigned long)lif->phc->state_page);
devm_kfree(lif->ionic->dev, lif->phc);
lif->phc = NULL;
}
diff --git a/include/uapi/rdma/ionic-abi.h b/include/uapi/rdma/ionic-abi.h
index 7b589d3e9728..97f695510380 100644
--- a/include/uapi/rdma/ionic-abi.h
+++ b/include/uapi/rdma/ionic-abi.h
@@ -112,4 +112,15 @@ struct ionic_srq_resp {
__aligned_u64 rq_cmb_offset;
};
+struct ionic_phc_state {
+ __u32 seq;
+ __u32 rsvd;
+ __aligned_u64 mask;
+ __aligned_u64 tick;
+ __aligned_u64 nsec;
+ __aligned_u64 frac;
+ __u32 mult;
+ __u32 shift;
+};
+
#endif /* IONIC_ABI_H */
--
2.43.0
^ permalink raw reply related
* [PATCH 1/4] net: ionic: register PHC for rdma timestamping
From: Abhijit Gangurde @ 2026-04-01 10:24 UTC (permalink / raw)
To: jgg, leon, brett.creeley, andrew+netdev, davem, edumazet, kuba,
pabeni
Cc: allen.hubbe, nikhil.agarwal, linux-rdma, netdev, linux-kernel,
Abhijit Gangurde
In-Reply-To: <20260401102501.3395305-1-abhijit.gangurde@amd.com>
Currently, the driver only registers the PTP Hardware Clock (PHC) if
Ethernet hardware timestamping is supported. Update the registration
logic to register the PHC if the device supports either Ethernet
hardware timestamping or RDMA completion timestamping.
Co-developed-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Allen Hubbe <allen.hubbe@amd.com>
Signed-off-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
---
.../net/ethernet/pensando/ionic/ionic_if.h | 1 +
.../net/ethernet/pensando/ionic/ionic_lif.c | 5 ++++-
.../net/ethernet/pensando/ionic/ionic_phc.c | 20 ++++++++++++-------
3 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_if.h b/drivers/net/ethernet/pensando/ionic/ionic_if.h
index 47559c909c8b..3c34d5913729 100644
--- a/drivers/net/ethernet/pensando/ionic/ionic_if.h
+++ b/drivers/net/ethernet/pensando/ionic/ionic_if.h
@@ -1179,6 +1179,7 @@ enum ionic_eth_hw_features {
IONIC_ETH_HW_TX_CSUM_GENEVE = BIT(18),
IONIC_ETH_HW_TSO_GENEVE = BIT(19),
IONIC_ETH_HW_TIMESTAMP = BIT(20),
+ IONIC_ETH_HW_RDMA_TIMESTAMP = BIT(21),
};
/**
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_lif.c b/drivers/net/ethernet/pensando/ionic/ionic_lif.c
index 637e635bbf03..9d86f795f5f6 100644
--- a/drivers/net/ethernet/pensando/ionic/ionic_lif.c
+++ b/drivers/net/ethernet/pensando/ionic/ionic_lif.c
@@ -1498,7 +1498,8 @@ static int ionic_set_nic_features(struct ionic_lif *lif,
ctx.cmd.lif_setattr.features = ionic_netdev_features_to_nic(features);
if (lif->phc)
- ctx.cmd.lif_setattr.features |= cpu_to_le64(IONIC_ETH_HW_TIMESTAMP);
+ ctx.cmd.lif_setattr.features |= lif->ionic->ident.lif.eth.config.features &
+ cpu_to_le64(IONIC_ETH_HW_TIMESTAMP | IONIC_ETH_HW_RDMA_TIMESTAMP);
err = ionic_adminq_post_wait(lif, &ctx);
if (err)
@@ -1549,6 +1550,8 @@ static int ionic_set_nic_features(struct ionic_lif *lif,
dev_dbg(dev, "feature ETH_HW_TSO_UDP_CSUM\n");
if (lif->hw_features & IONIC_ETH_HW_TIMESTAMP)
dev_dbg(dev, "feature ETH_HW_TIMESTAMP\n");
+ if (lif->hw_features & IONIC_ETH_HW_RDMA_TIMESTAMP)
+ dev_dbg(dev, "feature ETH_HW_RDMA_TIMESTAMP\n");
return 0;
}
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_phc.c b/drivers/net/ethernet/pensando/ionic/ionic_phc.c
index 05b44fc482f8..116408099974 100644
--- a/drivers/net/ethernet/pensando/ionic/ionic_phc.c
+++ b/drivers/net/ethernet/pensando/ionic/ionic_phc.c
@@ -77,7 +77,8 @@ static int ionic_lif_hwstamp_set_ts_config(struct ionic_lif *lif,
bool rx_all;
__le64 mask;
- if (!lif->phc || !lif->phc->ptp)
+ if (!lif->phc || !lif->phc->ptp ||
+ !(lif->hw_features & IONIC_ETH_HW_TIMESTAMP))
return -EOPNOTSUPP;
mutex_lock(&lif->phc->config_lock);
@@ -210,7 +211,8 @@ int ionic_hwstamp_set(struct net_device *netdev,
struct ionic_lif *lif = netdev_priv(netdev);
int err;
- if (!lif->phc || !lif->phc->ptp)
+ if (!lif->phc || !lif->phc->ptp ||
+ !(lif->hw_features & IONIC_ETH_HW_TIMESTAMP))
return -EOPNOTSUPP;
mutex_lock(&lif->queue_lock);
@@ -228,7 +230,8 @@ void ionic_lif_hwstamp_replay(struct ionic_lif *lif)
{
int err;
- if (!lif->phc || !lif->phc->ptp)
+ if (!lif->phc || !lif->phc->ptp ||
+ !(lif->hw_features & IONIC_ETH_HW_TIMESTAMP))
return;
mutex_lock(&lif->queue_lock);
@@ -242,7 +245,8 @@ void ionic_lif_hwstamp_recreate_queues(struct ionic_lif *lif)
{
int err;
- if (!lif->phc || !lif->phc->ptp)
+ if (!lif->phc || !lif->phc->ptp ||
+ !(lif->hw_features & IONIC_ETH_HW_TIMESTAMP))
return;
mutex_lock(&lif->phc->config_lock);
@@ -267,7 +271,8 @@ int ionic_hwstamp_get(struct net_device *netdev,
{
struct ionic_lif *lif = netdev_priv(netdev);
- if (!lif->phc || !lif->phc->ptp)
+ if (!lif->phc || !lif->phc->ptp ||
+ !(lif->hw_features & IONIC_ETH_HW_TIMESTAMP))
return -EOPNOTSUPP;
mutex_lock(&lif->phc->config_lock);
@@ -506,7 +511,8 @@ static const struct ptp_clock_info ionic_ptp_info = {
void ionic_lif_register_phc(struct ionic_lif *lif)
{
- if (!lif->phc || !(lif->hw_features & IONIC_ETH_HW_TIMESTAMP))
+ if (!lif->phc ||
+ !(lif->hw_features & (IONIC_ETH_HW_TIMESTAMP | IONIC_ETH_HW_RDMA_TIMESTAMP)))
return;
lif->phc->ptp = ptp_clock_register(&lif->phc->ptp_info, lif->ionic->dev);
@@ -545,7 +551,7 @@ void ionic_lif_alloc_phc(struct ionic_lif *lif)
return;
features = le64_to_cpu(ionic->ident.lif.eth.config.features);
- if (!(features & IONIC_ETH_HW_TIMESTAMP))
+ if (!(features & (IONIC_ETH_HW_TIMESTAMP | IONIC_ETH_HW_RDMA_TIMESTAMP)))
return;
phc = devm_kzalloc(ionic->dev, sizeof(*phc), GFP_KERNEL);
--
2.43.0
^ permalink raw reply related
* [PATCH 0/4] ionic: RDMA completion timestamping support
From: Abhijit Gangurde @ 2026-04-01 10:24 UTC (permalink / raw)
To: jgg, leon, brett.creeley, andrew+netdev, davem, edumazet, kuba,
pabeni
Cc: allen.hubbe, nikhil.agarwal, linux-rdma, netdev, linux-kernel,
Abhijit Gangurde
Hi,
This series adds RDMA completion timestamp support for ionic.
It enables PHC registration for RDMA timestamp capability, exposes a PHC
state page for safe user-space reads, maps that PHC state through RDMA
ucontext mmap, and extends the RDMA CQE format to carry completion
timestamps.
With this, user space can read completion timestamps and convert them to
wall time with low overhead.
Patches:
net: ionic: register PHC for rdma timestamping
net: ionic: Add PHC state page for user space access
RDMA/ionic: map PHC state into user space
RDMA/ionic: add completion timestamp to CQE format
Provider's PR: https://github.com/linux-rdma/rdma-core/pull/1724
Abhijit Gangurde (4):
net: ionic: register PHC for rdma timestamping
net: ionic: Add PHC state page for user space access
RDMA/ionic: map PHC state into user space
RDMA/ionic: add completion timestamp to CQE format
.../infiniband/hw/ionic/ionic_controlpath.c | 36 ++++++++++-
drivers/infiniband/hw/ionic/ionic_datapath.c | 43 ++++++-------
drivers/infiniband/hw/ionic/ionic_fw.h | 12 +++-
drivers/infiniband/hw/ionic/ionic_ibdev.h | 2 +
drivers/infiniband/hw/ionic/ionic_lif_cfg.c | 2 +
drivers/infiniband/hw/ionic/ionic_lif_cfg.h | 1 +
.../net/ethernet/pensando/ionic/ionic_if.h | 1 +
.../net/ethernet/pensando/ionic/ionic_lif.c | 5 +-
.../net/ethernet/pensando/ionic/ionic_lif.h | 3 +-
.../net/ethernet/pensando/ionic/ionic_phc.c | 61 ++++++++++++++++---
include/uapi/rdma/ionic-abi.h | 12 ++++
11 files changed, 144 insertions(+), 34 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH net v2] tipc: fix bc_ackers underflow on duplicate GRP_ACK_MSG
From: Oleh Konko @ 2026-04-01 10:24 UTC (permalink / raw)
To: netdev@vger.kernel.org
Cc: jmaloy@redhat.com, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
tipc-discussion@lists.sourceforge.net,
linux-kernel@vger.kernel.org, stable@vger.kernel.org
The GRP_ACK_MSG handler in tipc_group_proto_rcv() currently decrements
bc_ackers on every inbound group ACK, even when the same member has
already acknowledged the current broadcast round.
Because bc_ackers is a u16, a duplicate ACK received after the last
legitimate ACK wraps the counter to 65535. Once wrapped,
tipc_group_bc_cong() keeps reporting congestion and later group
broadcasts on the affected socket stay blocked until the group is
recreated.
Fix this by ignoring duplicate or stale ACKs before touching bc_acked or
bc_ackers. This makes repeated GRP_ACK_MSG handling idempotent and
prevents the underflow path.
Fixes: 75da2163dbb6 ("tipc: introduce communication groups")
Cc: stable@vger.kernel.org
Signed-off-by: Oleh Konko <security@1seal.org>
---
v2:
- make duplicate or stale GRP_ACK_MSG a full no-op via early return
- place acked in reverse xmas tree style
net/tipc/group.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/net/tipc/group.c b/net/tipc/group.c
index e0e6227b433..14e6732624e 100644
--- a/net/tipc/group.c
+++ b/net/tipc/group.c
@@ -746,6 +746,7 @@ void tipc_group_proto_rcv(struct tipc_group *grp, bool *usr_wakeup,
u32 port = msg_origport(hdr);
struct tipc_member *m, *pm;
u16 remitted, in_flight;
+ u16 acked;
if (!grp)
return;
@@ -798,7 +799,10 @@ void tipc_group_proto_rcv(struct tipc_group *grp, bool *usr_wakeup,
case GRP_ACK_MSG:
if (!m)
return;
- m->bc_acked = msg_grp_bc_acked(hdr);
+ acked = msg_grp_bc_acked(hdr);
+ if (less_eq(acked, m->bc_acked))
+ return;
+ m->bc_acked = acked;
if (--grp->bc_ackers)
return;
list_del_init(&m->small_win);
--
2.50.0
^ permalink raw reply related
* Re: [PATCH net-next V9 02/14] devlink: Add helpers to lock nested-in instances
From: Cosmin Ratiu @ 2026-04-01 10:22 UTC (permalink / raw)
To: jacob.e.keller@intel.com, Tariq Toukan, kuba@kernel.org
Cc: allison.henderson@oracle.com, jiri@resnulli.us, Moshe Shemesh,
davem@davemloft.net, daniel.zahka@gmail.com,
donald.hunter@gmail.com, netdev@vger.kernel.org,
matttbe@kernel.org, pabeni@redhat.com, horms@kernel.org,
Parav Pandit, corbet@lwn.net, razor@blackwall.org, Dragos Tatulea,
linux-kernel@vger.kernel.org, willemb@google.com, Jiri Pirko,
Adithya Jayachandran, Dan Jurgens, leon@kernel.org,
kees@kernel.org, vadim.fedorenko@linux.dev, Saeed Mahameed,
shuah@kernel.org, andrew+netdev@lunn.ch, Mark Bloch,
Shahar Shitrit, Carolina Jubran, Nimrod Oren,
daniel@iogearbox.net, minhquangbui99@gmail.com, dw@davidwei.uk,
skhan@linuxfoundation.org, Petr Machata, edumazet@google.com,
antonio@openvpn.net, mst@redhat.com,
linux-kselftest@vger.kernel.org, linux-rdma@vger.kernel.org,
Shay Drori, sdf@fomichev.me, chuck.lever@oracle.com, Gal Pressman,
joe@dama.to, linux-doc@vger.kernel.org
In-Reply-To: <c547be19-adaf-4442-be2b-debcbafa4191@intel.com>
On Tue, 2026-03-31 at 16:55 -0700, Jacob Keller wrote:
> On 3/31/2026 5:20 AM, Cosmin Ratiu wrote:
> > On Mon, 2026-03-30 at 19:08 -0700, Jakub Kicinski wrote:
> > > This is an AI-generated review of your patch. The human sending
> > > this
> > > email has considered the AI review valid, or at least plausible.
> > > ---
> > > devlink: Add helpers to lock nested-in instances
> > >
> > > Upcoming code will need to obtain a reference to locked nested-in
> > > devlink instances. Add helpers to lock, obtain an already locked
> > > reference and unlock/unref the nested-in instance.
> > >
> > > > diff --git a/net/devlink/core.c b/net/devlink/core.c
> > > > index eeb6a71f5f56e..db11248df7120 100644
> > > > --- a/net/devlink/core.c
> > > > +++ b/net/devlink/core.c
> > >
> > > [ ... ]
> > >
> > > > +/* Returns the nested in devlink object and validates its lock
> > > > is
> > > > held. */
> > > > +struct devlink *devlink_nested_in_get_locked(struct
> > > > devlink_rel
> > > > *rel)
> > > > +{
> > > > + struct devlink *devlink;
> > > > + unsigned long index;
> > > > +
> > > > + if (!rel)
> > > > + return NULL;
> > > > + index = rel->nested_in.devlink_index;
> > > > + devlink = xa_find(&devlinks, &index, index,
> > > > DEVLINK_REGISTERED);
> > > > + if (devlink)
> > > > + devl_assert_locked(devlink);
> > >
> > > Can this dereference a freed pointer if the lock is not actually
> > > held?
> > >
> > > xa_find() acquires and releases the RCU read lock internally.
> > > When it
> > > returns,
> > > the devlink pointer is no longer protected by RCU or a reference
> > > count.
> > >
> > > If a caller invokes this helper without holding the devlink lock
> > > (which is the
> > > exact scenario the assertion is designed to detect), another
> > > thread
> > > could
> > > concurrently unregister and free the devlink.
> > >
> > > If the RCU grace period expires before the assertion executes,
> > > devl_assert_locked() would dereference freed memory. Should the
> > > lookup and
> > > assertion be enclosed within an explicit rcu_read_lock() block to
> > > safely
> > > assert the lock?
> >
> > This function is meant to be invoked by callers already holding all
> > locks from a given instance up the nesting tree. Calling it outside
> > this context could indeed lead to a race as described, where
> > another
> > entity unregisters a devlink about-to-be-asserted on.
> >
>
> Hmm. I'm struggling to follow this. If you already expect the parent
> to
> hold the nested devlink's lock, it must have a pointer to this
> devlink
> instance. In that case, why would you even need
> devlink_nested_in_get_locked in the first place?
After some more intense staring, I realized that intermediate instances
don't actually need to be locked, only the ancestor needs to. With that
in mind, the code get simplified:
- devlink_nested_in_get_locked and devlink_nested_in_put_unlock can be
removed.
- recursive unlocking in devl_rate_unlock is gone.
>
> > All current callers hold the locks, but to make it more robust, I
> > will
> > add the rcu_read_lock/unlock section around the find+assert.
> >
>
> You should at least do that. It is the correct pattern when dealing
> with
> reference counting data structures from the xarray. That's already
> how
> devlinks_xa_get() is implemented.
>
> > >
> > > > + return devlink;
> > > > +}
> > > > +
> > > > +void devlink_nested_in_put_unlock(struct devlink_rel *rel)
> > > > +{
> > > > + struct devlink *devlink =
> > > > devlink_nested_in_get_locked(rel);
> > >
> > > Could this corrupt the mutex state and cause a refcount
> > > underflow?
> > >
> > > This helper takes a devlink_rel pointer instead of the devlink
> > > pointer
> > > acquired by devlink_nested_in_get_lock(), and performs a
> > > secondary
> > > global
> > > lookup to find the devlink.
> > >
> > > If a caller mistakenly calls this in an error cleanup path where
> > > they
> > > did not
> > > actually acquire the lock, the global xa_find() will still locate
> > > the
> > > registered devlink. This would execute devl_unlock() and
> > > devlink_put() on a
> > > devlink the current thread does not own.
> > >
>
> If the caller already held the lock, why is
> devlink_nested_in_put_unlock
> calling the devl_unlock instead of the caller anyways? That seems
> confusing. Wouldn't the normal pattern be to
> devlink_nested_in_get_lock()? Oh, that is a separate function. Ok I
> see.
>
> > > Would it be safer for unlock/put helpers to take the exact
> > > pointer
> > > returned by
> > > the lock/get helper to ensure safe resource cleanup?
> >
> > 2 issues here:
> > 1) Mistakenly calling this without having acquired the lock. This
> > is
> > akin to saying mutex_unlock is dangerous if the lock isn't held.
> > Technically true, but moot.
> > 2) The rel argument: It is intentional, so that all 3 functions are
> > symmetrical.
> >
>
> IMO it would make more sense for the put version to be a put on the
> returned devlink pointer. I guess its not symmetrical, but it removes
> the need to perform the second lookup and it makes it easier to
> reason
> about the pointer you're releasing being the same one.
>
> Having put take different arguments from get is the usual pattern for
> such a behavior.
>
> Also devlink_nested_in_get_locked() doesn't increase the ref count so
> it
> is sort of "relying" on the caller already having a reference to it,
> which makes me think its not very useful. The only valid way to call
> this function as it exists now safely is to already hold a reference
> to
> the object, which also already requires you to have a valid pointer
> making me wonder why you'd ever need to call it in the first place.
>
> The only example you have is to make devlink_nested_in_put_unlock()
> take
> a devlink_rel pointer as its argument instead of just calling it on
> the
> pointer returned by devlink_nested_in_get_lock().
>
> This implementation seems confusing and likely to lead to errors.
I hope the next version will be more suitable.
Thank you for the comments and suggestions.
>
> Thanks,
> Jake
^ permalink raw reply
* Re: [PATCH v25 05/11] sfc: create type2 cxl memdev
From: Alejandro Lucero Palau @ 2026-04-01 10:16 UTC (permalink / raw)
To: Dan Williams, alejandro.lucero-palau, linux-cxl, netdev,
dave.jiang, edward.cree, davem, kuba, pabeni, edumazet
Cc: Martin Habets, Fan Ni, Edward Cree, Jonathan Cameron
In-Reply-To: <69ccaa8448da1_1b0cc61009c@dwillia2-mobl4.notmuch>
On 4/1/26 06:17, Dan Williams wrote:
> alejandro.lucero-palau@ wrote:
>> From: Alejandro Lucero <alucerop@amd.com>
>>
>> Use cxl API for creating a cxl memory device using the type2
>> cxl_dev_state struct.
>>
>> Signed-off-by: Alejandro Lucero <alucerop@amd.com>
>> Reviewed-by: Martin Habets <habetsm.xilinx@gmail.com>
>> Reviewed-by: Fan Ni <fan.ni@samsung.com>
>> Acked-by: Edward Cree <ecree.xilinx@gmail.com>
>> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
>> Reviewed-by: Dave Jiang <dave.jiang@intel.com>
>> ---
>> drivers/net/ethernet/sfc/efx_cxl.c | 6 ++++++
>> 1 file changed, 6 insertions(+)
>>
>> diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c
>> index 6619084a77d8..63e6f277ae9f 100644
>> --- a/drivers/net/ethernet/sfc/efx_cxl.c
>> +++ b/drivers/net/ethernet/sfc/efx_cxl.c
>> @@ -75,6 +75,12 @@ int efx_cxl_init(struct efx_probe_data *probe_data)
>> return -ENODEV;
>> }
>>
>> + cxl->cxlmd = devm_cxl_add_memdev(&cxl->cxlds, NULL);
> Did this forget about:
>
> 29317f8dc6ed cxl/mem: Introduce cxl_memdev_attach for CXL-dependent operation
>
> ?
I did not. The idea behind attach does not make sense for sfc, so I do
not use that option. I did discuss this with you at LPC and based on
previous comments which had not replies (case 1 in here):
https://lore.kernel.org/linux-cxl/836d06d6-a36f-4ba3-b7c9-ba8687ba2190@amd.com/
From our conversation at LPC, there is no reason for a CXL device
connected to the CXL Root Bridge not having its memdev properly
initialized. I did ask you specifically about this, and you mentioned
links on CXL switches not ready, but as I said then and repeat now, this
is not what sfc driver expects at all. Only a port from a CXL Root
Bridge makes sense due to the latencies involved with more complex CXL
topologies.
So, from that list I wrote in that old thread where I tried to summarize
the problems and clarify the confusion behind different concerns, the
only issue is someone removing cxl_acpi. I do not think that should be
possible at all, so something should be added for other modules
depending on it avoiding its removal. I would say any CXL port created
is (in X86) dependent on cxl_acpi, so something at port creation
invoking a exported cxl_acpi function could do it, like a cxl_acpi_users
counter. This could also help for visibility to user space about its usage.
Therefore, I do not like the changes you propose here. But, if this
proposal is the only way this can go through in time for 7.1, I will
resign myself to it. It is not clear I'll be working on CXL in the near
future, so any progress even without my agreement is better than nothing.
> tl;dr: see the replacement patch below for this and the next several
> patches. Untested! I will write a cxl_test for it tomorrow. For now,
> take it as a "something like this".
>
> ---
> ...longer story.
>
> The difference between general memory expansion and other CXL memory is
> that the driver has a use case for the memory. For general memory
> expansion the memdev *is* the use case. A NULL attach parameter means
> there is value in continuing if the memdev is not attached to the CXL
> domain or not mapped in any region.
>
> A non-NULL @attach addresses all the caller's requirements at attach
> time. It also arranges by default to trigger ->remove() if anything in
> the CXL topology is disturbed while the region is in use. Anything from
> hotplug, to modprobe -r cxl_acpi, or cxl disable-port.
>
> For efx a finer grained failure mode can be built on top of this by
> adding a remove action that something like efx_tx_may_pio() could use to
> dynamically fallback away from CXL if something goes wrong.
>
> Until then though the CXL subsystem, because CXL allows for dynamic
> memory reassignment, goes to great lengths to ensure that it can all be
> dynamically torn down. So consumers that register relative to a cxl_port
> topology need to be prepared for that port hierarchy to be torn down and
> evacuate their usage of any regions.
>
> The rest of the patches in this set are racy because none of the results
> are stable (locks dropped on return) and any teardown is silent (no
> notification of teardown).
>
> All of this wants to stay local to the cxl_core and should not be
> anything that accelerator drivers need to worry about. Accelerator
> drivers just register, get a physical address range to map, and off they
> go.
>
> The meat of this patch is cxl_memdev_attach_region(). The rest of is
> protection against userspace messing up the region configuration.
>
> ---
> diff --git a/drivers/net/ethernet/sfc/efx_cxl.h b/drivers/net/ethernet/sfc/efx_cxl.h
> index 961639cef692..bfd8633a1e01 100644
> --- a/drivers/net/ethernet/sfc/efx_cxl.h
> +++ b/drivers/net/ethernet/sfc/efx_cxl.h
> @@ -24,10 +24,7 @@ struct efx_probe_data;
> struct efx_cxl {
> struct cxl_dev_state cxlds;
> struct cxl_memdev *cxlmd;
> - struct cxl_root_decoder *cxlrd;
> - struct cxl_port *endpoint;
> - struct cxl_endpoint_decoder *cxled;
> - struct cxl_region *efx_region;
> + struct cxl_attach_region attach;
> void __iomem *ctpio_cxl;
> };
>
> diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
> index 10a9b8fa2f6b..1698d15ec1ca 100644
> --- a/include/cxl/cxl.h
> +++ b/include/cxl/cxl.h
> @@ -153,6 +153,22 @@ struct cxl_memdev_attach {
> int (*probe)(struct cxl_memdev *cxlmd);
> };
>
> +/**
> + * struct cxl_attach_region - coordinate mapping a region at memdev registration
> + * @attach: common core attachment descriptor
> + * @region: physical address range of the region
> + *
> + * For the common simple case of a CXL device with private (non-general purpose
> + * / "accelerator") memory, enumerate firmware instantiated region, or
> + * instantiate a region for the device's capacity. Destroy the region on detach.
> + */
> +struct cxl_attach_region {
> + struct cxl_memdev_attach attach;
> + struct range region;
> +};
> +
> +int cxl_memdev_attach_region(struct cxl_memdev *cxlmd);
> +
> /**
> * struct cxl_dev_state - The driver device state
> *
> diff --git a/drivers/cxl/core/region.c b/drivers/cxl/core/region.c
> index 3edb5703d6de..5d60e6c0a89e 100644
> --- a/drivers/cxl/core/region.c
> +++ b/drivers/cxl/core/region.c
> @@ -406,6 +406,40 @@ static int __commit(struct cxl_region *cxlr)
> return 0;
> }
>
> +/*
> + * When a region's memdevs specify an @attach method the attach provider is
> + * responsible for dispositioning the region for both probe and userspace
> + * management
> + */
> +static bool cxl_region_has_memdev_attach(struct cxl_region *cxlr)
> +{
> + struct cxl_region_params *p = &cxlr->params;
> +
> + for (int i = 0; i < p->nr_targets; i++) {
> + struct cxl_endpoint_decoder *cxled = p->targets[i];
> + struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
> +
> + if (cxlmd->attach)
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static int check_region_mutable(struct cxl_region *cxlr)
> +{
> + int rc;
> +
> + ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
> + if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
> + return rc;
> +
> + if (cxl_region_has_memdev_attach(cxlr))
> + return -EBUSY;
> +
> + return 0;
> +}
> +
> static ssize_t commit_store(struct device *dev, struct device_attribute *attr,
> const char *buf, size_t len)
> {
> @@ -418,6 +452,10 @@ static ssize_t commit_store(struct device *dev, struct device_attribute *attr,
> if (rc)
> return rc;
>
> + rc = check_region_mutable(cxlr);
> + if (rc)
> + return rc;
> +
> if (commit) {
> rc = __commit(cxlr);
> if (rc)
> @@ -2768,17 +2806,23 @@ static ssize_t delete_region_store(struct device *dev,
> {
> struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
> struct cxl_port *port = to_cxl_port(dev->parent);
> - struct cxl_region *cxlr;
> + int rc;
>
> - cxlr = cxl_find_region_by_name(cxlrd, buf);
> + struct cxl_region *cxlr __free(put_cxl_region) =
> + cxl_find_region_by_name(cxlrd, buf);
> if (IS_ERR(cxlr))
> return PTR_ERR(cxlr);
>
> + rc = check_region_mutable(cxlr);
> + if (rc)
> + return rc;
> +
> devm_release_action(port->uport_dev, unregister_region, cxlr);
> - put_device(&cxlr->dev);
>
> return len;
> }
> +
> +
> DEVICE_ATTR_WO(delete_region);
>
> static void cxl_pmem_region_release(struct device *dev)
> @@ -4223,6 +4267,88 @@ static int cxl_region_can_probe(struct cxl_region *cxlr)
> return 0;
> }
>
> +static int first_mapped_decoder(struct device *dev, const void *data)
> +{
> + struct cxl_endpoint_decoder *cxled;
> +
> + if (!is_endpoint_decoder(dev))
> + return 0;
> +
> + cxled = to_cxl_endpoint_decoder(dev);
> + if (cxled->cxld.region)
> + return 1;
> +
> + return 0;
> +}
> +
> +/*
> + * As this is running in endpoint port remove context it does not race cxl_root
> + * destruction since port topologies are always removed depth first.
> + */
> +static void cxl_endpoint_region_autoremove(void *_cxlr)
> +{
> + struct cxl_region *cxlr = _cxlr;
> + struct cxl_root_decoder *cxlrd = cxlr->cxlrd;
> + struct cxl_port *port = cxlrd_to_port(cxlrd);
> +
> + devm_release_action(port->uport_dev, unregister_region, cxlr);
> +}
> +
> +/*
> + * Runs in cxl_mem_probe context after successful endpoint probe, assumes the
> + * simple case of single mapped decoder per memdev.
> + */
> +int cxl_memdev_attach_region(struct cxl_memdev *cxlmd)
> +{
> + struct cxl_attach_region *attach =
> + container_of(cxlmd->attach, typeof(*attach), attach);
> + struct cxl_port *endpoint = cxlmd->endpoint;
> + struct cxl_endpoint_decoder *cxled;
> + struct cxl_region *cxlr;
> + int rc;
> +
> + /* hold endpoint lock to setup autoremove of the region */
> + guard(device)(&endpoint->dev);
> + if (!endpoint->dev.driver)
> + return -ENXIO;
> + guard(rwsem_read)(&cxl_rwsem.region);
> + guard(rwsem_read)(&cxl_rwsem.dpa);
> +
> + /*
> + * TODO auto-instantiate a region, for now assume this will find an
> + * auto-region
> + */
> + struct device *dev __free(put_device) =
> + device_find_child(&endpoint->dev, NULL, first_mapped_decoder);
> +
> + if (!dev) {
> + dev_dbg(cxlmd->cxlds->dev, "no region found for memdev %s\n",
> + dev_name(&cxlmd->dev));
> + return -ENXIO;
> + }
> +
> + cxled = to_cxl_endpoint_decoder(dev);
> + cxlr = cxled->cxld.region;
> + rc = devm_add_action_or_reset(&endpoint->dev,
> + cxl_endpoint_region_autoremove, cxlr);
> + if (rc)
> + return rc;
> +
> + if (cxlr->params.state < CXL_CONFIG_COMMIT) {
> + dev_dbg(cxlmd->cxlds->dev,
> + "region %s not committed for memdev %s\n",
> + dev_name(&cxlr->dev), dev_name(&cxlmd->dev));
> + return -ENXIO;
> + }
> +
> + attach->region = (struct range) {
> + .start = cxlr->params.res->start,
> + .end = cxlr->params.res->end,
> + };
> + return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_memdev_attach_region, "CXL");
> +
> static int cxl_region_probe(struct device *dev)
> {
> struct cxl_region *cxlr = to_cxl_region(dev);
> @@ -4254,6 +4380,9 @@ static int cxl_region_probe(struct device *dev)
> if (rc)
> return rc;
>
> + if (cxl_region_has_memdev_attach(cxlr))
> + return 0;
> +
> switch (cxlr->mode) {
> case CXL_PARTMODE_PMEM:
> rc = devm_cxl_region_edac_register(cxlr);
> diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c
> index 6619084a77d8..a388f3120dd4 100644
> --- a/drivers/net/ethernet/sfc/efx_cxl.c
> +++ b/drivers/net/ethernet/sfc/efx_cxl.c
> @@ -75,6 +75,25 @@ int efx_cxl_init(struct efx_probe_data *probe_data)
> return -ENODEV;
> }
>
> + cxl->attach = (struct cxl_attach_region) {
> + .attach = {
> + .probe = cxl_memdev_attach_region,
> + },
> + };
> + cxl->cxlmd = devm_cxl_add_memdev(&cxl->cxlds, &cxl->attach.attach);
> + if (IS_ERR(cxl->cxlmd)) {
> + pci_err(pci_dev, "CXL accel memdev creation failed");
> + return PTR_ERR(cxl->cxlmd);
> + }
> +
> + cxl->ctpio_cxl = ioremap(cxl->attach.region.start,
> + range_len(&cxl->attach.region));
> + if (!cxl->ctpio_cxl) {
> + pci_err(pci_dev, "CXL ioremap region (%pra) failed", &range);
> + return -ENOMEM;
> + }
> +
> +
> probe_data->cxl = cxl;
>
> return 0;
> @@ -82,6 +101,10 @@ int efx_cxl_init(struct efx_probe_data *probe_data)
>
> void efx_cxl_exit(struct efx_probe_data *probe_data)
> {
> + if (!probe_data->cxl)
> + return;
> +
> + iounmap(probe_data->cxl->ctpio_cxl);
> }
>
> MODULE_IMPORT_NS("CXL");
^ permalink raw reply
* [PATCH 1/1] net: phy: realtek: Add support for PHY LEDs on RTL8221B
From: Chukun Pan @ 2026-04-01 10:00 UTC (permalink / raw)
To: David S . Miller
Cc: Andrew Lunn, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
Russell King, Daniel Golle, Heiner Kallweit, linux-kernel, netdev,
Chukun Pan
Realtek RTL8221B Ethernet PHY supports three LED pins which are used to
indicate link status and activity. Add netdev trigger support for them.
Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
---
drivers/net/phy/realtek/realtek_main.c | 146 +++++++++++++++++++++++++
1 file changed, 146 insertions(+)
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 023e47ad605b..8a22e18e5c56 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -150,6 +150,15 @@
#define RTL8221B_VND2_INSR 0xa4d4
+#define RTL822X_VND2_LED(x) (0xd032 + ((x) * 2))
+#define RTL822X_VND2_LCR_LINK_10 BIT(0)
+#define RTL822X_VND2_LCR_LINK_100 BIT(1)
+#define RTL822X_VND2_LCR_LINK_1000 BIT(2)
+#define RTL822X_VND2_LCR_LINK_2500 BIT(5)
+
+#define RTL822X_VND2_LCR6 0xd040
+#define RTL822X_VND2_LED_ACT(x) BIT(x)
+
#define RTL8224_MII_RTCT 0x11
#define RTL8224_MII_RTCT_ENABLE BIT(0)
#define RTL8224_MII_RTCT_PAIR_A BIT(4)
@@ -1661,6 +1670,135 @@ static int rtl822xb_c45_read_status(struct phy_device *phydev)
return 0;
}
+static int rtl822xb_led_brightness_set(struct phy_device *phydev, u8 index,
+ enum led_brightness value)
+{
+ int ret;
+
+ if (index >= RTL8211x_LED_COUNT)
+ return -EINVAL;
+
+ /* clear HW LED setup */
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_LED(index), 0);
+ if (ret < 0)
+ return ret;
+
+ /* clear HW LED blink */
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_LCR6,
+ RTL822X_VND2_LED_ACT(index));
+}
+
+static int rtl822xb_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ const unsigned long act_mask = BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+
+ const unsigned long link_mask = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_LINK_2500);
+
+ if (index >= RTL8211x_LED_COUNT)
+ return -EINVAL;
+
+ /* Filter out any other unsupported triggers. */
+ if (rules & ~(link_mask | act_mask))
+ return -EOPNOTSUPP;
+
+ /* RX and TX are not differentiated, they are not possible
+ * without combination with a link trigger.
+ */
+ if ((rules & act_mask) && !(rules & link_mask))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int rtl822xb_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ int val;
+
+ if (index >= RTL8211x_LED_COUNT)
+ return -EINVAL;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LED(index));
+ if (val < 0)
+ return val;
+
+ if (val & RTL822X_VND2_LCR_LINK_10)
+ __set_bit(TRIGGER_NETDEV_LINK_10, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_100)
+ __set_bit(TRIGGER_NETDEV_LINK_100, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_1000)
+ __set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+
+ if (val & RTL822X_VND2_LCR_LINK_2500)
+ __set_bit(TRIGGER_NETDEV_LINK_2500, rules);
+
+ if ((val & RTL822X_VND2_LCR_LINK_10) &&
+ (val & RTL822X_VND2_LCR_LINK_100) &&
+ (val & RTL822X_VND2_LCR_LINK_1000) &&
+ (val & RTL822X_VND2_LCR_LINK_2500))
+ __set_bit(TRIGGER_NETDEV_LINK, rules);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6);
+ if (val < 0)
+ return val;
+
+ if (val & RTL822X_VND2_LED_ACT(index)) {
+ __set_bit(TRIGGER_NETDEV_RX, rules);
+ __set_bit(TRIGGER_NETDEV_TX, rules);
+ }
+
+ return 0;
+}
+
+static int rtl822xb_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ u16 val = 0;
+ bool act;
+ int ret;
+
+ if (index >= RTL8211x_LED_COUNT)
+ return -EINVAL;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+ val |= RTL822X_VND2_LCR_LINK_10;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+ val |= RTL822X_VND2_LCR_LINK_100;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+ val |= RTL822X_VND2_LCR_LINK_1000;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &rules) ||
+ test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
+ val |= RTL822X_VND2_LCR_LINK_2500;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_LED(index), val);
+ if (ret < 0)
+ return ret;
+
+ act = test_bit(TRIGGER_NETDEV_RX, &rules) ||
+ test_bit(TRIGGER_NETDEV_TX, &rules);
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2, RTL822X_VND2_LCR6,
+ RTL822X_VND2_LED_ACT(index), act ?
+ RTL822X_VND2_LED_ACT(index) : 0);
+}
+
static int rtl8224_cable_test_start(struct phy_device *phydev)
{
u32 val;
@@ -2427,6 +2565,10 @@ static struct phy_driver realtek_drvs[] = {
.write_page = rtl821x_write_page,
.read_mmd = rtl822xb_read_mmd,
.write_mmd = rtl822xb_write_mmd,
+ .led_brightness_set = rtl822xb_led_brightness_set,
+ .led_hw_is_supported = rtl822xb_led_hw_is_supported,
+ .led_hw_control_get = rtl822xb_led_hw_control_get,
+ .led_hw_control_set = rtl822xb_led_hw_control_set,
}, {
.match_phy_device = rtl8221b_vm_cg_match_phy_device,
.name = "RTL8221B-VM-CG 2.5Gbps PHY",
@@ -2446,6 +2588,10 @@ static struct phy_driver realtek_drvs[] = {
.write_page = rtl821x_write_page,
.read_mmd = rtl822xb_read_mmd,
.write_mmd = rtl822xb_write_mmd,
+ .led_brightness_set = rtl822xb_led_brightness_set,
+ .led_hw_is_supported = rtl822xb_led_hw_is_supported,
+ .led_hw_control_get = rtl822xb_led_hw_control_get,
+ .led_hw_control_set = rtl822xb_led_hw_control_set,
}, {
.match_phy_device = rtl8251b_c45_match_phy_device,
.name = "RTL8251B 5Gbps PHY",
--
2.34.1
^ 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