* Re: [PATCH net 1/3 v2] net: Extend bpf_net_context lifetime to cover qdisc enqueue
From: Daniel Borkmann @ 2026-06-29 13:01 UTC (permalink / raw)
To: Jamal Hadi Salim, netdev
Cc: jiri, davem, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, toke, Steven Rostedt, Petr Machata,
Alexei Starovoitov, John Fastabend, Jesper Dangaard Brouer,
linux-rt-devel, bpf, security, stable, Victor Nogueira
In-Reply-To: <20260629102157.737306-2-jhs@mojatatu.com>
Hi Jamal,
On 6/29/26 12:21 PM, Jamal Hadi Salim wrote:
> The bpf_net_context used by sch_handle_egress() is stack-allocated and torn
> down in that function returned. By the time tcf_qevent_handle() runs
> current->bpf_net_context is NULL.
>
> When a filter attached to a qevent block (e.g. RED's early_drop or mark
> qevents, which always use shared blocks) returns TC_ACT_REDIRECT,
> tcf_qevent_handle() calls skb_do_redirect(), which in turn calls bpf helper
> bpf_net_ctx_get_ri(). That helper unconditionally dereferences
> current->bpf_net_context resulting in a NULL pointer dereference.
>
> Note: The same holds for actions that invoke BPF redirect helpers
> (e.g. act_bpf running a program that calls bpf_redirect()) during qevent
> classification itself.
>
> Fix:
> Move the bpf_net_context lifecycle out of sch_handle_egress() into
> __dev_queue_xmit(), so that it spans both the egress TC fast path and the
> qdisc enqueue.
> Note: The call is placed outside the egress_needed_key static branch
> to cover the case where clsact static key is disabled. Unfortunately this
> adds a small unconditional penalty to the code path _per packet_ only
> guarded by CONFIG_NET_XGRESS (two writes and one read).
>
> As pointed by sashiko [1]:
> The same context must also be set up in net_tx_action()'s qdisc drain
> path, since qdisc_run() -> netem_dequeue() -> qdisc_enqueue( RED child)
> can trigger qevent classification asynchronously from softirq context.
>
> This keeps all bpf_net_context management in net/core/dev.c i.e the
> existing boundary between tc core and BPF without requiring any net/sched/
> code to know about BPF plumbing.
>
> Reproducer:
>
> tc qdisc add dev eth0 root handle 1: red limit 1MB min 10KB max 20KB \
> avpkt 1000 burst 100 qevent early_drop block 10
> tc filter add block 10 pref 1 bpf obj redirect.o
>
> traffic through eth0 triggers red_enqueue() -> tcf_qevent_handle() and,
> on a redirect verdict, a NULL deref in skb_do_redirect().
>
> Fixes: 3625750f05ec ("net: sched: Introduce helpers for qevent blocks")
> Tested-by: Victor Nogueira <victor@mojatatu.com>
> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Could we simplify patch 1 & 2 by just moving the bpf_net_ctx_set() and
bpf_net_ctx_clear() into a tcf_classify_qdisc() wrapper where we don't
end up having to touch the core TX code?
Untested diff :
diff --git a/include/net/pkt_cls.h b/include/net/pkt_cls.h
index 3bd08d7f39c1..1828cc16c5d7 100644
--- a/include/net/pkt_cls.h
+++ b/include/net/pkt_cls.h
@@ -93,6 +93,8 @@ int tcf_classify(struct sk_buff *skb,
const struct tcf_block *block,
const struct tcf_proto *tp, struct tcf_result *res,
bool compat_mode);
+int tcf_classify_qdisc(struct sk_buff *skb, const struct tcf_proto *tp,
+ struct tcf_result *res, bool compat_mode);
static inline bool tc_cls_stats_dump(struct tcf_proto *tp,
struct tcf_walker *arg,
@@ -157,6 +159,13 @@ static inline int tcf_classify(struct sk_buff *skb,
return TC_ACT_UNSPEC;
}
+static inline int tcf_classify_qdisc(struct sk_buff *skb,
+ const struct tcf_proto *tp,
+ struct tcf_result *res, bool compat_mode)
+{
+ return tcf_classify(skb, NULL, tp, res, compat_mode);
+}
+
#endif
static inline unsigned long
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
index 3e67600a4a1a..982409702c7f 100644
--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -23,6 +23,7 @@
#include <linux/jhash.h>
#include <linux/rculist.h>
#include <linux/rhashtable.h>
+#include <linux/filter.h>
#include <net/net_namespace.h>
#include <net/sock.h>
#include <net/netlink.h>
@@ -1884,6 +1885,24 @@ int tcf_classify(struct sk_buff *skb,
}
EXPORT_SYMBOL(tcf_classify);
+int tcf_classify_qdisc(struct sk_buff *skb, const struct tcf_proto *tp,
+ struct tcf_result *res, bool compat_mode)
+{
+ struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx;
+ int ret;
+
+ bpf_net_ctx = bpf_net_ctx_set(&__bpf_net_ctx);
+ ret = tcf_classify(skb, NULL, tp, res, compat_mode);
+ bpf_net_ctx_clear(bpf_net_ctx);
+
+ if (unlikely(ret == TC_ACT_REDIRECT)) {
+ pr_warn_once("TC_ACT_REDIRECT from qdisc filter chains is not supported\n");
+ ret = TC_ACT_SHOT;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(tcf_classify_qdisc);
+
struct tcf_chain_info {
struct tcf_proto __rcu **pprev;
struct tcf_proto __rcu *next;
@@ -4033,7 +4052,7 @@ struct sk_buff *tcf_qevent_handle(struct tcf_qevent *qe, struct Qdisc *sch, stru
fl = rcu_dereference_bh(qe->filter_chain);
- switch (tcf_classify(skb, NULL, fl, &cl_res, false)) {
+ switch (tcf_classify_qdisc(skb, fl, &cl_res, false)) {
case TC_ACT_SHOT:
qdisc_qstats_drop(sch);
__qdisc_drop(skb, to_free);
@@ -4045,10 +4064,6 @@ struct sk_buff *tcf_qevent_handle(struct tcf_qevent *qe, struct Qdisc *sch, stru
__qdisc_drop(skb, to_free);
*ret = __NET_XMIT_STOLEN;
return NULL;
- case TC_ACT_REDIRECT:
- skb_do_redirect(skb);
- *ret = __NET_XMIT_STOLEN;
- return NULL;
case TC_ACT_CONSUMED:
*ret = __NET_XMIT_STOLEN;
return NULL;
diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c
index a3c185505afc..94eb47ac54ee 100644
--- a/net/sched/sch_cake.c
+++ b/net/sched/sch_cake.c
@@ -1730,7 +1730,7 @@ static u32 cake_classify(struct Qdisc *sch, struct cake_tin_data **t,
goto hash;
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
- result = tcf_classify(skb, NULL, filter, &res, false);
+ result = tcf_classify_qdisc(skb, filter, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c
index 020657f959b5..91b1ef824afa 100644
--- a/net/sched/sch_drr.c
+++ b/net/sched/sch_drr.c
@@ -312,7 +312,7 @@ static struct drr_class *drr_classify(struct sk_buff *skb, struct Qdisc *sch,
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
fl = rcu_dereference_bh(q->filter_list);
- result = tcf_classify(skb, NULL, fl, &res, false);
+ result = tcf_classify_qdisc(skb, fl, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c
index 5434df6ca8ef..98364f74211e 100644
--- a/net/sched/sch_dualpi2.c
+++ b/net/sched/sch_dualpi2.c
@@ -364,7 +364,7 @@ static int dualpi2_skb_classify(struct dualpi2_sched_data *q,
return NET_XMIT_SUCCESS;
}
- result = tcf_classify(skb, NULL, fl, &res, false);
+ result = tcf_classify_qdisc(skb, fl, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c
index cb8cf437ce87..25fcf4079fec 100644
--- a/net/sched/sch_ets.c
+++ b/net/sched/sch_ets.c
@@ -391,7 +391,7 @@ static struct ets_class *ets_classify(struct sk_buff *skb, struct Qdisc *sch,
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
if (TC_H_MAJ(skb->priority) != sch->handle) {
fl = rcu_dereference_bh(q->filter_list);
- err = tcf_classify(skb, NULL, fl, &res, false);
+ err = tcf_classify_qdisc(skb, fl, &res, false);
#ifdef CONFIG_NET_CLS_ACT
switch (err) {
case TC_ACT_STOLEN:
diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c
index cafd1f943d99..6cce86ba383c 100644
--- a/net/sched/sch_fq_codel.c
+++ b/net/sched/sch_fq_codel.c
@@ -91,7 +91,7 @@ static unsigned int fq_codel_classify(struct sk_buff *skb, struct Qdisc *sch,
return fq_codel_hash(q, skb) + 1;
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
- result = tcf_classify(skb, NULL, filter, &res, false);
+ result = tcf_classify_qdisc(skb, filter, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c
index 72f48fa4010b..069e1facd413 100644
--- a/net/sched/sch_fq_pie.c
+++ b/net/sched/sch_fq_pie.c
@@ -96,7 +96,7 @@ static unsigned int fq_pie_classify(struct sk_buff *skb, struct Qdisc *sch,
return fq_pie_hash(q, skb) + 1;
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
- result = tcf_classify(skb, NULL, filter, &res, false);
+ result = tcf_classify_qdisc(skb, filter, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c
index 7e537295b8b6..e87f5021a199 100644
--- a/net/sched/sch_hfsc.c
+++ b/net/sched/sch_hfsc.c
@@ -1143,7 +1143,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr)
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
head = &q->root;
tcf = rcu_dereference_bh(q->root.filter_list);
- while (tcf && (result = tcf_classify(skb, NULL, tcf, &res, false)) >= 0) {
+ while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
case TC_ACT_QUEUED:
diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c
index 908b9ba9ba2e..fdac0dc8f35a 100644
--- a/net/sched/sch_htb.c
+++ b/net/sched/sch_htb.c
@@ -243,7 +243,7 @@ static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch,
}
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
- while (tcf && (result = tcf_classify(skb, NULL, tcf, &res, false)) >= 0) {
+ while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
case TC_ACT_QUEUED:
diff --git a/net/sched/sch_multiq.c b/net/sched/sch_multiq.c
index 4e465d11e3d7..004f0d275caf 100644
--- a/net/sched/sch_multiq.c
+++ b/net/sched/sch_multiq.c
@@ -36,7 +36,7 @@ multiq_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr)
int err;
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
- err = tcf_classify(skb, NULL, fl, &res, false);
+ err = tcf_classify_qdisc(skb, fl, &res, false);
#ifdef CONFIG_NET_CLS_ACT
switch (err) {
case TC_ACT_STOLEN:
diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c
index e4dd56a89072..79437c587e7e 100644
--- a/net/sched/sch_prio.c
+++ b/net/sched/sch_prio.c
@@ -39,7 +39,7 @@ prio_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr)
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
if (TC_H_MAJ(skb->priority) != sch->handle) {
fl = rcu_dereference_bh(q->filter_list);
- err = tcf_classify(skb, NULL, fl, &res, false);
+ err = tcf_classify_qdisc(skb, fl, &res, false);
#ifdef CONFIG_NET_CLS_ACT
switch (err) {
case TC_ACT_STOLEN:
diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c
index cb56787e1d25..6f3b7273cb16 100644
--- a/net/sched/sch_qfq.c
+++ b/net/sched/sch_qfq.c
@@ -709,7 +709,7 @@ static struct qfq_class *qfq_classify(struct sk_buff *skb, struct Qdisc *sch,
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
fl = rcu_dereference_bh(q->filter_list);
- result = tcf_classify(skb, NULL, fl, &res, false);
+ result = tcf_classify_qdisc(skb, fl, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
diff --git a/net/sched/sch_sfb.c b/net/sched/sch_sfb.c
index b1d465094276..ed39869199c0 100644
--- a/net/sched/sch_sfb.c
+++ b/net/sched/sch_sfb.c
@@ -260,7 +260,7 @@ static bool sfb_classify(struct sk_buff *skb, struct tcf_proto *fl,
struct tcf_result res;
int result;
- result = tcf_classify(skb, NULL, fl, &res, false);
+ result = tcf_classify_qdisc(skb, fl, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c
index 758b88f21865..77675f9a4c46 100644
--- a/net/sched/sch_sfq.c
+++ b/net/sched/sch_sfq.c
@@ -171,7 +171,7 @@ static unsigned int sfq_classify(struct sk_buff *skb, struct Qdisc *sch,
return sfq_hash(q, skb) + 1;
*qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS;
- result = tcf_classify(skb, NULL, fl, &res, false);
+ result = tcf_classify_qdisc(skb, fl, &res, false);
if (result >= 0) {
#ifdef CONFIG_NET_CLS_ACT
switch (result) {
Thanks,
Daniel
^ permalink raw reply related
* [PATCH v2] Wireguard: Fix data-race in rx/tx counter
From: Rafael Passos @ 2026-06-29 12:59 UTC (permalink / raw)
To: andrew+netdev, tytso
Cc: rafael, Jason, andrew, davem, edumazet, kuba, linux-kernel,
netdev, pabeni, syzbot+9ca7674fa7521a3f1bc2, syzkaller-bugs,
wireguard
In-Reply-To: <DJL786MPR9HO.BWHI3U37PDVM@rcpassos.me>
Fix data-race in {rx/tx}_bytes counter for wireguard connection.
These values were incremented inside a read_lock_bh block, without
exclusive write protection.
Using per-cpu counters guarantees consistency, and move overhead only to
the read part: the least frequent operation.
Signed-off-by: Rafael Passos <rafael@rcpassos.me>
---
As we discussed in the thread, keeping this counters accurate might not
be worth this extra memory cost of per-cpu counter.
It was great reading and learning about it. I think this would have been
a good patch :)
When looking around drivers/net, I found a few u64_stats uses without
per-cpu. I will do some digging to understand the reasoning behind it,
compared to the original "bare u64 counters" in wireguard.
Thanks,
Rafael Passos
drivers/net/wireguard/netlink.c | 22 ++++++++++++++++++++--
drivers/net/wireguard/peer.c | 7 ++++++-
drivers/net/wireguard/peer.h | 7 ++++++-
drivers/net/wireguard/receive.c | 9 ++++++++-
drivers/net/wireguard/socket.c | 9 +++++++--
5 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireguard/netlink.c b/drivers/net/wireguard/netlink.c
index 1da7e98d0d50..f5978a8c2ca3 100644
--- a/drivers/net/wireguard/netlink.c
+++ b/drivers/net/wireguard/netlink.c
@@ -105,13 +105,31 @@ get_peer(struct wg_peer *peer, struct sk_buff *skb, struct dump_ctx *ctx)
if (fail)
goto err;
+ // read per-cpu counters for peer rx/tx_bytes
+ u64 total_rx_bytes = 0, total_tx_bytes = 0;
+ u64 rx_bytes = 0, tx_bytes = 0;
+ struct wg_peer_stats *pcpu_ptr;
+ unsigned int cpu, start;
+
+ for_each_possible_cpu(cpu) {
+ pcpu_ptr = per_cpu_ptr(peer->pcpu_stats, cpu);
+ do {
+ start = u64_stats_fetch_begin(&pcpu_ptr->syncp);
+ rx_bytes = u64_stats_read(&pcpu_ptr->rx_bytes);
+ tx_bytes = u64_stats_read(&pcpu_ptr->tx_bytes);
+ } while (u64_stats_fetch_retry(&pcpu_ptr->syncp, start));
+
+ total_rx_bytes += rx_bytes;
+ total_tx_bytes += tx_bytes;
+ }
+
if (nla_put(skb, WGPEER_A_LAST_HANDSHAKE_TIME,
sizeof(last_handshake), &last_handshake) ||
nla_put_u16(skb, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
peer->persistent_keepalive_interval) ||
- nla_put_u64_64bit(skb, WGPEER_A_TX_BYTES, peer->tx_bytes,
+ nla_put_u64_64bit(skb, WGPEER_A_TX_BYTES, total_tx_bytes,
WGPEER_A_UNSPEC) ||
- nla_put_u64_64bit(skb, WGPEER_A_RX_BYTES, peer->rx_bytes,
+ nla_put_u64_64bit(skb, WGPEER_A_RX_BYTES, total_rx_bytes,
WGPEER_A_UNSPEC) ||
nla_put_u32(skb, WGPEER_A_PROTOCOL_VERSION, 1))
goto err;
diff --git a/drivers/net/wireguard/peer.c b/drivers/net/wireguard/peer.c
index 1cb502a932e0..a37aa31f132a 100644
--- a/drivers/net/wireguard/peer.c
+++ b/drivers/net/wireguard/peer.c
@@ -36,6 +36,10 @@ struct wg_peer *wg_peer_create(struct wg_device *wg,
if (unlikely(dst_cache_init(&peer->endpoint_cache, GFP_KERNEL)))
goto err;
+ peer->pcpu_stats = alloc_percpu(struct wg_peer_stats);
+ if (unlikely(!peer->pcpu_stats))
+ goto err;
+
peer->device = wg;
wg_noise_handshake_init(&peer->handshake, &wg->static_identity,
public_key, preshared_key, peer);
@@ -189,7 +193,8 @@ static void rcu_release(struct rcu_head *rcu)
dst_cache_destroy(&peer->endpoint_cache);
WARN_ON(wg_prev_queue_peek(&peer->tx_queue) || wg_prev_queue_peek(&peer->rx_queue));
-
+
+ free_percpu(peer->pcpu_stats);
/* The final zeroing takes care of clearing any remaining handshake key
* material and other potentially sensitive information.
*/
diff --git a/drivers/net/wireguard/peer.h b/drivers/net/wireguard/peer.h
index 718fb42bdac7..e01781724aa1 100644
--- a/drivers/net/wireguard/peer.h
+++ b/drivers/net/wireguard/peer.h
@@ -34,6 +34,11 @@ struct endpoint {
};
};
+struct wg_peer_stats {
+ u64_stats_t rx_bytes, tx_bytes;
+ struct u64_stats_sync syncp;
+};
+
struct wg_peer {
struct wg_device *device;
struct prev_queue tx_queue, rx_queue;
@@ -49,7 +54,7 @@ struct wg_peer {
struct work_struct transmit_handshake_work, clear_peer_work, transmit_packet_work;
struct cookie latest_cookie;
struct hlist_node pubkey_hash;
- u64 rx_bytes, tx_bytes;
+ struct wg_peer_stats __percpu *pcpu_stats;
struct timer_list timer_retransmit_handshake, timer_send_keepalive;
struct timer_list timer_new_handshake, timer_zero_key_material;
struct timer_list timer_persistent_keepalive;
diff --git a/drivers/net/wireguard/receive.c b/drivers/net/wireguard/receive.c
index eb8851113654..d799370122b5 100644
--- a/drivers/net/wireguard/receive.c
+++ b/drivers/net/wireguard/receive.c
@@ -19,8 +19,15 @@
/* Must be called with bh disabled. */
static void update_rx_stats(struct wg_peer *peer, size_t len)
{
+ struct wg_peer_stats *pcpu_ptr;
+
dev_sw_netstats_rx_add(peer->device->dev, len);
- peer->rx_bytes += len;
+
+ pcpu_ptr = this_cpu_ptr(peer->pcpu_stats);
+
+ u64_stats_update_begin(&pcpu_ptr->syncp);
+ u64_stats_add(&pcpu_ptr->rx_bytes, len);
+ u64_stats_update_end(&pcpu_ptr->syncp);
}
#define SKB_TYPE_LE32(skb) (((struct message_header *)(skb)->data)->type)
diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c
index 0028ef17dc71..685ae6a0fb2c 100644
--- a/drivers/net/wireguard/socket.c
+++ b/drivers/net/wireguard/socket.c
@@ -166,6 +166,7 @@ static int send6(struct wg_device *wg, struct sk_buff *skb,
int wg_socket_send_skb_to_peer(struct wg_peer *peer, struct sk_buff *skb, u8 ds)
{
+ struct wg_peer_stats *pcpu_ptr;
size_t skb_len = skb->len;
int ret = -EAFNOSUPPORT;
@@ -178,8 +179,12 @@ int wg_socket_send_skb_to_peer(struct wg_peer *peer, struct sk_buff *skb, u8 ds)
&peer->endpoint_cache);
else
dev_kfree_skb(skb);
- if (likely(!ret))
- peer->tx_bytes += skb_len;
+ if (likely(!ret)) {
+ pcpu_ptr = this_cpu_ptr(peer->pcpu_stats);
+ u64_stats_update_begin(&pcpu_ptr->syncp);
+ u64_stats_add(&pcpu_ptr->tx_bytes, skb_len);
+ u64_stats_update_end(&pcpu_ptr->syncp);
+ }
read_unlock_bh(&peer->endpoint_lock);
return ret;
--
2.53.0
^ permalink raw reply related
* [PATCH] selftests/bpf: Mask socket type flags in mptcpify prog
From: Guillaume @layus Maudoux @ 2026-06-29 12:56 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau
Cc: Eduard Zingerman, Matthieu Baerts, Mat Martineau, Geliang Tang,
Shuah Khan, bpf, mptcp, netdev, linux-kselftest, linux-kernel,
Guillaume @layus Maudoux
The mptcpify BPF prog hooks update_socket_protocol() to rewrite
eligible TCP socket() calls to IPPROTO_MPTCP. It only does so when the
socket type is exactly SOCK_STREAM:
type == SOCK_STREAM
The problem is that update_socket_protocol() in __sys_socket() is
called on the raw type argument as passed from userspace, before
__sys_socket_create() strips the flag bits with
"type &= SOCK_TYPE_MASK". The type argument may therefore carry
SOCK_CLOEXEC and/or SOCK_NONBLOCK in its upper bits, and the equality
check above then fails.
As a result, any socket created with e.g.
socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0) -- which is what common
libraries do by default -- is silently left as plain TCP instead of
being upgraded to MPTCP. This was observed in practice with curl,
whose connections were not upgraded to MPTCP despite the prog being
attached.
The impact reaches beyond the test, because mptcpify.c is referenced
as example code for users who want to transparently enable MPTCP. The
same mistake is therefore likely to be copied into real deployments,
where it fails the same way and is hard to diagnose.
The fix is to mask off the flag bits before comparing, mirroring what
the socket core does:
(type & SOCK_TYPE_MASK) == SOCK_STREAM
Since SOCK_TYPE_MASK is not exposed through vmlinux.h, define it in
bpf_tracing_net.h.
To exercise the regression directly, extend the mptcpify test to also
create the server socket with SOCK_CLOEXEC and SOCK_NONBLOCK set.
Routing a flagged type through start_server() then revealed a second
instance of the same pattern: start_server_addr() compared the type
against SOCK_STREAM for equality to decide whether to set SO_REUSEADDR
and call listen(), and so would skip listening for a flagged type.
Mask the type there as well. As SOCK_TYPE_MASK is not exposed by
glibc's <sys/socket.h> either, define it in network_helpers.h,
mirroring prog_tests/socket_helpers.h.
Fixes: ddba122428a7 ("selftests/bpf: Add mptcpify test")
Signed-off-by: Guillaume @layus Maudoux <layus.on@gmail.com>
---
tools/testing/selftests/bpf/network_helpers.c | 4 ++--
tools/testing/selftests/bpf/network_helpers.h | 5 +++++
.../testing/selftests/bpf/prog_tests/mptcp.c | 20 +++++++++++++++----
.../selftests/bpf/progs/bpf_tracing_net.h | 3 +++
tools/testing/selftests/bpf/progs/mptcpify.c | 2 +-
5 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/bpf/network_helpers.c b/tools/testing/selftests/bpf/network_helpers.c
index b82f572641b7..db935a9d9fc1 100644
--- a/tools/testing/selftests/bpf/network_helpers.c
+++ b/tools/testing/selftests/bpf/network_helpers.c
@@ -111,7 +111,7 @@ int start_server_addr(int type, const struct sockaddr_storage *addr, socklen_t a
if (settimeo(fd, opts->timeout_ms))
goto error_close;
- if (type == SOCK_STREAM &&
+ if ((type & SOCK_TYPE_MASK) == SOCK_STREAM &&
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
log_err("Failed to enable SO_REUSEADDR");
goto error_close;
@@ -128,7 +128,7 @@ int start_server_addr(int type, const struct sockaddr_storage *addr, socklen_t a
goto error_close;
}
- if (type == SOCK_STREAM) {
+ if ((type & SOCK_TYPE_MASK) == SOCK_STREAM) {
if (listen(fd, opts->backlog ? MAX(opts->backlog, 0) : 1) < 0) {
log_err("Failed to listed on socket");
goto error_close;
diff --git a/tools/testing/selftests/bpf/network_helpers.h b/tools/testing/selftests/bpf/network_helpers.h
index 79a010c88e11..75133119c04a 100644
--- a/tools/testing/selftests/bpf/network_helpers.h
+++ b/tools/testing/selftests/bpf/network_helpers.h
@@ -25,6 +25,11 @@ typedef __u16 __sum16;
#define VIP_NUM 5
#define MAGIC_BYTES 123
+/* include/linux/net.h */
+#ifndef SOCK_TYPE_MASK
+#define SOCK_TYPE_MASK 0xf
+#endif
+
struct network_helper_opts {
int timeout_ms;
int proto;
diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing/selftests/bpf/prog_tests/mptcp.c
index 8fade8bdc451..faa001ea84ab 100644
--- a/tools/testing/selftests/bpf/prog_tests/mptcp.c
+++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c
@@ -264,7 +264,7 @@ static int verify_mptcpify(int server_fd, int client_fd)
return err;
}
-static int run_mptcpify(int cgroup_fd)
+static int run_mptcpify(int cgroup_fd, int type)
{
int server_fd, client_fd, err = 0;
struct mptcpify *mptcpify_skel;
@@ -280,7 +280,7 @@ static int run_mptcpify(int cgroup_fd)
goto out;
/* without MPTCP */
- server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
+ server_fd = start_server(AF_INET, type, NULL, 0, 0);
if (!ASSERT_GE(server_fd, 0, "start_server")) {
err = -EIO;
goto out;
@@ -307,7 +307,18 @@ static int run_mptcpify(int cgroup_fd)
static void test_mptcpify(void)
{
struct netns_obj *netns = NULL;
- int cgroup_fd;
+ int cgroup_fd, i;
+ int types[] = {
+ SOCK_STREAM,
+ /* userspace sets these flags together with the type, and the
+ * BPF prog must still upgrade the socket to MPTCP. See
+ * update_socket_protocol() in net/socket.c, which runs before
+ * the type is masked with SOCK_TYPE_MASK.
+ */
+ SOCK_STREAM | SOCK_CLOEXEC,
+ SOCK_STREAM | SOCK_NONBLOCK,
+ SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ };
cgroup_fd = test__join_cgroup("/mptcpify");
if (!ASSERT_GE(cgroup_fd, 0, "test__join_cgroup"))
@@ -317,7 +328,8 @@ static void test_mptcpify(void)
if (!ASSERT_OK_PTR(netns, "netns_new"))
goto fail;
- ASSERT_OK(run_mptcpify(cgroup_fd), "run_mptcpify");
+ for (i = 0; i < ARRAY_SIZE(types); i++)
+ ASSERT_OK(run_mptcpify(cgroup_fd, types[i]), "run_mptcpify");
fail:
netns_free(netns);
diff --git a/tools/testing/selftests/bpf/progs/bpf_tracing_net.h b/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
index d8dacef37c16..c4b438854565 100644
--- a/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
+++ b/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
@@ -8,6 +8,9 @@
#define AF_INET 2
#define AF_INET6 10
+/* include/linux/net.h */
+#define SOCK_TYPE_MASK 0xf
+
#define SOL_SOCKET 1
#define SO_REUSEADDR 2
#define SO_SNDBUF 7
diff --git a/tools/testing/selftests/bpf/progs/mptcpify.c b/tools/testing/selftests/bpf/progs/mptcpify.c
index cbdc730c3a47..e3f8cb54dbe9 100644
--- a/tools/testing/selftests/bpf/progs/mptcpify.c
+++ b/tools/testing/selftests/bpf/progs/mptcpify.c
@@ -15,7 +15,7 @@ int BPF_PROG(mptcpify, int family, int type, int protocol)
return protocol;
if ((family == AF_INET || family == AF_INET6) &&
- type == SOCK_STREAM &&
+ (type & SOCK_TYPE_MASK) == SOCK_STREAM &&
(!protocol || protocol == IPPROTO_TCP)) {
return IPPROTO_MPTCP;
}
--
2.54.0
^ permalink raw reply related
* Re: [PATCH 0/5] netfilter: nf_flow_table_path: L2 bridge offload
From: Florian Westphal @ 2026-06-29 12:56 UTC (permalink / raw)
To: Daniel Pawlik
Cc: netfilter-devel, netdev, pablo, phil, davem, edumazet, kuba,
pabeni, horms, andrew+netdev, razor, idosch, matthias.bgg,
angelogioacchino.delregno, bridge, coreteam, linux-mediatek,
linux-arm-kernel, rchen14b, lorenzo
In-Reply-To: <20260629123253.1912621-1-pawlik.dan@gmail.com>
Daniel Pawlik <pawlik.dan@gmail.com> wrote:
> -----------------------------
> 1. Load kmod-br-netfilter so that bridged IP traffic traverses the
> netfilter forward chain.
Ouch. br_netfilter should die. Really. Its a gross hack, never
use this thing...
> 2. Enable netfilter hooks on the bridge:
> echo 1 > /sys/class/net/<br>/bridge/nf_call_iptables
> echo 1 > /sys/class/net/<br>/bridge/nf_call_ip6tables
>
> 3. Register bridge member interfaces in the nft flowtable:
> table inet filter {
> flowtable f {
> hook ingress priority filter
> devices = { eth0, wlan0 }
> }
I think that bridge flowtable should use 'table bridge ...', not
use the br_netfilter compat hacks.
Sorry.
Are you aware of Eric Woudstras bridge flowtable patches?
https://lore.kernel.org/netfilter-devel/20250408142802.96101-5-ericwouds@gmail.com/
^ permalink raw reply
* Re: [PATCH RFC 0/8] clk: sunxi-ng: Add support for Allwinner A733 CCU and PRCM
From: Jerome Brunet @ 2026-06-29 12:52 UTC (permalink / raw)
To: Junhui Liu
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Philipp Zabel, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Richard Cochran, linux-clk, devicetree,
linux-arm-kernel, linux-sunxi, linux-kernel, linux-riscv, netdev
In-Reply-To: <20260310-a733-clk-v1-0-36b4e9b24457@pigmoral.tech>
On mar. 10 mars 2026 at 16:33, Junhui Liu <junhui.liu@pigmoral.tech> wrote:
> Add support for the main CCU and the PRCM module (R-CCU) found in the
> Allwinner A733 SoC. The clock architecture of the A733 is an evolution
> of the previous A523 design but introduces several significant changes.
>
> One of the key changes is the introduction of a "pll-ref" clock that
> normalizes the physical oscillator frequency (which can be 19.2MHz,
> 24MHz, or 26MHz) into a consistent 24MHz reference for the entire clock
> tree. Additionally, while the A733 inherits many module clock structures
> from the A523, the MCU_CCU has been removed, and the overall clock tree
> has been expanded to support more new functional units.
>
> Also update the sunxi-ng SDM (Sigma-Delta Modulation) helper to support
> a new dual-pattern register design. On the A733, the SDM enable bit has
> been moved from the main PLL register to a second pattern register
> (PATTERN1). The driver is updated to handle this register layout to
> ensure accurate frequency synthesis for "pll-audio0".
>
> This is marked as RFC because the parent clocks for several instances in
> the main CCU are difficult to determine as the user manual provides
> limited information on their specific clock sources. In these cases, the
> implementation follows vendor practices and previous SoC designs,
> generally defaulting to "hosc" where documentation is lacking. In
> contrast, the bus clock gates in the PRCM (R-CCU) are explicitly defined
> based on the Memory Map in the manual, which clearly associates each
> module with its respective bus. Feedback or insights on these specific
> clock parents would be greatly appreciated.
>
> This functionally relies on the RTC series for the A733 SoC [1].
>
> Link: https://lore.kernel.org/all/20260121-a733-rtc-v1-0-d359437f23a7@pigmoral.tech/ [1]
>
Tested-by: Jerome Brunet <jbrunet@baylibre.com>
> ---
> Junhui Liu (8):
> dt-bindings: clk: sun60i-a733-ccu: Add allwinner A733 support
> clk: sunxi-ng: sdm: Add dual patterns support
> clk: sunxi-ng: a733: Add PRCM CCU
> clk: sunxi-ng: a733: Add PLL clocks support
> clk: sunxi-ng: a733: Add bus clocks support
> clk: sunxi-ng: a733: Add mod clocks support
> clk: sunxi-ng: a733: Add bus clock gates
> clk: sunxi-ng: a733: Add reset lines
>
> .../bindings/clock/allwinner,sun60i-a733-ccu.yaml | 107 +
> drivers/clk/sunxi-ng/Kconfig | 10 +
> drivers/clk/sunxi-ng/Makefile | 4 +
> drivers/clk/sunxi-ng/ccu-sun60i-a733-r.c | 276 +++
> drivers/clk/sunxi-ng/ccu-sun60i-a733.c | 2375 ++++++++++++++++++++
> drivers/clk/sunxi-ng/ccu_sdm.c | 51 +-
> drivers/clk/sunxi-ng/ccu_sdm.h | 32 +-
> include/dt-bindings/clock/sun60i-a733-ccu.h | 289 +++
> include/dt-bindings/clock/sun60i-a733-r-ccu.h | 39 +
> include/dt-bindings/reset/sun60i-a733-ccu.h | 131 ++
> include/dt-bindings/reset/sun60i-a733-r-ccu.h | 23 +
> 11 files changed, 3311 insertions(+), 26 deletions(-)
> ---
> base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
> change-id: 20260202-a733-clk-0d4fc00a9f9c
>
> Best regards,
--
Jerome
^ permalink raw reply
* Re: [PATCH 3/7] cpufreq: rcpufreq_dt: use vertical import style
From: Zhongqiu Han @ 2026-06-29 12:43 UTC (permalink / raw)
To: Guru Das Srinagesh, Miguel Ojeda, rust-for-linux, linux-kernel
Cc: Danilo Krummrich, Abdiel Janulgue, Daniel Almeida, Robin Murphy,
Andreas Hindborg, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Drew Fustini, Guo Ren, Fu Wei,
Michal Wilczynski, Uwe Kleine-König, Rafael J. Wysocki,
Viresh Kumar, Jens Axboe, FUJITA Tomonori, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, David Airlie, Simona Vetter,
driver-core, linux-riscv, linux-pwm, linux-pm, linux-block,
netdev, nova-gpu, dri-devel, zhongqiu.han
In-Reply-To: <20260628-b4-rust-vertical-imports-v1-3-98bc71d4810b@gurudas.dev>
On 6/29/2026 11:38 AM, Guru Das Srinagesh wrote:
> Convert `use` imports to vertical layout for better readability and
> maintainability.
>
> Signed-off-by: Guru Das Srinagesh <linux@gurudas.dev>
> ---
> drivers/cpufreq/rcpufreq_dt.rs | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
Hi Guru Das,
> diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs
> index 10106fa13095..6f83cf8955a6 100644
> --- a/drivers/cpufreq/rcpufreq_dt.rs
> +++ b/drivers/cpufreq/rcpufreq_dt.rs
> @@ -6,7 +6,10 @@
> clk::Clk,
> cpu, cpufreq,
The change seems reasonable according to the Rust coding guidelines:
https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git/tree/Documentation/rust/coding-guidelines.rst#n44
"each item goes into its own line, and braces are used as soon as
there is more than one item in a list."
If the preferred style is to place each imported item on its own line,
shouldn't imports such as
cpu, cpufreq,
be formatted similarly as well? Have you run: "make LLVM=1 rustfmtcheck"
on this change?
> cpumask::CpumaskVar,
> - device::{Core, Device},
> + device::{
> + Core,
> + Device, //
> + },
> error::code::*,
> macros::vtable,
> module_platform_driver, of, opp, platform,
Likewise?
>
--
Thx and BRs,
Zhongqiu Han
^ permalink raw reply
* Re: [PATCH v2 00/12] rust: driver: use pointers instead of indices for ID info
From: Gary Guo @ 2026-06-29 12:41 UTC (permalink / raw)
To: Gary Guo, Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb
In-Reply-To: <20260629-id_info-v2-0-95a18280fe30@garyguo.net>
On Mon Jun 29, 2026 at 1:38 PM BST, Gary Guo wrote:
> Most C drivers use a pointer (and cast to kernel_ulong_t) for driver_data
> fields in device_id. Rust code currently does not do this, but rather use
> indices. These indices then needs to be translated to `&IdInfo` separately
> and this is by a side table.
>
> This leads to open-coded ACPI/OF handling in driver.rs, which is not
> desirable. Convert the code to use pointers (or rather, static references)
> instead.
>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
> Changes in v2:
> - Change USB to take `Option<&IdInfo>` in addition to PCI due to ability to
> dynamically add IDs.
> - Mention dyn IDs and driver_override in safety comments and justify why
> they're correct.
> - Link to v1: https://patch.msgid.link/20260618-id_info-v1-0-96af1e559ef9@garyguo.net
Oops, this cover letter is sent by mistake. Please see the 11-patch series.
Best,
Gary
^ permalink raw reply
* [PATCH v2 11/11] RFC: rust: driver: support map-like syntax for ID table
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
The device ID table and its associated info is really just a map. Add a
syntax to `module_device_table` macro that reflects that.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/device_id.rs | 11 +++++++++++
samples/rust/rust_driver_pci.rs | 7 +++----
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index c81fca5b4986..5f4d191fce51 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -183,6 +183,17 @@ macro_rules! module_device_table {
$table_type: literal, $device_id_ty: ty,
$table_name: ident, $id_info_type: ty,
[$(($id: expr, $info:expr $(,)?)),* $(,)?]
+ ) => {
+ $crate::module_device_table!(
+ $table_type, $device_id_ty, $table_name, $id_info_type,
+ {$($id=>$info,)*}
+ );
+ };
+
+ (
+ $table_type: literal, $device_id_ty: ty,
+ $table_name: ident, $id_info_type: ty,
+ {$($id: expr => $info:expr),* $(,)?}
) => {
#[export_name =
concat!("__mod_device_table__", ::core::line!(),
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 2282191e6292..652819dff082 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -75,10 +75,9 @@ struct SampleDriverData<'bound> {
kernel::pci_device_table!(
PCI_TABLE,
<SampleDriver as pci::Driver>::IdInfo,
- [(
- pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5),
- TestIndex::NO_EVENTFD
- )]
+ {
+ pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5) => TestIndex::NO_EVENTFD,
+ }
);
impl SampleDriverData<'_> {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 10/11] rust: driver: remove duplicate ID table
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
Previously, `IdArray` contains both device ID table and info table so we
keep a separate copy for MODULE_DEVICE_TABLE for hotplug (which needs to be
just the device ID table). With the info being changed to be carried via
pointers, `IdArray` is now layout compatible with raw ID table and hence
there is no longer a need to keep the distinction.
Deduplicate the code, and remove the redundant copy for hotplug purpose by
just giving the `IdArray` instance a proper symbol name.
While at it, also update the macro to use `::core::line!()` instead of just
`line!()`.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/device_id.rs | 76 +++++++++++++++++-------------------------------
1 file changed, 27 insertions(+), 49 deletions(-)
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index 50d82bfca9b8..c81fca5b4986 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -86,28 +86,23 @@ unsafe fn info_unchecked_opt<U>(&self) -> Option<&'static U> {
}
}
-/// A zero-terminated device id array.
+/// A zero-terminated device id array, followed by context data.
#[repr(C)]
-pub struct RawIdArray<T: RawDeviceId, const N: usize> {
+pub struct IdArray<T: RawDeviceId, U: 'static, const N: usize> {
// This is `MaybeUninit<T::RawType>` so any bytes inside it can carry provenance in CTFE.
// If this were `T::RawType`, integer fields would not be able to contain pointers.
ids: [MaybeUninit<T::RawType>; N],
sentinel: MaybeUninit<T::RawType>,
+ phantom: PhantomData<&'static U>,
}
-impl<T: RawDeviceId, const N: usize> RawIdArray<T, N> {
- #[doc(hidden)]
- pub const fn size(&self) -> usize {
- core::mem::size_of::<Self>()
- }
-}
+// SAFETY: device ID is plain data plus a `&'static U` and can thus be sent between threads safely
+// if `&U` can.
+unsafe impl<T: RawDeviceId, U: Sync + 'static, const N: usize> Send for IdArray<T, U, N> {}
-/// A zero-terminated device id array, followed by context data.
-#[repr(C)]
-pub struct IdArray<T: RawDeviceId, U: 'static, const N: usize> {
- raw_ids: RawIdArray<T, N>,
- phantom: PhantomData<&'static U>,
-}
+// SAFETY: device ID is plain data plus a `&'static U` and can thus be shared between threads safely
+// if `&U` can.
+unsafe impl<T: RawDeviceId, U: Sync + 'static, const N: usize> Sync for IdArray<T, U, N> {}
impl<T: RawDeviceId + RawDeviceIdIndex, U: 'static, const N: usize> IdArray<T, U, N> {
/// Creates a new instance of the array.
@@ -137,22 +132,13 @@ impl<T: RawDeviceId + RawDeviceIdIndex, U: 'static, const N: usize> IdArray<T, U
core::mem::forget(ids);
Self {
- raw_ids: RawIdArray {
- ids: raw_ids,
- sentinel: MaybeUninit::zeroed(),
- },
+ ids: raw_ids,
+ sentinel: MaybeUninit::zeroed(),
phantom: PhantomData,
}
}
}
-impl<T: RawDeviceId, U: 'static, const N: usize> IdArray<T, U, N> {
- /// Reference to the contained [`RawIdArray`].
- pub const fn raw_ids(&self) -> &RawIdArray<T, N> {
- &self.raw_ids
- }
-}
-
impl<T: RawDeviceId, const N: usize> IdArray<T, (), N> {
/// Creates a new instance of the array without writing index values.
///
@@ -164,10 +150,8 @@ impl<T: RawDeviceId, const N: usize> IdArray<T, (), N> {
core::mem::forget(ids);
Self {
- raw_ids: RawIdArray {
- ids: raw_ids,
- sentinel: MaybeUninit::zeroed(),
- },
+ ids: raw_ids,
+ sentinel: MaybeUninit::zeroed(),
phantom: PhantomData,
}
}
@@ -200,13 +184,17 @@ macro_rules! module_device_table {
$table_name: ident, $id_info_type: ty,
[$(($id: expr, $info:expr $(,)?)),* $(,)?]
) => {
- const $table_name: $crate::device_id::IdArray<
+ #[export_name =
+ concat!("__mod_device_table__", ::core::line!(),
+ "__kmod_", module_path!(),
+ "__", $table_type,
+ "__", stringify!($table_name))
+ ]
+ static $table_name: $crate::device_id::IdArray<
$device_id_ty,
$id_info_type,
{ <[$device_id_ty]>::len(&[$($id,)*]) },
> = $crate::device_id::IdArray::new([$(($id, &$info),)*]);
-
- $crate::module_device_table!($table_type, $table_name);
};
// Case for no ID info.
@@ -215,26 +203,16 @@ macro_rules! module_device_table {
$table_name: ident, @none,
[$($id: expr),* $(,)?]
) => {
- const $table_name: $crate::device_id::IdArray<
+ #[export_name =
+ concat!("__mod_device_table__", ::core::line!(),
+ "__kmod_", module_path!(),
+ "__", $table_type,
+ "__", stringify!($table_name))
+ ]
+ static $table_name: $crate::device_id::IdArray<
$device_id_ty,
(),
{ <[$device_id_ty]>::len(&[$($id,)*]) },
> = $crate::device_id::IdArray::new_without_index([$($id),*]);
-
- $crate::module_device_table!($table_type, $table_name);
- };
-
- ($table_type: literal, $table_name:ident) => {
- const _: () = {
- #[rustfmt::skip]
- #[export_name =
- concat!("__mod_device_table__", line!(),
- "__kmod_", module_path!(),
- "__", $table_type,
- "__", stringify!($table_name))
- ]
- static MOD_DEVICE_TABLE: [::core::mem::MaybeUninit<u8>; $table_name.raw_ids().size()] =
- unsafe { ::core::mem::transmute_copy($table_name.raw_ids()) };
- };
};
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 09/11] rust: driver: remove open-coded matching logic
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
With device ID info now including pointers instead of indices, the
open-coded ACPI/OF matching is no longer needed and can be replaced with
`device_get_match_data`.
With the removal of open-coded matching, the exposed functions and helpers
are also removed; this effectively reverts most of commit 2690d071584e
("rust: ACPI: fix missing match data for PRP0001").
Signed-off-by: Gary Guo <gary@garyguo.net>
---
MAINTAINERS | 1 -
drivers/acpi/bus.c | 6 +--
include/acpi/acpi_bus.h | 11 ----
rust/helpers/acpi.c | 16 ------
rust/helpers/helpers.c | 1 -
rust/kernel/driver.rs | 137 ++++--------------------------------------------
rust/kernel/i2c.rs | 6 ++-
rust/kernel/platform.rs | 3 +-
8 files changed, 20 insertions(+), 161 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..07240e0b7451 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -297,7 +297,6 @@ F: include/linux/acpi.h
F: include/linux/fwnode.h
F: include/linux/fw_table.h
F: lib/fw_table.c
-F: rust/helpers/acpi.c
F: rust/kernel/acpi.rs
F: tools/power/acpi/
diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
index a30a904f6535..1fb0b10bf924 100644
--- a/drivers/acpi/bus.c
+++ b/drivers/acpi/bus.c
@@ -898,9 +898,9 @@ const struct acpi_device *acpi_companion_match(const struct device *dev)
* identifiers and a _DSD object with the "compatible" property, use that
* property to match against the given list of identifiers.
*/
-bool acpi_of_match_device(const struct acpi_device *adev,
- const struct of_device_id *of_match_table,
- const struct of_device_id **of_id)
+static bool acpi_of_match_device(const struct acpi_device *adev,
+ const struct of_device_id *of_match_table,
+ const struct of_device_id **of_id)
{
const union acpi_object *of_compatible, *obj;
int i, nval;
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 714d111d8053..7e57f9698f7c 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -189,10 +189,6 @@ struct acpi_driver {
* -----------
*/
-bool acpi_of_match_device(const struct acpi_device *adev,
- const struct of_device_id *of_match_table,
- const struct of_device_id **of_id);
-
/* Status (_STA) */
struct acpi_device_status {
@@ -1000,13 +996,6 @@ int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices);
u32 arch_acpi_add_auto_dep(acpi_handle handle);
#else /* CONFIG_ACPI */
-static inline bool acpi_of_match_device(const struct acpi_device *adev,
- const struct of_device_id *of_match_table,
- const struct of_device_id **of_id)
-{
- return false;
-}
-
static inline int register_acpi_bus_type(void *bus) { return 0; }
static inline int unregister_acpi_bus_type(void *bus) { return 0; }
diff --git a/rust/helpers/acpi.c b/rust/helpers/acpi.c
deleted file mode 100644
index e75c9807bbad..000000000000
--- a/rust/helpers/acpi.c
+++ /dev/null
@@ -1,16 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-#include <linux/acpi.h>
-#include <acpi/acpi_bus.h>
-
-__rust_helper bool rust_helper_acpi_of_match_device(const struct acpi_device *adev,
- const struct of_device_id *of_match_table,
- const struct of_device_id **of_id)
-{
- return acpi_of_match_device(adev, of_match_table, of_id);
-}
-
-__rust_helper struct acpi_device *rust_helper_to_acpi_device_node(struct fwnode_handle *fwnode)
-{
- return to_acpi_device_node(fwnode);
-}
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 998e31052e66..fadd7acdd81c 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -38,7 +38,6 @@
#define __rust_helper __always_inline
#endif
-#include "acpi.c"
#include "atomic.c"
#include "atomic_ext.c"
#include "auxiliary.c"
diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
index 824899d76fed..c9c74c4dde8f 100644
--- a/rust/kernel/driver.rs
+++ b/rust/kernel/driver.rs
@@ -107,7 +107,6 @@
use crate::{
acpi,
device,
- device_id::RawDeviceIdIndex,
of,
prelude::*,
types::Opaque,
@@ -292,26 +291,6 @@ fn init(
}
}
-// Calling the FFI function directly from the `Adapter` impl may result in it being called
-// directly from driver modules. This happens since the Rust compiler will use monomorphisation, so
-// it might happen that functions are instantiated within the calling driver module. For now, work
-// around this with `#[inline(never)]` helpers.
-//
-// TODO: Remove once a more generic solution has been implemented. For instance, we may be able to
-// leverage `bindgen` to take care of this depending on whether a symbol is (already) exported.
-#[inline(never)]
-#[allow(clippy::missing_safety_doc)]
-#[allow(dead_code)]
-#[must_use]
-unsafe fn acpi_of_match_device(
- adev: *const bindings::acpi_device,
- of_match_table: *const bindings::of_device_id,
- of_id: *mut *const bindings::of_device_id,
-) -> bool {
- // SAFETY: Safety requirements are the same as `bindings::acpi_of_match_device`.
- unsafe { bindings::acpi_of_match_device(adev, of_match_table, of_id) }
-}
-
/// The bus independent adapter to match a drivers and a devices.
///
/// This trait should be implemented by the bus specific adapter, which represents the connection
@@ -325,117 +304,23 @@ pub trait Adapter {
/// The [`acpi::IdTable`] of the corresponding driver
fn acpi_id_table() -> Option<acpi::IdTable<Self::IdInfo>>;
- /// Returns the driver's private data from the matching entry in the [`acpi::IdTable`], if any.
- ///
- /// If this returns `None`, it means there is no match with an entry in the [`acpi::IdTable`].
- fn acpi_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
- #[cfg(not(CONFIG_ACPI))]
- {
- let _ = dev;
- None
- }
-
- #[cfg(CONFIG_ACPI)]
- {
- let table = Self::acpi_id_table()?;
-
- // SAFETY:
- // - `table` has static lifetime, hence it's valid for read,
- // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`.
- let raw_id = unsafe { bindings::acpi_match_device(table.as_ptr(), dev.as_raw()) };
-
- if raw_id.is_null() {
- None
- } else {
- // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct acpi_device_id`
- // and does not add additional invariants, so it's safe to transmute.
- let id = unsafe { &*raw_id.cast::<acpi::DeviceId>() };
-
- // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
- Some(unsafe { id.info_unchecked::<Self::IdInfo>() })
- }
- }
- }
-
/// The [`of::IdTable`] of the corresponding driver.
fn of_id_table() -> Option<of::IdTable<Self::IdInfo>>;
- /// Returns the driver's private data from the matching entry in the [`of::IdTable`], if any.
- ///
- /// If this returns `None`, it means there is no match with an entry in the [`of::IdTable`].
- fn of_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
- let table = Self::of_id_table()?;
-
- #[cfg(not(any(CONFIG_OF, CONFIG_ACPI)))]
- {
- let _ = (dev, table);
- }
-
- #[cfg(CONFIG_OF)]
- {
- // SAFETY:
- // - `table` has static lifetime, hence it's valid for read,
- // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`.
- let raw_id = unsafe { bindings::of_match_device(table.as_ptr(), dev.as_raw()) };
-
- if !raw_id.is_null() {
- // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct of_device_id`
- // and does not add additional invariants, so it's safe to transmute.
- let id = unsafe { &*raw_id.cast::<of::DeviceId>() };
-
- // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
- return Some(unsafe { id.info_unchecked::<Self::IdInfo>() });
- }
- }
-
- #[cfg(CONFIG_ACPI)]
- {
- use core::ptr;
- use device::property::FwNode;
-
- let mut raw_id = ptr::null();
-
- let fwnode = dev.fwnode().map_or(ptr::null_mut(), FwNode::as_raw);
-
- // SAFETY: `fwnode` is a pointer to a valid `fwnode_handle`. A null pointer will be
- // passed through the function.
- let adev = unsafe { bindings::to_acpi_device_node(fwnode) };
-
- // SAFETY:
- // - `adev` is a valid pointer to `acpi_device` or is null. It is guaranteed to be
- // valid as long as `dev` is alive.
- // - `table` has static lifetime, hence it's valid for read.
- if unsafe { acpi_of_match_device(adev, table.as_ptr(), &raw mut raw_id) } {
- // SAFETY:
- // - the function returns true, therefore `raw_id` has been set to a pointer to a
- // valid `of_device_id`.
- // - `DeviceId` is a `#[repr(transparent)]` wrapper of `struct of_device_id`
- // and does not add additional invariants, so it's safe to transmute.
- let id = unsafe { &*raw_id.cast::<of::DeviceId>() };
-
- // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
- return Some(unsafe { id.info_unchecked::<Self::IdInfo>() });
- }
- }
-
- None
- }
-
/// Returns the driver's private data from the matching entry of any of the ID tables, if any.
///
/// If this returns `None`, it means that there is no match in any of the ID tables directly
/// associated with a [`device::Device`].
- fn id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
- let id = Self::acpi_id_info(dev);
- if id.is_some() {
- return id;
- }
-
- let id = Self::of_id_info(dev);
- if id.is_some() {
- return id;
- }
-
- None
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that the `dev` matched data is of type `Self::IdInfo`.
+ #[inline]
+ unsafe fn id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
+ // SAFETY: `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`.
+ let data = unsafe { bindings::device_get_match_data(dev.as_raw()) };
+
+ // SAFETY: Per safety requirement, `data` is of type `Self::IdInfo`.
+ unsafe { data.cast::<Self::IdInfo>().as_ref() }
}
}
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index 31f2cfe6be31..2d2191164e25 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -149,8 +149,10 @@ extern "C" fn probe_callback(idev: *mut bindings::i2c_client) -> kernel::ffi::c_
// INVARIANT: `idev` is valid for the duration of `probe_callback()`.
let idev = unsafe { &*idev.cast::<I2cClient<device::CoreInternal<'_>>>() };
- let info =
- Self::i2c_id_info(idev).or_else(|| <Self as driver::Adapter>::id_info(idev.as_ref()));
+ let info = Self::i2c_id_info(idev).or_else(|| {
+ // SAFETY: `idev` matched data is of type `Self::IdInfo`.
+ unsafe { <Self as driver::Adapter>::id_info(idev.as_ref()) }
+ });
from_result(|| {
let data = T::probe(idev, info);
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index 210a815925ce..e12e88113ca5 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -100,7 +100,8 @@ extern "C" fn probe_callback(pdev: *mut bindings::platform_device) -> kernel::ff
//
// INVARIANT: `pdev` is valid for the duration of `probe_callback()`.
let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal<'_>>>() };
- let info = <Self as driver::Adapter>::id_info(pdev.as_ref());
+ // SAFETY: `pdev` matched data is of type `Self::IdInfo`.
+ let info = unsafe { <Self as driver::Adapter>::id_info(pdev.as_ref()) };
from_result(|| {
let data = T::probe(pdev, info);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 08/11] rust: driver: store pointers in `DeviceId`
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
The common practice in C drivers is to store pointers into `driver_data`
field of device IDs. The Rust code is however currently storing indices
into the fields and then carry a side table that maps the index to
pointers.
It is much simpler to just have `DeviceId` carry the pointer like C code
does. However, just doing so naively would cause a "pointers cannot be cast
to integers during const eval" error, as kernel_ulong_t does not have
provenance while pointers do, and Rust forbids `expose_provenance` during
consteval.
Work around this limitation by wrapping raw IDs in `MaybeUninit`.
`MaybeUninit` is allowed to host arbitrary bytes with or without
provenance, so we can just then use `unsafe` to store a pointer with
provenance there. This has the same effect as changing the C-side
definition to use `void*` instead of `kernel_ulong_t`, but without actually
changing the C side.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/acpi.rs | 4 ---
rust/kernel/auxiliary.rs | 8 ++---
rust/kernel/device_id.rs | 88 +++++++++++++++++++++++++++++-------------------
rust/kernel/driver.rs | 14 ++++----
rust/kernel/i2c.rs | 7 ++--
rust/kernel/of.rs | 4 ---
rust/kernel/pci.rs | 12 +++----
rust/kernel/usb.rs | 11 +++---
8 files changed, 77 insertions(+), 71 deletions(-)
diff --git a/rust/kernel/acpi.rs b/rust/kernel/acpi.rs
index 315f2f2af446..ea2ce61ee393 100644
--- a/rust/kernel/acpi.rs
+++ b/rust/kernel/acpi.rs
@@ -25,10 +25,6 @@ unsafe impl RawDeviceId for DeviceId {
// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
unsafe impl RawDeviceIdIndex for DeviceId {
const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::acpi_device_id, driver_data);
-
- fn index(&self) -> usize {
- self.0.driver_data
- }
}
impl DeviceId {
diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs
index 59787c9bff26..aa13d8866a19 100644
--- a/rust/kernel/auxiliary.rs
+++ b/rust/kernel/auxiliary.rs
@@ -93,7 +93,9 @@ extern "C" fn probe_callback(
// SAFETY: `DeviceId` is a `#[repr(transparent)`] wrapper of `struct auxiliary_device_id`
// and does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*id.cast::<DeviceId>() };
- let info = T::ID_TABLE.info(id.index());
+
+ // SAFETY: `id` comes from `T::ID_TABLE` which is of type `IdArray<_, T::IdInfo>`.
+ let info = unsafe { id.info_unchecked::<T::IdInfo>() };
from_result(|| {
let data = T::probe(adev, info);
@@ -169,10 +171,6 @@ unsafe impl RawDeviceId for DeviceId {
unsafe impl RawDeviceIdIndex for DeviceId {
const DRIVER_DATA_OFFSET: usize =
core::mem::offset_of!(bindings::auxiliary_device_id, driver_data);
-
- fn index(&self) -> usize {
- self.0.driver_data
- }
}
/// IdTable type for auxiliary drivers.
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index 022f0101871f..50d82bfca9b8 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -5,7 +5,10 @@
//! Each bus / subsystem that matches device and driver through a bus / subsystem specific ID is
//! expected to implement [`RawDeviceId`].
-use core::mem::MaybeUninit;
+use core::{
+ marker::PhantomData,
+ mem::MaybeUninit, //
+};
/// Marker trait to indicate a Rust device ID type represents a corresponding C device ID type.
///
@@ -47,15 +50,48 @@ pub unsafe trait RawDeviceIdIndex: RawDeviceId {
/// The offset (in bytes) to the context/data field in the raw device ID.
const DRIVER_DATA_OFFSET: usize;
- /// The index stored at `DRIVER_DATA_OFFSET` of the implementor of the [`RawDeviceIdIndex`]
- /// trait.
- fn index(&self) -> usize;
+ /// Obtain the data pointer stored inside the device ID.
+ ///
+ /// # Safety
+ ///
+ /// `&Self` must be stored inside a `IdArray<Self, U>`.
+ unsafe fn info_unchecked<U>(&self) -> &'static U {
+ // SAFETY: By safety requirement of the trait, this is `self.driver_data as *const U` and by
+ // the safety requirement of the function, this is stored in `IdArray<Self, U>` so is
+ // convertible to `&'static U`.
+ unsafe {
+ core::ptr::from_ref(self)
+ .byte_add(Self::DRIVER_DATA_OFFSET)
+ .cast::<&U>()
+ .read()
+ }
+ }
+
+ /// Obtain the data pointer stored inside the device ID.
+ ///
+ /// # Safety
+ ///
+ /// `&Self` must be stored inside a `IdArray<Self, U>`, or has NULL (or 0) as driver data.
+ unsafe fn info_unchecked_opt<U>(&self) -> Option<&'static U> {
+ // SAFETY: By safety requirement of the trait, this is `self.driver_data as *const U` and by
+ // the safety requirement of the function, if this is stored in `IdArray<Self, U>`, this is
+ // convertible to `Option<&'static U>`. Otherwise it is NULL which is `None` as
+ // `Option<&U>`.
+ unsafe {
+ core::ptr::from_ref(self)
+ .byte_add(Self::DRIVER_DATA_OFFSET)
+ .cast::<Option<&U>>()
+ .read()
+ }
+ }
}
/// A zero-terminated device id array.
#[repr(C)]
pub struct RawIdArray<T: RawDeviceId, const N: usize> {
- ids: [T::RawType; N],
+ // This is `MaybeUninit<T::RawType>` so any bytes inside it can carry provenance in CTFE.
+ // If this were `T::RawType`, integer fields would not be able to contain pointers.
+ ids: [MaybeUninit<T::RawType>; N],
sentinel: MaybeUninit<T::RawType>,
}
@@ -68,18 +104,17 @@ pub const fn size(&self) -> usize {
/// A zero-terminated device id array, followed by context data.
#[repr(C)]
-pub struct IdArray<T: RawDeviceId, U, const N: usize> {
+pub struct IdArray<T: RawDeviceId, U: 'static, const N: usize> {
raw_ids: RawIdArray<T, N>,
- id_infos: [U; N],
+ phantom: PhantomData<&'static U>,
}
-impl<T: RawDeviceId + RawDeviceIdIndex, U, const N: usize> IdArray<T, U, N> {
+impl<T: RawDeviceId + RawDeviceIdIndex, U: 'static, const N: usize> IdArray<T, U, N> {
/// Creates a new instance of the array.
///
/// The contents are derived from the given identifiers and context information.
- pub const fn new(ids: [(T, U); N]) -> Self {
+ pub const fn new(ids: [(T, &'static U); N]) -> Self {
let mut raw_ids = [const { MaybeUninit::<T::RawType>::uninit() }; N];
- let mut infos = [const { MaybeUninit::uninit() }; N];
let mut i = 0usize;
while i < N {
@@ -87,18 +122,15 @@ impl<T: RawDeviceId + RawDeviceIdIndex, U, const N: usize> IdArray<T, U, N> {
// layout-wise compatible with `RawType`.
raw_ids[i] = unsafe { core::mem::transmute_copy(&ids[i].0) };
// SAFETY: by the safety requirement of `RawDeviceIdIndex`, this would be effectively
- // `raw_ids[i].driver_data = i;`.
+ // `raw_ids[i].driver_data = ids[i].1;`.
unsafe {
raw_ids[i]
.as_mut_ptr()
.byte_add(T::DRIVER_DATA_OFFSET)
- .cast::<usize>()
- .write(i);
+ .cast::<&U>()
+ .write(ids[i].1);
}
- // SAFETY: this is effectively a move: `infos[i] = ids[i].1`. We make a copy here but
- // later forget `ids`.
- infos[i] = MaybeUninit::new(unsafe { core::ptr::read(&ids[i].1) });
i += 1;
}
@@ -106,20 +138,15 @@ impl<T: RawDeviceId + RawDeviceIdIndex, U, const N: usize> IdArray<T, U, N> {
Self {
raw_ids: RawIdArray {
- // SAFETY: this is effectively `array_assume_init`, which is unstable, so we use
- // `transmute_copy` instead. We have initialized all elements of `raw_ids` so this
- // `array_assume_init` is safe.
- ids: unsafe { core::mem::transmute_copy(&raw_ids) },
+ ids: raw_ids,
sentinel: MaybeUninit::zeroed(),
},
- // SAFETY: We have initialized all elements of `infos` so this `array_assume_init` is
- // safe.
- id_infos: unsafe { core::mem::transmute_copy(&infos) },
+ phantom: PhantomData,
}
}
}
-impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
+impl<T: RawDeviceId, U: 'static, const N: usize> IdArray<T, U, N> {
/// Reference to the contained [`RawIdArray`].
pub const fn raw_ids(&self) -> &RawIdArray<T, N> {
&self.raw_ids
@@ -133,7 +160,7 @@ impl<T: RawDeviceId, const N: usize> IdArray<T, (), N> {
/// If the device implements [`RawDeviceIdIndex`], consider using [`IdArray::new`] instead.
pub const fn new_without_index(ids: [T; N]) -> Self {
// SAFETY: `T` is layout-wise compatible with `T::RawType`, so is the array of them.
- let raw_ids: [T::RawType; N] = unsafe { core::mem::transmute_copy(&ids) };
+ let raw_ids: [MaybeUninit<T::RawType>; N] = unsafe { core::mem::transmute_copy(&ids) };
core::mem::forget(ids);
Self {
@@ -141,7 +168,7 @@ impl<T: RawDeviceId, const N: usize> IdArray<T, (), N> {
ids: raw_ids,
sentinel: MaybeUninit::zeroed(),
},
- id_infos: [(); N],
+ phantom: PhantomData,
}
}
}
@@ -155,9 +182,6 @@ impl<T: RawDeviceId, const N: usize> IdArray<T, (), N> {
pub trait IdTable<T: RawDeviceId, U> {
/// Obtain the pointer to the ID table.
fn as_ptr(&self) -> *const T::RawType;
-
- /// Obtain the pointer to the driver-specific information from an index.
- fn info(&self, index: usize) -> &U;
}
impl<T: RawDeviceId, U, const N: usize> IdTable<T, U> for IdArray<T, U, N> {
@@ -166,10 +190,6 @@ fn as_ptr(&self) -> *const T::RawType {
// to access the sentinel.
core::ptr::from_ref(self).cast()
}
-
- fn info(&self, index: usize) -> &U {
- &self.id_infos[index]
- }
}
/// Create device table alias for modpost.
@@ -184,7 +204,7 @@ macro_rules! module_device_table {
$device_id_ty,
$id_info_type,
{ <[$device_id_ty]>::len(&[$($id,)*]) },
- > = $crate::device_id::IdArray::new([$(($id, $info),)*]);
+ > = $crate::device_id::IdArray::new([$(($id, &$info),)*]);
$crate::module_device_table!($table_type, $table_name);
};
diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
index bf5ba0d27553..824899d76fed 100644
--- a/rust/kernel/driver.rs
+++ b/rust/kernel/driver.rs
@@ -107,6 +107,7 @@
use crate::{
acpi,
device,
+ device_id::RawDeviceIdIndex,
of,
prelude::*,
types::Opaque,
@@ -350,7 +351,8 @@ fn acpi_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
// and does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*raw_id.cast::<acpi::DeviceId>() };
- Some(table.info(<acpi::DeviceId as crate::device_id::RawDeviceIdIndex>::index(id)))
+ // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
+ Some(unsafe { id.info_unchecked::<Self::IdInfo>() })
}
}
}
@@ -381,9 +383,8 @@ fn of_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
// and does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*raw_id.cast::<of::DeviceId>() };
- return Some(table.info(
- <of::DeviceId as crate::device_id::RawDeviceIdIndex>::index(id),
- ));
+ // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
+ return Some(unsafe { id.info_unchecked::<Self::IdInfo>() });
}
}
@@ -412,9 +413,8 @@ fn of_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> {
// and does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*raw_id.cast::<of::DeviceId>() };
- return Some(table.info(
- <of::DeviceId as crate::device_id::RawDeviceIdIndex>::index(id),
- ));
+ // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
+ return Some(unsafe { id.info_unchecked::<Self::IdInfo>() });
}
}
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index 6f2dcd467e72..31f2cfe6be31 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -65,10 +65,6 @@ unsafe impl RawDeviceId for DeviceId {
// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
unsafe impl RawDeviceIdIndex for DeviceId {
const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::i2c_device_id, driver_data);
-
- fn index(&self) -> usize {
- self.0.driver_data
- }
}
/// IdTable type for I2C
@@ -212,7 +208,8 @@ fn i2c_id_info(dev: &I2cClient) -> Option<&'static <Self as driver::Adapter>::Id
// does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*raw_id.cast::<DeviceId>() };
- Some(table.info(<DeviceId as RawDeviceIdIndex>::index(id)))
+ // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`.
+ Some(unsafe { id.info_unchecked::<T::IdInfo>() })
}
}
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index 35aa6d36d309..d0318f62afd7 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -25,10 +25,6 @@ unsafe impl RawDeviceId for DeviceId {
// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `data` field.
unsafe impl RawDeviceIdIndex for DeviceId {
const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::of_device_id, data);
-
- fn index(&self) -> usize {
- self.0.data as usize
- }
}
impl DeviceId {
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index a3dd48f76353..925cf280dae2 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -110,10 +110,14 @@ extern "C" fn probe_callback(
// SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct pci_device_id` and
// does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*id.cast::<DeviceId>() };
- let info = T::ID_TABLE.info(id.index());
+
+ // SAFETY: `id` comes from `T::ID_TABLE` which is of type `IdArray<_, T::IdInfo>` or
+ // `pci_device_id_any` which has 0 as driver_data. It can also come from dynamic IDs, which
+ // will ensure that `driver_data` exists in `T::ID_TABLE`.
+ let info = unsafe { id.info_unchecked_opt::<T::IdInfo>() };
from_result(|| {
- let data = T::probe(pdev, Some(info));
+ let data = T::probe(pdev, info);
pdev.as_ref().set_drvdata(data)?;
Ok(0)
@@ -233,10 +237,6 @@ unsafe impl RawDeviceId for DeviceId {
// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
unsafe impl RawDeviceIdIndex for DeviceId {
const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::pci_device_id, driver_data);
-
- fn index(&self) -> usize {
- self.0.driver_data
- }
}
/// `IdTable` type for PCI.
diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index d8cffbe594ff..aec7dce04955 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -89,8 +89,11 @@ extern "C" fn probe_callback(
// does not add additional invariants, so it's safe to transmute.
let id = unsafe { &*id.cast::<DeviceId>() };
- let info = T::ID_TABLE.info(id.index());
- let data = T::probe(intf, id, Some(info));
+ // SAFETY: `id` comes from `T::ID_TABLE` which is of type `IdArray<_, T::IdInfo>`. It
+ // can also come from dynamic IDs, which will ensure that `driver_data` exists in
+ // `T::ID_TABLE` or is 0.
+ let info = unsafe { id.info_unchecked_opt::<T::IdInfo>() };
+ let data = T::probe(intf, id, info);
let dev: &device::Device<device::CoreInternal<'_>> = intf.as_ref();
dev.set_drvdata(data)?;
@@ -242,10 +245,6 @@ unsafe impl RawDeviceId for DeviceId {
// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_info` field.
unsafe impl RawDeviceIdIndex for DeviceId {
const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::usb_device_id, driver_info);
-
- fn index(&self) -> usize {
- self.0.driver_info
- }
}
/// [`IdTable`](kernel::device_id::IdTable) type for USB.
--
2.54.0
^ permalink raw reply related
* [PATCH v2 06/11] rust: driver: centralize device ID handling
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
Move the `IdArray` creation from individual buses to be handled by shared
code in `device_id.rs`.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/acpi.rs | 10 ++--------
rust/kernel/auxiliary.rs | 10 ++--------
rust/kernel/device_id.rs | 31 ++++++++++++++++++++++++++++++-
rust/kernel/i2c.rs | 10 ++--------
rust/kernel/net/phy.rs | 10 ++++------
rust/kernel/of.rs | 10 ++--------
rust/kernel/pci.rs | 10 ++--------
rust/kernel/usb.rs | 10 ++--------
8 files changed, 46 insertions(+), 55 deletions(-)
diff --git a/rust/kernel/acpi.rs b/rust/kernel/acpi.rs
index 9b8efa623130..315f2f2af446 100644
--- a/rust/kernel/acpi.rs
+++ b/rust/kernel/acpi.rs
@@ -53,13 +53,7 @@ pub const fn new(id: &'static CStr) -> Self {
/// Create an ACPI `IdTable` with an "alias" for modpost.
#[macro_export]
macro_rules! acpi_device_table {
- ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
- const $table_name: $crate::device_id::IdArray<
- $crate::acpi::DeviceId,
- $id_info_type,
- { $table_data.len() },
- > = $crate::device_id::IdArray::new($table_data);
-
- $crate::module_device_table!("acpi", $module_table_name, $table_name);
+ ($($tt:tt)*) => {
+ $crate::module_device_table!("acpi", $crate::acpi::DeviceId, $($tt)*);
};
}
diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs
index c42928d5a239..59787c9bff26 100644
--- a/rust/kernel/auxiliary.rs
+++ b/rust/kernel/auxiliary.rs
@@ -181,14 +181,8 @@ fn index(&self) -> usize {
/// Create a auxiliary `IdTable` with its alias for modpost.
#[macro_export]
macro_rules! auxiliary_device_table {
- ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
- const $table_name: $crate::device_id::IdArray<
- $crate::auxiliary::DeviceId,
- $id_info_type,
- { $table_data.len() },
- > = $crate::device_id::IdArray::new($table_data);
-
- $crate::module_device_table!("auxiliary", $module_table_name, $table_name);
+ ($($tt:tt)*) => {
+ $crate::module_device_table!("auxiliary", $crate::auxiliary::DeviceId, $($tt)*);
};
}
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index eeef3f5e7b63..0239f89d5f69 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -175,7 +175,36 @@ fn info(&self, index: usize) -> &U {
/// Create device table alias for modpost.
#[macro_export]
macro_rules! module_device_table {
- ($table_type: literal, $module_table_name:ident, $table_name:ident) => {
+ (
+ $table_type: literal, $device_id_ty: ty,
+ $table_name: ident, $module_table_name: ident, $id_info_type: ty,
+ [$(($id: expr, $info:expr $(,)?)),* $(,)?]
+ ) => {
+ const $table_name: $crate::device_id::IdArray<
+ $device_id_ty,
+ $id_info_type,
+ { <[$device_id_ty]>::len(&[$($id,)*]) },
+ > = $crate::device_id::IdArray::new([$(($id, $info),)*]);
+
+ $crate::module_device_table!($table_type, $module_table_name, $table_name);
+ };
+
+ // Case for no ID info.
+ (
+ $table_type: literal, $device_id_ty: ty,
+ $table_name: ident, $module_table_name: ident, @none,
+ [$($id: expr),* $(,)?]
+ ) => {
+ const $table_name: $crate::device_id::IdArray<
+ $device_id_ty,
+ (),
+ { <[$device_id_ty]>::len(&[$($id,)*]) },
+ > = $crate::device_id::IdArray::new_without_index([$($id),*]);
+
+ $crate::module_device_table!($table_type, $module_table_name, $table_name);
+ };
+
+ ($table_type: literal, $module_table_name: ident, $table_name:ident) => {
#[rustfmt::skip]
#[export_name =
concat!("__mod_device_table__", line!(),
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index 624b971ca8b0..a1f968fd873d 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -77,14 +77,8 @@ fn index(&self) -> usize {
/// Create a I2C `IdTable` with its alias for modpost.
#[macro_export]
macro_rules! i2c_device_table {
- ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
- const $table_name: $crate::device_id::IdArray<
- $crate::i2c::DeviceId,
- $id_info_type,
- { $table_data.len() },
- > = $crate::device_id::IdArray::new($table_data);
-
- $crate::module_device_table!("i2c", $module_table_name, $table_name);
+ ($($tt:tt)*) => {
+ $crate::module_device_table!("i2c", $crate::i2c::DeviceId, $($tt)*);
};
}
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 965ecca7d55f..166572861e61 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -809,12 +809,10 @@ macro_rules! module_phy_driver {
};
(@device_table [$($dev:expr),+]) => {
- const N: usize = $crate::module_phy_driver!(@count_devices $($dev),+);
-
- const TABLE: $crate::device_id::IdArray<$crate::net::phy::DeviceId, (), N> =
- $crate::device_id::IdArray::new_without_index([ $($dev),+, ]);
-
- $crate::module_device_table!("mdio", phydev, TABLE);
+ $crate::module_device_table!(
+ "mdio", $crate::net::phy::DeviceId,
+ phydev, TABLE, @none, [$($dev),+]
+ );
};
(drivers: [$($driver:ident),+ $(,)?], device_table: [$($dev:expr),+ $(,)?], $($f:tt)*) => {
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index 58b20c367f99..35aa6d36d309 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -53,13 +53,7 @@ pub const fn new(compatible: &'static CStr) -> Self {
/// Create an OF `IdTable` with an "alias" for modpost.
#[macro_export]
macro_rules! of_device_table {
- ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
- const $table_name: $crate::device_id::IdArray<
- $crate::of::DeviceId,
- $id_info_type,
- { $table_data.len() },
- > = $crate::device_id::IdArray::new($table_data);
-
- $crate::module_device_table!("of", $module_table_name, $table_name);
+ ($($tt:tt)*) => {
+ $crate::module_device_table!("of", $crate::of::DeviceId, $($tt)*);
};
}
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 0e055e4df99e..34e07a53244d 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -245,14 +245,8 @@ fn index(&self) -> usize {
/// Create a PCI `IdTable` with its alias for modpost.
#[macro_export]
macro_rules! pci_device_table {
- ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
- const $table_name: $crate::device_id::IdArray<
- $crate::pci::DeviceId,
- $id_info_type,
- { $table_data.len() },
- > = $crate::device_id::IdArray::new($table_data);
-
- $crate::module_device_table!("pci", $module_table_name, $table_name);
+ ($($tt:tt)*) => {
+ $crate::module_device_table!("pci", $crate::pci::DeviceId, $($tt)*);
};
}
diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 6750a49e466b..3797f4a79b79 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -254,14 +254,8 @@ fn index(&self) -> usize {
/// Create a USB `IdTable` with its alias for modpost.
#[macro_export]
macro_rules! usb_device_table {
- ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
- const $table_name: $crate::device_id::IdArray<
- $crate::usb::DeviceId,
- $id_info_type,
- { $table_data.len() },
- > = $crate::device_id::IdArray::new($table_data);
-
- $crate::module_device_table!("usb", $module_table_name, $table_name);
+ ($($tt:tt)*) => {
+ $crate::module_device_table!("usb", $crate::usb::DeviceId, $($tt)*);
};
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 05/11] rust: net/phy: remove expansion from doc
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
The expansion serves little purpose and it can easily diverge.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/net/phy.rs | 56 --------------------------------------------------
1 file changed, 56 deletions(-)
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 2868e3a9e02c..965ecca7d55f 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -800,62 +800,6 @@ const fn as_int(&self) -> u32 {
/// }
/// # }
/// ```
-///
-/// This expands to the following code:
-///
-/// ```ignore
-/// use kernel::net::phy::{self, DeviceId};
-/// use kernel::prelude::*;
-///
-/// struct Module {
-/// _reg: ::kernel::net::phy::Registration,
-/// }
-///
-/// module! {
-/// type: Module,
-/// name: "rust_sample_phy",
-/// authors: ["Rust for Linux Contributors"],
-/// description: "Rust sample PHYs driver",
-/// license: "GPL",
-/// }
-///
-/// struct PhySample;
-///
-/// #[vtable]
-/// impl phy::Driver for PhySample {
-/// const NAME: &'static CStr = c"PhySample";
-/// const PHY_DEVICE_ID: phy::DeviceId = phy::DeviceId::new_with_exact_mask(0x00000001);
-/// }
-///
-/// const _: () = {
-/// static mut DRIVERS: [::kernel::net::phy::DriverVTable; 1] =
-/// [::kernel::net::phy::create_phy_driver::<PhySample>()];
-///
-/// impl ::kernel::Module for Module {
-/// fn init(module: &'static ::kernel::ThisModule) -> Result<Self> {
-/// let drivers = unsafe { &mut DRIVERS };
-/// let mut reg = ::kernel::net::phy::Registration::register(
-/// module,
-/// ::core::pin::Pin::static_mut(drivers),
-/// )?;
-/// Ok(Module { _reg: reg })
-/// }
-/// }
-/// };
-///
-/// const N: usize = 1;
-///
-/// const TABLE: ::kernel::device_id::IdArray<::kernel::net::phy::DeviceId, (), N> =
-/// ::kernel::device_id::IdArray::new_without_index([
-/// ::kernel::net::phy::DeviceId(
-/// ::kernel::bindings::mdio_device_id {
-/// phy_id: 0x00000001,
-/// phy_id_mask: 0xffffffff,
-/// }),
-/// ]);
-///
-/// ::kernel::module_device_table!("mdio", phydev, TABLE);
-/// ```
#[macro_export]
macro_rules! module_phy_driver {
(@replace_expr $_t:tt $sub:expr) => {$sub};
--
2.54.0
^ permalink raw reply related
* [PATCH v2 07/11] rust: driver: remove `$module_table_name` from `module_device_table`
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
Wrap the generated code in a `const _: ()` block to avoid symbol conflict.
This removes the need of creating a new identifier.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/cpufreq/rcpufreq_dt.rs | 1 -
drivers/gpu/drm/nova/driver.rs | 1 -
drivers/gpu/drm/tyr/driver.rs | 1 -
drivers/gpu/nova-core/driver.rs | 1 -
drivers/pwm/pwm_th1520.rs | 1 -
rust/kernel/device_id.rs | 30 ++++++++++++++++--------------
rust/kernel/i2c.rs | 3 ---
rust/kernel/net/phy.rs | 2 +-
rust/kernel/pci.rs | 1 -
rust/kernel/platform.rs | 2 --
rust/kernel/usb.rs | 1 -
samples/rust/rust_debugfs.rs | 1 -
samples/rust/rust_dma.rs | 1 -
samples/rust/rust_driver_auxiliary.rs | 2 --
samples/rust/rust_driver_i2c.rs | 3 ---
samples/rust/rust_driver_pci.rs | 1 -
samples/rust/rust_driver_platform.rs | 2 --
samples/rust/rust_driver_usb.rs | 1 -
samples/rust/rust_i2c_client.rs | 2 --
samples/rust/rust_soc.rs | 2 --
20 files changed, 17 insertions(+), 42 deletions(-)
diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs
index 10106fa13095..145daa12072f 100644
--- a/drivers/cpufreq/rcpufreq_dt.rs
+++ b/drivers/cpufreq/rcpufreq_dt.rs
@@ -194,7 +194,6 @@ fn register_em(policy: &mut cpufreq::Policy) {
kernel::of_device_table!(
OF_TABLE,
- MODULE_OF_TABLE,
<CPUFreqDTDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c"operating-points-v2"), ())]
);
diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs
index 48933d86ddda..43f15cdfeb09 100644
--- a/drivers/gpu/drm/nova/driver.rs
+++ b/drivers/gpu/drm/nova/driver.rs
@@ -43,7 +43,6 @@ pub(crate) struct NovaData {
kernel::auxiliary_device_table!(
AUX_TABLE,
- MODULE_AUX_TABLE,
<NovaDriver as auxiliary::Driver>::IdInfo,
[(
auxiliary::DeviceId::new(NOVA_CORE_MODULE_NAME, AUXILIARY_NAME),
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index d063bc664cc1..218e9af899c7 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -87,7 +87,6 @@ fn issue_soft_reset(dev: &Device, iomem: &IoMem<'_>) -> Result {
kernel::of_device_table!(
OF_TABLE,
- MODULE_OF_TABLE,
<TyrPlatformDriver as platform::Driver>::IdInfo,
[
(of::DeviceId::new(c"rockchip,rk3588-mali"), ()),
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index 5a5f0b63e0f3..0c53b7239ac9 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -40,7 +40,6 @@ pub(crate) struct NovaCore<'bound> {
kernel::pci_device_table!(
PCI_TABLE,
- MODULE_PCI_TABLE,
<NovaCoreDriver as pci::Driver>::IdInfo,
[
// Modern NVIDIA GPUs will show up as either VGA or 3D controllers.
diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs
index 3e3fa51ccef9..1df752330e8f 100644
--- a/drivers/pwm/pwm_th1520.rs
+++ b/drivers/pwm/pwm_th1520.rs
@@ -303,7 +303,6 @@ fn drop(self: Pin<&mut Self>) {
kernel::of_device_table!(
OF_TABLE,
- MODULE_OF_TABLE,
<Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c"thead,th1520-pwm"), ())]
);
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index 0239f89d5f69..022f0101871f 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -177,7 +177,7 @@ fn info(&self, index: usize) -> &U {
macro_rules! module_device_table {
(
$table_type: literal, $device_id_ty: ty,
- $table_name: ident, $module_table_name: ident, $id_info_type: ty,
+ $table_name: ident, $id_info_type: ty,
[$(($id: expr, $info:expr $(,)?)),* $(,)?]
) => {
const $table_name: $crate::device_id::IdArray<
@@ -186,13 +186,13 @@ macro_rules! module_device_table {
{ <[$device_id_ty]>::len(&[$($id,)*]) },
> = $crate::device_id::IdArray::new([$(($id, $info),)*]);
- $crate::module_device_table!($table_type, $module_table_name, $table_name);
+ $crate::module_device_table!($table_type, $table_name);
};
// Case for no ID info.
(
$table_type: literal, $device_id_ty: ty,
- $table_name: ident, $module_table_name: ident, @none,
+ $table_name: ident, @none,
[$($id: expr),* $(,)?]
) => {
const $table_name: $crate::device_id::IdArray<
@@ -201,18 +201,20 @@ macro_rules! module_device_table {
{ <[$device_id_ty]>::len(&[$($id,)*]) },
> = $crate::device_id::IdArray::new_without_index([$($id),*]);
- $crate::module_device_table!($table_type, $module_table_name, $table_name);
+ $crate::module_device_table!($table_type, $table_name);
};
- ($table_type: literal, $module_table_name: ident, $table_name:ident) => {
- #[rustfmt::skip]
- #[export_name =
- concat!("__mod_device_table__", line!(),
- "__kmod_", module_path!(),
- "__", $table_type,
- "__", stringify!($table_name))
- ]
- static $module_table_name: [::core::mem::MaybeUninit<u8>; $table_name.raw_ids().size()] =
- unsafe { ::core::mem::transmute_copy($table_name.raw_ids()) };
+ ($table_type: literal, $table_name:ident) => {
+ const _: () = {
+ #[rustfmt::skip]
+ #[export_name =
+ concat!("__mod_device_table__", line!(),
+ "__kmod_", module_path!(),
+ "__", $table_type,
+ "__", stringify!($table_name))
+ ]
+ static MOD_DEVICE_TABLE: [::core::mem::MaybeUninit<u8>; $table_name.raw_ids().size()] =
+ unsafe { ::core::mem::transmute_copy($table_name.raw_ids()) };
+ };
};
}
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index a1f968fd873d..6f2dcd467e72 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -261,7 +261,6 @@ macro_rules! module_i2c_driver {
///
/// kernel::acpi_device_table!(
/// ACPI_TABLE,
-/// MODULE_ACPI_TABLE,
/// <MyDriver as i2c::Driver>::IdInfo,
/// [
/// (acpi::DeviceId::new(c"LNUXBEEF"), ())
@@ -270,7 +269,6 @@ macro_rules! module_i2c_driver {
///
/// kernel::i2c_device_table!(
/// I2C_TABLE,
-/// MODULE_I2C_TABLE,
/// <MyDriver as i2c::Driver>::IdInfo,
/// [
/// (i2c::DeviceId::new(c"rust_driver_i2c"), ())
@@ -279,7 +277,6 @@ macro_rules! module_i2c_driver {
///
/// kernel::of_device_table!(
/// OF_TABLE,
-/// MODULE_OF_TABLE,
/// <MyDriver as i2c::Driver>::IdInfo,
/// [
/// (of::DeviceId::new(c"test,device"), ())
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 166572861e61..1e86b901c391 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -811,7 +811,7 @@ macro_rules! module_phy_driver {
(@device_table [$($dev:expr),+]) => {
$crate::module_device_table!(
"mdio", $crate::net::phy::DeviceId,
- phydev, TABLE, @none, [$($dev),+]
+ TABLE, @none, [$($dev),+]
);
};
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 34e07a53244d..a3dd48f76353 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -261,7 +261,6 @@ macro_rules! pci_device_table {
///
/// kernel::pci_device_table!(
/// PCI_TABLE,
-/// MODULE_PCI_TABLE,
/// <MyDriver as pci::Driver>::IdInfo,
/// [
/// (
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index 9b362e0495d3..210a815925ce 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -176,7 +176,6 @@ macro_rules! module_platform_driver {
///
/// kernel::of_device_table!(
/// OF_TABLE,
-/// MODULE_OF_TABLE,
/// <MyDriver as platform::Driver>::IdInfo,
/// [
/// (of::DeviceId::new(c"test,device"), ())
@@ -185,7 +184,6 @@ macro_rules! module_platform_driver {
///
/// kernel::acpi_device_table!(
/// ACPI_TABLE,
-/// MODULE_ACPI_TABLE,
/// <MyDriver as platform::Driver>::IdInfo,
/// [
/// (acpi::DeviceId::new(c"LNUXBEEF"), ())
diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 3797f4a79b79..d8cffbe594ff 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -271,7 +271,6 @@ macro_rules! usb_device_table {
///
/// kernel::usb_device_table!(
/// USB_TABLE,
-/// MODULE_USB_TABLE,
/// <MyDriver as usb::Driver>::IdInfo,
/// [
/// (usb::DeviceId::from_id(0x1234, 0x5678), ()),
diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs
index 1f59e08aaa4b..181fd98ae5b6 100644
--- a/samples/rust/rust_debugfs.rs
+++ b/samples/rust/rust_debugfs.rs
@@ -110,7 +110,6 @@ fn from_str(s: &str) -> Result<Self> {
kernel::acpi_device_table!(
ACPI_TABLE,
- MODULE_ACPI_TABLE,
<RustDebugFs as platform::Driver>::IdInfo,
[(acpi::DeviceId::new(c"LNUXBEEF"), ())]
);
diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs
index 9beb37275e0d..80c309ce07e9 100644
--- a/samples/rust/rust_dma.rs
+++ b/samples/rust/rust_dma.rs
@@ -51,7 +51,6 @@ unsafe impl kernel::transmute::FromBytes for MyStruct {}
kernel::pci_device_table!(
PCI_TABLE,
- MODULE_PCI_TABLE,
<DmaSampleDriver as pci::Driver>::IdInfo,
[(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())]
);
diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs
index 73c63afc046a..704567a072ba 100644
--- a/samples/rust/rust_driver_auxiliary.rs
+++ b/samples/rust/rust_driver_auxiliary.rs
@@ -24,7 +24,6 @@
kernel::auxiliary_device_table!(
AUX_TABLE,
- MODULE_AUX_TABLE,
<AuxiliaryDriver as auxiliary::Driver>::IdInfo,
[(auxiliary::DeviceId::new(MODULE_NAME, AUXILIARY_NAME), ())]
);
@@ -66,7 +65,6 @@ struct ParentData<'bound> {
kernel::pci_device_table!(
PCI_TABLE,
- MODULE_PCI_TABLE,
<ParentDriver as pci::Driver>::IdInfo,
[(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())]
);
diff --git a/samples/rust/rust_driver_i2c.rs b/samples/rust/rust_driver_i2c.rs
index ead8263a7d48..a0df0c6097c4 100644
--- a/samples/rust/rust_driver_i2c.rs
+++ b/samples/rust/rust_driver_i2c.rs
@@ -14,21 +14,18 @@
kernel::acpi_device_table! {
ACPI_TABLE,
- MODULE_ACPI_TABLE,
<SampleDriver as i2c::Driver>::IdInfo,
[(acpi::DeviceId::new(c"LNUXBEEF"), 0)]
}
kernel::i2c_device_table! {
I2C_TABLE,
- MODULE_I2C_TABLE,
<SampleDriver as i2c::Driver>::IdInfo,
[(i2c::DeviceId::new(c"rust_driver_i2c"), 0)]
}
kernel::of_device_table! {
OF_TABLE,
- MODULE_OF_TABLE,
<SampleDriver as i2c::Driver>::IdInfo,
[(of::DeviceId::new(c"test,rust_driver_i2c"), 0)]
}
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 5547dd704a1b..2282191e6292 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -74,7 +74,6 @@ struct SampleDriverData<'bound> {
kernel::pci_device_table!(
PCI_TABLE,
- MODULE_PCI_TABLE,
<SampleDriver as pci::Driver>::IdInfo,
[(
pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5),
diff --git a/samples/rust/rust_driver_platform.rs b/samples/rust/rust_driver_platform.rs
index ec0d6cac4f57..710145b3605a 100644
--- a/samples/rust/rust_driver_platform.rs
+++ b/samples/rust/rust_driver_platform.rs
@@ -87,14 +87,12 @@ struct SampleDriver {
kernel::of_device_table!(
OF_TABLE,
- MODULE_OF_TABLE,
<SampleDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c"test,rust-device"), Info(42))]
);
kernel::acpi_device_table!(
ACPI_TABLE,
- MODULE_ACPI_TABLE,
<SampleDriver as platform::Driver>::IdInfo,
[(acpi::DeviceId::new(c"LNUXBEEF"), Info(0))]
);
diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs
index 176ef625ed75..7ef04e177c80 100644
--- a/samples/rust/rust_driver_usb.rs
+++ b/samples/rust/rust_driver_usb.rs
@@ -19,7 +19,6 @@ struct SampleDriver {
kernel::usb_device_table!(
USB_TABLE,
- MODULE_USB_TABLE,
<SampleDriver as usb::Driver>::IdInfo,
[(usb::DeviceId::from_id(0x1234, 0x5678), ()),]
);
diff --git a/samples/rust/rust_i2c_client.rs b/samples/rust/rust_i2c_client.rs
index 2d876f4e3ee0..c8a23875ef5b 100644
--- a/samples/rust/rust_i2c_client.rs
+++ b/samples/rust/rust_i2c_client.rs
@@ -87,14 +87,12 @@ struct SampleDriver {
kernel::of_device_table!(
OF_TABLE,
- MODULE_OF_TABLE,
<SampleDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c"test,rust-device"), ())]
);
kernel::acpi_device_table!(
ACPI_TABLE,
- MODULE_ACPI_TABLE,
<SampleDriver as platform::Driver>::IdInfo,
[(acpi::DeviceId::new(c"LNUXBEEF"), ())]
);
diff --git a/samples/rust/rust_soc.rs b/samples/rust/rust_soc.rs
index 808d58200eb6..f5e5f2f9adf7 100644
--- a/samples/rust/rust_soc.rs
+++ b/samples/rust/rust_soc.rs
@@ -23,14 +23,12 @@ struct SampleSocDriver {
kernel::of_device_table!(
OF_TABLE,
- MODULE_OF_TABLE,
<SampleSocDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c"test,rust-device"), ())]
);
kernel::acpi_device_table!(
ACPI_TABLE,
- MODULE_ACPI_TABLE,
<SampleSocDriver as platform::Driver>::IdInfo,
[(acpi::DeviceId::new(c"LNUXBEEF"), ())]
);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 02/11] rust: driver: simplify `IdArray::new_without_index`
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
This method can very easily construct the `IdArray` on its own without
delegating to `Self::build`. Doing so also simplifies the phy device table
macro because it does not need to construct tuples anymore.
This also allows simplification of `new` and `build` which removes the
`unsafe`.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/device_id.rs | 64 ++++++++++++++++++++----------------------------
rust/kernel/net/phy.rs | 2 +-
2 files changed, 28 insertions(+), 38 deletions(-)
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index fbf6d8e6afb9..eeef3f5e7b63 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -73,19 +73,11 @@ pub struct IdArray<T: RawDeviceId, U, const N: usize> {
id_infos: [U; N],
}
-impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
+impl<T: RawDeviceId + RawDeviceIdIndex, U, const N: usize> IdArray<T, U, N> {
/// Creates a new instance of the array.
///
/// The contents are derived from the given identifiers and context information.
- ///
- /// # Safety
- ///
- /// `data_offset` as `None` is always safe.
- /// If `data_offset` is `Some(data_offset)`, then:
- /// - `data_offset` must be the correct offset (in bytes) to the context/data field
- /// (e.g., the `driver_data` field) within the raw device ID structure.
- /// - The field at `data_offset` must be correctly sized to hold a `usize`.
- const unsafe fn build(ids: [(T, U); N], data_offset: Option<usize>) -> Self {
+ pub const fn new(ids: [(T, U); N]) -> Self {
let mut raw_ids = [const { MaybeUninit::<T::RawType>::uninit() }; N];
let mut infos = [const { MaybeUninit::uninit() }; N];
@@ -94,16 +86,14 @@ impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
// SAFETY: by the safety requirement of `RawDeviceId`, we're guaranteed that `T` is
// layout-wise compatible with `RawType`.
raw_ids[i] = unsafe { core::mem::transmute_copy(&ids[i].0) };
- if let Some(data_offset) = data_offset {
- // SAFETY: by the safety requirement of this function, this would be effectively
- // `raw_ids[i].driver_data = i;`.
- unsafe {
- raw_ids[i]
- .as_mut_ptr()
- .byte_add(data_offset)
- .cast::<usize>()
- .write(i);
- }
+ // SAFETY: by the safety requirement of `RawDeviceIdIndex`, this would be effectively
+ // `raw_ids[i].driver_data = i;`.
+ unsafe {
+ raw_ids[i]
+ .as_mut_ptr()
+ .byte_add(T::DRIVER_DATA_OFFSET)
+ .cast::<usize>()
+ .write(i);
}
// SAFETY: this is effectively a move: `infos[i] = ids[i].1`. We make a copy here but
@@ -127,32 +117,32 @@ impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
id_infos: unsafe { core::mem::transmute_copy(&infos) },
}
}
+}
- /// Creates a new instance of the array without writing index values.
- ///
- /// The contents are derived from the given identifiers and context information.
- /// If the device implements [`RawDeviceIdIndex`], consider using [`IdArray::new`] instead.
- pub const fn new_without_index(ids: [(T, U); N]) -> Self {
- // SAFETY: Calling `Self::build` with `offset = None` is always safe,
- // because no raw memory writes are performed in this case.
- unsafe { Self::build(ids, None) }
- }
-
+impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
/// Reference to the contained [`RawIdArray`].
pub const fn raw_ids(&self) -> &RawIdArray<T, N> {
&self.raw_ids
}
}
-impl<T: RawDeviceId + RawDeviceIdIndex, U, const N: usize> IdArray<T, U, N> {
- /// Creates a new instance of the array.
+impl<T: RawDeviceId, const N: usize> IdArray<T, (), N> {
+ /// Creates a new instance of the array without writing index values.
///
/// The contents are derived from the given identifiers and context information.
- pub const fn new(ids: [(T, U); N]) -> Self {
- // SAFETY: by the safety requirement of `RawDeviceIdIndex`,
- // `T::DRIVER_DATA_OFFSET` is guaranteed to be the correct offset (in bytes) to
- // a field within `T::RawType`.
- unsafe { Self::build(ids, Some(T::DRIVER_DATA_OFFSET)) }
+ /// If the device implements [`RawDeviceIdIndex`], consider using [`IdArray::new`] instead.
+ pub const fn new_without_index(ids: [T; N]) -> Self {
+ // SAFETY: `T` is layout-wise compatible with `T::RawType`, so is the array of them.
+ let raw_ids: [T::RawType; N] = unsafe { core::mem::transmute_copy(&ids) };
+ core::mem::forget(ids);
+
+ Self {
+ raw_ids: RawIdArray {
+ ids: raw_ids,
+ sentinel: MaybeUninit::zeroed(),
+ },
+ id_infos: [(); N],
+ }
}
}
diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 3ca99db5cccf..2868e3a9e02c 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -868,7 +868,7 @@ macro_rules! module_phy_driver {
const N: usize = $crate::module_phy_driver!(@count_devices $($dev),+);
const TABLE: $crate::device_id::IdArray<$crate::net::phy::DeviceId, (), N> =
- $crate::device_id::IdArray::new_without_index([ $(($dev,())),+, ]);
+ $crate::device_id::IdArray::new_without_index([ $($dev),+, ]);
$crate::module_device_table!("mdio", phydev, TABLE);
};
--
2.54.0
^ permalink raw reply related
* [PATCH v2 04/11] rust: usb: use `Option<&IdInfo>` for device ID info
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
It is possible that ID without driver_data will be passed to the driver,
e.g. `new_id` is used to dynamically create a new ID without data.
Therefore, the driver must be able to handle the case where `driver_data`
is 0. Thus, update the `probe` functions to get `Option`.
The current code cannot tell if the info does not exist or is the first
entry; however this will be achievable once the code is updated to use a
`&'static IdInfo` pointer instead of indices.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/usb.rs | 6 +++---
samples/rust/rust_driver_usb.rs | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs
index 7aff0c82d0af..6750a49e466b 100644
--- a/rust/kernel/usb.rs
+++ b/rust/kernel/usb.rs
@@ -90,7 +90,7 @@ extern "C" fn probe_callback(
let id = unsafe { &*id.cast::<DeviceId>() };
let info = T::ID_TABLE.info(id.index());
- let data = T::probe(intf, id, info);
+ let data = T::probe(intf, id, Some(info));
let dev: &device::Device<device::CoreInternal<'_>> = intf.as_ref();
dev.set_drvdata(data)?;
@@ -293,7 +293,7 @@ macro_rules! usb_device_table {
/// fn probe<'bound>(
/// _interface: &'bound usb::Interface<Core<'_>>,
/// _id: &usb::DeviceId,
-/// _info: &'bound Self::IdInfo,
+/// _info: Option<&'bound Self::IdInfo>,
/// ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
/// Err(ENODEV)
/// }
@@ -322,7 +322,7 @@ pub trait Driver {
fn probe<'bound>(
interface: &'bound Interface<device::Core<'_>>,
id: &DeviceId,
- id_info: &'bound Self::IdInfo,
+ id_info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self::Data<'bound>, Error> + 'bound;
/// USB driver disconnect.
diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs
index 02bd5085f9bc..176ef625ed75 100644
--- a/samples/rust/rust_driver_usb.rs
+++ b/samples/rust/rust_driver_usb.rs
@@ -32,7 +32,7 @@ impl usb::Driver for SampleDriver {
fn probe<'bound>(
intf: &'bound usb::Interface<Core<'_>>,
_id: &usb::DeviceId,
- _info: &'bound Self::IdInfo,
+ _info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self, Error> + 'bound {
let dev: &device::Device<Core<'_>> = intf.as_ref();
dev_info!(dev, "Rust USB driver sample probed\n");
--
2.54.0
^ permalink raw reply related
* [PATCH v2 00/11] rust: driver: use pointers instead of indices for ID info
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
Most C drivers use a pointer (and cast to kernel_ulong_t) for driver_data
fields in device_id. Rust code currently does not do this, but rather use
indices. These indices then needs to be translated to `&IdInfo` separately
and this is by a side table.
This leads to open-coded ACPI/OF handling in driver.rs, which is not
desirable. Convert the code to use pointers (or rather, static references)
instead.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
Changes in v2:
- Change USB to take `Option<&IdInfo>` in addition to PCI due to ability to
dynamically add IDs.
- Mention dyn IDs and driver_override in safety comments and justify why
they're correct.
- Link to v1: https://patch.msgid.link/20260618-id_info-v1-0-96af1e559ef9@garyguo.net
---
Gary Guo (11):
rust: driver: remove `IdTable::id`
rust: driver: simplify `IdArray::new_without_index`
rust: pci: use `Option<&IdInfo>` for device ID info
rust: usb: use `Option<&IdInfo>` for device ID info
rust: net/phy: remove expansion from doc
rust: driver: centralize device ID handling
rust: driver: remove `$module_table_name` from `module_device_table`
rust: driver: store pointers in `DeviceId`
rust: driver: remove open-coded matching logic
rust: driver: remove duplicate ID table
RFC: rust: driver: support map-like syntax for ID table
MAINTAINERS | 1 -
drivers/acpi/bus.c | 6 +-
drivers/cpufreq/rcpufreq_dt.rs | 1 -
drivers/gpu/drm/nova/driver.rs | 1 -
drivers/gpu/drm/tyr/driver.rs | 1 -
drivers/gpu/nova-core/driver.rs | 3 +-
drivers/pwm/pwm_th1520.rs | 1 -
include/acpi/acpi_bus.h | 11 --
rust/helpers/acpi.c | 16 ---
rust/helpers/helpers.c | 1 -
rust/kernel/acpi.rs | 14 +--
rust/kernel/auxiliary.rs | 18 +--
rust/kernel/device_id.rs | 207 +++++++++++++++++++---------------
rust/kernel/driver.rs | 137 ++--------------------
rust/kernel/i2c.rs | 26 ++---
rust/kernel/net/phy.rs | 66 +----------
rust/kernel/of.rs | 14 +--
rust/kernel/pci.rs | 25 ++--
rust/kernel/platform.rs | 5 +-
rust/kernel/usb.rs | 24 ++--
samples/rust/rust_debugfs.rs | 1 -
samples/rust/rust_dma.rs | 3 +-
samples/rust/rust_driver_auxiliary.rs | 4 +-
samples/rust/rust_driver_i2c.rs | 3 -
samples/rust/rust_driver_pci.rs | 11 +-
samples/rust/rust_driver_platform.rs | 2 -
samples/rust/rust_driver_usb.rs | 3 +-
samples/rust/rust_i2c_client.rs | 2 -
samples/rust/rust_soc.rs | 2 -
29 files changed, 178 insertions(+), 431 deletions(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260612-id_info-23eca472ccd8
Best regards,
--
Gary Guo <gary@garyguo.net>
^ permalink raw reply
* [PATCH v2 03/11] rust: pci: use `Option<&IdInfo>` for device ID info
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
It is possible that `pci_device_id_any` will be passed to the driver, e.g.
`driver_override` is used on the device. Therefore, the driver must be able
to handle the case where `driver_data` is 0. Thus, update the `probe`
functions to get `Option`.
The current code cannot tell if the info does not exist or is the first
entry; however this will be achievable once the code is updated to use a
`&'static IdInfo` pointer instead of indices.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/gpu/nova-core/driver.rs | 2 +-
rust/kernel/pci.rs | 6 +++---
samples/rust/rust_dma.rs | 2 +-
samples/rust/rust_driver_auxiliary.rs | 2 +-
samples/rust/rust_driver_pci.rs | 3 ++-
5 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index 5738d4ac521b..5a5f0b63e0f3 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -70,7 +70,7 @@ impl pci::Driver for NovaCoreDriver {
fn probe<'bound>(
pdev: &'bound pci::Device<Core<'_>>,
- _info: &'bound Self::IdInfo,
+ _info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
pin_init::pin_init_scope(move || {
dev_dbg!(pdev, "Probe Nova Core GPU driver.\n");
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 5071cae6543f..0e055e4df99e 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -113,7 +113,7 @@ extern "C" fn probe_callback(
let info = T::ID_TABLE.info(id.index());
from_result(|| {
- let data = T::probe(pdev, info);
+ let data = T::probe(pdev, Some(info));
pdev.as_ref().set_drvdata(data)?;
Ok(0)
@@ -284,7 +284,7 @@ macro_rules! pci_device_table {
///
/// fn probe<'bound>(
/// _pdev: &'bound pci::Device<Core<'_>>,
-/// _id_info: &'bound Self::IdInfo,
+/// _id_info: Option<&'bound Self::IdInfo>,
/// ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
/// Err(ENODEV)
/// }
@@ -313,7 +313,7 @@ pub trait Driver {
/// attempt to initialize the device here.
fn probe<'bound>(
dev: &'bound Device<device::Core<'_>>,
- id_info: &'bound Self::IdInfo,
+ id_info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self::Data<'bound>, Error> + 'bound;
/// PCI driver unbind.
diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs
index 5046b4628d0e..9beb37275e0d 100644
--- a/samples/rust/rust_dma.rs
+++ b/samples/rust/rust_dma.rs
@@ -63,7 +63,7 @@ impl pci::Driver for DmaSampleDriver {
fn probe<'bound>(
pdev: &'bound pci::Device<Core<'_>>,
- _info: &'bound Self::IdInfo,
+ _info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self, Error> + 'bound {
pin_init::pin_init_scope(move || {
dev_info!(pdev, "Probe DMA test driver.\n");
diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs
index 2c1351040e45..73c63afc046a 100644
--- a/samples/rust/rust_driver_auxiliary.rs
+++ b/samples/rust/rust_driver_auxiliary.rs
@@ -79,7 +79,7 @@ impl pci::Driver for ParentDriver {
fn probe<'bound>(
pdev: &'bound pci::Device<Core<'_>>,
- _info: &'bound Self::IdInfo,
+ _info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
Ok(ParentData {
// SAFETY: `ParentData` is the driver's private data, which is dropped when the
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 1aa8197d8698..5547dd704a1b 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -144,7 +144,7 @@ impl pci::Driver for SampleDriver {
fn probe<'bound>(
pdev: &'bound pci::Device<Core<'_>>,
- info: &'bound Self::IdInfo,
+ info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
let vendor = pdev.vendor_id();
dev_dbg!(
@@ -153,6 +153,7 @@ fn probe<'bound>(
vendor,
pdev.device_id()
);
+ let info = info.ok_or(ENODEV)?;
pdev.enable_device_mem()?;
pdev.set_master();
--
2.54.0
^ permalink raw reply related
* [PATCH v2 01/11] rust: driver: remove `IdTable::id`
From: Gary Guo @ 2026-06-29 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
In-Reply-To: <20260629-id_info-v2-0-56fccbe9c5ef@garyguo.net>
This is unused.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/device_id.rs | 7 -------
1 file changed, 7 deletions(-)
diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs
index 8e9721446014..fbf6d8e6afb9 100644
--- a/rust/kernel/device_id.rs
+++ b/rust/kernel/device_id.rs
@@ -166,9 +166,6 @@ pub trait IdTable<T: RawDeviceId, U> {
/// Obtain the pointer to the ID table.
fn as_ptr(&self) -> *const T::RawType;
- /// Obtain the pointer to the bus specific device ID from an index.
- fn id(&self, index: usize) -> &T::RawType;
-
/// Obtain the pointer to the driver-specific information from an index.
fn info(&self, index: usize) -> &U;
}
@@ -180,10 +177,6 @@ fn as_ptr(&self) -> *const T::RawType {
core::ptr::from_ref(self).cast()
}
- fn id(&self, index: usize) -> &T::RawType {
- &self.raw_ids.ids[index]
- }
-
fn info(&self, index: usize) -> &U {
&self.id_infos[index]
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 00/12] rust: driver: use pointers instead of indices for ID info
From: Gary Guo @ 2026-06-29 12:38 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Daniel Almeida,
Tamir Duberstein, Alexandre Courbot, Onur Özkan,
FUJITA Tomonori, David Airlie, Simona Vetter, Bjorn Helgaas,
Krzysztof Wilczyński, Abdiel Janulgue, Robin Murphy,
Dave Ertman, Ira Weiny, Leon Romanovsky, Len Brown, Igor Korotin,
Rob Herring, Saravana Kannan, Viresh Kumar, Michal Wilczynski,
Drew Fustini, Guo Ren, Fu Wei, Uwe Kleine-König
Cc: driver-core, rust-for-linux, linux-kernel, netdev, nova-gpu,
dri-devel, linux-pci, linux-acpi, devicetree, linux-pm, linux-pwm,
linux-usb, Gary Guo
Most C drivers use a pointer (and cast to kernel_ulong_t) for driver_data
fields in device_id. Rust code currently does not do this, but rather use
indices. These indices then needs to be translated to `&IdInfo` separately
and this is by a side table.
This leads to open-coded ACPI/OF handling in driver.rs, which is not
desirable. Convert the code to use pointers (or rather, static references)
instead.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
Changes in v2:
- Change USB to take `Option<&IdInfo>` in addition to PCI due to ability to
dynamically add IDs.
- Mention dyn IDs and driver_override in safety comments and justify why
they're correct.
- Link to v1: https://patch.msgid.link/20260618-id_info-v1-0-96af1e559ef9@garyguo.net
---
Gary Guo (12):
rust: driver: remove `IdTable::id`
rust: driver: simplify `IdArray::new_without_index`
rust: pci: use `Option<&IdInfo>` for device ID info
rust: usb: use `Option<&IdInfo>` for device ID info
rust: net/phy: remove expansion from doc
rust: driver: centralize device ID handling
rust: driver: remove `$module_table_name` from `module_device_table`
rust: driver: store pointers in `DeviceId`
rust: driver: remove open-coded matching logic
rust: driver: remove duplicate ID table
RFC: rust: driver: support map-like syntax for ID table
pci: fix UAF when probe runs concurrent to dyn ID removal
MAINTAINERS | 1 -
drivers/acpi/bus.c | 6 +-
drivers/cpufreq/rcpufreq_dt.rs | 1 -
drivers/gpu/drm/nova/driver.rs | 1 -
drivers/gpu/drm/tyr/driver.rs | 1 -
drivers/gpu/nova-core/driver.rs | 3 +-
drivers/pci/pci-driver.c | 64 +++++------
drivers/pwm/pwm_th1520.rs | 1 -
include/acpi/acpi_bus.h | 11 --
rust/helpers/acpi.c | 16 ---
rust/helpers/helpers.c | 1 -
rust/kernel/acpi.rs | 14 +--
rust/kernel/auxiliary.rs | 18 +--
rust/kernel/device_id.rs | 207 +++++++++++++++++++---------------
rust/kernel/driver.rs | 137 ++--------------------
rust/kernel/i2c.rs | 26 ++---
rust/kernel/net/phy.rs | 66 +----------
rust/kernel/of.rs | 14 +--
rust/kernel/pci.rs | 25 ++--
rust/kernel/platform.rs | 5 +-
rust/kernel/usb.rs | 24 ++--
samples/rust/rust_debugfs.rs | 1 -
samples/rust/rust_dma.rs | 3 +-
samples/rust/rust_driver_auxiliary.rs | 4 +-
samples/rust/rust_driver_i2c.rs | 3 -
samples/rust/rust_driver_pci.rs | 11 +-
samples/rust/rust_driver_platform.rs | 2 -
samples/rust/rust_driver_usb.rs | 3 +-
samples/rust/rust_i2c_client.rs | 2 -
samples/rust/rust_soc.rs | 2 -
30 files changed, 209 insertions(+), 464 deletions(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260612-id_info-23eca472ccd8
Best regards,
--
Gary Guo <gary@garyguo.net>
^ permalink raw reply
* [PATCH 5/5] netfilter: nf_flow_table_path: add VLAN passthrough support
From: Daniel Pawlik @ 2026-06-29 12:32 UTC (permalink / raw)
To: netfilter-devel, netdev
Cc: pablo, fw, phil, davem, edumazet, kuba, pabeni, horms,
andrew+netdev, razor, idosch, matthias.bgg,
angelogioacchino.delregno, bridge, coreteam, linux-mediatek,
linux-arm-kernel, rchen14b, lorenzo, Daniel Pawlik
In-Reply-To: <20260629123253.1912621-1-pawlik.dan@gmail.com>
From: Ryan Chen <rchen14b@gmail.com>
VLAN passthrough packets can be offloaded when bridge-nf-filter-vlan-tagged
is enabled. When a packet has a VLAN tag and the bridge does not have VLAN
filtering enabled (passthrough mode), record the VLAN encap info so the
hardware flow offload entry includes the correct VLAN tag.
Without this change, VLAN-tagged bridged traffic cannot be offloaded by PPE
because the VLAN encap information is missing from the flow entry.
Enable with: echo 1 > /proc/sys/net/bridge/bridge-nf-filter-vlan-tagged
Based on a MediaTek SDK patch by Chak-Kei Lam <chak-kei.lam@mediatek.com>.
Signed-off-by: Ryan Chen <rchen14b@gmail.com>
Signed-off-by: Daniel Pawlik <pawlik.dan@gmail.com>
---
net/netfilter/nf_flow_table_path.c | 32 ++++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 580aa1db3cb4..d15c425c88c4 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -17,6 +17,7 @@
#include <net/netfilter/nf_flow_table.h>
#include <linux/if_bridge.h>
#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
#include <net/route.h>
#include <net/ip6_route.h>
@@ -136,6 +137,29 @@ struct nft_forward_info {
enum flow_offload_xmit_type xmit_type;
};
+static void nft_fill_vlan_passthrough_info(const struct nft_pktinfo *pkt,
+ struct nft_forward_info *info)
+{
+ if (!skb_vlan_tag_present(pkt->skb))
+ return;
+
+ rcu_read_lock();
+ /* when bridge VLAN filtering is enabled, the bridge handles the tag */
+ if (netif_is_bridge_port(pkt->skb->dev) &&
+ !br_vlan_is_enabled_rcu(pkt->skb->dev)) {
+ if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) {
+ info->indev = NULL;
+ } else {
+ info->encap[info->num_encaps].id =
+ skb_vlan_tag_get_id(pkt->skb);
+ info->encap[info->num_encaps].proto =
+ pkt->skb->vlan_proto;
+ info->num_encaps++;
+ }
+ }
+ rcu_read_unlock();
+}
+
static int nft_dev_path_info(const struct net_device_path_stack *stack,
struct nft_forward_info *info,
unsigned char *ha, struct nf_flowtable *flowtable)
@@ -326,8 +350,12 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
nft_br_vlan_dev_fill_forward_path(pkt, &ctx);
}
- if (nft_dev_fill_forward_path(&ctx, route, dst, ct, dir, ha, &stack) < 0 ||
- nft_dev_path_info(&stack, &info, ha, &ft->data) < 0)
+ if (nft_dev_fill_forward_path(&ctx, route, dst, ct, dir, ha, &stack) < 0)
+ return -ENOENT;
+
+ nft_fill_vlan_passthrough_info(pkt, &info);
+
+ if (nft_dev_path_info(&stack, &info, ha, &ft->data) < 0)
return -ENOENT;
if (!nft_flowtable_find_dev(info.indev, ft))
--
2.54.0
^ permalink raw reply related
* [PATCH 4/5] netfilter: nf_flow_table_path: handle DEV_PATH_MTK_WDMA in path info
From: Daniel Pawlik @ 2026-06-29 12:32 UTC (permalink / raw)
To: netfilter-devel, netdev
Cc: pablo, fw, phil, davem, edumazet, kuba, pabeni, horms,
andrew+netdev, razor, idosch, matthias.bgg,
angelogioacchino.delregno, bridge, coreteam, linux-mediatek,
linux-arm-kernel, rchen14b, lorenzo, Daniel Pawlik
In-Reply-To: <20260629123253.1912621-1-pawlik.dan@gmail.com>
From: Ryan Chen <rchen14b@gmail.com>
Without this change, nft_dev_path_info() hits the default -ENOENT path
for WiFi bridge offload via WDMA on MT7996. When a bridged flow goes
through the MT7996 WiFi device, the DEV_PATH_MTK_WDMA step does not set
h_source, causing the PPE entry to receive a zero source MAC and packets
to stall in both software fastpath and hardware path.
Based on a MediaTek SDK patch by Bo-Cun Chen <bc-bocun.chen@mediatek.com>.
Signed-off-by: Ryan Chen <rchen14b@gmail.com>
Signed-off-by: Daniel Pawlik <pawlik.dan@gmail.com>
---
net/netfilter/nf_flow_table_path.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 6c470854127f..580aa1db3cb4 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -219,6 +219,10 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
}
info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
break;
+ case DEV_PATH_MTK_WDMA:
+ if (is_zero_ether_addr(info->h_source))
+ memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
+ break;
default:
return -1;
}
--
2.54.0
^ permalink raw reply related
* [PATCH 3/5] netfilter: nf_flow_table_path: add L2 bridge offload
From: Daniel Pawlik @ 2026-06-29 12:32 UTC (permalink / raw)
To: netfilter-devel, netdev
Cc: pablo, fw, phil, davem, edumazet, kuba, pabeni, horms,
andrew+netdev, razor, idosch, matthias.bgg,
angelogioacchino.delregno, bridge, coreteam, linux-mediatek,
linux-arm-kernel, rchen14b, lorenzo, Daniel Pawlik
In-Reply-To: <20260629123253.1912621-1-pawlik.dan@gmail.com>
From: Ryan Chen <rchen14b@gmail.com>
Allow nft_flow_offload to accelerate traffic forwarded at layer 2 through
Linux bridge ports.
Detection: nft_flow_offload_is_bridging() identifies bridged flows by
checking that the ingress device is a bridge port and that the destination
MAC appears in the bridge FDB with a forwarding destination port (non-local
entry). VLAN resolution and FDB lookup are combined in a single
br_port_get_rcu() call via br_fdb_has_forwarding_entry_rcu().
Routing: nft_flow_route_bridging() allocates minimal dst entries anchored
to the bridge master device via rt_dst_alloc()/ip6_dst_alloc(). A full
routing table lookup via nf_route() is intentionally avoided: it fails for
prefixes that are only bridged, not routed, through the bridge interface
(e.g. when the bridge has no IP address or the bridged subnet is not in
the routing table).
MAC addresses: for bridged flows, nft_dev_forward_path() copies Ethernet
addresses directly from the packet header instead of going through the
neighbour table. Direction (original vs reply) is resolved against the
conntrack direction so both flow directions receive the correct MAC pair.
VLAN context: nft_br_vlan_dev_fill_forward_path() pre-populates the
net_device_path_ctx with the port VLAN id and protocol before the forward
path walk, enabling VLAN-aware hardware offload entries.
Also:
- info->indev is updated for every path type in nft_dev_path_info() so
the bridge ingress device is correctly tracked regardless of path type.
- nft_flow_route() is now a thin dispatcher that delegates to
nft_flow_route_routing() (routed traffic) or nft_flow_route_bridging()
(bridged traffic); the exported API is unchanged.
Path discovery infrastructure was moved to nf_flow_table_path.c in
commit 93d7a7ed0734 ("netfilter: flowtable: move path discovery
infrastructure to its own file"), so all changes land in that file.
Based on a MediaTek SDK patch by Bo-Cun Chen <bc-bocun.chen@mediatek.com>.
Co-developed-by: Daniel Pawlik <pawlik.dan@gmail.com>
Signed-off-by: Daniel Pawlik <pawlik.dan@gmail.com>
Signed-off-by: Ryan Chen <rchen14b@gmail.com>
---
net/netfilter/nf_flow_table_path.c | 167 +++++++++++++++++++++++++++--
1 file changed, 157 insertions(+), 10 deletions(-)
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 98c03b487f52..6c470854127f 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -15,6 +15,10 @@
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_extend.h>
#include <net/netfilter/nf_flow_table.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <net/route.h>
+#include <net/ip6_route.h>
static enum flow_offload_xmit_type nft_xmit_type(struct dst_entry *dst)
{
@@ -42,7 +46,25 @@ static bool nft_is_valid_ether_device(const struct net_device *dev)
return true;
}
-static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
+static bool nft_flow_offload_is_bridging(struct sk_buff *skb)
+{
+ bool ret;
+
+ if (!netif_is_bridge_port(skb->dev))
+ return false;
+ if (!skb_mac_header_was_set(skb))
+ return false;
+
+ rcu_read_lock();
+ ret = br_fdb_has_forwarding_entry_rcu(skb->dev, skb,
+ eth_hdr(skb)->h_dest);
+ rcu_read_unlock();
+
+ return ret;
+}
+
+static int nft_dev_fill_forward_path(struct net_device_path_ctx *ctx,
+ const struct nf_flow_route *route,
const struct dst_entry *dst_cache,
const struct nf_conn *ct,
enum ip_conntrack_dir dir, u8 *ha,
@@ -58,6 +80,12 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
goto out;
}
+ /* Bridging fastpath copies Ethernet addresses into ha; do not replace
+ * them via neighbour lookup on the routed destination device.
+ */
+ if (!is_zero_ether_addr(ha))
+ goto out;
+
n = dst_neigh_lookup(dst_cache, daddr);
if (!n)
return -1;
@@ -72,7 +100,23 @@ static int nft_dev_fill_forward_path(const struct nf_flow_route *route,
return -1;
out:
- return dev_fill_forward_path(dev, ha, stack);
+ return __dev_fill_forward_path(ctx, ha, stack);
+}
+
+static void nft_br_vlan_dev_fill_forward_path(const struct nft_pktinfo *pkt,
+ struct net_device_path_ctx *ctx)
+{
+ __be16 proto = 0;
+ u16 vlan_id;
+
+ rcu_read_lock();
+ vlan_id = br_vlan_get_offload_info_rcu(pkt->skb->dev, pkt->skb, &proto);
+ if (vlan_id) {
+ ctx->num_vlans = 1;
+ ctx->vlan[0].id = vlan_id;
+ ctx->vlan[0].proto = proto;
+ }
+ rcu_read_unlock();
}
struct nft_forward_info {
@@ -103,13 +147,13 @@ static int nft_dev_path_info(const struct net_device_path_stack *stack,
for (i = 0; i < stack->num_paths; i++) {
path = &stack->path[i];
+ info->indev = path->dev;
switch (path->type) {
case DEV_PATH_ETHERNET:
case DEV_PATH_DSA:
case DEV_PATH_VLAN:
case DEV_PATH_PPPOE:
case DEV_PATH_TUN:
- info->indev = path->dev;
if (is_zero_ether_addr(info->h_source))
memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
@@ -244,6 +288,7 @@ static int nft_flow_tunnel_update_route(const struct nft_pktinfo *pkt,
}
static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
+ bool is_bridging,
struct nf_flow_route *route,
const struct nf_conn *ct,
enum ip_conntrack_dir dir,
@@ -251,11 +296,33 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
{
const struct dst_entry *dst = route->tuple[dir].dst;
struct net_device_path_stack stack;
+ struct net_device_path_ctx ctx = {
+ .dev = dst->dev,
+ };
struct nft_forward_info info = {};
+ enum ip_conntrack_info pkt_ctinfo;
+ enum ip_conntrack_dir skb_dir;
+ struct ethhdr *eth;
unsigned char ha[ETH_ALEN];
int i;
- if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) < 0 ||
+ memset(ha, 0, sizeof(ha));
+
+ if (is_bridging) {
+ nf_ct_get(pkt->skb, &pkt_ctinfo);
+ eth = eth_hdr(pkt->skb);
+ skb_dir = CTINFO2DIR(pkt_ctinfo);
+ if (skb_dir != dir) {
+ memcpy(ha, eth->h_source, ETH_ALEN);
+ memcpy(info.h_source, eth->h_dest, ETH_ALEN);
+ } else {
+ memcpy(ha, eth->h_dest, ETH_ALEN);
+ memcpy(info.h_source, eth->h_source, ETH_ALEN);
+ }
+ nft_br_vlan_dev_fill_forward_path(pkt, &ctx);
+ }
+
+ if (nft_dev_fill_forward_path(&ctx, route, dst, ct, dir, ha, &stack) < 0 ||
nft_dev_path_info(&stack, &info, ha, &ft->data) < 0)
return -ENOENT;
@@ -292,9 +359,11 @@ static int nft_dev_forward_path(const struct nft_pktinfo *pkt,
return 0;
}
-int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,
- struct nf_flow_route *route, enum ip_conntrack_dir dir,
- struct nft_flowtable *ft)
+static int nft_flow_route_routing(const struct nft_pktinfo *pkt,
+ const struct nf_conn *ct,
+ struct nf_flow_route *route,
+ enum ip_conntrack_dir dir,
+ struct nft_flowtable *ft)
{
struct dst_entry *this_dst = skb_dst(pkt->skb);
struct dst_entry *other_dst = NULL;
@@ -334,12 +403,12 @@ int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,
nft_default_forward_path(route, this_dst, dir);
nft_default_forward_path(route, other_dst, !dir);
- if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH &&
- nft_dev_forward_path(pkt, route, ct, dir, ft) < 0)
+ if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH &&
+ nft_dev_forward_path(pkt, false, route, ct, dir, ft) < 0)
goto err_dst_release;
if (route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH &&
- nft_dev_forward_path(pkt, route, ct, !dir, ft) < 0)
+ nft_dev_forward_path(pkt, false, route, ct, !dir, ft) < 0)
goto err_dst_release;
return 0;
@@ -349,4 +418,82 @@ int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,
dst_release(route->tuple[!dir].dst);
return -ENOENT;
}
+
+static int nft_flow_route_bridging(const struct nft_pktinfo *pkt,
+ const struct nf_conn *ct,
+ struct nf_flow_route *route,
+ enum ip_conntrack_dir dir,
+ struct nft_flowtable *ft)
+{
+ struct dst_entry *dsts[IP_CT_DIR_MAX] = {};
+ struct net_device *br_dev;
+ int i;
+
+ /* Allocate minimal dsts anchored to the bridge master device to supply
+ * xmit_type and MTU. A full routing lookup via nf_route() is avoided
+ * because it fails for prefixes that are bridged but not routed.
+ */
+ rcu_read_lock();
+ br_dev = netdev_master_upper_dev_get_rcu(pkt->skb->dev);
+ if (!br_dev || !netif_is_bridge_master(br_dev)) {
+ rcu_read_unlock();
+ return -ENOENT;
+ }
+
+ for (i = 0; i < IP_CT_DIR_MAX; i++) {
+ switch (nft_pf(pkt)) {
+ case NFPROTO_IPV4: {
+ struct rtable *rt;
+
+ rt = rt_dst_alloc(br_dev, 0, RTN_UNICAST, true);
+ if (rt)
+ dsts[i] = &rt->dst;
+ break;
+ }
+ case NFPROTO_IPV6: {
+ struct rt6_info *rt;
+
+ rt = ip6_dst_alloc(nft_net(pkt), br_dev, 0);
+ if (rt)
+ dsts[i] = &rt->dst;
+ break;
+ }
+ }
+ }
+ rcu_read_unlock();
+
+ if (!dsts[dir] || !dsts[!dir]) {
+ dst_release(dsts[dir]);
+ dst_release(dsts[!dir]);
+ return -ENOENT;
+ }
+
+ nft_default_forward_path(route, dsts[dir], dir);
+ nft_default_forward_path(route, dsts[!dir], !dir);
+ /* Drop allocation references; route->tuple[*].dst holds the clones. */
+ dst_release(dsts[dir]);
+ dst_release(dsts[!dir]);
+
+ if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH &&
+ route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH) {
+ if (nft_dev_forward_path(pkt, true, route, ct, dir, ft) ||
+ nft_dev_forward_path(pkt, true, route, ct, !dir, ft)) {
+ dst_release(route->tuple[dir].dst);
+ dst_release(route->tuple[!dir].dst);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,
+ struct nf_flow_route *route, enum ip_conntrack_dir dir,
+ struct nft_flowtable *ft)
+{
+ if (nft_flow_offload_is_bridging(pkt->skb))
+ return nft_flow_route_bridging(pkt, ct, route, dir, ft);
+
+ return nft_flow_route_routing(pkt, ct, route, dir, ft);
+}
EXPORT_SYMBOL_GPL(nft_flow_route);
--
2.54.0
^ permalink raw reply related
* [PATCH 2/5] net: bridge: add flow offload helpers
From: Daniel Pawlik @ 2026-06-29 12:32 UTC (permalink / raw)
To: netfilter-devel, netdev
Cc: pablo, fw, phil, davem, edumazet, kuba, pabeni, horms,
andrew+netdev, razor, idosch, matthias.bgg,
angelogioacchino.delregno, bridge, coreteam, linux-mediatek,
linux-arm-kernel, rchen14b, lorenzo, Daniel Pawlik
In-Reply-To: <20260629123253.1912621-1-pawlik.dan@gmail.com>
Add three helpers that expose the bridge state needed by nft_flow_offload
without requiring callers to include net/bridge/br_private.h. Each
performs a single br_port_get_rcu() lookup:
- br_fdb_has_forwarding_entry_rcu(): resolves the VLAN id for the packet
(skb tag or PVID when filtering is on, 0 otherwise) then checks whether
the bridge FDB contains a forwarding entry (dst != NULL, non-local) for
the resulting MAC/VLAN pair.
- br_vlan_get_offload_info_rcu(): when VLAN filtering is active, returns
the VLAN id (skb tag or PVID) and writes the bridge VLAN protocol to
*proto in a single port lookup. Returns 0 when filtering is off.
- br_vlan_is_enabled_rcu(): returns true when VLAN filtering is enabled
on the bridge a port device belongs to.
Based on MediaTek SDK patches by Bo-Cun Chen <bc-bocun.chen@mediatek.com>
and the OpenWrt bridge offload series by Ryan Chen <rchen14b@gmail.com>.
Signed-off-by: Daniel Pawlik <pawlik.dan@gmail.com>
---
include/linux/if_bridge.h | 23 ++++++++++++++++++++
net/bridge/br_fdb.c | 32 ++++++++++++++++++++++++++++
net/bridge/br_vlan.c | 45 +++++++++++++++++++++++++++++++++++++++
3 files changed, 100 insertions(+)
diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
index 75673b8bffcb..c1cae54749c5 100644
--- a/include/linux/if_bridge.h
+++ b/include/linux/if_bridge.h
@@ -148,6 +148,9 @@ int br_vlan_get_info(const struct net_device *dev, u16 vid,
struct bridge_vlan_info *p_vinfo);
int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
struct bridge_vlan_info *p_vinfo);
+u16 br_vlan_get_offload_info_rcu(const struct net_device *dev,
+ const struct sk_buff *skb, __be16 *proto);
+bool br_vlan_is_enabled_rcu(const struct net_device *dev);
bool br_mst_enabled(const struct net_device *dev);
int br_mst_get_info(const struct net_device *dev, u16 msti, unsigned long *vids);
int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state);
@@ -184,6 +187,17 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
return -EINVAL;
}
+static inline u16 br_vlan_get_offload_info_rcu(const struct net_device *dev,
+ const struct sk_buff *skb,
+ __be16 *proto)
+{
+ return 0;
+}
+
+static inline bool br_vlan_is_enabled_rcu(const struct net_device *dev)
+{
+ return false;
+}
static inline bool br_mst_enabled(const struct net_device *dev)
{
return false;
@@ -209,6 +223,8 @@ void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
u8 br_port_get_stp_state(const struct net_device *dev);
clock_t br_get_ageing_time(const struct net_device *br_dev);
+bool br_fdb_has_forwarding_entry_rcu(const struct net_device *dev,
+ const struct sk_buff *skb, const u8 *addr);
#else
static inline struct net_device *
br_fdb_find_port(const struct net_device *br_dev,
@@ -237,6 +253,13 @@ static inline clock_t br_get_ageing_time(const struct net_device *br_dev)
{
return 0;
}
+
+static inline bool br_fdb_has_forwarding_entry_rcu(const struct net_device *dev,
+ const struct sk_buff *skb,
+ const u8 *addr)
+{
+ return false;
+}
#endif
#endif
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
index e4570bbed854..3161c2689f6a 100644
--- a/net/bridge/br_fdb.c
+++ b/net/bridge/br_fdb.c
@@ -267,6 +267,38 @@ struct net_bridge_fdb_entry *br_fdb_find_rcu(struct net_bridge *br,
return fdb_find_rcu(&br->fdb_hash_tbl, addr, vid);
}
+/**
+ * br_fdb_has_forwarding_entry_rcu - check if a MAC can be forwarded by the bridge
+ * @dev: bridge port network device
+ * @skb: packet buffer (used to determine VLAN id)
+ * @addr: destination MAC address
+ *
+ * Resolves the VLAN id for @skb on @dev (skb VLAN tag when present, PVID
+ * when VLAN filtering is enabled, 0 otherwise) then checks whether the bridge
+ * FDB contains a forwarding entry (dst != NULL, not a local/self entry) for
+ * @addr and that VLAN id. Single br_port_get_rcu() lookup.
+ * Must be called under RCU read lock.
+ */
+bool br_fdb_has_forwarding_entry_rcu(const struct net_device *dev,
+ const struct sk_buff *skb, const u8 *addr)
+{
+ struct net_bridge_port *port = br_port_get_rcu(dev);
+ struct net_bridge_fdb_entry *fdb;
+ u16 vid = 0;
+
+ if (!port)
+ return false;
+ if (br_opt_get(port->br, BROPT_VLAN_ENABLED)) {
+ if (skb_vlan_tag_present(skb))
+ vid = skb_vlan_tag_get_id(skb);
+ else
+ br_vlan_get_pvid_rcu(dev, &vid);
+ }
+ fdb = br_fdb_find_rcu(port->br, addr, vid);
+ return fdb && fdb->dst;
+}
+EXPORT_SYMBOL_GPL(br_fdb_has_forwarding_entry_rcu);
+
/* When a static FDB entry is added, the mac address from the entry is
* added to the bridge private HW address list and all required ports
* are then updated with the new information.
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index 5560afcaaca3..0b296362adf7 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -1559,6 +1559,51 @@ int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
}
EXPORT_SYMBOL_GPL(br_vlan_get_info_rcu);
+/**
+ * br_vlan_get_offload_info_rcu - get VLAN id and protocol for bridge flow offload
+ * @dev: bridge port network device
+ * @skb: packet buffer
+ * @proto: output for the bridge VLAN protocol (set only when return value != 0)
+ *
+ * When VLAN filtering is enabled, resolves the VLAN id for flow offload (skb
+ * VLAN tag id if present, PVID otherwise) and writes the bridge VLAN protocol
+ * to @proto. Returns 0 when filtering is off or @dev is not a bridge port.
+ * Single br_port_get_rcu() lookup. Must be called under RCU read lock.
+ */
+u16 br_vlan_get_offload_info_rcu(const struct net_device *dev,
+ const struct sk_buff *skb, __be16 *proto)
+{
+ struct net_bridge_port *port = br_port_get_rcu(dev);
+ u16 vid = 0;
+
+ if (!port || !br_opt_get(port->br, BROPT_VLAN_ENABLED))
+ return 0;
+ if (skb_vlan_tag_present(skb))
+ vid = skb_vlan_tag_get_id(skb);
+ else
+ br_vlan_get_pvid_rcu(dev, &vid);
+ if (vid)
+ *proto = port->br->vlan_proto;
+ return vid;
+}
+EXPORT_SYMBOL_GPL(br_vlan_get_offload_info_rcu);
+
+/**
+ * br_vlan_is_enabled_rcu - check if VLAN filtering is active on a port's bridge
+ * @dev: bridge port network device
+ *
+ * Returns true if VLAN filtering is enabled on the bridge @dev belongs to.
+ * Returns false when @dev is not a bridge port or filtering is off.
+ * Must be called under RCU read lock.
+ */
+bool br_vlan_is_enabled_rcu(const struct net_device *dev)
+{
+ struct net_bridge_port *port = br_port_get_rcu(dev);
+
+ return port && br_opt_get(port->br, BROPT_VLAN_ENABLED);
+}
+EXPORT_SYMBOL_GPL(br_vlan_is_enabled_rcu);
+
static int br_vlan_is_bind_vlan_dev(const struct net_device *dev)
{
return is_vlan_dev(dev) &&
--
2.54.0
^ permalink raw reply related
* [PATCH 1/5] net: export __dev_fill_forward_path
From: Daniel Pawlik @ 2026-06-29 12:32 UTC (permalink / raw)
To: netfilter-devel, netdev
Cc: pablo, fw, phil, davem, edumazet, kuba, pabeni, horms,
andrew+netdev, razor, idosch, matthias.bgg,
angelogioacchino.delregno, bridge, coreteam, linux-mediatek,
linux-arm-kernel, rchen14b, lorenzo, Daniel Pawlik
In-Reply-To: <20260629123253.1912621-1-pawlik.dan@gmail.com>
From: Ryan Chen <rchen14b@gmail.com>
Export __dev_fill_forward_path() which accepts a caller-supplied
net_device_path_ctx, allowing callers to pre-populate context (e.g.
VLAN state) before the forward path walk. The existing
dev_fill_forward_path() is refactored to call it.
This is a prerequisite for nft_flow_offload bridge offload, which needs
to supply a pre-populated ctx for bridge port devices.
Signed-off-by: Ryan Chen <rchen14b@gmail.com>
Signed-off-by: Daniel Pawlik <pawlik.dan@gmail.com>
---
include/linux/netdevice.h | 2 ++
net/core/dev.c | 32 ++++++++++++++++++++------------
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b5..c1d0b897de95 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3422,6 +3422,8 @@ int dev_get_iflink(const struct net_device *dev);
int dev_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb);
int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
struct net_device_path_stack *stack);
+int __dev_fill_forward_path(struct net_device_path_ctx *ctx, const u8 *daddr,
+ struct net_device_path_stack *stack);
struct net_device *dev_get_by_name(struct net *net, const char *name);
struct net_device *dev_get_by_name_rcu(struct net *net, const char *name);
struct net_device *__dev_get_by_name(struct net *net, const char *name);
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e0..62f1d0b64c76 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -750,44 +750,52 @@ static struct net_device_path *dev_fwd_path(struct net_device_path_stack *stack)
return &stack->path[k];
}
-int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
- struct net_device_path_stack *stack)
+int __dev_fill_forward_path(struct net_device_path_ctx *ctx, const u8 *daddr,
+ struct net_device_path_stack *stack)
{
const struct net_device *last_dev;
- struct net_device_path_ctx ctx = {
- .dev = dev,
- };
struct net_device_path *path;
int ret = 0;
- memcpy(ctx.daddr, daddr, sizeof(ctx.daddr));
+ memcpy(ctx->daddr, daddr, sizeof(ctx->daddr));
stack->num_paths = 0;
- while (ctx.dev && ctx.dev->netdev_ops->ndo_fill_forward_path) {
- last_dev = ctx.dev;
+ while (ctx->dev && ctx->dev->netdev_ops->ndo_fill_forward_path) {
+ last_dev = ctx->dev;
path = dev_fwd_path(stack);
if (!path)
return -1;
memset(path, 0, sizeof(struct net_device_path));
- ret = ctx.dev->netdev_ops->ndo_fill_forward_path(&ctx, path);
+ ret = ctx->dev->netdev_ops->ndo_fill_forward_path(ctx, path);
if (ret < 0)
return -1;
- if (WARN_ON_ONCE(last_dev == ctx.dev))
+ if (WARN_ON_ONCE(last_dev == ctx->dev))
return -1;
}
- if (!ctx.dev)
+ if (!ctx->dev)
return ret;
path = dev_fwd_path(stack);
if (!path)
return -1;
path->type = DEV_PATH_ETHERNET;
- path->dev = ctx.dev;
+ path->dev = ctx->dev;
return ret;
}
+EXPORT_SYMBOL_GPL(__dev_fill_forward_path);
+
+int dev_fill_forward_path(const struct net_device *dev, const u8 *daddr,
+ struct net_device_path_stack *stack)
+{
+ struct net_device_path_ctx ctx = {
+ .dev = dev,
+ };
+
+ return __dev_fill_forward_path(&ctx, daddr, stack);
+}
EXPORT_SYMBOL_GPL(dev_fill_forward_path);
/* must be called under rcu_read_lock(), as we dont take a reference */
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox