* Re: Re: [PATCH net-next v03 4/6] hinic3: Add ethtool rss ops
From: Fan Gong @ 2026-04-02 7:17 UTC (permalink / raw)
To: mohsin.bashr
Cc: andrew+netdev, davem, edumazet, gongfan1, guoxin09, horms,
ioana.ciornei, kuba, linux-doc, linux-kernel, luosifu,
maxime.chevallier, netdev, pabeni, shijing34, wulike1,
zhengjiezhen, zhoushuai28, zhuyikai1
In-Reply-To: <3b252b2b-6be7-4a8d-9782-39695e948635@gmail.com>
On 4/1/2026 3:53 PM, Mohsin Bashir wrote:
> > + err = hinic3_update_rss_hash_opts(netdev, cmd, rss_type);
> > + if (err)
> > + return err;
> > +
> > + err = hinic3_set_rss_type(nic_dev->hwdev, *rss_type);
>
> So if we fail here, we have already modified the rss_type in-place. From this on-wards, the HW state would diverge from in-memory state. How about use a local copy and only update if no error?
>
> > + if (err) {
> > + netdev_err(netdev, "Failed to set rss type\n");
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
Thanks for your four patch reviews.
For patch 01, we'll add nic_info to inform the user that depth is timmed and restore q_params.
For patch 02, we'll remove const.
For patch 03, we'll change errcode and modify rx_max_coalesced_frames_high judgement condition.
As tx and rx share interrupt, we only use ETHTOOL_COALESCE_RX_USECS to avoid user misunderstanding.
So we do not add ETHTOOL_COALESCE_TX_USECS.
For patch 04, we overlooked the recovery of rss_type in error handling and we'll fix it.
We will fix them in next version.
Fan gong
^ permalink raw reply
* Re: [PATCH net v2] net/x25: Fix overflow when accumulating packets
From: Martin Schiller @ 2026-04-02 7:14 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, edumazet, pabeni, yimingqian591, linux-x25, netdev,
linux-kernel, security
In-Reply-To: <20260402024855.842920-1-kuba@kernel.org>
On 2026-04-02 04:48, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> net/x25: Fix overflow when accumulating packets
>
> This commit adds a check to prevent x25_sock.fraglen from overflowing
> during packet accumulation. It also resets the fragment length when
> purging the fragment queue.
>
>> diff --git a/net/x25/x25_in.c b/net/x25/x25_in.c
>> index b981a4828d08..cb84c683d249 100644
>> --- a/net/x25/x25_in.c
>> +++ b/net/x25/x25_in.c
>> @@ -34,6 +34,12 @@ static int x25_queue_rx_frame(struct sock *sk,
>> struct sk_buff *skb, int more)
>> struct sk_buff *skbo, *skbn = skb;
>> struct x25_sock *x25 = x25_sk(sk);
>>
>> + /* make sure we don't overflow */
>> + if (x25->fraglen + skb->len > USHRT_MAX) {
>> + kfree_skb(skb);
>> + return 1;
>> + }
>
> Does this cause the skb to be freed twice?
>
> When x25_queue_rx_frame() frees the skb and returns 1, the caller
> x25_state3_machine() leaves its local queued variable as 0 and returns
> 0.
>
> This 0 value propagates up through x25_process_rx_frame() to callers
> like x25_backlog_rcv() or x25_lapb_receive_frame(). Since the return
> value indicates the packet was not consumed by the protocol stack,
> these
> upper-level functions will call kfree_skb(skb) again on the already
> freed skb.
>
> Would it be safer to return 1 without calling kfree_skb(skb) here so
> the callers can handle freeing the unconsumed packet?
This is already fixed / changed in the v4 patch set.
^ permalink raw reply
* [PATCH] net: qualcomm: qca_uart: report the consumed byte on RX skb allocation failure
From: Pengpeng Hou @ 2026-04-02 7:12 UTC (permalink / raw)
To: Stefan Wahren, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, pengpeng, stable
qca_tty_receive() consumes each input byte before checking whether a
completed frame needs a fresh receive skb. When the current byte completes
a frame, the driver delivers that frame and then allocates a new skb for
the next one.
If that allocation fails, the current code returns i even though data[i]
has already been consumed and may already have completed the delivered
frame. Since serdev interprets the return value as the number of accepted
bytes, this under-reports progress by one byte and can replay the final
byte of the completed frame into a fresh parser state on the next call.
Return i + 1 in that failure path so the accepted-byte count matches the
actual receive-state progress.
Fixes: dfc768fbe618 ("net: qualcomm: add QCA7000 UART driver")
Cc: stable@vger.kernel.org
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/qualcomm/qca_uart.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/qualcomm/qca_uart.c b/drivers/net/ethernet/qualcomm/qca_uart.c
index 37efb1ea9fcd..847a5f928e41 100644
--- a/drivers/net/ethernet/qualcomm/qca_uart.c
+++ b/drivers/net/ethernet/qualcomm/qca_uart.c
@@ -100,7 +100,7 @@ qca_tty_receive(struct serdev_device *serdev, const u8 *data, size_t count)
if (!qca->rx_skb) {
netdev_dbg(netdev, "recv: out of RX resources\n");
n_stats->rx_errors++;
- return i;
+ return i + 1;
}
}
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* Re: [PATCH net-next v3 0/2] net: phy: microchip: add downshift support for LAN88xx
From: Nicolai Buchwitz @ 2026-04-02 7:11 UTC (permalink / raw)
To: Jakub Kicinski; +Cc: netdev, Phil Elwell, Russell King, Andrew Lunn
In-Reply-To: <20260401184833.024cdb7f@kernel.org>
On 2.4.2026 03:48, Jakub Kicinski wrote:
> On Wed, 1 Apr 2026 14:38:43 +0200 Nicolai Buchwitz wrote:
>> Add standard ETHTOOL_PHY_DOWNSHIFT tunable support for the Microchip
>> LAN88xx PHY, following the same pattern used by Marvell and other PHY
>> drivers.
>
> Please do not post patches in reply to a previous posting.
Sorry, that was accidental. Previous versions were sent as fresh
threads and I'll make sure to do so going forward.
> Add a lore link to the previous posting in the changelog.
Makes sense, I will add a lore link to the previous patches in future
changelogs.
> You're submitting quite a lot of patches, it'd be great if
> you could give our docs a read before sending more :/
> https://www.kernel.org/doc/html/next/process/maintainer-netdev.html
Will do, thanks for the pointer.
Nicolai
^ permalink raw reply
* [PATCH] usb: rtl8150: avoid using uninitialized CSCR value
From: Morduan Zang @ 2026-04-02 7:07 UTC (permalink / raw)
To: Petko Manolov
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-usb, netdev, linux-kernel,
syzbot+9db6c624635564ad813c, Morduan Zang
Check get_registers() when reading CSCR in set_carrier().
If the control transfer fails, don't use the stack value.
Reported-by: syzbot+9db6c624635564ad813c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=9db6c624635564ad813c
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Morduan Zang <zhangdandan@uniontech.com>
---
drivers/net/usb/rtl8150.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c
index 4cda0643afb6..7e32726d3e6f 100644
--- a/drivers/net/usb/rtl8150.c
+++ b/drivers/net/usb/rtl8150.c
@@ -722,7 +722,11 @@ static void set_carrier(struct net_device *netdev)
rtl8150_t *dev = netdev_priv(netdev);
short tmp;
- get_registers(dev, CSCR, 2, &tmp);
+ if (get_registers(dev, CSCR, 2, &tmp) < 0) {
+ netif_carrier_off(netdev);
+ return;
+ }
+
if (tmp & CSCR_LINK_STATUS)
netif_carrier_on(netdev);
else
--
2.50.1
^ permalink raw reply related
* [PATCH net] ipv4: icmp: fix null-ptr-deref in icmp_build_probe()
From: Yiqi Sun @ 2026-04-02 7:04 UTC (permalink / raw)
To: davem, dsahern, edumazet, kuba, pabeni; +Cc: horms, netdev, Yiqi Sun
ipv6_stub->ipv6_dev_find() may return ERR_PTR(-EAFNOSUPPORT) when the
IPv6 stack is not active (CONFIG_IPV6=m and not loaded), and passing
this error pointer to dev_hold() will cause a kernel crash with
null-ptr-deref.
Instead, silently discard the request. RFC 8335 does not appear to
define a specific response for the case where an IPv6 interface
identifier is syntactically valid but the implementation cannot perform
the lookup at runtime, and silently dropping the request may safer than
misreporting "No Such Interface".
Fixes: d329ea5bd884 ("icmp: add response to RFC 8335 PROBE messages")
Signed-off-by: Yiqi Sun <sunyiqixm@gmail.com>
---
net/ipv4/icmp.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 568bd1e95d44..d294666c68d9 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -1270,7 +1270,8 @@ static enum skb_drop_reason icmp_echo(struct sk_buff *skb)
* Searches for net_device that matches PROBE interface identifier
* and builds PROBE reply message in icmphdr.
*
- * Returns false if PROBE responses are disabled via sysctl
+ * Returns false if PROBE responses are disabled via sysctl or
+ * the request should be silently discarded.
*/
bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
@@ -1346,6 +1347,13 @@ bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
if (iio->ident.addr.ctype3_hdr.addrlen != sizeof(struct in6_addr))
goto send_mal_query;
dev = ipv6_stub->ipv6_dev_find(net, &iio->ident.addr.ip_addr.ipv6_addr, dev);
+ /*
+ * If IPv6 identifier lookup is unavailable, silently
+ * discard the request instead of misreporting NO_IF.
+ */
+ if (IS_ERR(dev))
+ return false;
+
dev_hold(dev);
break;
#endif
--
2.34.1
^ permalink raw reply related
* Re: "Dead loop on virtual device" error without softirq-BKL on PREEMPT_RT
From: Sebastian Andrzej Siewior @ 2026-04-02 7:03 UTC (permalink / raw)
To: Daniel Vacek
Cc: edumazet, kuba, linux-kernel, linux-rt-devel, netdev, spasswolf,
tglx
In-Reply-To: <CAPjX3FePCKQTaxB6zuo+_V09PZct=utw3vgJUTM_GrZQtv7_TA@mail.gmail.com>
On 2026-04-01 18:55:43 [+0200], Daniel Vacek wrote:
> > > > The above was me thinking and does not even compile for !RT. Commit
> > > > b824c3e16c190 ("net: Provide a PREEMPT_RT specific check for
> > > > netdev_queue::_xmit_lock") is what was merged in the end.
>
> Thinking about it again, wouldn't it be better to have one generic
> solution rather then special-casing for PREEMPT_RT vs. !PREEMPT_RT?
PREEMPT_RT and !PREEMPT_RT is fundamentally different here. The one is
not preemptible and records the CPU of the lock owner to detect a
recursive deadlock.
The other is preemptible, uses a different locking type/ class which
records the lock owner which can be utilised for this purpose.
A generic thing would be to remove this and rely on lockdep. This could
work if it is only a devel thing and never "I setup something and make a
loop" sort of thing.
> --nX
Sebastian
^ permalink raw reply
* [PATCH bpf-next v2 1/1] bpf: Refactor dynptr mutability tracking
From: Amery Hung @ 2026-04-02 6:50 UTC (permalink / raw)
To: bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, memxor, eddyz87,
yatsenko, martin.lau, ameryhung, kernel-team
In-Reply-To: <20260402065013.884228-1-ameryhung@gmail.com>
Redefine dynptr mutability and fix inconsistency in the verifier and
kfunc signatures. Dynptr mutability is at two levels. The first is
the bpf_dynptr structure and the second is the memory the dynptr points
to. The verifer currently tracks the mutability of the bpf_dynptr struct
through helper and kfunc prototypes, where "const struct bpf_dynptr *"
means the structure itself is immutable. The second level is tracked
in upper bit of bpf_dynptr->size in runtime and is not changed in this
patch.
There are two type of inconsistency in the verfier regarding the
mutability of the bpf_dynptr struct. First, there are many existing
kfuncs whose prototypes are wrong. For example, bpf_dynptr_adjust()
mutates a dynptr's start and offset but marks the argument as a const
pointer. At the same time many other kfuncs that does not mutate the
dynptr but mark themselves as mutable. Second, the verifier currently
does not honor the const qualifier in kfunc prototypes as it determines
whether tagging the arg_type with MEM_RDONLY or not based on the register
state.
Since all the verifier care is to prevent CONST_PTR_TO_DYNPTR from
being destroyed in callback and global subprogram, redefine the
mutability at the bpf_dynptr level to just bpf_dynptr_kern->data. Then,
explicitly prohibit passing CONST_PTR_TO_DYNPTR to an argument tagged
with MEM_UNINIT or OBJ_RELEASE. The mutability of a dynptr's view is not
really interesting so drop MEM_RDONLY annotation for dynptr from the
helpers and kfuncs. Plus, if the mutability of the entire bpf_dynptr
were to be done correctly, it would kill the bpf_dynptr_adjust() usage
in callback and global subporgram.
Implementation wise
- First, make sure all kfunc arg are correctly tagged: Tag the dynptr
argument of bpf_dynptr_file_discard() with OBJ_RELEASE.
- Then, in process_dynptr_func(), make sure CONST_PTR_TO_DYNPTR cannot
be passed to argument tagged with MEM_UNINIT or OBJ_RELEASE. For
MEM_UNINIT, it is already checked by is_dynptr_reg_valid_uninit().
For OBJ_RELEASE, check against OBJ_RELEASE instead of MEM_RDONLY and
drop a now identical check in umark_stack_slots_dynptr().
- Remove the mutual exclusive check between MEM_UNINIT and MEM_RDONLY,
but don't add a MEM_UNINIT and OBJ_RELEASE version as it is obviously
wrong.
Note that while this patch stops following the C semantic for the
mutability of bpf_dynptr, the prototype of kfuncs are still fixed to
maintain the correct C semantics in the helper implementation. Adding or
removing the const qualifier does not break backward compatibility.
In test_kfunc_dynptr_param.c, initialize dynptr to 0 to avoid
-Wuninitialized-const-pointer warning.
Signed-off-by: Amery Hung <ameryhung@gmail.com>
---
fs/verity/measure.c | 2 +-
include/linux/bpf.h | 8 +--
kernel/bpf/btf.c | 2 +-
kernel/bpf/helpers.c | 18 ++---
kernel/bpf/verifier.c | 68 +++++--------------
kernel/trace/bpf_trace.c | 18 ++---
tools/testing/selftests/bpf/bpf_kfuncs.h | 8 +--
.../selftests/bpf/progs/dynptr_success.c | 6 +-
.../bpf/progs/test_kfunc_dynptr_param.c | 9 +--
9 files changed, 51 insertions(+), 88 deletions(-)
diff --git a/fs/verity/measure.c b/fs/verity/measure.c
index 6a35623ebdf0..3840436e4510 100644
--- a/fs/verity/measure.c
+++ b/fs/verity/measure.c
@@ -118,7 +118,7 @@ __bpf_kfunc_start_defs();
*
* Return: 0 on success, a negative value on error.
*/
-__bpf_kfunc int bpf_get_fsverity_digest(struct file *file, struct bpf_dynptr *digest_p)
+__bpf_kfunc int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynptr *digest_p)
{
struct bpf_dynptr_kern *digest_ptr = (struct bpf_dynptr_kern *)digest_p;
const struct inode *inode = file_inode(file);
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 05b34a6355b0..329b78940b79 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3621,8 +3621,8 @@ static inline int bpf_fd_reuseport_array_update_elem(struct bpf_map *map,
struct bpf_key *bpf_lookup_user_key(s32 serial, u64 flags);
struct bpf_key *bpf_lookup_system_key(u64 id);
void bpf_key_put(struct bpf_key *bkey);
-int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
- struct bpf_dynptr *sig_p,
+int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,
+ const struct bpf_dynptr *sig_p,
struct bpf_key *trusted_keyring);
#else
@@ -3640,8 +3640,8 @@ static inline void bpf_key_put(struct bpf_key *bkey)
{
}
-static inline int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
- struct bpf_dynptr *sig_p,
+static inline int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,
+ const struct bpf_dynptr *sig_p,
struct bpf_key *trusted_keyring)
{
return -EOPNOTSUPP;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a62d78581207..3c2aaa3c5004 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7973,7 +7973,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
bpf_log(log, "arg#%d has invalid combination of tags\n", i);
return -EINVAL;
}
- sub->args[i].arg_type = ARG_PTR_TO_DYNPTR | MEM_RDONLY;
+ sub->args[i].arg_type = ARG_PTR_TO_DYNPTR;
continue;
}
if (tags & ARG_TAG_TRUSTED) {
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 2d8538bf4cfa..98a76f28b76e 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1929,7 +1929,7 @@ static const struct bpf_func_proto bpf_dynptr_read_proto = {
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
- .arg3_type = ARG_PTR_TO_DYNPTR | MEM_RDONLY,
+ .arg3_type = ARG_PTR_TO_DYNPTR,
.arg4_type = ARG_ANYTHING,
.arg5_type = ARG_ANYTHING,
};
@@ -1986,7 +1986,7 @@ static const struct bpf_func_proto bpf_dynptr_write_proto = {
.func = bpf_dynptr_write,
.gpl_only = false,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_DYNPTR | MEM_RDONLY,
+ .arg1_type = ARG_PTR_TO_DYNPTR,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_PTR_TO_MEM | MEM_RDONLY,
.arg4_type = ARG_CONST_SIZE_OR_ZERO,
@@ -2029,7 +2029,7 @@ static const struct bpf_func_proto bpf_dynptr_data_proto = {
.func = bpf_dynptr_data,
.gpl_only = false,
.ret_type = RET_PTR_TO_DYNPTR_MEM_OR_NULL,
- .arg1_type = ARG_PTR_TO_DYNPTR | MEM_RDONLY,
+ .arg1_type = ARG_PTR_TO_DYNPTR,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO,
};
@@ -3057,7 +3057,7 @@ __bpf_kfunc void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *p, u64 offset,
return bpf_dynptr_slice(p, offset, buffer__nullable, buffer__szk);
}
-__bpf_kfunc int bpf_dynptr_adjust(const struct bpf_dynptr *p, u64 start, u64 end)
+__bpf_kfunc int bpf_dynptr_adjust(struct bpf_dynptr *p, u64 start, u64 end)
{
struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p;
u64 size;
@@ -3130,8 +3130,8 @@ __bpf_kfunc int bpf_dynptr_clone(const struct bpf_dynptr *p,
* Copies data from source dynptr to destination dynptr.
* Returns 0 on success; negative error, otherwise.
*/
-__bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u64 dst_off,
- struct bpf_dynptr *src_ptr, u64 src_off, u64 size)
+__bpf_kfunc int bpf_dynptr_copy(const struct bpf_dynptr *dst_ptr, u64 dst_off,
+ const struct bpf_dynptr *src_ptr, u64 src_off, u64 size)
{
struct bpf_dynptr_kern *dst = (struct bpf_dynptr_kern *)dst_ptr;
struct bpf_dynptr_kern *src = (struct bpf_dynptr_kern *)src_ptr;
@@ -3185,7 +3185,7 @@ __bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u64 dst_off,
* at @offset with the constant byte @val.
* Returns 0 on success; negative error, otherwise.
*/
-__bpf_kfunc int bpf_dynptr_memset(struct bpf_dynptr *p, u64 offset, u64 size, u8 val)
+__bpf_kfunc int bpf_dynptr_memset(const struct bpf_dynptr *p, u64 offset, u64 size, u8 val)
{
struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p;
u64 chunk_sz, write_off;
@@ -4199,8 +4199,8 @@ __bpf_kfunc void bpf_key_put(struct bpf_key *bkey)
*
* Return: 0 on success, a negative value on error.
*/
-__bpf_kfunc int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_p,
- struct bpf_dynptr *sig_p,
+__bpf_kfunc int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_p,
+ const struct bpf_dynptr *sig_p,
struct bpf_key *trusted_keyring)
{
#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 8c1cf2eb6cbb..15543252e15c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -864,15 +864,6 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
struct bpf_func_state *state = func(env, reg);
int spi, ref_obj_id, i;
- /*
- * This can only be set for PTR_TO_STACK, as CONST_PTR_TO_DYNPTR cannot
- * be released by any dynptr helper. Hence, unmark_stack_slots_dynptr
- * is safe to do directly.
- */
- if (reg->type == CONST_PTR_TO_DYNPTR) {
- verifier_bug(env, "CONST_PTR_TO_DYNPTR cannot be released");
- return -EFAULT;
- }
spi = dynptr_get_spi(env, reg);
if (spi < 0)
return spi;
@@ -8873,23 +8864,12 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
* bytes as STACK_DYNPTR in case of PTR_TO_STACK. In case of
* CONST_PTR_TO_DYNPTR, we are guaranteed to get the beginning of the object.
*
- * Mutability of bpf_dynptr is at two levels, one is at the level of struct
- * bpf_dynptr itself, i.e. whether the helper is receiving a pointer to struct
- * bpf_dynptr or pointer to const struct bpf_dynptr. In the former case, it can
- * mutate the view of the dynptr and also possibly destroy it. In the latter
- * case, it cannot mutate the bpf_dynptr itself but it can still mutate the
- * memory that dynptr points to.
- *
- * The verifier will keep track both levels of mutation (bpf_dynptr's in
- * reg->type and the memory's in reg->dynptr.type), but there is no support for
- * readonly dynptr view yet, hence only the first case is tracked and checked.
- *
- * This is consistent with how C applies the const modifier to a struct object,
- * where the pointer itself inside bpf_dynptr becomes const but not what it
- * points to.
- *
- * Helpers which do not mutate the bpf_dynptr set MEM_RDONLY in their argument
- * type, and declare it as 'const struct bpf_dynptr *' in their prototype.
+ * Mutability of bpf_dynptr is at two levels: the dynptr and the memory the
+ * dynptr points to. At the first level, the verifier will make sure a
+ * CONST_PTR_TO_DYNPTR cannot be reinitialized or destroyed. The mutability of
+ * a dynptr's view (i.e., start and offset) is not tracked as there is not such
+ * use case. The second level is tracked using the upper bit of bpf_dynptr->size
+ * and checked dynamically during runtime.
*/
static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn_idx,
enum bpf_arg_type arg_type, int clone_ref_obj_id)
@@ -8904,14 +8884,6 @@ static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn
return -EINVAL;
}
- /* MEM_UNINIT and MEM_RDONLY are exclusive, when applied to an
- * ARG_PTR_TO_DYNPTR (or ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_*):
- */
- if ((arg_type & (MEM_UNINIT | MEM_RDONLY)) == (MEM_UNINIT | MEM_RDONLY)) {
- verifier_bug(env, "misconfigured dynptr helper type flags");
- return -EFAULT;
- }
-
/* MEM_UNINIT - Points to memory that is an appropriate candidate for
* constructing a mutable bpf_dynptr object.
*
@@ -8919,13 +8891,12 @@ static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn
* pointing to a region of at least 16 bytes which doesn't
* contain an existing bpf_dynptr.
*
- * MEM_RDONLY - Points to a initialized bpf_dynptr that will not be
- * mutated or destroyed. However, the memory it points to
- * may be mutated.
+ * OBJ_RELEASE - Points to a initialized bpf_dynptr that will be
+ * destroyed.
*
- * None - Points to a initialized dynptr that can be mutated and
- * destroyed, including mutation of the memory it points
- * to.
+ * None - Points to a initialized dynptr that cannot be
+ * reinitialized or destroyed. However, the view of the
+ * dynptr and the memory it points to may be mutated.
*/
if (arg_type & MEM_UNINIT) {
int i;
@@ -8944,10 +8915,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn
}
err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx, clone_ref_obj_id);
- } else /* MEM_RDONLY and None case from above */ {
+ } else /* OBJ_RELEASE and None case from above */ {
/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
- if (reg->type == CONST_PTR_TO_DYNPTR && !(arg_type & MEM_RDONLY)) {
- verbose(env, "cannot pass pointer to const bpf_dynptr, the helper mutates it\n");
+ if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
+ verbose(env, "CONST_PTR_TO_DYNPTR cannot be released");
return -EINVAL;
}
@@ -8958,7 +8929,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn
return -EINVAL;
}
- /* Fold modifiers (in this case, MEM_RDONLY) when checking expected type */
+ /* Fold modifiers (in this case, OBJ_RELEASE) when checking expected type */
if (!is_dynptr_type_expected(env, reg, arg_type & ~MEM_RDONLY)) {
verbose(env,
"Expected a dynptr of type %s as arg #%d\n",
@@ -10803,7 +10774,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
bpf_log(log, "R%d is not a pointer to arena or scalar.\n", regno);
return -EINVAL;
}
- } else if (arg->arg_type == (ARG_PTR_TO_DYNPTR | MEM_RDONLY)) {
+ } else if (arg->arg_type == ARG_PTR_TO_DYNPTR) {
ret = check_func_arg_reg_off(env, reg, regno, ARG_PTR_TO_DYNPTR);
if (ret)
return ret;
@@ -13718,9 +13689,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
enum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR;
int clone_ref_obj_id = 0;
- if (reg->type == CONST_PTR_TO_DYNPTR)
- dynptr_arg_type |= MEM_RDONLY;
-
if (is_kfunc_arg_uninit(btf, &args[i]))
dynptr_arg_type |= MEM_UNINIT;
@@ -13733,7 +13701,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file]) {
dynptr_arg_type |= DYNPTR_TYPE_FILE;
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {
- dynptr_arg_type |= DYNPTR_TYPE_FILE;
+ dynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;
meta->release_regno = regno;
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] &&
(dynptr_arg_type & MEM_UNINIT)) {
@@ -24785,7 +24753,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
} else if (arg->arg_type == ARG_ANYTHING) {
reg->type = SCALAR_VALUE;
mark_reg_unknown(env, regs, i);
- } else if (arg->arg_type == (ARG_PTR_TO_DYNPTR | MEM_RDONLY)) {
+ } else if (arg->arg_type == ARG_PTR_TO_DYNPTR) {
/* assume unspecial LOCAL dynptr type */
__mark_dynptr_reg(reg, BPF_DYNPTR_TYPE_LOCAL, true, ++env->id_gen);
} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 0b040a417442..5f35ecdd5341 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -3393,7 +3393,7 @@ typedef int (*copy_fn_t)(void *dst, const void *src, u32 size, struct task_struc
* direct calls into all the specific callback implementations
* (copy_user_data_sleepable, copy_user_data_nofault, and so on)
*/
-static __always_inline int __bpf_dynptr_copy_str(struct bpf_dynptr *dptr, u64 doff, u64 size,
+static __always_inline int __bpf_dynptr_copy_str(const struct bpf_dynptr *dptr, u64 doff, u64 size,
const void *unsafe_src,
copy_fn_t str_copy_fn,
struct task_struct *tsk)
@@ -3535,49 +3535,49 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid
return bpf_send_signal_common(sig, type, task, value);
}
-__bpf_kfunc int bpf_probe_read_user_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_probe_read_user_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign)
{
return __bpf_dynptr_copy(dptr, off, size, (const void __force *)unsafe_ptr__ign,
copy_user_data_nofault, NULL);
}
-__bpf_kfunc int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_probe_read_kernel_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr__ign)
{
return __bpf_dynptr_copy(dptr, off, size, unsafe_ptr__ign,
copy_kernel_data_nofault, NULL);
}
-__bpf_kfunc int bpf_probe_read_user_str_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_probe_read_user_str_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign)
{
return __bpf_dynptr_copy_str(dptr, off, size, (const void __force *)unsafe_ptr__ign,
copy_user_str_nofault, NULL);
}
-__bpf_kfunc int bpf_probe_read_kernel_str_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_probe_read_kernel_str_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr__ign)
{
return __bpf_dynptr_copy_str(dptr, off, size, unsafe_ptr__ign,
copy_kernel_str_nofault, NULL);
}
-__bpf_kfunc int bpf_copy_from_user_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_copy_from_user_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign)
{
return __bpf_dynptr_copy(dptr, off, size, (const void __force *)unsafe_ptr__ign,
copy_user_data_sleepable, NULL);
}
-__bpf_kfunc int bpf_copy_from_user_str_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_copy_from_user_str_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign)
{
return __bpf_dynptr_copy_str(dptr, off, size, (const void __force *)unsafe_ptr__ign,
copy_user_str_sleepable, NULL);
}
-__bpf_kfunc int bpf_copy_from_user_task_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_copy_from_user_task_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign,
struct task_struct *tsk)
{
@@ -3585,7 +3585,7 @@ __bpf_kfunc int bpf_copy_from_user_task_dynptr(struct bpf_dynptr *dptr, u64 off,
copy_user_data_sleepable, tsk);
}
-__bpf_kfunc int bpf_copy_from_user_task_str_dynptr(struct bpf_dynptr *dptr, u64 off,
+__bpf_kfunc int bpf_copy_from_user_task_str_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign,
struct task_struct *tsk)
{
diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
index 7dad01439391..ae71e9b69051 100644
--- a/tools/testing/selftests/bpf/bpf_kfuncs.h
+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h
@@ -40,7 +40,7 @@ extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, __u64 offset,
extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, __u64 offset, void *buffer,
__u64 buffer__szk) __ksym __weak;
-extern int bpf_dynptr_adjust(const struct bpf_dynptr *ptr, __u64 start, __u64 end) __ksym __weak;
+extern int bpf_dynptr_adjust(struct bpf_dynptr *ptr, __u64 start, __u64 end) __ksym __weak;
extern bool bpf_dynptr_is_null(const struct bpf_dynptr *ptr) __ksym __weak;
extern bool bpf_dynptr_is_rdonly(const struct bpf_dynptr *ptr) __ksym __weak;
extern __u64 bpf_dynptr_size(const struct bpf_dynptr *ptr) __ksym __weak;
@@ -70,13 +70,13 @@ extern void *bpf_rdonly_cast(const void *obj, __u32 btf_id) __ksym __weak;
extern int bpf_get_file_xattr(struct file *file, const char *name,
struct bpf_dynptr *value_ptr) __ksym;
-extern int bpf_get_fsverity_digest(struct file *file, struct bpf_dynptr *digest_ptr) __ksym;
+extern int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynptr *digest_ptr) __ksym;
extern struct bpf_key *bpf_lookup_user_key(__s32 serial, __u64 flags) __ksym;
extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
extern void bpf_key_put(struct bpf_key *key) __ksym;
-extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
- struct bpf_dynptr *sig_ptr,
+extern int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_ptr,
+ const struct bpf_dynptr *sig_ptr,
struct bpf_key *trusted_keyring) __ksym;
struct dentry;
diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c
index e0d672d93adf..e0745b6e467e 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_success.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -914,7 +914,7 @@ void *user_ptr;
char expected_str[384];
__u32 test_len[7] = {0/* placeholder */, 0, 1, 2, 255, 256, 257};
-typedef int (*bpf_read_dynptr_fn_t)(struct bpf_dynptr *dptr, u64 off,
+typedef int (*bpf_read_dynptr_fn_t)(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr);
/* Returns the offset just before the end of the maximum sized xdp fragment.
@@ -1106,7 +1106,7 @@ int test_copy_from_user_str_dynptr(void *ctx)
return 0;
}
-static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u64 off,
+static int bpf_copy_data_from_user_task(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr)
{
struct task_struct *task = bpf_get_current_task_btf();
@@ -1114,7 +1114,7 @@ static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u64 off,
return bpf_copy_from_user_task_dynptr(dptr, off, size, unsafe_ptr, task);
}
-static int bpf_copy_data_from_user_task_str(struct bpf_dynptr *dptr, u64 off,
+static int bpf_copy_data_from_user_task_str(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr)
{
struct task_struct *task = bpf_get_current_task_btf();
diff --git a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
index d249113ed657..1c6cfd0888ba 100644
--- a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
+++ b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
@@ -11,12 +11,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
-
-extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
-extern void bpf_key_put(struct bpf_key *key) __ksym;
-extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
- struct bpf_dynptr *sig_ptr,
- struct bpf_key *trusted_keyring) __ksym;
+#include "bpf_kfuncs.h"
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
@@ -38,7 +33,7 @@ SEC("?lsm.s/bpf")
__failure __msg("cannot pass in dynptr at an offset=-8")
int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
- unsigned long val;
+ unsigned long val = 0;
return bpf_verify_pkcs7_signature((struct bpf_dynptr *)&val,
(struct bpf_dynptr *)&val, NULL);
--
2.52.0
^ permalink raw reply related
* [PATCH bpf-next v2 0/1] Refactor dynptr mutability tracking
From: Amery Hung @ 2026-04-02 6:50 UTC (permalink / raw)
To: bpf
Cc: netdev, alexei.starovoitov, andrii, daniel, memxor, eddyz87,
yatsenko, martin.lau, ameryhung, kernel-team
A heads up that this independent patch is separated from the object
relation tracking refactor patchset [0] to breakup the larger patchset
and make the review easier.
[0] https://lore.kernel.org/bpf/20260307064439.3247440-1-ameryhung@gmail.com/
v1 -> v2
- Change the definition of the mutatbility of bpf_dynptr
to only bpf_dynptr->data instead of the whole structure. Basically,
only care about preventing passing CONST_PTR_TO_DYNPTR to arguments
tagged with OBJ_RELEASE or MEM_UNINT
- Drop MEM_RDONLY annotation as now we dont care
- Drop passing CONST_PTR_TO_DYNPTR to MEM_RDONLY kfunc selftest
Amery Hung (1):
bpf: Refactor dynptr mutability tracking
fs/verity/measure.c | 2 +-
include/linux/bpf.h | 8 +--
kernel/bpf/btf.c | 2 +-
kernel/bpf/helpers.c | 18 ++---
kernel/bpf/verifier.c | 68 +++++--------------
kernel/trace/bpf_trace.c | 18 ++---
tools/testing/selftests/bpf/bpf_kfuncs.h | 8 +--
.../selftests/bpf/progs/dynptr_success.c | 6 +-
.../bpf/progs/test_kfunc_dynptr_param.c | 9 +--
9 files changed, 51 insertions(+), 88 deletions(-)
--
2.52.0
^ permalink raw reply
* [ANN] LPC 2026: Networking track CFP
From: Paolo Abeni @ 2026-04-02 6:40 UTC (permalink / raw)
To: netdev@vger.kernel.org
We are pleased to announce the Call for Proposals (CFP) for the
Networking track at the 2026 edition of the Linux Plumbers Conference
(LPC) which is taking place in Prague, Czechia, on October 5th - 7th, 2026.
LPC Networking track is an in-person (and virtual) manifestation of the
netdev mailing list, bringing together developers, users and vendors to
discuss topics related to Linux networking.
Relevant topics span from proposals for kernel changes, through user
space tooling, to presenting interesting use cases, new protocols or
new, interesting problems waiting for a solution.
The goal is to allow gathering early feedback on proposals, reach
consensus on long running mailing list discussions and raise awareness
of interesting work or use cases. We are seeking proposals of 30 min in
length (including Q&A discussion). Presenting in person is preferred,
however, exceptions could be given for remotely presenting if attending
in person is challenging.
Please submit your proposals through the official LPC website at:
https://lpc.events/event/20/abstracts/
Like last years, we have separate tracks for BPF and networking, please
submit to the track which feels suitable, the committee will transfer
submissions between tracks as it deems necessary.
- netdev maintainers
^ permalink raw reply
* Re: [PATCH] udp_bpf: fix use-after-free in udp_bpf_recvmsg()
From: Deepanshu Kartikey @ 2026-04-02 6:37 UTC (permalink / raw)
To: Jiayuan Chen
Cc: john.fastabend, jakub, davem, dsahern, edumazet, kuba, pabeni,
horms, Kuniyuki Iwashima, ast, cong.wang, netdev, bpf,
linux-kernel, syzbot+431f9a9e3f5227fbb904
In-Reply-To: <f64c57ad-c162-480e-910a-e7a5a3460104@linux.dev>
On Thu, Apr 2, 2026 at 11:33 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
>
> Kuniyuki is already working on this. Please see the
> existing discussion.
>
> https://lore.kernel.org/bpf/20260221233234.3814768-4-kuniyu@google.com/
>
>
I was not aware of it. Thanks for sharing.
Deepanshu
^ permalink raw reply
* Re: [PATCH net-next v4 1/2] net: hsr: require valid EOT supervision TLV
From: Luka Gejak @ 2026-04-02 6:34 UTC (permalink / raw)
To: Fernando Fernandez Mancera, davem, edumazet, kuba, pabeni
Cc: netdev, fmaurer, horms, luka.gejak
In-Reply-To: <78c9c953-1aaa-4d44-9c1d-b4e52a5cead3@suse.de>
On April 2, 2026 1:30:57 AM GMT+02:00, Fernando Fernandez Mancera <fmancera@suse.de> wrote:
>On 4/1/26 7:05 PM, Luka Gejak wrote:
>> On Wed Apr 1, 2026 at 11:52 AM CEST, Fernando Fernandez Mancera wrote:
>>> On 4/1/26 11:23 AM, luka.gejak@linux.dev wrote:
>>>> From: Luka Gejak <luka.gejak@linux.dev>
>>>>
>>>> Supervision frames are only valid if terminated with a zero-length EOT
>>>> TLV. The current check fails to reject non-EOT entries as the terminal
>>>> TLV, potentially allowing malformed supervision traffic.
>>>>
>>>> Fix this by strictly requiring the terminal TLV to be HSR_TLV_EOT
>>>> with a length of zero.
>>>>
>>>> Reviewed-by: Felix Maurer <fmaurer@redhat.com>
>>>> Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
>>>> ---
>>>
>>> Hi,
>>>
>>> This has not been reviewed by Felix. Felix provided his Reviewed-by tag
>>> for the v1 which was completely different than this.
>>>
>>> Revisions of this patch:
>>>
>>> v3: https://lore.kernel.org/all/20260329112313.17164-4-luka.gejak@linux.dev/
>>>
>>> v2: https://lore.kernel.org/all/20260326154715.38405-4-luka.gejak@linux.dev/
>>>
>>> v1:
>>> https://lore.kernel.org/all/20260324143503.187642-4-luka.gejak@linux.dev/
>>>
>>> Are these contributions LLM/AI generated? I believe so based on the
>>> email history.
>>>
>>> AI generated review on rtl8723bs:
>>> https://lore.kernel.org/all/B2394A3C-25FD-4CEA-8557-3E68F1F60357@linux.dev/
>>>
>>> Another AI generated review on rtl8723bs:
>>> https://lore.kernel.org/all/3831D599-655E-40B2-9E5D-9DF956013088@linux.dev/
>>>
>>> Likely an AI generated review on a 1 year old HSR patch:
>>> https://lore.kernel.org/all/DHFG26KI6L23.1YCOVQ5SSYMO5@linux.dev/
>>>
>>> If these are indeed, AI generated contributions or reviews they should
>>> be disclosed beforehand. Also there is the Assisted-by: tag. Also note
>>> that developer must take full responsibility for the contribution which
>>> means understanding it completely.
>>>
>>> https://docs.kernel.org/process/coding-assistants.html#signed-off-by-and-developer-certificate-of-origin
>>>
>>> Thanks,
>>> Fernando.
>>
>> HI Fernando,
>> One more question, should I include Assisted-by tag in v5 if AI was not
>> used for writing code but only for formating and translation of the
>> emails to English as I previously mentioned.
>>
>
>I think yes, you should. I also think that AI was actually used for the generated code and also for spotting the valid and invalid issues. If you don't have a real environment for HSR, why would you look into it?
>
>Sorry, it is not my intention to be harsh but there are some things that don't add up for me. Maybe I am being too careful here and you just coincidentally found these problems.
>
>> Best regards,
>> Luka Gejak
>>
>
Hi Fernando,
I understand the concern. To clarify, I am a student, and I discovered
these issues while reading the HSR source to learn about network
redundancy. Since I lack industrial hardware, I verified the logic
using network namespaces and veth pairs on my Arch laptop. The code
and logic are entirely my. As previously mentioned, I only use AI for
English translation and formatting to ensure my communication is
clear. As requested, I will wait for Felix's review before proceeding
with any changes for v5.
Best regards,
Luka Gejak
^ permalink raw reply
* Re: [PATCH v25 05/11] sfc: create type2 cxl memdev
From: Alejandro Lucero Palau @ 2026-04-02 6:30 UTC (permalink / raw)
To: Dan Williams, alejandro.lucero-palau, linux-cxl, netdev,
dave.jiang, edward.cree, davem, kuba, pabeni, edumazet
Cc: Martin Habets, Fan Ni, Edward Cree, Jonathan Cameron
In-Reply-To: <69cd93e334c11_1b0cc6100fc@dwillia2-mobl4.notmuch>
On 4/1/26 22:53, Dan Williams wrote:
> Alejandro Lucero Palau wrote:
>> On 4/1/26 06:17, Dan Williams wrote:
>>> alejandro.lucero-palau@ wrote:
>>>> From: Alejandro Lucero <alucerop@amd.com>
>>>>
>>>> Use cxl API for creating a cxl memory device using the type2
>>>> cxl_dev_state struct.
>>>>
>>>> Signed-off-by: Alejandro Lucero <alucerop@amd.com>
>>>> Reviewed-by: Martin Habets <habetsm.xilinx@gmail.com>
>>>> Reviewed-by: Fan Ni <fan.ni@samsung.com>
>>>> Acked-by: Edward Cree <ecree.xilinx@gmail.com>
>>>> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
>>>> Reviewed-by: Dave Jiang <dave.jiang@intel.com>
>>>> ---
>>>> drivers/net/ethernet/sfc/efx_cxl.c | 6 ++++++
>>>> 1 file changed, 6 insertions(+)
>>>>
>>>> diff --git a/drivers/net/ethernet/sfc/efx_cxl.c b/drivers/net/ethernet/sfc/efx_cxl.c
>>>> index 6619084a77d8..63e6f277ae9f 100644
>>>> --- a/drivers/net/ethernet/sfc/efx_cxl.c
>>>> +++ b/drivers/net/ethernet/sfc/efx_cxl.c
>>>> @@ -75,6 +75,12 @@ int efx_cxl_init(struct efx_probe_data *probe_data)
>>>> return -ENODEV;
>>>> }
>>>>
>>>> + cxl->cxlmd = devm_cxl_add_memdev(&cxl->cxlds, NULL);
>>> Did this forget about:
>>>
>>> 29317f8dc6ed cxl/mem: Introduce cxl_memdev_attach for CXL-dependent operation
>>>
>>> ?
>>
>> I did not. The idea behind attach does not make sense for sfc, so I do
>> not use that option. I did discuss this with you at LPC and based on
>> previous comments which had not replies (case 1 in here):
>>
>>
>> https://lore.kernel.org/linux-cxl/836d06d6-a36f-4ba3-b7c9-ba8687ba2190@amd.com/
>>
>>
>> From our conversation at LPC, there is no reason for a CXL device
>> connected to the CXL Root Bridge not having its memdev properly
>> initialized.
> The cxl_core can not make assumptions about attachment topology. It has
> always been the case that the cxl_core needs to be prepared to handle
> everything that the firmware did not.
This is too much generic as an argument. If you want to convince me, I
would need a real case where this can be a problem.
The current assumption and the only option supported with this last
version is the BIOS *is* configuring the device HDM and there should be
nothing to do from the cxl core in that regard:
1) BIOS configured and committed the device HDM: CXL.mem is used.
2) BIOS did not configure the HDM: CXL.mem is not used.
Any problem with the memdev initialization *requiring* your attach
option is helpless ... if there exists a reason for it being helpful
because the memdev initialization triggering the port and hdm
initilization was not completely done, please explain why that could
happen in the scenario I am presenting with an accelerator connected to
a CXL Host Bridge port and the BIOS configuring the HDM based on the CFMWS.
> Error handling and kexec also lead to
> situations where the boot configuration gets invalidated and needs
> recovery.
Error handling based on RAS is a follow-up work. CXL reset is not
supported ... and AFAIK, it is not supported by Type3 either or it has
not been for a good bunch of time. If so, I do not understand why this
should stop this patchset.
About kexec, it happens without a shutdown ... so if the system does it
without proper handling, nothing we can do. If it is properly handled,
the cxl core initilaization will happen ... is it handling the case of
finding HDMs already configured (kexec will leave them untouched, won't
it?)? From a type2 point of view and using this patchset, if kexec was
executed after removing first the sfc driver, there should not be any
problem. If I am missing something about this, please explain it in
detail or give any reference I can look at. FWIW, I did use kexec a lot
in embedded systems and had to deal with some hardware needing special
treatment before triggering it, so happy to learn more about the
implications with CXL.
>
>> I did ask you specifically about this, and you mentioned links on CXL
>> switches not ready, but as I said then and repeat now, this is not
>> what sfc driver expects at all. Only a port from a CXL Root Bridge
>> makes sense due to the latencies involved with more complex CXL
>> topologies.
>>
>>
>> So, from that list I wrote in that old thread where I tried to summarize
>> the problems and clarify the confusion behind different concerns, the
>> only issue is someone removing cxl_acpi. I do not think that should be
>> possible at all,
> ...but it *is* possible to remove cxl_acpi, it *is* possible to invoke
> 'cxl disable-port'. The fact that it is possible contributes to
> complexity, but it also supports flexibility and is a building block for
> error handling / recovery.
It is possible but, should it? When should it be allowed with drivers
relying on its functionality?
>> so something should be added for other modules depending on it
>> avoiding its removal. I would say any CXL port created is (in X86)
>> dependent on cxl_acpi, so something at port creation invoking a
>> exported cxl_acpi function could do it, like a cxl_acpi_users counter.
>> This could also help for visibility to user space about its usage.
> There are two choices, fail removal or handle removal. There are some
> degrees of freedom that can be pared back, but outright blocking removal
> is not one of them.
Why not?
>> Therefore, I do not like the changes you propose here. But, if this
>> proposal is the only way
> It is not a matter of like or dislike, it is a matter of functional
> correctness relative to the state of the subsystem today. Simply put,
> one proposal handles the locking and lifetime concerns, the other does
> not.
^ permalink raw reply
* Re: [PATCH v5 23/27] clk: mediatek: Add MT8196 disp-ao clock support
From: Jason-JH Lin (林睿祥) @ 2026-04-02 6:30 UTC (permalink / raw)
To: sboyd@kernel.org, robh@kernel.org, Laura Nao, krzk+dt@kernel.org,
p.zabel@pengutronix.de, mturquette@baylibre.com,
conor+dt@kernel.org, richardcochran@gmail.com,
matthias.bgg@gmail.com, AngeloGioacchino Del Regno
Cc: Guangjie Song (宋光杰), kernel@collabora.com,
Sirius Wang (王皓昱),
Nancy Lin (林欣螢),
linux-kernel@vger.kernel.org,
Project_Global_Chrome_Upstream_Group,
Paul-pl Chen (陳柏霖),
linux-mediatek@lists.infradead.org,
Jason-JH Lin (林睿祥),
devicetree@vger.kernel.org, Nicolas Prado,
Singo Chang (張興國), wenst@chromium.org,
linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
linux-clk@vger.kernel.org
In-Reply-To: <20250829091913.131528-24-laura.nao@collabora.com>
On Fri, 2025-08-29 at 11:19 +0200, Laura Nao wrote:
> Add support for the MT8196 disp-ao clock controller, which provides
> clock gate control for the display system. It is integrated with the
> mtk-mmsys driver, which registers the disp-ao clock driver via
> platform_device_register_data().
>
> Reviewed-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
> Reviewed-by: AngeloGioacchino Del Regno
> <angelogioacchino.delregno@collabora.com>
> Signed-off-by: Laura Nao <laura.nao@collabora.com>
> ---
> drivers/clk/mediatek/Makefile | 2 +-
> drivers/clk/mediatek/clk-mt8196-vdisp_ao.c | 80
> ++++++++++++++++++++++
> 2 files changed, 81 insertions(+), 1 deletion(-)
> create mode 100644 drivers/clk/mediatek/clk-mt8196-vdisp_ao.c
>
> diff --git a/drivers/clk/mediatek/Makefile
> b/drivers/clk/mediatek/Makefile
> index fe5699411d8b..5b8969ff1985 100644
> --- a/drivers/clk/mediatek/Makefile
> +++ b/drivers/clk/mediatek/Makefile
> @@ -157,7 +157,7 @@ obj-$(CONFIG_COMMON_CLK_MT8196_IMP_IIC_WRAP) +=
> clk-mt8196-imp_iic_wrap.o
> obj-$(CONFIG_COMMON_CLK_MT8196_MCUSYS) += clk-mt8196-mcu.o
> obj-$(CONFIG_COMMON_CLK_MT8196_MDPSYS) += clk-mt8196-mdpsys.o
> obj-$(CONFIG_COMMON_CLK_MT8196_MFGCFG) += clk-mt8196-mfg.o
> -obj-$(CONFIG_COMMON_CLK_MT8196_MMSYS) += clk-mt8196-disp0.o clk-
> mt8196-disp1.o
> +obj-$(CONFIG_COMMON_CLK_MT8196_MMSYS) += clk-mt8196-disp0.o clk-
> mt8196-disp1.o clk-mt8196-vdisp_ao.o
> obj-$(CONFIG_COMMON_CLK_MT8196_PEXTPSYS) += clk-mt8196-pextp.o
> obj-$(CONFIG_COMMON_CLK_MT8196_UFSSYS) += clk-mt8196-ufs_ao.o
> obj-$(CONFIG_COMMON_CLK_MT8365) += clk-mt8365-apmixedsys.o clk-
> mt8365.o
> diff --git a/drivers/clk/mediatek/clk-mt8196-vdisp_ao.c
> b/drivers/clk/mediatek/clk-mt8196-vdisp_ao.c
> new file mode 100644
> index 000000000000..fddb69d1c3eb
> --- /dev/null
> +++ b/drivers/clk/mediatek/clk-mt8196-vdisp_ao.c
> @@ -0,0 +1,80 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025 MediaTek Inc.
> + * Guangjie Song <guangjie.song@mediatek.com>
> + * Copyright (c) 2025 Collabora Ltd.
> + * Laura Nao <laura.nao@collabora.com>
> + */
> +#include <dt-bindings/clock/mediatek,mt8196-clock.h>
> +
> +#include <linux/clk-provider.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#include "clk-gate.h"
> +#include "clk-mtk.h"
> +
> +static const struct mtk_gate_regs mm_v_cg_regs = {
> + .set_ofs = 0x104,
> + .clr_ofs = 0x108,
> + .sta_ofs = 0x100,
> +};
> +
> +static const struct mtk_gate_regs mm_v_hwv_regs = {
> + .set_ofs = 0x0030,
> + .clr_ofs = 0x0034,
> + .sta_ofs = 0x2c18,
> +};
> +
> +#define GATE_MM_AO_V(_id, _name, _parent, _shift) { \
> + .id = _id, \
> + .name = _name, \
> + .parent_name = _parent, \
> + .regs = &mm_v_cg_regs, \
> + .shift = _shift, \
> + .ops = &mtk_clk_gate_ops_setclr, \
> + .flags = CLK_OPS_PARENT_ENABLE | \
> + CLK_IS_CRITICAL, \
> + }
> +
> +#define GATE_HWV_MM_V(_id, _name, _parent, _shift) { \
> + .id = _id, \
> + .name = _name, \
> + .parent_name = _parent, \
> + .regs = &mm_v_cg_regs, \
> + .hwv_regs = &mm_v_hwv_regs, \
> + .shift = _shift, \
> + .ops = &mtk_clk_gate_hwv_ops_setclr, \
> + .flags = CLK_OPS_PARENT_ENABLE, \
> + }
> +
> +static const struct mtk_gate mm_v_clks[] = {
> + GATE_HWV_MM_V(CLK_MM_V_DISP_VDISP_AO_CONFIG,
> "mm_v_disp_vdisp_ao_config", "disp", 0),
> + GATE_HWV_MM_V(CLK_MM_V_DISP_DPC, "mm_v_disp_dpc", "disp",
> 16),
> + GATE_MM_AO_V(CLK_MM_V_SMI_SUB_SOMM0, "mm_v_smi_sub_somm0",
> "disp", 2),
> +};
> +
> +static const struct mtk_clk_desc mm_v_mcd = {
> + .clks = mm_v_clks,
> + .num_clks = ARRAY_SIZE(mm_v_clks),
> +};
> +
> +static const struct of_device_id of_match_clk_mt8196_vdisp_ao[] = {
> + { .compatible = "mediatek,mt8196-vdisp-ao", .data =
> &mm_v_mcd },
Hi Laura,
We are going to send mtk-mmsys driver for MT8196 recently, but we found
the compatible name is used here.
As your commit message, vdisp-ao is integrated with the mtk-mmsys
driver, which registers the vdisp-ao clock driver via
platform_device_register_data().
Shouldn't this compatible name belong to mmsys driver for MT8196?
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, of_match_clk_mt8196_vdisp_ao);
> +
> +static struct platform_driver clk_mt8196_vdisp_ao_drv = {
> + .probe = mtk_clk_pdev_probe,
> + .remove = mtk_clk_pdev_remove,
> + .driver = {
> + .name = "clk-mt8196-vdisp-ao",
> + .of_match_table = of_match_clk_mt8196_vdisp_ao,
In clk-mt8196-ovl0/ovl1/disp0/disp1.c, they use `.id_table` instead of
`.of_match_table` here. Shouldn't this align to them?
Regards,
Jason-JH Lin
> + },
> +};
> +module_platform_driver(clk_mt8196_vdisp_ao_drv);
> +
> +MODULE_DESCRIPTION("MediaTek MT8196 vdisp_ao clocks driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply
* [PATCH net-next v3 7/7] selftests: net: Add tests for team driver decoupled tx and rx control
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
Use ping and tcpdump to verify that independent rx and tx enablement
of team driver member interfaces works as intended.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- None
Changes in v2:
- Fix shellcheck failures.
- Link to v1: https://lore.kernel.org/all/20260331053353.2504254-8-marcharvey@google.com/
---
tools/testing/selftests/drivers/net/team/Makefile | 1 +
.../drivers/net/team/decoupled_enablement.sh | 249 +++++++++++++++++++++
.../testing/selftests/drivers/net/team/options.sh | 99 +++++++-
3 files changed, 348 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/team/Makefile b/tools/testing/selftests/drivers/net/team/Makefile
index dab922d7f83d..7c58cf82121e 100644
--- a/tools/testing/selftests/drivers/net/team/Makefile
+++ b/tools/testing/selftests/drivers/net/team/Makefile
@@ -2,6 +2,7 @@
# Makefile for net selftests
TEST_PROGS := \
+ decoupled_enablement.sh \
dev_addr_lists.sh \
non_ether_header_ops.sh \
options.sh \
diff --git a/tools/testing/selftests/drivers/net/team/decoupled_enablement.sh b/tools/testing/selftests/drivers/net/team/decoupled_enablement.sh
new file mode 100755
index 000000000000..e2944af02d57
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/team/decoupled_enablement.sh
@@ -0,0 +1,249 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# These tests verify the decoupled RX and TX enablement of team driver member
+# interfaces.
+#
+# Topology
+#
+# +---------------------+ NS1
+# | test_team1 |
+# | | |
+# | eth0 |
+# | | |
+# | | |
+# +---------------------+
+# |
+# +---------------------+ NS2
+# | | |
+# | | |
+# | eth0 |
+# | | |
+# | test_team2 |
+# +---------------------+
+
+export ALL_TESTS="
+ team_test_tx_enablement
+ team_test_rx_enablement
+"
+
+test_dir="$(dirname "$0")"
+# shellcheck disable=SC1091
+source "${test_dir}/../../../net/lib.sh"
+# shellcheck disable=SC1091
+source "${test_dir}/team_lib.sh"
+
+NS1=""
+NS2=""
+export NODAD="nodad"
+PREFIX_LENGTH="64"
+NS1_IP="fd00::1"
+NS2_IP="fd00::2"
+NS1_IP4="192.168.0.1"
+NS2_IP4="192.168.0.2"
+MEMBERS=("eth0")
+PING_COUNT=5
+PING_TIMEOUT_S=1
+PING_INTERVAL=0.1
+
+while getopts "4" opt; do
+ case $opt in
+ 4)
+ echo "IPv4 mode selected."
+ export NODAD=
+ PREFIX_LENGTH="24"
+ NS1_IP="${NS1_IP4}"
+ NS2_IP="${NS2_IP4}"
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2
+ exit 1
+ ;;
+ esac
+done
+
+# This has to be sourced after opts are gathered...
+export REQUIRE_MZ=no
+export NUM_NETIFS=0
+# shellcheck disable=SC1091
+source "${test_dir}/../../../net/forwarding/lib.sh"
+
+# Create the network namespaces, veth pair, and team devices in the specified
+# mode.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+# Arguments:
+# mode - The team driver mode to use for the team devices.
+environment_create()
+{
+ trap cleanup_all_ns EXIT
+ setup_ns ns1 ns2
+ NS1="${NS_LIST[0]}"
+ NS2="${NS_LIST[1]}"
+
+ # Create the interfaces.
+ ip -n "${NS1}" link add eth0 type veth peer name eth0 netns "${NS2}"
+ ip -n "${NS1}" link add test_team1 type team
+ ip -n "${NS2}" link add test_team2 type team
+
+ # Set up the receiving network namespace's team interface.
+ setup_team "${NS2}" test_team2 roundrobin "${NS2_IP}" \
+ "${PREFIX_LENGTH}" "${MEMBERS[@]}"
+}
+
+# Set a particular option value for team or team port.
+# Arguments:
+# namespace - The namespace name that has the team.
+# option_name - The option name to set.
+# option_value - The value to set the option to.
+# team_name - The name of team to set the option for.
+# member_name - The (optional) optional name of the member port.
+set_option_value()
+{
+ local namespace="$1"
+ local option_name="$2"
+ local option_value="$3"
+ local team_name="$4"
+ local member_name="$5"
+ local port_flag="--port=${member_name}"
+
+ ip netns exec "${namespace}" teamnl "${team_name}" setoption \
+ "${option_name}" "${option_value}" "${port_flag}"
+ return $?
+}
+
+# Send some pings and return the ping command return value.
+try_ping()
+{
+ ip netns exec "${NS1}" ping -i "${PING_INTERVAL}" -c "${PING_COUNT}" \
+ "${NS2_IP}" -W "${PING_TIMEOUT_S}"
+}
+
+# Checks tcpdump output from net/forwarding lib, and checks if there are any
+# ICMP(4 or 6) packets.
+# Arguments:
+# interface - The interface name to search for.
+# ip_address - The destination IP address (4 or 6) to search for.
+did_interface_receive_icmp()
+{
+ local interface="$1"
+ local ip_address="$2"
+ local packet_count
+
+ packet_count=$(tcpdump_show "$interface" | grep -c \
+ "> ${ip_address}: ICMP")
+ echo "Packet count for ${interface} was ${packet_count}"
+
+ if [[ "$packet_count" -gt 0 ]]; then
+ true
+ else
+ false
+ fi
+}
+
+# Test JUST tx enablement with a given mode.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+# Arguments:
+# mode - The mode to set the team interfaces to.
+team_test_mode_tx_enablement()
+{
+ local mode="$1"
+ export RET=0
+
+ # Set up the sender team with the correct mode.
+ setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \
+ "${PREFIX_LENGTH}" "${MEMBERS[@]}"
+ check_err $? "Failed to set up sender team"
+
+ ### Scenario 1: Member interface initially enabled.
+ # Expect ping to pass
+ try_ping
+ check_err $? "Ping failed when TX enabled"
+
+ ### Scenario 2: Once tx-side interface disabled.
+ # Expect ping to fail.
+ set_option_value "${NS1}" tx_enabled false test_team1 eth0
+ check_err $? "Failed to disable TX"
+ tcpdump_start eth0 "${NS2}"
+ try_ping
+ check_fail $? "Ping succeeded when TX disabled"
+ tcpdump_stop eth0
+ # Expect no packets to be transmitted, since TX is disabled.
+ did_interface_receive_icmp eth0 "${NS2_IP}"
+ check_fail $? "eth0 IS transmitting when TX disabled"
+ tcpdump_cleanup eth0
+
+ ### Scenario 3: The interface has tx re-enabled.
+ # Expect ping to pass.
+ set_option_value "${NS1}" tx_enabled true test_team1 eth0
+ check_err $? "Failed to reenable TX"
+ try_ping
+ check_err $? "Ping failed when TX reenabled"
+
+ log_test "TX failover of '${mode}' test"
+}
+
+# Test JUST rx enablement with a given mode.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+# Arguments:
+# mode - The mode to set the team interfaces to.
+team_test_mode_rx_enablement()
+{
+ local mode="$1"
+ export RET=0
+
+ # Set up the sender team with the correct mode.
+ setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \
+ "${PREFIX_LENGTH}" "${MEMBERS[@]}"
+ check_err $? "Failed to set up sender team"
+
+ ### Scenario 1: Member interface initially enabled.
+ # Expect ping to pass
+ try_ping
+ check_err $? "Ping failed when RX enabled"
+
+ ### Scenario 2: Once rx-side interface disabled.
+ # Expect ping to fail.
+ set_option_value "${NS1}" rx_enabled false test_team1 eth0
+ check_err $? "Failed to disable RX"
+ tcpdump_start eth0 "${NS2}"
+ try_ping
+ check_fail $? "Ping succeeded when RX disabled"
+ tcpdump_stop eth0
+ # Expect packets to be transmitted, since only RX is disabled.
+ did_interface_receive_icmp eth0 "${NS2_IP}"
+ check_err $? "eth0 not transmitting when RX disabled"
+ tcpdump_cleanup eth0
+
+ ### Scenario 3: The interface has rx re-enabled.
+ # Expect ping to pass.
+ set_option_value "${NS1}" rx_enabled true test_team1 eth0
+ check_err $? "Failed to reenable RX"
+ try_ping
+ check_err $? "Ping failed when RX reenabled"
+
+ log_test "RX failover of '${mode}' test"
+}
+
+team_test_tx_enablement()
+{
+ team_test_mode_tx_enablement broadcast
+ team_test_mode_tx_enablement roundrobin
+ team_test_mode_tx_enablement random
+}
+
+team_test_rx_enablement()
+{
+ team_test_mode_rx_enablement broadcast
+ team_test_mode_rx_enablement roundrobin
+ team_test_mode_rx_enablement random
+}
+
+require_command teamnl
+require_command tcpdump
+require_command ping
+environment_create
+tests_run
+exit "${EXIT_STATUS}"
diff --git a/tools/testing/selftests/drivers/net/team/options.sh b/tools/testing/selftests/drivers/net/team/options.sh
index 44888f32b513..c87cf998a427 100755
--- a/tools/testing/selftests/drivers/net/team/options.sh
+++ b/tools/testing/selftests/drivers/net/team/options.sh
@@ -11,10 +11,14 @@ if [[ $# -eq 0 ]]; then
exit $?
fi
-ALL_TESTS="
+export ALL_TESTS="
team_test_options
+ team_test_enabled_implicit_changes
+ team_test_rx_enabled_implicit_changes
+ team_test_tx_enabled_implicit_changes
"
+# shellcheck disable=SC1091
source "${test_dir}/../../../net/lib.sh"
TEAM_PORT="team0"
@@ -176,12 +180,105 @@ team_test_options()
team_test_option mcast_rejoin_count 0 5
team_test_option mcast_rejoin_interval 0 5
team_test_option enabled true false "${MEMBER_PORT}"
+ team_test_option rx_enabled true false "${MEMBER_PORT}"
+ team_test_option tx_enabled true false "${MEMBER_PORT}"
team_test_option user_linkup true false "${MEMBER_PORT}"
team_test_option user_linkup_enabled true false "${MEMBER_PORT}"
team_test_option priority 10 20 "${MEMBER_PORT}"
team_test_option queue_id 0 1 "${MEMBER_PORT}"
}
+team_test_enabled_implicit_changes()
+{
+ export RET=0
+
+ attach_port_if_specified "${MEMBER_PORT}"
+ check_err $? "Couldn't attach ${MEMBER_PORT} to master"
+
+ # Set enabled to true.
+ set_and_check_get enabled true "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'enabled' to true"
+
+ # Show that both rx enabled and tx enabled are true.
+ get_and_check_value rx_enabled true "--port=${MEMBER_PORT}"
+ check_err $? "'Rx_enabled' wasn't implicitly set to true"
+ get_and_check_value tx_enabled true "--port=${MEMBER_PORT}"
+ check_err $? "'Tx_enabled' wasn't implicitly set to true"
+
+ # Set enabled to false.
+ set_and_check_get enabled false "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'enabled' to true"
+
+ # Show that both rx enabled and tx enabled are false.
+ get_and_check_value rx_enabled false "--port=${MEMBER_PORT}"
+ check_err $? "'Rx_enabled' wasn't implicitly set to false"
+ get_and_check_value tx_enabled false "--port=${MEMBER_PORT}"
+ check_err $? "'Tx_enabled' wasn't implicitly set to false"
+
+ log_test "'Enabled' implicit changes"
+}
+
+team_test_rx_enabled_implicit_changes()
+{
+ export RET=0
+
+ attach_port_if_specified "${MEMBER_PORT}"
+ check_err $? "Couldn't attach ${MEMBER_PORT} to master"
+
+ # Set enabled to true.
+ set_and_check_get enabled true "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'enabled' to true"
+
+ # Set rx_enabled to false.
+ set_and_check_get rx_enabled false "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'rx_enabled' to false"
+
+ # Show that enabled is false.
+ get_and_check_value enabled false "--port=${MEMBER_PORT}"
+ check_err $? "'enabled' wasn't implicitly set to false"
+
+ # Set rx_enabled to true.
+ set_and_check_get rx_enabled true "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'rx_enabled' to true"
+
+ # Show that enabled is true.
+ get_and_check_value enabled true "--port=${MEMBER_PORT}"
+ check_err $? "'enabled' wasn't implicitly set to true"
+
+ log_test "'Rx_enabled' implicit changes"
+}
+
+team_test_tx_enabled_implicit_changes()
+{
+ export RET=0
+
+ attach_port_if_specified "${MEMBER_PORT}"
+ check_err $? "Couldn't attach ${MEMBER_PORT} to master"
+
+ # Set enabled to true.
+ set_and_check_get enabled true "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'enabled' to true"
+
+ # Set tx_enabled to false.
+ set_and_check_get tx_enabled false "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'tx_enabled' to false"
+
+ # Show that enabled is false.
+ get_and_check_value enabled false "--port=${MEMBER_PORT}"
+ check_err $? "'enabled' wasn't implicitly set to false"
+
+ # Set tx_enabled to true.
+ set_and_check_get tx_enabled true "--port=${MEMBER_PORT}"
+ check_err $? "Failed to set 'tx_enabled' to true"
+
+ # Show that enabled is true.
+ get_and_check_value enabled true "--port=${MEMBER_PORT}"
+ check_err $? "'enabled' wasn't implicitly set to true"
+
+ log_test "'Tx_enabled' implicit changes"
+}
+
+
require_command teamnl
setup
tests_run
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 6/7] net: team: Decouple rx and tx enablement in the team driver
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
There are use cases where an aggregated port should send traffic, but
not receive traffic, and vice versa. For example, in the IEEE
802.3ad-2000 specification, there is an optional "Independent Control"
version of the mux machine. Currently there is no way create
implementations like this for the team driver.
Separate the existing "enabled" per-port option into tx and rx
specific enablement options, but do so without breaking the existing
"enabled" option. The existing "enabled" option is now defined as
(rx_enabled AND tx_enabled), so if one is independently disabled, then
the old "enabled" option will also not be enabled. However, setting
the old "enabled" option affects both the rx_enabled and tx_enabled
options. This is the first case where setting an option can affect
another option.
Note that teamd and any other software that exclusively uses "coupled"
enablement will continue to work without any changes.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- None
Changes in v2:
- None
---
drivers/net/team/team_core.c | 238 ++++++++++++++++++++++++++-----
drivers/net/team/team_mode_loadbalance.c | 4 +-
drivers/net/team/team_mode_random.c | 4 +-
drivers/net/team/team_mode_roundrobin.c | 2 +-
include/linux/if_team.h | 60 +++++---
5 files changed, 245 insertions(+), 63 deletions(-)
diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c
index 2ce31999c99f..308ff7cd4b15 100644
--- a/drivers/net/team/team_core.c
+++ b/drivers/net/team/team_core.c
@@ -87,7 +87,7 @@ static void team_lower_state_changed(struct team_port *port)
struct netdev_lag_lower_state_info info;
info.link_up = port->linkup;
- info.tx_enabled = team_port_enabled(port);
+ info.tx_enabled = team_port_tx_enabled(port);
netdev_lower_state_changed(port->dev, &info);
}
@@ -532,13 +532,13 @@ static void team_adjust_ops(struct team *team)
* correct ops are always set.
*/
- if (!team->en_port_count || !team_is_mode_set(team) ||
+ if (!team->tx_en_port_count || !team_is_mode_set(team) ||
!team->mode->ops->transmit)
team->ops.transmit = team_dummy_transmit;
else
team->ops.transmit = team->mode->ops->transmit;
- if (!team->en_port_count || !team_is_mode_set(team) ||
+ if (!team->rx_en_port_count || !team_is_mode_set(team) ||
!team->mode->ops->receive)
team->ops.receive = team_dummy_receive;
else
@@ -734,7 +734,7 @@ static rx_handler_result_t team_handle_frame(struct sk_buff **pskb)
port = team_port_get_rcu(skb->dev);
team = port->team;
- if (!team_port_enabled(port)) {
+ if (!team_port_rx_enabled(port)) {
if (is_link_local_ether_addr(eth_hdr(skb)->h_dest))
/* link-local packets are mostly useful when stack receives them
* with the link they arrive on.
@@ -831,7 +831,7 @@ static bool team_queue_override_port_has_gt_prio_than(struct team_port *port,
return true;
if (port->priority > cur->priority)
return false;
- if (port->index < cur->index)
+ if (port->tx_index < cur->tx_index)
return true;
return false;
}
@@ -876,7 +876,7 @@ static void __team_queue_override_enabled_check(struct team *team)
static void team_queue_override_port_prio_changed(struct team *team,
struct team_port *port)
{
- if (!port->queue_id || !team_port_enabled(port))
+ if (!port->queue_id || !team_port_tx_enabled(port))
return;
__team_queue_override_port_del(team, port);
__team_queue_override_port_add(team, port);
@@ -887,7 +887,7 @@ static void team_queue_override_port_change_queue_id(struct team *team,
struct team_port *port,
u16 new_queue_id)
{
- if (team_port_enabled(port)) {
+ if (team_port_tx_enabled(port)) {
__team_queue_override_port_del(team, port);
port->queue_id = new_queue_id;
__team_queue_override_port_add(team, port);
@@ -927,58 +927,172 @@ static bool team_port_find(const struct team *team,
return false;
}
+static void __team_port_enable_rx(struct team *team,
+ struct team_port *port)
+{
+ team->rx_en_port_count++;
+ WRITE_ONCE(port->rx_enabled, true);
+}
+
+static void __team_port_disable_rx(struct team *team,
+ struct team_port *port)
+{
+ team->rx_en_port_count--;
+ WRITE_ONCE(port->rx_enabled, false);
+}
+
+static void team_port_enable_rx(struct team *team,
+ struct team_port *port)
+{
+ if (team_port_rx_enabled(port))
+ return;
+
+ __team_port_enable_rx(team, port);
+
+ team_adjust_ops(team);
+
+ team_notify_peers(team);
+ team_mcast_rejoin(team);
+}
+
+static void team_port_disable_rx(struct team *team,
+ struct team_port *port)
+{
+ if (!team_port_rx_enabled(port))
+ return;
+
+ __team_port_disable_rx(team, port);
+ team_adjust_ops(team);
+}
+
+/*
+ * Enable just TX on the port by adding to tx-enabled port hashlist and
+ * setting port->tx_index (Might be racy so reader could see incorrect
+ * ifindex when processing a flying packet, but that is not a problem).
+ * Write guarded by RTNL.
+ */
+static void __team_port_enable_tx(struct team *team,
+ struct team_port *port)
+{
+ WRITE_ONCE(port->tx_index, team->tx_en_port_count);
+ WRITE_ONCE(team->tx_en_port_count, team->tx_en_port_count + 1);
+ hlist_add_head_rcu(&port->tx_hlist,
+ team_tx_port_index_hash(team, port->tx_index));
+}
+
+static void team_port_enable_tx(struct team *team,
+ struct team_port *port)
+{
+ if (team_port_tx_enabled(port))
+ return;
+
+ __team_port_enable_tx(team, port);
+ team_adjust_ops(team);
+ team_queue_override_port_add(team, port);
+
+ /* Don't rejoin multicast, since this port might not be receiving. */
+ team_notify_peers(team);
+ team_lower_state_changed(port);
+}
+
/*
- * Enable/disable port by adding to enabled port hashlist and setting
- * port->index (Might be racy so reader could see incorrect ifindex when
- * processing a flying packet, but that is not a problem). Write guarded
- * by RTNL.
+ * Enable TX AND RX on the port.
*/
static void team_port_enable(struct team *team,
struct team_port *port)
{
+ bool rx_was_enabled;
+ bool tx_was_enabled;
+
if (team_port_enabled(port))
return;
- WRITE_ONCE(port->index, team->en_port_count);
- WRITE_ONCE(team->en_port_count, team->en_port_count + 1);
- hlist_add_head_rcu(&port->hlist,
- team_port_index_hash(team, port->index));
+
+ rx_was_enabled = team_port_rx_enabled(port);
+ tx_was_enabled = team_port_tx_enabled(port);
+
+ if (!rx_was_enabled)
+ __team_port_enable_rx(team, port);
+ if (!tx_was_enabled) {
+ __team_port_enable_tx(team, port);
+ team_queue_override_port_add(team, port);
+ }
+
team_adjust_ops(team);
- team_queue_override_port_add(team, port);
team_notify_peers(team);
- team_mcast_rejoin(team);
- team_lower_state_changed(port);
+
+ if (!rx_was_enabled)
+ team_mcast_rejoin(team);
+ if (!tx_was_enabled)
+ team_lower_state_changed(port);
}
static void __reconstruct_port_hlist(struct team *team, int rm_index)
{
- int i;
+ struct hlist_head *tx_port_index_hash;
struct team_port *port;
+ int i;
- for (i = rm_index + 1; i < team->en_port_count; i++) {
- port = team_get_port_by_index(team, i);
- hlist_del_rcu(&port->hlist);
- WRITE_ONCE(port->index, port->index - 1);
- hlist_add_head_rcu(&port->hlist,
- team_port_index_hash(team, port->index));
+ for (i = rm_index + 1; i < team->tx_en_port_count; i++) {
+ port = team_get_port_by_tx_index(team, i);
+ hlist_del_rcu(&port->tx_hlist);
+ WRITE_ONCE(port->tx_index, port->tx_index - 1);
+ tx_port_index_hash = team_tx_port_index_hash(team,
+ port->tx_index);
+ hlist_add_head_rcu(&port->tx_hlist, tx_port_index_hash);
}
}
-static void team_port_disable(struct team *team,
- struct team_port *port)
+static void __team_port_disable_tx(struct team *team,
+ struct team_port *port)
{
- if (!team_port_enabled(port))
- return;
if (team->ops.port_tx_disabled)
team->ops.port_tx_disabled(team, port);
- hlist_del_rcu(&port->hlist);
- __reconstruct_port_hlist(team, port->index);
- WRITE_ONCE(port->index, -1);
- WRITE_ONCE(team->en_port_count, team->en_port_count - 1);
+
+ hlist_del_rcu(&port->tx_hlist);
+ __reconstruct_port_hlist(team, port->tx_index);
+
+ WRITE_ONCE(port->tx_index, -1);
+ WRITE_ONCE(team->tx_en_port_count, team->tx_en_port_count - 1);
+}
+
+static void team_port_disable_tx(struct team *team,
+ struct team_port *port)
+{
+ if (!team_port_tx_enabled(port))
+ return;
+
+ __team_port_disable_tx(team, port);
+
team_queue_override_port_del(team, port);
team_adjust_ops(team);
team_lower_state_changed(port);
}
+static void team_port_disable(struct team *team,
+ struct team_port *port)
+{
+ bool rx_was_enabled;
+ bool tx_was_enabled;
+
+ if (!team_port_tx_enabled(port) && !team_port_rx_enabled(port))
+ return;
+
+ rx_was_enabled = team_port_rx_enabled(port);
+ tx_was_enabled = team_port_tx_enabled(port);
+
+ if (tx_was_enabled) {
+ __team_port_disable_tx(team, port);
+ team_queue_override_port_del(team, port);
+ }
+ if (rx_was_enabled)
+ __team_port_disable_rx(team, port);
+
+ team_adjust_ops(team);
+
+ if (tx_was_enabled)
+ team_lower_state_changed(port);
+}
+
static int team_port_enter(struct team *team, struct team_port *port)
{
int err = 0;
@@ -1244,7 +1358,7 @@ static int team_port_add(struct team *team, struct net_device *port_dev,
netif_addr_unlock_bh(dev);
}
- WRITE_ONCE(port->index, -1);
+ WRITE_ONCE(port->tx_index, -1);
list_add_tail_rcu(&port->list, &team->port_list);
team_port_enable(team, port);
netdev_compute_master_upper_features(dev, true);
@@ -1429,6 +1543,46 @@ static int team_port_en_option_set(struct team *team,
return 0;
}
+static void team_port_tx_en_option_get(struct team *team,
+ struct team_gsetter_ctx *ctx)
+{
+ struct team_port *port = ctx->info->port;
+
+ ctx->data.bool_val = team_port_tx_enabled(port);
+}
+
+static int team_port_tx_en_option_set(struct team *team,
+ struct team_gsetter_ctx *ctx)
+{
+ struct team_port *port = ctx->info->port;
+
+ if (ctx->data.bool_val)
+ team_port_enable_tx(team, port);
+ else
+ team_port_disable_tx(team, port);
+ return 0;
+}
+
+static void team_port_rx_en_option_get(struct team *team,
+ struct team_gsetter_ctx *ctx)
+{
+ struct team_port *port = ctx->info->port;
+
+ ctx->data.bool_val = team_port_rx_enabled(port);
+}
+
+static int team_port_rx_en_option_set(struct team *team,
+ struct team_gsetter_ctx *ctx)
+{
+ struct team_port *port = ctx->info->port;
+
+ if (ctx->data.bool_val)
+ team_port_enable_rx(team, port);
+ else
+ team_port_disable_rx(team, port);
+ return 0;
+}
+
static void team_user_linkup_option_get(struct team *team,
struct team_gsetter_ctx *ctx)
{
@@ -1550,6 +1704,20 @@ static const struct team_option team_options[] = {
.getter = team_port_en_option_get,
.setter = team_port_en_option_set,
},
+ {
+ .name = "tx_enabled",
+ .type = TEAM_OPTION_TYPE_BOOL,
+ .per_port = true,
+ .getter = team_port_tx_en_option_get,
+ .setter = team_port_tx_en_option_set,
+ },
+ {
+ .name = "rx_enabled",
+ .type = TEAM_OPTION_TYPE_BOOL,
+ .per_port = true,
+ .getter = team_port_rx_en_option_get,
+ .setter = team_port_rx_en_option_set,
+ },
{
.name = "user_linkup",
.type = TEAM_OPTION_TYPE_BOOL,
@@ -1595,7 +1763,7 @@ static int team_init(struct net_device *dev)
return -ENOMEM;
for (i = 0; i < TEAM_PORT_HASHENTRIES; i++)
- INIT_HLIST_HEAD(&team->en_port_hlist[i]);
+ INIT_HLIST_HEAD(&team->tx_en_port_hlist[i]);
INIT_LIST_HEAD(&team->port_list);
err = team_queue_override_init(team);
if (err)
diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c
index 840f409d250b..38a459649569 100644
--- a/drivers/net/team/team_mode_loadbalance.c
+++ b/drivers/net/team/team_mode_loadbalance.c
@@ -120,7 +120,7 @@ static struct team_port *lb_hash_select_tx_port(struct team *team,
{
int port_index = team_num_to_port_index(team, hash);
- return team_get_port_by_index_rcu(team, port_index);
+ return team_get_port_by_tx_index_rcu(team, port_index);
}
/* Hash to port mapping select tx port */
@@ -380,7 +380,7 @@ static int lb_tx_hash_to_port_mapping_set(struct team *team,
list_for_each_entry(port, &team->port_list, list) {
if (ctx->data.u32_val == port->dev->ifindex &&
- team_port_enabled(port)) {
+ team_port_tx_enabled(port)) {
rcu_assign_pointer(LB_HTPM_PORT_BY_HASH(lb_priv, hash),
port);
return 0;
diff --git a/drivers/net/team/team_mode_random.c b/drivers/net/team/team_mode_random.c
index 169a7bc865b2..370e974f3dca 100644
--- a/drivers/net/team/team_mode_random.c
+++ b/drivers/net/team/team_mode_random.c
@@ -16,8 +16,8 @@ static bool rnd_transmit(struct team *team, struct sk_buff *skb)
struct team_port *port;
int port_index;
- port_index = get_random_u32_below(READ_ONCE(team->en_port_count));
- port = team_get_port_by_index_rcu(team, port_index);
+ port_index = get_random_u32_below(READ_ONCE(team->tx_en_port_count));
+ port = team_get_port_by_tx_index_rcu(team, port_index);
if (unlikely(!port))
goto drop;
port = team_get_first_port_txable_rcu(team, port);
diff --git a/drivers/net/team/team_mode_roundrobin.c b/drivers/net/team/team_mode_roundrobin.c
index dd405d82c6ac..ecbeef28c221 100644
--- a/drivers/net/team/team_mode_roundrobin.c
+++ b/drivers/net/team/team_mode_roundrobin.c
@@ -27,7 +27,7 @@ static bool rr_transmit(struct team *team, struct sk_buff *skb)
port_index = team_num_to_port_index(team,
rr_priv(team)->sent_packets++);
- port = team_get_port_by_index_rcu(team, port_index);
+ port = team_get_port_by_tx_index_rcu(team, port_index);
if (unlikely(!port))
goto drop;
port = team_get_first_port_txable_rcu(team, port);
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index 740cb3100dfc..3d21e06fda67 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -27,10 +27,11 @@ struct team;
struct team_port {
struct net_device *dev;
- struct hlist_node hlist; /* node in enabled ports hash list */
+ struct hlist_node tx_hlist; /* node in tx-enabled ports hash list */
struct list_head list; /* node in ordinary list */
struct team *team;
- int index; /* index of enabled port. If disabled, it's set to -1 */
+ int tx_index; /* index of tx enabled port. If disabled, -1 */
+ bool rx_enabled;
bool linkup; /* either state.linkup or user.linkup */
@@ -75,14 +76,24 @@ static inline struct team_port *team_port_get_rcu(const struct net_device *dev)
return rcu_dereference(dev->rx_handler_data);
}
+static inline bool team_port_rx_enabled(struct team_port *port)
+{
+ return READ_ONCE(port->rx_enabled);
+}
+
+static inline bool team_port_tx_enabled(struct team_port *port)
+{
+ return READ_ONCE(port->tx_index) != -1;
+}
+
static inline bool team_port_enabled(struct team_port *port)
{
- return READ_ONCE(port->index) != -1;
+ return team_port_rx_enabled(port) && team_port_tx_enabled(port);
}
static inline bool team_port_txable(struct team_port *port)
{
- return port->linkup && team_port_enabled(port);
+ return port->linkup && team_port_tx_enabled(port);
}
static inline bool team_port_dev_txable(const struct net_device *port_dev)
@@ -190,10 +201,11 @@ struct team {
const struct header_ops *header_ops_cache;
/*
- * List of enabled ports and their count
+ * List of tx-enabled ports and counts of rx and tx-enabled ports.
*/
- int en_port_count;
- struct hlist_head en_port_hlist[TEAM_PORT_HASHENTRIES];
+ int tx_en_port_count;
+ int rx_en_port_count;
+ struct hlist_head tx_en_port_hlist[TEAM_PORT_HASHENTRIES];
struct list_head port_list; /* list of all ports */
@@ -237,41 +249,43 @@ static inline int team_dev_queue_xmit(struct team *team, struct team_port *port,
return dev_queue_xmit(skb);
}
-static inline struct hlist_head *team_port_index_hash(struct team *team,
- int port_index)
+static inline struct hlist_head *team_tx_port_index_hash(struct team *team,
+ int tx_port_index)
{
- return &team->en_port_hlist[port_index & (TEAM_PORT_HASHENTRIES - 1)];
+ unsigned int list_entry = tx_port_index & (TEAM_PORT_HASHENTRIES - 1);
+
+ return &team->tx_en_port_hlist[list_entry];
}
-static inline struct team_port *team_get_port_by_index(struct team *team,
- int port_index)
+static inline struct team_port *team_get_port_by_tx_index(struct team *team,
+ int tx_port_index)
{
+ struct hlist_head *head = team_tx_port_index_hash(team, tx_port_index);
struct team_port *port;
- struct hlist_head *head = team_port_index_hash(team, port_index);
- hlist_for_each_entry(port, head, hlist)
- if (port->index == port_index)
+ hlist_for_each_entry(port, head, tx_hlist)
+ if (port->tx_index == tx_port_index)
return port;
return NULL;
}
static inline int team_num_to_port_index(struct team *team, unsigned int num)
{
- int en_port_count = READ_ONCE(team->en_port_count);
+ int tx_en_port_count = READ_ONCE(team->tx_en_port_count);
- if (unlikely(!en_port_count))
+ if (unlikely(!tx_en_port_count))
return 0;
- return num % en_port_count;
+ return num % tx_en_port_count;
}
-static inline struct team_port *team_get_port_by_index_rcu(struct team *team,
- int port_index)
+static inline struct team_port *team_get_port_by_tx_index_rcu(struct team *team,
+ int tx_port_index)
{
+ struct hlist_head *head = team_tx_port_index_hash(team, tx_port_index);
struct team_port *port;
- struct hlist_head *head = team_port_index_hash(team, port_index);
- hlist_for_each_entry_rcu(port, head, hlist)
- if (READ_ONCE(port->index) == port_index)
+ hlist_for_each_entry_rcu(port, head, tx_hlist)
+ if (READ_ONCE(port->tx_index) == tx_port_index)
return port;
return NULL;
}
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 5/7] selftests: net: Add test for enablement of ports with teamd
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
There are no tests that verify enablement and disablement of team driver
ports with teamd. This should work even with changes to the enablement
option, so it is important to test.
This test sets up an active-backup network configuration across two
network namespaces, and tries to send traffic while changing which
link is the active one.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changed in v3:
- Make test cleanup kill teamd instead of terminate.
- Link to v2: https://lore.kernel.org/netdev/20260401-teaming-driver-internal-v2-5-f80c1291727b@google.com/
Changes in v2:
- Fix shellcheck failures.
- Remove dependency on net forwarding lib and pipe viewer tools.
- Use iperf3 for tcp instead of netcat.
- Link to v1: https://lore.kernel.org/all/20260331053353.2504254-6-marcharvey@google.com/
---
tools/testing/selftests/drivers/net/team/Makefile | 1 +
.../drivers/net/team/teamd_activebackup.sh | 217 +++++++++++++++++++++
tools/testing/selftests/net/lib.sh | 13 ++
3 files changed, 231 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/team/Makefile b/tools/testing/selftests/drivers/net/team/Makefile
index 777da2e0429e..dab922d7f83d 100644
--- a/tools/testing/selftests/drivers/net/team/Makefile
+++ b/tools/testing/selftests/drivers/net/team/Makefile
@@ -7,6 +7,7 @@ TEST_PROGS := \
options.sh \
propagation.sh \
refleak.sh \
+ teamd_activebackup.sh \
transmit_failover.sh \
# end of TEST_PROGS
diff --git a/tools/testing/selftests/drivers/net/team/teamd_activebackup.sh b/tools/testing/selftests/drivers/net/team/teamd_activebackup.sh
new file mode 100755
index 000000000000..aa882e1ee3d8
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/team/teamd_activebackup.sh
@@ -0,0 +1,217 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# These tests verify that teamd is able to enable and disable ports via the
+# active backup runner.
+#
+# Topology:
+#
+# +-------------------------+ NS1
+# | test_team1 |
+# | + |
+# | eth0 | eth1 |
+# | +---+---+ |
+# | | | |
+# +-------------------------+
+# | |
+# +-------------------------+ NS2
+# | | | |
+# | +-------+ |
+# | eth0 | eth1 |
+# | + |
+# | test_team2 |
+# +-------------------------+
+
+export ALL_TESTS="teamd_test_active_backup"
+
+test_dir="$(dirname "$0")"
+# shellcheck disable=SC1091
+source "${test_dir}/../../../net/lib.sh"
+# shellcheck disable=SC1091
+source "${test_dir}/team_lib.sh"
+
+NS1=""
+NS2=""
+export NODAD="nodad"
+PREFIX_LENGTH="64"
+NS1_IP="fd00::1"
+NS2_IP="fd00::2"
+NS1_IP4="192.168.0.1"
+NS2_IP4="192.168.0.2"
+NS1_TEAMD_CONF=""
+NS2_TEAMD_CONF=""
+NS1_TEAMD_PID=""
+NS2_TEAMD_PID=""
+
+while getopts "4" opt; do
+ case $opt in
+ 4)
+ echo "IPv4 mode selected."
+ export NODAD=
+ PREFIX_LENGTH="24"
+ NS1_IP="${NS1_IP4}"
+ NS2_IP="${NS2_IP4}"
+ ;;
+ \?)
+ echo "Invalid option: -${OPTARG}" >&2
+ exit 1
+ ;;
+ esac
+done
+
+teamd_config_create()
+{
+ local runner=$1
+ local dev=$2
+ local conf
+
+ conf=$(mktemp)
+
+ cat > "${conf}" <<-EOF
+ {
+ "device": "${dev}",
+ "runner": {"name": "${runner}"},
+ "ports": {
+ "eth0": {},
+ "eth1": {}
+ }
+ }
+ EOF
+ echo "${conf}"
+}
+
+# Create the network namespaces, veth pair, and team devices in the specified
+# runner.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+# Arguments:
+# runner - The Teamd runner to use for the Team devices.
+environment_create()
+{
+ local runner=$1
+
+ echo "Setting up two-link aggregation for runner ${runner}"
+ trap environment_destroy EXIT
+
+ setup_ns ns1 ns2
+ NS1="${NS_LIST[0]}"
+ NS2="${NS_LIST[1]}"
+
+ for link in $(seq 0 1); do
+ ip -n "${NS1}" link add "eth${link}" type veth peer name \
+ "eth${link}" netns "${NS2}"
+ check_err $? "Failed to create veth pair"
+ done
+
+ NS1_TEAMD_CONF=$(teamd_config_create "${runner}" "test_team1")
+ NS2_TEAMD_CONF=$(teamd_config_create "${runner}" "test_team2")
+ echo "Conf files are ${NS1_TEAMD_CONF} and ${NS2_TEAMD_CONF}"
+
+ ip netns exec "${NS1}" teamd -d -f "${NS1_TEAMD_CONF}"
+ check_err $? "Failed to create team device in ${NS1}"
+ NS1_TEAMD_PID=$(pgrep -f "teamd -d -f ${NS1_TEAMD_CONF}")
+
+ ip netns exec "${NS2}" teamd -d -f "${NS2_TEAMD_CONF}"
+ check_err $? "Failed to create team device in ${NS2}"
+ NS2_TEAMD_PID=$(pgrep -f "teamd -d -f ${NS2_TEAMD_CONF}")
+
+ echo "Created team devices"
+
+ rm "${NS1_TEAMD_CONF}"
+ rm "${NS2_TEAMD_CONF}"
+ NS1_TEAMD_CONF=""
+ NS2_TEAMD_CONF=""
+
+ for link in $(seq 0 1); do
+ in_all_ns "ip link set eth${link} up"
+ check_err $? "Failed to set eth${link} up"
+ done
+
+ ip -n "${NS1}" link set test_team1 up
+ check_err $? "Failed to set test_team1 up in ${NS1}"
+ ip -n "${NS2}" link set test_team2 up
+ check_err $? "Failed to set test_team2 up in ${NS2}"
+
+ ip -n "${NS1}" addr add "${NS1_IP}/${PREFIX_LENGTH}" "${NODAD}" dev \
+ test_team1
+ check_err $? "Failed to add address to team device in ${NS1}"
+ ip -n "${NS2}" addr add "${NS2_IP}/${PREFIX_LENGTH}" "${NODAD}" dev \
+ test_team2
+ check_err $? "Failed to add address to team device in ${NS2}"
+
+ slowwait 2 timeout 0.5 ip netns exec "${NS1}" ping -W 1 -c 1 "${NS2_IP}"
+}
+
+# Tear down the environment: kill teamd and delete network namespaces.
+environment_destroy()
+{
+ echo "Tearing down two-link aggregation"
+ if [ -n "${NS1_TEAMD_PID}" ]; then
+ kill -9 "${NS1_TEAMD_PID}" 2>/dev/null
+ fi
+ if [ -n "${NS2_TEAMD_PID}" ]; then
+ kill -9 "${NS2_TEAMD_PID}" 2>/dev/null
+ fi
+ cleanup_all_ns
+}
+
+# Change the active port for an active-backup mode team.
+# Arguments:
+# namespace - The network namespace that the team is in.
+# team - The name of the team.
+# active_port - The port to make active.
+set_active_port()
+{
+ local namespace=$1
+ local team=$2
+ local active_port=$3
+
+ ip netns exec "${namespace}" teamdctl "${team}" state item set \
+ runner.active_port "${active_port}"
+ slowwait 2 bash -c "ip netns exec ${namespace} teamdctl ${team} state \
+ item get runner.active_port | grep -q ${active_port}"
+}
+
+# Test that active backup runner can change active ports.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+teamd_test_active_backup()
+{
+ export RET=0
+
+ start_listening_and_sending
+
+ ### Scenario 1: Don't manually set active port, just make sure team
+ # works.
+ did_interface_receive test_team2 "${NS2}"
+ check_err $? "Traffic did not reach team interface in NS2."
+
+ ### Scenario 2: Choose active port.
+ set_active_port "${NS1}" test_team1 eth1
+ set_active_port "${NS2}" test_team2 eth1
+
+ did_interface_receive eth0 "${NS2}"
+ check_fail $? "eth0 IS transmitting when disabled"
+ did_interface_receive eth1 "${NS2}"
+ check_err $? "eth1 not transmitting when enabled"
+
+ ### Scenario 3: Change active port.
+ set_active_port "${NS1}" test_team1 eth0
+ set_active_port "${NS2}" test_team2 eth0
+
+ did_interface_receive eth0 "${NS2}"
+ check_err $? "eth0 not transmitting when enabled"
+ did_interface_receive eth1 "${NS2}"
+ check_fail $? "eth1 IS transmitting when disabled"
+
+ log_test "teamd active backup runner test"
+
+ stop_sending_and_listening
+}
+
+require_command teamd
+require_command teamdctl
+require_command iperf3
+environment_create activebackup
+tests_run
+exit "${EXIT_STATUS}"
diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh
index b40694573f4c..992342b1dd7c 100644
--- a/tools/testing/selftests/net/lib.sh
+++ b/tools/testing/selftests/net/lib.sh
@@ -224,6 +224,19 @@ setup_ns()
NS_LIST+=("${ns_list[@]}")
}
+in_all_ns()
+{
+ local ret=0
+ local ns_list=("${NS_LIST[@]}")
+
+ for ns in "${ns_list[@]}"; do
+ ip netns exec "${ns}" bash -c "$@"
+ (( ret = ret || $? ))
+ done
+
+ return "${ret}"
+}
+
# Create netdevsim with given id and net namespace.
create_netdevsim() {
local id="$1"
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 4/7] selftests: net: Add tests for failover of team-aggregated ports
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
There are currently no kernel tests that verify the effect of setting
the enabled team driver option. In a followup patch, there will be
changes to this option, so it will be important to make sure it still
behaves as it does now.
The test verifies that tcp continues to work across two different team
devices in separate network namespaces, even when member links are
manually disabled.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- None
Changes in v2:
- Fix shellcheck failures.
- Remove dependency on net forwarding lib and pipe viewer tools.
- Use iperf3 for tcp instead of netcat.
- Link to v1: https://lore.kernel.org/all/20260331053353.2504254-5-marcharvey@google.com/
---
tools/testing/selftests/drivers/net/team/Makefile | 2 +
tools/testing/selftests/drivers/net/team/config | 4 +
.../testing/selftests/drivers/net/team/team_lib.sh | 115 ++++++++++++++++
.../drivers/net/team/transmit_failover.sh | 151 +++++++++++++++++++++
4 files changed, 272 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/team/Makefile b/tools/testing/selftests/drivers/net/team/Makefile
index 02d6f51d5a06..777da2e0429e 100644
--- a/tools/testing/selftests/drivers/net/team/Makefile
+++ b/tools/testing/selftests/drivers/net/team/Makefile
@@ -7,9 +7,11 @@ TEST_PROGS := \
options.sh \
propagation.sh \
refleak.sh \
+ transmit_failover.sh \
# end of TEST_PROGS
TEST_INCLUDES := \
+ team_lib.sh \
../bonding/lag_lib.sh \
../../../net/forwarding/lib.sh \
../../../net/in_netns.sh \
diff --git a/tools/testing/selftests/drivers/net/team/config b/tools/testing/selftests/drivers/net/team/config
index 5d36a22ef080..8f04ae419c53 100644
--- a/tools/testing/selftests/drivers/net/team/config
+++ b/tools/testing/selftests/drivers/net/team/config
@@ -6,4 +6,8 @@ CONFIG_NETDEVSIM=m
CONFIG_NET_IPGRE=y
CONFIG_NET_TEAM=y
CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=y
+CONFIG_NET_TEAM_MODE_BROADCAST=y
CONFIG_NET_TEAM_MODE_LOADBALANCE=y
+CONFIG_NET_TEAM_MODE_RANDOM=y
+CONFIG_NET_TEAM_MODE_ROUNDROBIN=y
+CONFIG_VETH=y
diff --git a/tools/testing/selftests/drivers/net/team/team_lib.sh b/tools/testing/selftests/drivers/net/team/team_lib.sh
new file mode 100644
index 000000000000..fce421869381
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/team/team_lib.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# Create a team interface inside of a given network namespace with a given
+# mode, members, and IP address.
+# Arguments:
+# namespace - Network namespace to put the team interface into.
+# team - The name of the team interface to setup.
+# mode - The team mode of the interface.
+# ip_address - The IP address to assign to the team interface.
+# prefix_length - The prefix length for the IP address subnet.
+# $@ - members - The member interfaces of the aggregation.
+setup_team()
+{
+ local namespace=$1
+ local team=$2
+ local mode=$3
+ local ip_address=$4
+ local prefix_length=$5
+ shift 5
+ local members=("$@")
+
+ # Prerequisite: team must have no members
+ for member in "${members[@]}"; do
+ ip -n "${namespace}" link set "${member}" nomaster
+ done
+
+ # Prerequisite: team must have no address in order to set it
+ # shellcheck disable=SC2086
+ ip -n "${namespace}" addr del "${ip_address}/${prefix_length}" \
+ ${NODAD} dev "${team}"
+
+ echo "Setting team in ${namespace} to mode ${mode}"
+
+ if ! ip -n "${namespace}" link set "${team}" down; then
+ echo "Failed to bring team device down"
+ return 1
+ fi
+ if ! ip netns exec "${namespace}" teamnl "${team}" setoption mode \
+ "${mode}"; then
+ echo "Failed to set ${team} mode to '${mode}'"
+ return 1
+ fi
+
+ # Aggregate the members into teams.
+ for member in "${members[@]}"; do
+ ip -n "${namespace}" link set "${member}" master "${team}"
+ done
+
+ # Bring team devices up and give them addresses.
+ if ! ip -n "${namespace}" link set "${team}" up; then
+ echo "Failed to set ${team} up"
+ return 1
+ fi
+
+ # shellcheck disable=SC2086
+ if ! ip -n "${namespace}" addr add "${ip_address}/${prefix_length}" \
+ ${NODAD} dev "${team}"; then
+ echo "Failed to give ${team} IP address in ${namespace}"
+ return 1
+ fi
+}
+
+# This is global used to keep track of the sender's iperf3 process, so that it
+# can be terminated.
+declare sender_pid
+
+# Start sending and receiving TCP traffic with iperf3.
+# Globals:
+# sender_pid - The process ID of the iperf3 sender process. Used to kill it
+# later.
+start_listening_and_sending()
+{
+ ip netns exec "${NS2}" iperf3 -s -p 1234 --logfile /dev/null &
+ # Wait for server to become reachable before starting client.
+ slowwait 5 ip netns exec "${NS1}" iperf3 -c "${NS2_IP}" -p 1234 -t 1 \
+ --logfile /dev/null
+ ip netns exec "${NS1}" iperf3 -c "${NS2_IP}" -p 1234 -b 1M -l 1K -t 0 \
+ --logfile /dev/null &
+ sender_pid=$!
+}
+
+# Stop sending TCP traffic with iperf3.
+# Globals:
+# sender_pid - The process IF of the iperf3 sender process.
+stop_sending_and_listening()
+{
+ kill "${sender_pid}" && wait "${sender_pid}" 2>/dev/null || true
+}
+
+# Read RX packet counters to determine if interface received traffic.
+# Arguments:
+# interface - The name of the interface to count packets for.
+# namespace - The name of the namespace that the interface is in.
+did_interface_receive()
+{
+ local interface="$1"
+ local namespace="$2"
+ local count1 count2 diff
+
+ count1=$(ip netns exec "${namespace}" cat \
+ "/sys/class/net/${interface}/statistics/rx_packets")
+ sleep 1
+ count2=$(ip netns exec "${namespace}" cat \
+ "/sys/class/net/${interface}/statistics/rx_packets")
+ diff=$((count2 - count1))
+
+ echo "Packet count for ${interface} was ${diff}"
+
+ if [[ "${diff}" -gt 0 ]]; then
+ true
+ else
+ false
+ fi
+}
diff --git a/tools/testing/selftests/drivers/net/team/transmit_failover.sh b/tools/testing/selftests/drivers/net/team/transmit_failover.sh
new file mode 100755
index 000000000000..212d650caf66
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/team/transmit_failover.sh
@@ -0,0 +1,151 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# These tests verify the basic failover capability of the team driver via the
+# `enabled` team driver option across different team driver modes. This does not
+# rely on teamd, and instead just uses teamnl to set the `enabled` option
+# directly.
+#
+# Topology:
+#
+# +-------------------------+ NS1
+# | test_team1 |
+# | + |
+# | eth0 | eth1 |
+# | +---+---+ |
+# | | | |
+# +-------------------------+
+# | |
+# +-------------------------+ NS2
+# | | | |
+# | +-------+ |
+# | eth0 | eth1 |
+# | + |
+# | test_team2 |
+# +-------------------------+
+
+export ALL_TESTS="team_test_failover"
+
+test_dir="$(dirname "$0")"
+# shellcheck disable=SC1091
+source "${test_dir}/../../../net/lib.sh"
+# shellcheck disable=SC1091
+source "${test_dir}/team_lib.sh"
+
+NS1=""
+NS2=""
+export NODAD="nodad"
+PREFIX_LENGTH="64"
+NS1_IP="fd00::1"
+NS2_IP="fd00::2"
+NS1_IP4="192.168.0.1"
+NS2_IP4="192.168.0.2"
+MEMBERS=("eth0" "eth1")
+
+while getopts "4" opt; do
+ case $opt in
+ 4)
+ echo "IPv4 mode selected."
+ export NODAD=
+ PREFIX_LENGTH="24"
+ NS1_IP="${NS1_IP4}"
+ NS2_IP="${NS2_IP4}"
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2
+ exit 1
+ ;;
+ esac
+done
+
+# Create the network namespaces, veth pair, and team devices in the specified
+# mode.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+# Arguments:
+# mode - The team driver mode to use for the team devices.
+environment_create()
+{
+ trap cleanup_all_ns EXIT
+ setup_ns ns1 ns2
+ NS1="${NS_LIST[0]}"
+ NS2="${NS_LIST[1]}"
+
+ # Create the interfaces.
+ ip -n "${NS1}" link add eth0 type veth peer name eth0 netns "${NS2}"
+ ip -n "${NS1}" link add eth1 type veth peer name eth1 netns "${NS2}"
+ ip -n "${NS1}" link add test_team1 type team
+ ip -n "${NS2}" link add test_team2 type team
+
+ # Set up the receiving network namespace's team interface.
+ setup_team "${NS2}" test_team2 roundrobin "${NS2_IP}" \
+ "${PREFIX_LENGTH}" "${MEMBERS[@]}"
+}
+
+
+# Check that failover works for a specific team driver mode.
+# Globals:
+# RET - Used by test infra, set by `check_err` functions.
+# Arguments:
+# mode - The mode to set the team interfaces to.
+team_test_mode_failover()
+{
+ local mode="$1"
+ export RET=0
+
+ # Set up the sender team with the correct mode.
+ setup_team "${NS1}" test_team1 "${mode}" "${NS1_IP}" \
+ "${PREFIX_LENGTH}" "${MEMBERS[@]}"
+ check_err $? "Failed to set up sender team"
+
+ start_listening_and_sending
+
+ ### Scenario 1: All interfaces initially enabled.
+ did_interface_receive eth0 "${NS2}"
+ check_err $? "eth0 not transmitting when both links enabled"
+ did_interface_receive eth1 "${NS2}"
+ check_err $? "eth1 not transmitting when both links enabled"
+
+ ### Scenario 2: One tx-side interface disabled.
+ ip netns exec "${NS1}" teamnl test_team1 setoption enabled false \
+ --port=eth1
+ slowwait 2 bash -c "ip netns exec ${NS1} teamnl test_team1 getoption \
+ enabled --port=eth1 | grep -q false"
+
+ did_interface_receive eth0 "${NS2}"
+ check_err $? "eth0 not transmitting when enabled"
+ did_interface_receive eth1 "${NS2}"
+ check_fail $? "eth1 IS transmitting when disabled"
+
+ ### Scenario 3: The interface is re-enabled.
+ ip netns exec "${NS1}" teamnl test_team1 setoption enabled true \
+ --port=eth1
+ slowwait 2 bash -c "ip netns exec ${NS1} teamnl test_team1 getoption \
+ enabled --port=eth1 | grep -q true"
+
+ did_interface_receive eth0 "${NS2}"
+ check_err $? "eth0 not transmitting when both links enabled"
+ did_interface_receive eth1 "${NS2}"
+ check_err $? "eth1 not transmitting when both links enabled"
+
+ log_test "Failover of '${mode}' test"
+
+ # Clean up
+ stop_sending_and_listening
+}
+
+team_test_failover()
+{
+ team_test_mode_failover broadcast
+ team_test_mode_failover roundrobin
+ team_test_mode_failover random
+ # Don't test `activebackup` or `loadbalance` modes, since they are too
+ # complicated for just setting `enabled` to work. They use more than
+ # the `enabled` option for transmit.
+}
+
+require_command teamnl
+require_command iperf3
+environment_create
+tests_run
+exit "${EXIT_STATUS}"
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 3/7] net: team: Rename port_disabled team mode op to port_tx_disabled
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
This team mode op is only used by the load balance mode, and it only
uses it in the tx path.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- None
Changes in v2:
- None
---
drivers/net/team/team_core.c | 4 ++--
drivers/net/team/team_mode_loadbalance.c | 4 ++--
include/linux/if_team.h | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c
index e54bd21bd068..2ce31999c99f 100644
--- a/drivers/net/team/team_core.c
+++ b/drivers/net/team/team_core.c
@@ -968,8 +968,8 @@ static void team_port_disable(struct team *team,
{
if (!team_port_enabled(port))
return;
- if (team->ops.port_disabled)
- team->ops.port_disabled(team, port);
+ if (team->ops.port_tx_disabled)
+ team->ops.port_tx_disabled(team, port);
hlist_del_rcu(&port->hlist);
__reconstruct_port_hlist(team, port->index);
WRITE_ONCE(port->index, -1);
diff --git a/drivers/net/team/team_mode_loadbalance.c b/drivers/net/team/team_mode_loadbalance.c
index 684954c2a8de..840f409d250b 100644
--- a/drivers/net/team/team_mode_loadbalance.c
+++ b/drivers/net/team/team_mode_loadbalance.c
@@ -655,7 +655,7 @@ static void lb_port_leave(struct team *team, struct team_port *port)
free_percpu(lb_port_priv->pcpu_stats);
}
-static void lb_port_disabled(struct team *team, struct team_port *port)
+static void lb_port_tx_disabled(struct team *team, struct team_port *port)
{
lb_tx_hash_to_port_mapping_null_port(team, port);
}
@@ -665,7 +665,7 @@ static const struct team_mode_ops lb_mode_ops = {
.exit = lb_exit,
.port_enter = lb_port_enter,
.port_leave = lb_port_leave,
- .port_disabled = lb_port_disabled,
+ .port_tx_disabled = lb_port_tx_disabled,
.receive = lb_receive,
.transmit = lb_transmit,
};
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index a761f5282bcf..740cb3100dfc 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -121,7 +121,7 @@ struct team_mode_ops {
int (*port_enter)(struct team *team, struct team_port *port);
void (*port_leave)(struct team *team, struct team_port *port);
void (*port_change_dev_addr)(struct team *team, struct team_port *port);
- void (*port_disabled)(struct team *team, struct team_port *port);
+ void (*port_tx_disabled)(struct team *team, struct team_port *port);
};
extern int team_modeop_port_enter(struct team *team, struct team_port *port);
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 2/7] net: team: Remove unused team_mode_op, port_enabled
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
This team_mode_op wasn't used by any of the team modes, so remove it.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- None
Changes in v2:
- None
---
drivers/net/team/team_core.c | 2 --
include/linux/if_team.h | 1 -
2 files changed, 3 deletions(-)
diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c
index becd066279a6..e54bd21bd068 100644
--- a/drivers/net/team/team_core.c
+++ b/drivers/net/team/team_core.c
@@ -944,8 +944,6 @@ static void team_port_enable(struct team *team,
team_port_index_hash(team, port->index));
team_adjust_ops(team);
team_queue_override_port_add(team, port);
- if (team->ops.port_enabled)
- team->ops.port_enabled(team, port);
team_notify_peers(team);
team_mcast_rejoin(team);
team_lower_state_changed(port);
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index 06f4d7400c1e..a761f5282bcf 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -121,7 +121,6 @@ struct team_mode_ops {
int (*port_enter)(struct team *team, struct team_port *port);
void (*port_leave)(struct team *team, struct team_port *port);
void (*port_change_dev_addr)(struct team *team, struct team_port *port);
- void (*port_enabled)(struct team *team, struct team_port *port);
void (*port_disabled)(struct team *team, struct team_port *port);
};
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 1/7] net: team: Annotate reads and writes for mixed lock accessed values
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
In-Reply-To: <20260402-teaming-driver-internal-v3-0-e8cfdec3b5c2@google.com>
The team_port's "index" and the team's "en_port_count" are read in
the hot transmit path, but are only written to when holding the rtnl
lock.
Use READ_ONCE() for all lockless reads of these values, and use
WRITE_ONCE() for all writes.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- None
Changes in v2:
- None
---
drivers/net/team/team_core.c | 11 ++++++-----
drivers/net/team/team_mode_random.c | 2 +-
include/linux/if_team.h | 4 ++--
3 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c
index 566a5d102c23..becd066279a6 100644
--- a/drivers/net/team/team_core.c
+++ b/drivers/net/team/team_core.c
@@ -938,7 +938,8 @@ static void team_port_enable(struct team *team,
{
if (team_port_enabled(port))
return;
- port->index = team->en_port_count++;
+ WRITE_ONCE(port->index, team->en_port_count);
+ WRITE_ONCE(team->en_port_count, team->en_port_count + 1);
hlist_add_head_rcu(&port->hlist,
team_port_index_hash(team, port->index));
team_adjust_ops(team);
@@ -958,7 +959,7 @@ static void __reconstruct_port_hlist(struct team *team, int rm_index)
for (i = rm_index + 1; i < team->en_port_count; i++) {
port = team_get_port_by_index(team, i);
hlist_del_rcu(&port->hlist);
- port->index--;
+ WRITE_ONCE(port->index, port->index - 1);
hlist_add_head_rcu(&port->hlist,
team_port_index_hash(team, port->index));
}
@@ -973,8 +974,8 @@ static void team_port_disable(struct team *team,
team->ops.port_disabled(team, port);
hlist_del_rcu(&port->hlist);
__reconstruct_port_hlist(team, port->index);
- port->index = -1;
- team->en_port_count--;
+ WRITE_ONCE(port->index, -1);
+ WRITE_ONCE(team->en_port_count, team->en_port_count - 1);
team_queue_override_port_del(team, port);
team_adjust_ops(team);
team_lower_state_changed(port);
@@ -1245,7 +1246,7 @@ static int team_port_add(struct team *team, struct net_device *port_dev,
netif_addr_unlock_bh(dev);
}
- port->index = -1;
+ WRITE_ONCE(port->index, -1);
list_add_tail_rcu(&port->list, &team->port_list);
team_port_enable(team, port);
netdev_compute_master_upper_features(dev, true);
diff --git a/drivers/net/team/team_mode_random.c b/drivers/net/team/team_mode_random.c
index 53d0ce34b8ce..169a7bc865b2 100644
--- a/drivers/net/team/team_mode_random.c
+++ b/drivers/net/team/team_mode_random.c
@@ -16,7 +16,7 @@ static bool rnd_transmit(struct team *team, struct sk_buff *skb)
struct team_port *port;
int port_index;
- port_index = get_random_u32_below(team->en_port_count);
+ port_index = get_random_u32_below(READ_ONCE(team->en_port_count));
port = team_get_port_by_index_rcu(team, port_index);
if (unlikely(!port))
goto drop;
diff --git a/include/linux/if_team.h b/include/linux/if_team.h
index ccb5327de26d..06f4d7400c1e 100644
--- a/include/linux/if_team.h
+++ b/include/linux/if_team.h
@@ -77,7 +77,7 @@ static inline struct team_port *team_port_get_rcu(const struct net_device *dev)
static inline bool team_port_enabled(struct team_port *port)
{
- return port->index != -1;
+ return READ_ONCE(port->index) != -1;
}
static inline bool team_port_txable(struct team_port *port)
@@ -272,7 +272,7 @@ static inline struct team_port *team_get_port_by_index_rcu(struct team *team,
struct hlist_head *head = team_port_index_hash(team, port_index);
hlist_for_each_entry_rcu(port, head, hlist)
- if (port->index == port_index)
+ if (READ_ONCE(port->index) == port_index)
return port;
return NULL;
}
--
2.53.0.1185.g05d4b7b318-goog
^ permalink raw reply related
* [PATCH net-next v3 0/7] Decouple receive and transmit enablement in team driver
From: Marc Harvey @ 2026-04-02 6:24 UTC (permalink / raw)
To: Jiri Pirko, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Marc Harvey
Allow independent control over receive and transmit enablement states
for aggregated ports in the team driver.
The motivation is that IEE 802.3ad LACP "independent control" can't
be implemented for the team driver currently. This was added to the
bonding driver in commit 240fd405528b ("bonding: Add independent
control state machine").
This series also has a few patches that add tests to show that the old
coupled enablement still works and that the new decoupled enablement
works as intended (4, 5, and 7).
There are three patches with small fixes as well, with the goal of
making the final decouplement patch clearer (1, 2, and 3).
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
Changes in v3:
- Patch 5: In test cleanup, kill teamd to fix timeout.
- Link to v2: https://lore.kernel.org/r/20260401-teaming-driver-internal-v2-0-f80c1291727b@google.com
Changes in v2:
- Patch 4 and 5: Fix shellcheck errors and warnings, use iperf3
instead of netcat+pv, fix dependency checking.
- Patch 7: Fix shellcheck errors and warnings, fix dependency
checking.
- Link to v1: https://lore.kernel.org/all/20260331053353.2504254-1-marcharvey@google.com/
---
Marc Harvey (7):
net: team: Annotate reads and writes for mixed lock accessed values
net: team: Remove unused team_mode_op, port_enabled
net: team: Rename port_disabled team mode op to port_tx_disabled
selftests: net: Add tests for failover of team-aggregated ports
selftests: net: Add test for enablement of ports with teamd
net: team: Decouple rx and tx enablement in the team driver
selftests: net: Add tests for team driver decoupled tx and rx control
drivers/net/team/team_core.c | 241 +++++++++++++++++---
drivers/net/team/team_mode_loadbalance.c | 8 +-
drivers/net/team/team_mode_random.c | 4 +-
drivers/net/team/team_mode_roundrobin.c | 2 +-
include/linux/if_team.h | 63 +++---
tools/testing/selftests/drivers/net/team/Makefile | 4 +
tools/testing/selftests/drivers/net/team/config | 4 +
.../drivers/net/team/decoupled_enablement.sh | 249 +++++++++++++++++++++
.../testing/selftests/drivers/net/team/options.sh | 99 +++++++-
.../testing/selftests/drivers/net/team/team_lib.sh | 115 ++++++++++
.../drivers/net/team/teamd_activebackup.sh | 217 ++++++++++++++++++
.../drivers/net/team/transmit_failover.sh | 151 +++++++++++++
tools/testing/selftests/net/lib.sh | 13 ++
13 files changed, 1100 insertions(+), 70 deletions(-)
---
base-commit: cee10a01e286e88e0949979e91231270ca9fdb8e
change-id: 20260401-teaming-driver-internal-83f2f0074d68
Best regards,
--
Marc Harvey <marcharvey@google.com>
^ permalink raw reply
* Re: [PATCH v3 04/15] firmware: qcom: Add a PAS TEE service
From: Sumit Garg @ 2026-04-02 6:23 UTC (permalink / raw)
To: Harshal Dev
Cc: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc, andersson,
konradybcio, robh, krzk+dt, conor+dt, robin.clark, sean, akhilpo,
lumag, abhinav.kumar, jesszhan0024, marijn.suijten, airlied,
simona, vikash.garodia, dikshita.agarwal, bod, mchehab, elder,
andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
linux-kernel, Sumit Garg
In-Reply-To: <000abdb5-a8b4-47c4-860b-5666e650b545@oss.qualcomm.com>
On Mon, Mar 30, 2026 at 04:06:47PM +0530, Harshal Dev wrote:
>
>
> On 3/27/2026 6:40 PM, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > Add support for Peripheral Authentication Service (PAS) driver based
> > on TEE bus with OP-TEE providing the backend PAS service implementation.
> >
> > The TEE PAS service ABI is designed to be extensible with additional API
> > as PTA_QCOM_PAS_CAPABILITIES. This allows to accommodate any future
> > extensions of the PAS service needed while still maintaining backwards
> > compatibility.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/firmware/qcom/Kconfig | 10 +
> > drivers/firmware/qcom/Makefile | 1 +
> > drivers/firmware/qcom/qcom_pas_tee.c | 478 +++++++++++++++++++++++++++
> > 3 files changed, 489 insertions(+)
> > create mode 100644 drivers/firmware/qcom/qcom_pas_tee.c
> >
> [...]
>
> > diff --git a/drivers/firmware/qcom/qcom_pas_tee.c b/drivers/firmware/qcom/qcom_pas_tee.c
> > new file mode 100644
> > index 000000000000..d63122c34f04
> > --- /dev/null
> > +++ b/drivers/firmware/qcom/qcom_pas_tee.c
> > @@ -0,0 +1,478 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/of.h>
> > +#include <linux/firmware/qcom/qcom_pas.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <linux/tee_drv.h>
> > +#include <linux/uuid.h>
> > +
> > +#include "qcom_pas.h"
> > +
> > +/*
> > + * Peripheral Authentication Service (PAS) supported.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + */
> > +#define TA_QCOM_PAS_IS_SUPPORTED 1
> > +
> > +/*
> > + * PAS capabilities.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [out] params[1].value.a: PAS capability flags
> > + */
> > +#define TA_QCOM_PAS_CAPABILITIES 2
> > +
> > +/*
> > + * PAS image initialization.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[1].memref: Loadable firmware metadata
> > + */
> > +#define TA_QCOM_PAS_INIT_IMAGE 3
> > +
> > +/*
> > + * PAS memory setup.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[0].value.b: Relocatable firmware size
> > + * [in] params[1].value.a: 32bit LSB relocatable firmware memory address
> > + * [in] params[1].value.b: 32bit MSB relocatable firmware memory address
> > + */
> > +#define TA_QCOM_PAS_MEM_SETUP 4
> > +
> > +/*
> > + * PAS get resource table.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [inout] params[1].memref: Resource table config
> > + */
> > +#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5
> > +
> > +/*
> > + * PAS image authentication and co-processor reset.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[0].value.b: Firmware size
> > + * [in] params[1].value.a: 32bit LSB firmware memory address
> > + * [in] params[1].value.b: 32bit MSB firmware memory address
> > + * [in] params[2].memref: Optional fw memory space shared/lent
> > + */
> > +#define TA_QCOM_PAS_AUTH_AND_RESET 6
> > +
> > +/*
> > + * PAS co-processor set suspend/resume state.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + * [in] params[0].value.b: Co-processor state identifier
> > + */
> > +#define TA_QCOM_PAS_SET_REMOTE_STATE 7
> > +
> > +/*
> > + * PAS co-processor shutdown.
> > + *
> > + * [in] params[0].value.a: Unique 32bit remote processor identifier
> > + */
> > +#define TA_QCOM_PAS_SHUTDOWN 8
> > +
> > +#define TEE_NUM_PARAMS 4
> > +
> > +/**
> > + * struct qcom_pas_tee_private - PAS service private data
> > + * @dev: PAS service device.
> > + * @ctx: TEE context handler.
> > + * @session_id: PAS TA session identifier.
>
> Nit: Column alignment of comment descriptions.
These are aligned if you look at them after applying the patch-set.
Plain email text isn't good at showing the alignment here.
>
> > + */
> > +struct qcom_pas_tee_private {
> > + struct device *dev;
> > + struct tee_context *ctx;
> > + u32 session_id;
> > +};
> > +
> > +static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_IS_SUPPORTED,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS not supported, pas_id: %d, err: %x\n",
> > + pas_id, inv_arg.ret);
>
> I can see that similar error handling pattern for tee_client_invoke_func() is
> being done by other clients. But it seems that this error log doesn't really convey
> whether we failed before or after entering OPTEE (or some other TEE) for invoking
> the service.
>
> Could be better if we print 'ret' when ret < 0. And print 'inv_arg.ret' when
> inv_arg.ret != 0. So for example, if the param marshalling fails, or some other
> issue is encountered when processing the params in a different back-end TEE
> driver, we could know from the logs.
Okay, I will add print for "ret" as well.
>
> > + return false;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id,
> > + const void *metadata, size_t size,
> > + struct qcom_pas_context *ctx)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_INIT_IMAGE,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
> > + }
> > + };
> > + struct tee_shm *mdata_shm;
> > + u8 *mdata_buf = NULL;
> > + int ret;
> > +
> > + if (!ctx)
> > + return -EINVAL;
> > +
> > + mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size);
>
> Why not move the DEFINE_FREE() above this function so we can use scoped free
> here as well for mdata_shm?
>
> struct tee_shm *mdata_shm __free(shm_free) = ...
mdata_shm gets freed as part of qcom_pas_tee_metadata_release(), so no
automatic free here.
>
> > + if (IS_ERR(mdata_shm)) {
> > + dev_err(dev, "mdata_shm allocation failed\n");
> > + return PTR_ERR(mdata_shm);
> > + }
> > +
> > + mdata_buf = tee_shm_get_va(mdata_shm, 0);
> > + if (IS_ERR(mdata_buf)) {
> > + dev_err(dev, "mdata_buf get VA failed\n");
> > + tee_shm_free(mdata_shm);
> > + return PTR_ERR(mdata_buf);
> > + }
> > + memcpy(mdata_buf, metadata, size);
> > +
> > + param[1].u.memref.shm = mdata_shm;
> > + param[1].u.memref.size = size;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS init image failed, pas_id: %d, err: %x\n",
>
> Nit: We can prefix %x with 0x. "err: 0x%x\n."
Ack
>
> > + pas_id, inv_arg.ret);
> > + tee_shm_free(mdata_shm);
> > + return -EINVAL;
> > + }
> > + ctx->ptr = (void *)mdata_shm;
> > +
> > + return 0;
> > +}
> > +
> > +static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id,
> > + phys_addr_t addr, phys_addr_t size)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_MEM_SETUP,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id,
> > + .u.value.b = size,
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = lower_32_bits(addr),
> > + .u.value.b = upper_32_bits(addr),
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS mem setup failed, pas_id: %d, err: %x\n",
> > + pas_id, inv_arg.ret);
> > + return -EINVAL;
>
> I can see that originally in-case of error, qcom_scm_pas_mem_setup() bubbles
> up the return value from qcom_scm_call(). Perhaps we should do the same as well,
> return ret here instead of hard-coded '-EINVAL' which overrides any useful
> return values returned from the back-end TEE driver.
ret can even be 0 while still error from TEE. Let's rather do following:
return ret ? ret : -EINVAL;
>
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T))
> > +
> > +static void *qcom_pas_tee_get_rsc_table(struct device *dev,
> > + struct qcom_pas_context *ctx,
> > + void *input_rt, size_t input_rt_size,
> > + size_t *output_rt_size)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_GET_RESOURCE_TABLE,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = ctx->pas_id,
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
> > + .u.memref.size = input_rt_size,
> > + }
> > + };
> > + void *rt_buf = NULL;
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS get RT failed, pas_id: %d, err: %x\n",
> > + ctx->pas_id, inv_arg.ret);
> > + return ERR_PTR(-EINVAL);
>
> Same comment here, we could return ERR_PTR(ret) instead of overriding to
> '-EINVAL'.
>
> > + }
> > +
> > + if (param[1].u.memref.size) {
> > + struct tee_shm *rt_shm __free(shm_free) =
> > + tee_shm_alloc_kernel_buf(data->ctx,
> > + param[1].u.memref.size);
> > + void *rt_shm_va;
> > +
> > + if (IS_ERR(rt_shm)) {
> > + dev_err(dev, "rt_shm allocation failed\n");
> > + return rt_shm;
> > + }
> > +
> > + rt_shm_va = tee_shm_get_va(rt_shm, 0);
> > + if (IS_ERR_OR_NULL(rt_shm_va)) {
> > + dev_err(dev, "rt_shm get VA failed\n");
> > + return ERR_PTR(-EINVAL);
>
> Why not just return rt_shm_va? It already encodes the error in the pointer.
Again let me add a null check while returning rt_shm_va.
>
> > + }
> > + memcpy(rt_shm_va, input_rt, input_rt_size);
> > +
> > + param[1].u.memref.shm = rt_shm;
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS get RT failed, pas_id: %d, err: %x\n",
> > + ctx->pas_id, inv_arg.ret);
> > + return ERR_PTR(-EINVAL);
>
> Same comment.
>
> > + }
> > +
> > + if (param[1].u.memref.size) {
>
> Is it possible for param[1].u.memref.size to be 0 after a successful tee_client_invoke_func()?
Yeah it's possible if there is no resource table entry needed for a
particular PAS id.
>
> > + *output_rt_size = param[1].u.memref.size;
> > + rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL);
> > + if (!rt_buf)
> > + return ERR_PTR(-ENOMEM);
> > + }
> > + }
> > +
> > + return rt_buf;
> > +}
> > +
> > +static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id,
> > + phys_addr_t mem_phys, size_t mem_size)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_AUTH_AND_RESET,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id,
> > + .u.value.b = mem_size,
> > + },
> > + [1] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = lower_32_bits(mem_phys),
> > + .u.value.b = upper_32_bits(mem_phys),
> > + },
> > + /* Reserved for fw memory space to be shared or lent */
>
> Can you explain this comment a bit more? Plan is to allow passing a MEM_REF here
> which could be lent/shared to TEE via FFA ABI?
This param is added for future compatibility where PAS firmware is
loaded in arbitrarity allocated memory region by the kernel. Then that
can be shared or lend with TZ for authentication and reset sequence.
Right now the kernel is already loading the PAS firmware is fixed
reserved memory regions, for which this param is unused.
>
> > + [2] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
> > + }
> > + };
> > + int ret;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS auth reset failed, pas_id: %d, err: %x\n",
> > + pas_id, inv_arg.ret);
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id)
>
> I'm guessing once all clients have migrated to PAS, plan is to drop this wrapper?
Yeah, this patch-set doesn't aim to change the kernel client PAS API
contract apart from just renaming APIs. Surely future work can change
the PAS API contract as needed.
>
> > +{
> > + return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0);
> > +}
> > +
> > +static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev,
> > + struct qcom_pas_context *ctx)
> > +{
> > + return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys,
> > + ctx->mem_size);
> > +}
> > +
> > +static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state,
> > + u32 pas_id)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_SET_REMOTE_STATE,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id,
> > + .u.value.b = state,
> > + }
> > + };
> > + int ret;
>
> Nit: Why not ret = 0 initialize and always use ret?
zero init will still be redundant..
>
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS shutdown failed, pas_id: %d, err: %x\n",
> > + pas_id, inv_arg.ret);
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
.. will rather change this to return ret.
> > +}
> > +
> > +static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id)
> > +{
> > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
> > + struct tee_ioctl_invoke_arg inv_arg = {
> > + .func = TA_QCOM_PAS_SHUTDOWN,
> > + .session = data->session_id,
> > + .num_params = TEE_NUM_PARAMS
> > + };
> > + struct tee_param param[4] = {
> > + [0] = {
> > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
> > + .u.value.a = pas_id
> > + }
> > + };
> > + int ret = 0;
> > +
> > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
> > + if (ret < 0 || inv_arg.ret != 0) {
> > + dev_err(dev, "PAS shutdown failed, pas_id: %d, err: %x\n",
> > + pas_id, inv_arg.ret);
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
>
> You could just return 'ret' here. :)
Ack.
>
> > +}
> > +
> > +static void qcom_pas_tee_metadata_release(struct device *dev,
> > + struct qcom_pas_context *ctx)
> > +{
> > + struct tee_shm *mdata_shm = ctx->ptr;
> > +
>
> Nit: Unnecessary extra line.
Extra line is recommended after variables declaration.
>
> > + tee_shm_free(mdata_shm);
> > +}
> > +
>
> [...]
>
> > +
> > +module_tee_client_driver(optee_pas_tee_driver);
> > +
> > +MODULE_LICENSE("GPL");
> > +MODULE_DESCRIPTION("TEE bus based Qualcomm PAS driver");
>
> Since this is a tee_client driver, isn't it self-explanatary that it's TEE bus based?
>
I can rather say:
MODULE_DESCRIPTION("Qualcomm PAS TEE driver");
-Sumit
^ permalink raw reply
* Re: [PATCH net-next v2] ppp: update Kconfig help message
From: Jaco Kroon @ 2026-04-02 6:04 UTC (permalink / raw)
To: Qingfang Deng, linux-ppp, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Julian Braha,
Eric Biggers, netdev, linux-kernel
Cc: Paul Mackerras, Dianne Skoll, James Carlson
In-Reply-To: <20260402050053.144250-1-qingfang.deng@linux.dev>
Hi,
On 2026/04/02 07:00, Qingfang Deng wrote:
> Both links of the PPPoE section are no longer valid, and the CVS version
> is no longer relevant.
>
> - Replace the TLDP URL with the pppd project homepage.
> - Update pppd version requirement for PPPoE.
> - Update RP-PPPoE project homepage, and clarify that it's only needed
> for server mode.
>
> Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev>
FWIW: Acked-by: Jaco Kroon <jaco@uls.co.za>
Looks good to me.
> ---
> v2:
> - replace TLDP URL with pppd homepage
> - update pppd version requirement
> - document that rp-pppoe is only required for server
> https://lore.kernel.org/netdev/20260331033303.5664-1-dqfext@gmail.com/
>
> drivers/net/ppp/Kconfig | 16 +++++++---------
> 1 file changed, 7 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/net/ppp/Kconfig b/drivers/net/ppp/Kconfig
> index f57fba84fe55..753354b4e36c 100644
> --- a/drivers/net/ppp/Kconfig
> +++ b/drivers/net/ppp/Kconfig
> @@ -13,10 +13,9 @@ config PPP
> otherwise you can't use it; most Internet access providers these
> days support PPP rather than SLIP.
>
> - To use PPP, you need an additional program called pppd as described
> - in the PPP-HOWTO, available at
> - <http://www.tldp.org/docs.html#howto>. Make sure that you have
> - the version of pppd recommended in <file:Documentation/Changes>.
> + To use PPP, you need an additional program called pppd, available at
> + <https://ppp.samba.org>. Make sure that you have the version of pppd
> + recommended in <file:Documentation/Changes>.
> The PPP option enlarges your kernel by about 16 KB.
>
> There are actually two versions of PPP: the traditional PPP for
> @@ -116,11 +115,10 @@ config PPPOE
> help
> Support for PPP over Ethernet.
>
> - This driver requires the latest version of pppd from the CVS
> - repository at cvs.samba.org. Alternatively, see the
> - RoaringPenguin package (<http://www.roaringpenguin.com/pppoe>)
> - which contains instruction on how to use this driver (under
> - the heading "Kernel mode PPPoE").
> + To work in client mode, pppd version 2.4.2 or later is required.
> + To work in server mode, the pppoe-server command from the RP-PPPoE
> + package is also required, available at
> + <https://dianne.skoll.ca/projects/rp-pppoe/>.
>
> choice
> prompt "Number of PPPoE hash bits"
^ permalink raw reply
* Re: [PATCH] udp_bpf: fix use-after-free in udp_bpf_recvmsg()
From: Jiayuan Chen @ 2026-04-02 6:02 UTC (permalink / raw)
To: Deepanshu Kartikey, john.fastabend, jakub, davem, dsahern,
edumazet, kuba, pabeni, horms, Kuniyuki Iwashima
Cc: ast, cong.wang, netdev, bpf, linux-kernel,
syzbot+431f9a9e3f5227fbb904
In-Reply-To: <20260402050928.32946-1-kartikey406@gmail.com>
On 4/2/26 1:09 PM, Deepanshu Kartikey wrote:
> udp_bpf_recvmsg() calls sk_msg_recvmsg() without holding lock_sock(),
> unlike tcp_bpf_recvmsg() which properly acquires lock_sock() before
> calling __sk_msg_recvmsg(). This allows concurrent tasks to race inside
> sk_msg_recvmsg() on the same psock ingress queue, where one task can
> free msg_rx via kfree_sk_msg() while another task is still reading it
> via sk_msg_elem(), causing a slab-use-after-free.
>
> Fix this by adding lock_sock()/release_sock() around the sk_msg_recvmsg()
> path in udp_bpf_recvmsg(), consistent with tcp_bpf_recvmsg(). Also make
> udp_msg_wait_data() release lock_sock() before sleeping and reacquire it
> after waking, so it can be called with the socket lock held, consistent
> with how tcp_msg_wait_data() uses sk_wait_event() which does the same
> internally.
>
> Note: syzbot testing shows a separate pre-existing warning:
> sk->sk_forward_alloc
> WARNING: net/ipv4/af_inet.c:162 inet_sock_destruct
> This warning triggers from the idle CPU path (pv_native_safe_halt)
> and is unrelated to this patch. It appears to be a pre-existing
> memory accounting issue in the UDP sockmap path that requires
> separate investigation.
>
>
> Reported-by: syzbot+431f9a9e3f5227fbb904@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=431f9a9e3f5227fbb904
> Fixes: 1f5be6b3b063 ("udp: Implement udp_bpf_recvmsg() for sockmap")
> Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
> ---
> net/ipv4/udp_bpf.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/net/ipv4/udp_bpf.c b/net/ipv4/udp_bpf.c
> index 9f33b07b1481..f924b255cee6 100644
> --- a/net/ipv4/udp_bpf.c
> +++ b/net/ipv4/udp_bpf.c
> @@ -50,7 +50,9 @@ static int udp_msg_wait_data(struct sock *sk, struct sk_psock *psock,
> sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);
> ret = udp_msg_has_data(sk, psock);
> if (!ret) {
> + release_sock(sk);
> wait_woken(&wait, TASK_INTERRUPTIBLE, timeo);
> + lock_sock(sk);
> ret = udp_msg_has_data(sk, psock);
> }
> sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);
> @@ -79,6 +81,7 @@ static int udp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
> goto out;
> }
>
> + lock_sock(sk);
> msg_bytes_ready:
> copied = sk_msg_recvmsg(sk, psock, msg, len, flags);
> if (!copied) {
> @@ -90,12 +93,14 @@ static int udp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
> if (data) {
> if (psock_has_data(psock))
> goto msg_bytes_ready;
> + release_sock(sk);
> ret = sk_udp_recvmsg(sk, msg, len, flags);
> goto out;
> }
> copied = -EAGAIN;
> }
> ret = copied;
> + release_sock(sk);
> out:
> sk_psock_put(sk, psock);
> return ret;
Kuniyuki is already working on this. Please see the
existing discussion.
https://lore.kernel.org/bpf/20260221233234.3814768-4-kuniyu@google.com/
^ permalink raw reply
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