* [PATCH net v1] net: skb: fix cross-cache free of KFENCE-allocated skb head
From: Jiayuan Chen @ 2026-04-02 3:31 UTC (permalink / raw)
To: netdev
Cc: Jiayuan Chen, Antonius, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jason Xing,
Kuniyuki Iwashima, Michal Luczaj, Mina Almasry, Eric Biggers,
Toke Høiland-Jørgensen, Soheil Hassas Yeganeh,
Alexander Duyck, linux-kernel, bpf
SKB_SMALL_HEAD_CACHE_SIZE is intentionally set to a non-power-of-2
value (e.g. 704 on x86_64) to avoid collisions with generic kmalloc
bucket sizes. This ensures that skb_kfree_head() can reliably use
skb_end_offset to distinguish skb heads allocated from
skb_small_head_cache vs. generic kmalloc caches.
However, when KFENCE is enabled, kfence_ksize() returns the exact
requested allocation size instead of the slab bucket size. If a caller
(e.g. bpf_test_init) allocates skb head data via kzalloc() and the
requested size happens to equal SKB_SMALL_HEAD_CACHE_SIZE, then
slab_build_skb() → ksize() returns that exact value. After subtracting
skb_shared_info overhead, skb_end_offset ends up matching
SKB_SMALL_HEAD_HEADROOM, causing skb_kfree_head() to incorrectly free
the object to skb_small_head_cache instead of back to the original
kmalloc cache, resulting in a slab cross-cache free:
kmem_cache_free(skbuff_small_head): Wrong slab cache. Expected
skbuff_small_head but got kmalloc-1k
Fix this by adding an is_kfence_address() check in skb_kfree_head().
When the head is a KFENCE object, we skip the kmem_cache_free() path
and fall through to kfree(), which correctly handles KFENCE objects
via kfence_free(). The check compiles away when CONFIG_KFENCE is
disabled.
Fixes: bf9f1baa279f ("net: add dedicated kmem_cache for typical/small skb->head")
Reported-by: Antonius <antonius@bluedragonsec.com>
Closes: https://lore.kernel.org/netdev/CAK8a0jxC5L5N7hq-DT2_NhUyjBxrPocoiDazzsBk4TGgT1r4-A@mail.gmail.com/
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
net/core/skbuff.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 0e217041958a..87cecd40381b 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -91,6 +91,7 @@
#include <linux/user_namespace.h>
#include <linux/indirect_call_wrapper.h>
#include <linux/textsearch.h>
+#include <linux/kfence.h>
#include "dev.h"
#include "devmem.h"
@@ -1083,7 +1084,8 @@ static int skb_pp_frag_ref(struct sk_buff *skb)
static void skb_kfree_head(void *head, unsigned int end_offset)
{
- if (end_offset == SKB_SMALL_HEAD_HEADROOM)
+ if (end_offset == SKB_SMALL_HEAD_HEADROOM &&
+ !is_kfence_address(head))
kmem_cache_free(net_hotdata.skb_small_head_cache, head);
else
kfree(head);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net-next v5 0/2] r8152: Add support for the RTL8157 5Gbit USB Ethernet chip
From: Jakub Kicinski @ 2026-04-02 3:37 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
linux-usb, netdev, linux-kernel
In-Reply-To: <20260331-rtl8157_next-v5-0-deb3095f8380@birger-koblitz.de>
On Tue, 31 Mar 2026 17:55:52 +0200 Birger Koblitz wrote:
> Add support for the RTL8157, which is a 5GBit USB-Ethernet adapter
> chip in the RTL815x family of chips.
>
> The RTL8157 uses a different frame descriptor format, and different
> SRAM/ADV access methods, plus offers 5GBit/s Ethernet, so support for these
> features is added in addition to chip initialization and configuration.
This version does not seem to apply to net-next.
Please make sure you base it on net-next not linux-next.
^ permalink raw reply
* Re: [PATCH net-next v4 00/14] net: sleepable ndo_set_rx_mode
From: Jakub Kicinski @ 2026-04-02 3:38 UTC (permalink / raw)
To: Stanislav Fomichev; +Cc: netdev, davem, edumazet, pabeni
In-Reply-To: <20260401011914.1716692-1-sdf@fomichev.me>
On Tue, 31 Mar 2026 18:19:00 -0700 Stanislav Fomichev wrote:
> sleepable ndo_set_rx_mode
Appears not to be "buildable" on 32bit x86 tho..
--
pw-bot: cr
^ permalink raw reply
* [PATCH v4 net-next] net: advance skb_defer_disable_key check in napi_consume_skb
From: Jason Xing @ 2026-04-02 3:41 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, horms; +Cc: netdev, Jason Xing
From: Jason Xing <kernelxing@tencent.com>
When net.core.skb_defer_max is adjusted to zero, napi_consume_skb()
shouldn't go into that deeper in skb_attempt_defer_free() because it adds
an additional pair of local_bh_enable/disable() which is evidently not
needed. Advancing the check of the static key saves more cycles and
benefits non defer case.
Signed-off-by: Jason Xing <kernelxing@tencent.com>
---
v4
Link: https://lore.kernel.org/all/20260401033211.44463-1-kerneljasonxing@gmail.com/
1. add the missing header file.
Since a new sysctl might not be preferrable, I deicded to apply the
current minimum patch to handle it. If in the future we receive some
reports about this point, we will then consider it one more time.
v3
Link: https://lore.kernel.org/all/20260327153347.98647-1-kerneljasonxing@gmail.com/
1. use a simpler approach to avoid adding a new sysctl. This is the
minimum change to improvement defer_max 0 case.
V2
Link: https://lore.kernel.org/all/20260326144249.97213-1-kerneljasonxing@gmail.com/
1. reuse proc_do_static_key() (Eric)
2. add doc (Stan)
---
net/core/skbuff.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 3d6978dd0aa8..4045d7c484a1 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -94,6 +94,7 @@
#include "dev.h"
#include "devmem.h"
+#include "net-sysfs.h"
#include "netmem_priv.h"
#include "sock_destructor.h"
@@ -1519,7 +1520,8 @@ void napi_consume_skb(struct sk_buff *skb, int budget)
DEBUG_NET_WARN_ON_ONCE(!in_softirq());
- if (skb->alloc_cpu != smp_processor_id() && !skb_shared(skb)) {
+ if (!static_branch_unlikely(&skb_defer_disable_key) &&
+ skb->alloc_cpu != smp_processor_id() && !skb_shared(skb)) {
skb_release_head_state(skb);
return skb_attempt_defer_free(skb);
}
--
2.41.3
^ permalink raw reply related
* Re: [net-next PATCH 04/10] bitfield.h: add FIELD_WIDTH()
From: Luiz Angelo Daros de Luca @ 2026-04-02 4:00 UTC (permalink / raw)
To: Yury Norov
Cc: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Linus Walleij,
Alvin Šipraga, Yury Norov, Rasmus Villemoes, Russell King,
netdev, linux-kernel
In-Reply-To: <acx_1dOF3EjvGt4B@yury>
> > +/**
> > + * FIELD_WIDTH() - return the width of a bitfield
> > + * @_mask: shifted mask defining the field's length and position
> > + *
> > + * Returns the number of contiguous bits covered by @_mask.
> > + * This corresponds to the bit width of FIELD_MAX(@_mask).
> > + */
> > +#define FIELD_WIDTH(_mask) \
>
> Please no underscored names unless necessary.
I used _mask to maintain consistency with the existing public macros
in this file, such as FIELD_GET, FIELD_PREP, FIELD_MAX, and FIELD_FIT.
All of them use the underscore prefix for parameters. Should I diverge
from them?
> > + ({ \
> > + __BF_FIELD_CHECK(_mask, 0ULL, 0ULL, "FIELD_WIDTH: "); \
> > + __bf_shf(~FIELD_MAX(_mask)); \
> > + })
>
> I believe, this should be:
>
> #define FIELD_WIDTH(mask) ({ \
> __BF_FIELD_CHECK_MASK(mask, 0ULL, "FIELD_WIDTH: "); \
> HWEIGHT(mask); \
> })
HWEIGHT() is indeed much cleaner. However, to keep bitfield.h
self-contained and avoid adding more includes, I'll try
__builtin_popcountll() in a similar way __builtin_ffsll is already
used.
I also noticed the suggestion to use __BF_FIELD_CHECK_MASK instead of
__BF_FIELD_CHECK. Both FIELD_MAX and FIELD_FIT currently use the full
__BF_FIELD_CHECK with 0ULL as a dummy register to ensure the mask fits
within the header's supported limits. If __BF_FIELD_CHECK_MASK is
preferred for FIELD_WIDTH, I can certainly use it, but should we also
update FIELD_MAX and FIELD_FIT for consistency?
I intend to send a v2 with the following implementation:
#define __bf_shf(x) (__builtin_ffsll(x) - 1)
+#define __bf_hweight(x) __builtin_popcountll(x)
#define __scalar_type_to_unsigned_cases(type) \
unsigned type: (unsigned type)0, \
@@ -111,6 +112,19 @@
(typeof(_mask))((_mask) >> __bf_shf(_mask)); \
})
+/**
+ * FIELD_WIDTH() - return the width of a bitfield
+ * @_mask: shifted mask defining the field's length and position
+ *
+ * Returns the number of contiguous bits covered by @_mask.
+ * This corresponds to the bit width of FIELD_MAX(@_mask).
+ */
+#define FIELD_WIDTH(_mask) \
+ ({ \
+ __BF_FIELD_CHECK_MASK(_mask, 0ULL, "FIELD_WIDTH: "); \
+ (typeof(_mask))__bf_hweight(_mask); \
+ })
+
Regards,
Luiz
^ permalink raw reply
* Re: [PATCH net v1] net: skb: fix cross-cache free of KFENCE-allocated skb head
From: Eric Dumazet @ 2026-04-02 4:01 UTC (permalink / raw)
To: Jiayuan Chen
Cc: netdev, Antonius, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jason Xing, Kuniyuki Iwashima, Michal Luczaj,
Mina Almasry, Eric Biggers, Toke Høiland-Jørgensen,
Soheil Hassas Yeganeh, Alexander Duyck, linux-kernel, bpf
In-Reply-To: <20260402033138.388574-1-jiayuan.chen@linux.dev>
On Wed, Apr 1, 2026 at 8:32 PM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
> SKB_SMALL_HEAD_CACHE_SIZE is intentionally set to a non-power-of-2
> value (e.g. 704 on x86_64) to avoid collisions with generic kmalloc
> bucket sizes. This ensures that skb_kfree_head() can reliably use
> skb_end_offset to distinguish skb heads allocated from
> skb_small_head_cache vs. generic kmalloc caches.
>
> However, when KFENCE is enabled, kfence_ksize() returns the exact
> requested allocation size instead of the slab bucket size. If a caller
> (e.g. bpf_test_init) allocates skb head data via kzalloc() and the
> requested size happens to equal SKB_SMALL_HEAD_CACHE_SIZE, then
> slab_build_skb() → ksize() returns that exact value. After subtracting
> skb_shared_info overhead, skb_end_offset ends up matching
> SKB_SMALL_HEAD_HEADROOM, causing skb_kfree_head() to incorrectly free
> the object to skb_small_head_cache instead of back to the original
> kmalloc cache, resulting in a slab cross-cache free:
>
> kmem_cache_free(skbuff_small_head): Wrong slab cache. Expected
> skbuff_small_head but got kmalloc-1k
>
> Fix this by adding an is_kfence_address() check in skb_kfree_head().
> When the head is a KFENCE object, we skip the kmem_cache_free() path
> and fall through to kfree(), which correctly handles KFENCE objects
> via kfence_free(). The check compiles away when CONFIG_KFENCE is
> disabled.
>
> Fixes: bf9f1baa279f ("net: add dedicated kmem_cache for typical/small skb->head")
> Reported-by: Antonius <antonius@bluedragonsec.com>
> Closes: https://lore.kernel.org/netdev/CAK8a0jxC5L5N7hq-DT2_NhUyjBxrPocoiDazzsBk4TGgT1r4-A@mail.gmail.com/
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
> net/core/skbuff.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> index 0e217041958a..87cecd40381b 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c
> @@ -91,6 +91,7 @@
> #include <linux/user_namespace.h>
> #include <linux/indirect_call_wrapper.h>
> #include <linux/textsearch.h>
> +#include <linux/kfence.h>
>
> #include "dev.h"
> #include "devmem.h"
> @@ -1083,7 +1084,8 @@ static int skb_pp_frag_ref(struct sk_buff *skb)
>
> static void skb_kfree_head(void *head, unsigned int end_offset)
> {
> - if (end_offset == SKB_SMALL_HEAD_HEADROOM)
> + if (end_offset == SKB_SMALL_HEAD_HEADROOM &&
> + !is_kfence_address(head))
> kmem_cache_free(net_hotdata.skb_small_head_cache, head);
> else
> kfree(head);
Oh well, time to simply call kfree(head) ?
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 3d6978dd0aa83f63984b994359d0c914c6427a00..6b35aed23b8936409f6b0d4ee8d378f726c569c6
100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -1072,10 +1072,7 @@ static int skb_pp_frag_ref(struct sk_buff *skb)
static void skb_kfree_head(void *head, unsigned int end_offset)
{
- if (end_offset == SKB_SMALL_HEAD_HEADROOM)
- kmem_cache_free(net_hotdata.skb_small_head_cache, head);
- else
- kfree(head);
+ kfree(head);
}
static void skb_free_head(struct sk_buff *skb)
^ permalink raw reply
* Re: [PATCH net v1] net: skb: fix cross-cache free of KFENCE-allocated skb head
From: Jiayuan Chen @ 2026-04-02 4:15 UTC (permalink / raw)
To: Eric Dumazet
Cc: netdev, Antonius, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jason Xing, Kuniyuki Iwashima, Michal Luczaj,
Mina Almasry, Eric Biggers, Toke Høiland-Jørgensen,
Soheil Hassas Yeganeh, Alexander Duyck, linux-kernel, bpf
In-Reply-To: <CANn89iLdRZd-qO4tRpSGamHpggh=r5vM4xes5npYCnkwcNG89A@mail.gmail.com>
On 4/2/26 12:01 PM, Eric Dumazet wrote:
> On Wed, Apr 1, 2026 at 8:32 PM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>> SKB_SMALL_HEAD_CACHE_SIZE is intentionally set to a non-power-of-2
>> value (e.g. 704 on x86_64) to avoid collisions with generic kmalloc
>> bucket sizes. This ensures that skb_kfree_head() can reliably use
>> skb_end_offset to distinguish skb heads allocated from
>> skb_small_head_cache vs. generic kmalloc caches.
>>
>> However, when KFENCE is enabled, kfence_ksize() returns the exact
>> requested allocation size instead of the slab bucket size. If a caller
>> (e.g. bpf_test_init) allocates skb head data via kzalloc() and the
>> requested size happens to equal SKB_SMALL_HEAD_CACHE_SIZE, then
>> slab_build_skb() → ksize() returns that exact value. After subtracting
>> skb_shared_info overhead, skb_end_offset ends up matching
>> SKB_SMALL_HEAD_HEADROOM, causing skb_kfree_head() to incorrectly free
>> the object to skb_small_head_cache instead of back to the original
>> kmalloc cache, resulting in a slab cross-cache free:
>>
>> kmem_cache_free(skbuff_small_head): Wrong slab cache. Expected
>> skbuff_small_head but got kmalloc-1k
>>
>> Fix this by adding an is_kfence_address() check in skb_kfree_head().
>> When the head is a KFENCE object, we skip the kmem_cache_free() path
>> and fall through to kfree(), which correctly handles KFENCE objects
>> via kfence_free(). The check compiles away when CONFIG_KFENCE is
>> disabled.
>>
>> Fixes: bf9f1baa279f ("net: add dedicated kmem_cache for typical/small skb->head")
>> Reported-by: Antonius <antonius@bluedragonsec.com>
>> Closes: https://lore.kernel.org/netdev/CAK8a0jxC5L5N7hq-DT2_NhUyjBxrPocoiDazzsBk4TGgT1r4-A@mail.gmail.com/
>> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
>> ---
>> net/core/skbuff.c | 4 +++-
>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>
>> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
>> index 0e217041958a..87cecd40381b 100644
>> --- a/net/core/skbuff.c
>> +++ b/net/core/skbuff.c
>> @@ -91,6 +91,7 @@
>> #include <linux/user_namespace.h>
>> #include <linux/indirect_call_wrapper.h>
>> #include <linux/textsearch.h>
>> +#include <linux/kfence.h>
>>
>> #include "dev.h"
>> #include "devmem.h"
>> @@ -1083,7 +1084,8 @@ static int skb_pp_frag_ref(struct sk_buff *skb)
>>
>> static void skb_kfree_head(void *head, unsigned int end_offset)
>> {
>> - if (end_offset == SKB_SMALL_HEAD_HEADROOM)
>> + if (end_offset == SKB_SMALL_HEAD_HEADROOM &&
>> + !is_kfence_address(head))
>> kmem_cache_free(net_hotdata.skb_small_head_cache, head);
>> else
>> kfree(head);
> Oh well, time to simply call kfree(head) ?
>
> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> index 3d6978dd0aa83f63984b994359d0c914c6427a00..6b35aed23b8936409f6b0d4ee8d378f726c569c6
> 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c
> @@ -1072,10 +1072,7 @@ static int skb_pp_frag_ref(struct sk_buff *skb)
>
> static void skb_kfree_head(void *head, unsigned int end_offset)
> {
> - if (end_offset == SKB_SMALL_HEAD_HEADROOM)
> - kmem_cache_free(net_hotdata.skb_small_head_cache, head);
> - else
> - kfree(head);
> + kfree(head);
> }
>
> static void skb_free_head(struct sk_buff *skb)
If we no longer care about the cost of accessing a struct page in the
free path, which the original commit was trying to avoid, this is
indeed the simplest fix — kfree() correctly handles objects via
virt_to_slab.
^ permalink raw reply
* Re: [PATCH net] ipvs: fix MTU check for GSO packets in tunnel mode
From: Julian Anastasov @ 2026-04-02 4:11 UTC (permalink / raw)
To: Yingnan Zhang
Cc: horms, pablo, fw, phil, davem, edumazet, kuba, pabeni, netdev,
lvs-devel, netfilter-devel, coreteam, linux-kernel
In-Reply-To: <tencent_4A3E1C339C75D359093BE4F08648AFAA6009@qq.com>
Hello,
On Wed, 1 Apr 2026, Yingnan Zhang wrote:
> Currently, IPVS skips MTU checks for GSO packets by excluding them with
> the !skb_is_gso(skb) condition. This creates problems when IPVS tunnel
> mode encapsulates GSO packets with IPIP headers.
>
> The issue manifests in two ways:
>
> 1. MTU violation after encapsulation:
> When a GSO packet passes through IPVS tunnel mode, the original MTU
> check is bypassed. After adding the IPIP tunnel header, the packet
> size may exceed the outgoing interface MTU, leading to unexpected
> fragmentation at the IP layer.
>
> 2. Fragmentation with problematic IP IDs:
> When net.ipv4.vs.pmtu_disc=1 and a GSO packet with multiple segments
> is fragmented after encapsulation, each segment gets a sequentially
> incremented IP ID (0, 1, 2, ...). This happens because:
>
> a) The GSO packet bypasses MTU check and gets encapsulated
> b) At __ip_finish_output, the oversized GSO packet is split into
> separate SKBs (one per segment), with IP IDs incrementing
> c) Each SKB is then fragmented again based on the actual MTU
>
> This sequential IP ID allocation differs from the expected behavior
> and can cause issues with fragment reassembly and packet tracking.
>
> Fix this by removing the GSO packet exception from the MTU check and
> properly validating GSO packets using skb_gso_validate_network_len().
> This function correctly validates whether the GSO segments will fit
> within the MTU after segmentation. If validation fails, send an ICMP
> Fragmentation Needed message to enable proper PMTU discovery.
>
> Fixes: 4cdd34084d53 ("netfilter: nf_conntrack_ipv6: improve fragmentation handling")
> Signed-off-by: Yingnan Zhang <342144303@qq.com>
> ---
> net/netfilter/ipvs/ip_vs_xmit.c | 9 ++++++++-
> 1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
> index 3601eb86d..82f2e7a32 100644
> --- a/net/netfilter/ipvs/ip_vs_xmit.c
> +++ b/net/netfilter/ipvs/ip_vs_xmit.c
> @@ -232,8 +232,15 @@ static inline bool ensure_mtu_is_adequate(struct netns_ipvs *ipvs, int skb_af,
> return true;
>
> if (unlikely(ip_hdr(skb)->frag_off & htons(IP_DF) &&
> - skb->len > mtu && !skb_is_gso(skb) &&
> + skb->len > mtu &&
> !ip_vs_iph_icmp(ipvsh))) {
> + if (skb_is_gso(skb)) {
> + if (skb_gso_validate_network_len(skb, mtu))
> + return true;
> + icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
> + IP_VS_DBG(1, "frag needed for %pI4\n", &ip_hdr(skb)->saddr);
> + return false;
> + }
Also, as we duplicate code, this can be reorganized as follows:
if (unlikely(ip_hdr(skb)->frag_off & htons(IP_DF) &&
- skb->len > mtu && !skb_is_gso(skb) &&
- !ip_vs_iph_icmp(ipvsh))) {
+ skb->len > mtu && !ip_vs_iph_icmp(ipvsh) &&
+ !(skb_is_gso(skb) &&
+ skb_gso_validate_network_len(skb, mtu)))) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(mtu));
IP_VS_DBG(1, "frag needed for %pI4\n",
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply
* [PATCH] nfc: pn533: allocate rx skb before consuming bytes
From: Pengpeng Hou @ 2026-04-02 4:21 UTC (permalink / raw)
To: netdev
Cc: Lars Poeschel, Duoming Zhou, Rikard Falkeborn, linux-kernel,
pengpeng, stable
pn532_receive_buf() reports the number of accepted bytes to the serdev
core. The current code consumes bytes into recv_skb and may already
hand a complete frame to pn533_recv_frame() before allocating a fresh
receive buffer.
If that alloc_skb() fails, the callback returns 0 even though it has
already consumed bytes, and it leaves recv_skb as NULL for the next
receive callback. That breaks the receive_buf() accounting contract and
can also lead to a NULL dereference on the next skb_put_u8().
Allocate the receive skb lazily before consuming the next byte instead.
If allocation fails, return the number of bytes already accepted.
Fixes: c656aa4c27b1 ("nfc: pn533: add UART phy driver")
Cc: stable@vger.kernel.org
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/nfc/pn533/uart.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/nfc/pn533/uart.c b/drivers/nfc/pn533/uart.c
index 6d2f520a5bc8..412d341602aa 100644
--- a/drivers/nfc/pn533/uart.c
+++ b/drivers/nfc/pn533/uart.c
@@ -211,14 +211,19 @@ static size_t pn532_receive_buf(struct serdev_device *serdev,
timer_delete(&dev->cmd_timeout);
for (i = 0; i < count; i++) {
+ if (!dev->recv_skb) {
+ dev->recv_skb = alloc_skb(PN532_UART_SKB_BUFF_LEN,
+ GFP_KERNEL);
+ if (!dev->recv_skb)
+ return i;
+ }
+
skb_put_u8(dev->recv_skb, *data++);
if (!pn532_uart_rx_is_frame(dev->recv_skb))
continue;
pn533_recv_frame(dev->priv, dev->recv_skb, 0);
- dev->recv_skb = alloc_skb(PN532_UART_SKB_BUFF_LEN, GFP_KERNEL);
- if (!dev->recv_skb)
- return 0;
+ dev->recv_skb = NULL;
}
return i;
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH] nfc: s3fwrn5: allocate rx skb before consuming bytes
From: Pengpeng Hou @ 2026-04-02 4:21 UTC (permalink / raw)
To: Krzysztof Kozlowski; +Cc: Bongsu Jeon, netdev, linux-kernel, pengpeng, stable
s3fwrn82_uart_read() reports the number of accepted bytes to the serdev
core. The current code consumes bytes into recv_skb and may already
deliver a complete frame before allocating a fresh receive buffer.
If that alloc_skb() fails, the callback returns 0 even though it has
already consumed bytes, and it leaves recv_skb as NULL for the next
receive callback. That breaks the receive_buf() accounting contract and
can also lead to a NULL dereference on the next skb_put_u8().
Allocate the receive skb lazily before consuming the next byte instead.
If allocation fails, return the number of bytes already accepted.
Fixes: 3f52c2cb7e3a ("nfc: s3fwrn5: Support a UART interface")
Cc: stable@vger.kernel.org
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/nfc/s3fwrn5/uart.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c
index 9c09c10c2a46..4ee481bd7e96 100644
--- a/drivers/nfc/s3fwrn5/uart.c
+++ b/drivers/nfc/s3fwrn5/uart.c
@@ -58,6 +58,12 @@ static size_t s3fwrn82_uart_read(struct serdev_device *serdev,
size_t i;
for (i = 0; i < count; i++) {
+ if (!phy->recv_skb) {
+ phy->recv_skb = alloc_skb(NCI_SKB_BUFF_LEN, GFP_KERNEL);
+ if (!phy->recv_skb)
+ return i;
+ }
+
skb_put_u8(phy->recv_skb, *data++);
if (phy->recv_skb->len < S3FWRN82_NCI_HEADER)
@@ -69,9 +75,7 @@ static size_t s3fwrn82_uart_read(struct serdev_device *serdev,
s3fwrn5_recv_frame(phy->common.ndev, phy->recv_skb,
phy->common.mode);
- phy->recv_skb = alloc_skb(NCI_SKB_BUFF_LEN, GFP_KERNEL);
- if (!phy->recv_skb)
- return 0;
+ phy->recv_skb = NULL;
}
return i;
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH] vsock: avoid timeout for non-blocking accept() with empty backlog
From: Laurence Rowe @ 2026-04-02 4:46 UTC (permalink / raw)
To: Stefano Garzarella; +Cc: virtualization, netdev, Laurence Rowe
A common pattern in epoll network servers is to eagerly accept all
pending connections from the non-blocking listening socket after
epoll_wait indicates the socket is ready by calling accept in a loop
until EAGAIN is returned indicating that the backlog is empty.
Scheduling a timeout for a non-blocking accept with an empty backlog
meant AF_VSOCK sockets used by epoll network servers incurred hundreds
of microseconds of additional latency per accept loop compared to
AF_INET or AF_UNIX sockets.
Signed-off-by: Laurence Rowe <laurencerowe@gmail.com>
---
This fixes the observed issue for me:
1. With loopback vsock on the host running Linux v6.19.10 built with
config-6.17.0-19-generic from Ubuntu 24.04 and make olddefconfig.
2. With Firecracker guests with current torvalds/master, v6.19.10, and
amazonlinux/microvm-kernel-6.1.166-24.303.amzn2023 used in Firecracker
CI and examples. (Firecracker guest vsocks are unix sockets on the host
side so this fix works there with just a fixed guest kernel.)
I struggled to build a generic 6.1.166 kernel that worked as a
Firecracker guest but the patch applies (conflict due to change of
`flags` to `arg->flags` in surrounding context) so I believe it should
work for generic v6.1.166 kernel.
Alternatively a minimal version of this fix is to just wrap the
`schedule_timeout` in an `if (timeout != 0)` but that leaves an
unnecessary additional `lock_sock` call.
There are ftrace's and reproduction tools at:
https://github.com/lrowe/linux-vsock-accept-timeout-investigation
---
net/vmw_vsock/af_vsock.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 2f7d94d682..483889b6d8 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -1850,11 +1850,11 @@ static int vsock_accept(struct socket *sock, struct socket *newsock,
* created upon connection establishment.
*/
timeout = sock_rcvtimeo(listener, arg->flags & O_NONBLOCK);
- prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE);
while ((connected = vsock_dequeue_accept(listener)) == NULL &&
- listener->sk_err == 0) {
+ listener->sk_err == 0 && timeout != 0) {
release_sock(listener);
+ prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE);
timeout = schedule_timeout(timeout);
finish_wait(sk_sleep(listener), &wait);
lock_sock(listener);
@@ -1862,17 +1862,15 @@ static int vsock_accept(struct socket *sock, struct socket *newsock,
if (signal_pending(current)) {
err = sock_intr_errno(timeout);
goto out;
- } else if (timeout == 0) {
- err = -EAGAIN;
- goto out;
}
-
- prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE);
}
- finish_wait(sk_sleep(listener), &wait);
- if (listener->sk_err)
+ if (listener->sk_err) {
err = -listener->sk_err;
+ } else if (timeout == 0 && connected == NULL) {
+ err = -EAGAIN;
+ goto out;
+ }
if (connected) {
sk_acceptq_removed(listener);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v2] ppp: update Kconfig help message
From: Qingfang Deng @ 2026-04-02 5:00 UTC (permalink / raw)
To: linux-ppp, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Qingfang Deng, Julian Braha,
Eric Biggers, netdev, linux-kernel
Cc: Paul Mackerras, Dianne Skoll, Jaco Kroon, James Carlson
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>
---
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"
--
2.43.0
^ permalink raw reply related
* Re: [RFC PATCH 3/4] nfs: make nfs_page pin-aware
From: Christoph Hellwig @ 2026-04-02 5:04 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: trond.myklebust, anna, davem, kuba, edumazet, pabeni, chuck.lever,
jlayton, tom, okorniev, neil, dai.ngo, linux-nfs, netdev
In-Reply-To: <20260401194501.2269200-4-praan@google.com>
This conversion really should go first as it is badly needed independent
of any P2P support. And I wonder if it should go further - currently
the NFS I/O code is using folios for buffered I/O, but pages for direct
I/O, which makes larger I/O very inefficient.
The iov_iter_extract_bvecs wrapper allows to extract bvecs instead, which
might be a good choice here either by passing down the bvecs or
converting to an nfs_page inline. Or just open coding a variant of
iov_iter_extract_bvecs that converts to nfs_page structures instead of
bvecs. This would pair with a helper similar to __bio_release_pages on
the unlock side.
> + req = nfs_page_create_from_page(dreq->ctx, pagevec[i], false,
> pgbase, pos, req_len);
>
A lot of this code reads pretty odd as it's overflowing the lines.
^ permalink raw reply
* Re: [RFC PATCH 4/4] nfs: allow P2PDMA in direct I/O path
From: Christoph Hellwig @ 2026-04-02 5:05 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: trond.myklebust, anna, davem, kuba, edumazet, pabeni, chuck.lever,
jlayton, tom, okorniev, neil, dai.ngo, linux-nfs, netdev
In-Reply-To: <20260401194501.2269200-5-praan@google.com>
On Wed, Apr 01, 2026 at 07:45:00PM +0000, Pranjal Shrivastava wrote:
> Migrate the NFS Direct I/O path from the legacy iov_iter_get_pages_alloc2()
> API to the modern iov_iter_extract_pages() API. This migration enables
> support for PCI Peer-to-Peer DMA (P2PDMA) by allowing the setting the
> ITER_ALLOW_P2PDMA flag.
>
> Pass ITER_ALLOW_P2PDMA to iov_iter_extract_pages() only if the local
> mount indicates support via the NFS_CAP_P2PDMA capability bit (detected
> at mount time for RDMA transports).
Please split theconversion to iov_iter_extract_pages into a separate
preparation patch, and even series. That is a long overdue change
that fixes potential data corruption in XFS.
^ permalink raw reply
* Re: [RFC PATCH 0/4] nfs: Enable PCI Peer-to-Peer DMA (P2PDMA) support
From: Christoph Hellwig @ 2026-04-02 5:07 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: trond.myklebust, anna, davem, kuba, edumazet, pabeni, chuck.lever,
jlayton, tom, okorniev, neil, dai.ngo, linux-nfs, netdev
In-Reply-To: <20260401194501.2269200-1-praan@google.com>
Besides the point of splitting out the iov_iter_extract* conversion this
seems to ignore pNFS. You also need to check the layout driver for the
current file range and propagate P2P support or lack of through that.
Note that the block-style layouts can also trivially support P2P and not
just RPC-based ones.
^ permalink raw reply
* [PATCH] udp_bpf: fix use-after-free in udp_bpf_recvmsg()
From: Deepanshu Kartikey @ 2026-04-02 5:09 UTC (permalink / raw)
To: john.fastabend, jakub, davem, dsahern, edumazet, kuba, pabeni,
horms
Cc: ast, cong.wang, netdev, bpf, linux-kernel, Deepanshu Kartikey,
syzbot+431f9a9e3f5227fbb904
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;
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2] selftests/bpf: Reject malformed IPv4/IPv6 skb test input
From: Martin KaFai Lau @ 2026-04-02 5:13 UTC (permalink / raw)
To: sun jian
Cc: ast, daniel, andrii, eddyz87, song, yonghong.song, john.fastabend,
kpsingh, sdf, haoluo, jolsa, davem, edumazet, kuba, pabeni, horms,
bpf, netdev, linux-kernel, syzbot+619b9ef527f510a57cfc
In-Reply-To: <CABFUUZEdh2-bpYG+inQeUwxs9UC7D-xm5tR4RtAsPHrG58WBSg@mail.gmail.com>
On Thu, Apr 02, 2026 at 10:54:41AM +0800, sun jian wrote:
> Ack, I'll respin a v2.
>
> BTW, v1 was mainly meant as a minimal proof of the fix, so I
> kept the existing structure intact.
This is already v2.
The minimal proof of the fix is a selftest for a tricky case
like this, instead of spamming the list, and now also the
AI-review tokens, with an unlandable patch.
It is a few line change, and I don't see how duplicating the
existing switch case makes the RFC review easier.
^ permalink raw reply
* [PATCH v1 0/2] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support
From: a0282524688 @ 2026-04-02 5:14 UTC (permalink / raw)
To: tmyu0, linusw, brgl, linux, andi.shyti, lee, mkl, mailhol,
alexandre.belloni, wim
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
The Nuvoton NCT6694 is a peripheral expander that provides GPIO, I2C,
CAN-FD, Watchdog, HWMON, PWM, and RTC sub-devices. Currently, the
driver only supports USB as the host transport interface.
This series refactors the NCT6694 MFD core to support multiple transport
backends and adds a new Host Interface (HIF) transport driver that
communicates over eSPI using Super-I/O shared memory.
Ming Yu (2):
mfd: nct6694: Switch to devm_mfd_add_devices() and drop IDA
mfd: Add Host Interface (HIF) support for Nuvoton NCT6694
MAINTAINERS | 1 +
drivers/gpio/gpio-nct6694.c | 26 +-
drivers/hwmon/nct6694-hwmon.c | 21 -
drivers/i2c/busses/i2c-nct6694.c | 26 +-
drivers/mfd/Kconfig | 47 +-
drivers/mfd/Makefile | 3 +-
drivers/mfd/nct6694-hif.c | 649 ++++++++++++++++++++++++++++
drivers/mfd/nct6694.c | 180 ++++----
drivers/net/can/usb/nct6694_canfd.c | 18 +-
drivers/rtc/rtc-nct6694.c | 7 -
drivers/watchdog/nct6694_wdt.c | 27 +-
include/linux/mfd/nct6694.h | 57 ++-
12 files changed, 829 insertions(+), 233 deletions(-)
create mode 100644 drivers/mfd/nct6694-hif.c
--
2.34.1
^ permalink raw reply
* [PATCH v1 1/2] mfd: nct6694: Switch to devm_mfd_add_devices() and drop IDA
From: a0282524688 @ 2026-04-02 5:14 UTC (permalink / raw)
To: tmyu0, linusw, brgl, linux, andi.shyti, lee, mkl, mailhol,
alexandre.belloni, wim
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20260402051442.1426672-1-a0282524688@gmail.com>
From: Ming Yu <a0282524688@gmail.com>
Currently, the nct6694 core driver uses mfd_add_hotplug_devices()
and an IDA to manage subdevice IDs.
Switch the core implementation to use the managed
devm_mfd_add_devices() API, which simplifies the error handling and
device lifecycle management. Concurrently, drop the custom IDA
implementation and transition to using pdev->id.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
drivers/gpio/gpio-nct6694.c | 19 +------
drivers/i2c/busses/i2c-nct6694.c | 19 +------
drivers/mfd/nct6694.c | 83 ++++++++++++-----------------
drivers/net/can/usb/nct6694_canfd.c | 12 +----
drivers/watchdog/nct6694_wdt.c | 20 +------
include/linux/mfd/nct6694.h | 8 +--
6 files changed, 43 insertions(+), 118 deletions(-)
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
index a8607f0d9915..3703a61209e6 100644
--- a/drivers/gpio/gpio-nct6694.c
+++ b/drivers/gpio/gpio-nct6694.c
@@ -7,7 +7,6 @@
#include <linux/bits.h>
#include <linux/gpio/driver.h>
-#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
@@ -381,14 +380,6 @@ static void nct6694_irq_dispose_mapping(void *d)
irq_dispose_mapping(data->irq);
}
-static void nct6694_gpio_ida_free(void *d)
-{
- struct nct6694_gpio_data *data = d;
- struct nct6694 *nct6694 = data->nct6694;
-
- ida_free(&nct6694->gpio_ida, data->group);
-}
-
static int nct6694_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -403,15 +394,7 @@ static int nct6694_gpio_probe(struct platform_device *pdev)
return -ENOMEM;
data->nct6694 = nct6694;
-
- ret = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL);
- if (ret < 0)
- return ret;
- data->group = ret;
-
- ret = devm_add_action_or_reset(dev, nct6694_gpio_ida_free, data);
- if (ret)
- return ret;
+ data->group = pdev->id;
names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *),
GFP_KERNEL);
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index 1413ab6f9462..7d8ad997f6d2 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -6,7 +6,6 @@
*/
#include <linux/i2c.h>
-#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
@@ -134,14 +133,6 @@ static int nct6694_i2c_set_baudrate(struct nct6694_i2c_data *data)
return 0;
}
-static void nct6694_i2c_ida_free(void *d)
-{
- struct nct6694_i2c_data *data = d;
- struct nct6694 *nct6694 = data->nct6694;
-
- ida_free(&nct6694->i2c_ida, data->port);
-}
-
static int nct6694_i2c_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -155,15 +146,7 @@ static int nct6694_i2c_probe(struct platform_device *pdev)
data->dev = dev;
data->nct6694 = nct6694;
-
- ret = ida_alloc(&nct6694->i2c_ida, GFP_KERNEL);
- if (ret < 0)
- return ret;
- data->port = ret;
-
- ret = devm_add_action_or_reset(dev, nct6694_i2c_ida_free, data);
- if (ret)
- return ret;
+ data->port = pdev->id;
ret = nct6694_i2c_set_baudrate(data);
if (ret)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 308b2fda3055..8ce2c4985aab 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -11,7 +11,6 @@
#include <linux/bits.h>
#include <linux/interrupt.h>
-#include <linux/idr.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
@@ -23,35 +22,35 @@
#include <linux/usb.h>
static const struct mfd_cell nct6694_devs[] = {
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
-
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
-
- MFD_CELL_NAME("nct6694-canfd"),
- MFD_CELL_NAME("nct6694-canfd"),
-
- MFD_CELL_NAME("nct6694-wdt"),
- MFD_CELL_NAME("nct6694-wdt"),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
+
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
+
+ MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
+
+ MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
MFD_CELL_NAME("nct6694-hwmon"),
@@ -307,23 +306,18 @@ static int nct6694_usb_probe(struct usb_interface *iface,
nct6694->dev = dev;
nct6694->udev = udev;
- ida_init(&nct6694->gpio_ida);
- ida_init(&nct6694->i2c_ida);
- ida_init(&nct6694->canfd_ida);
- ida_init(&nct6694->wdt_ida);
-
spin_lock_init(&nct6694->irq_lock);
ret = devm_mutex_init(dev, &nct6694->access_lock);
if (ret)
- goto err_ida;
+ goto err_irq_domain;
interface = iface->cur_altsetting;
int_endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(int_endpoint)) {
ret = -ENODEV;
- goto err_ida;
+ goto err_irq_domain;
}
usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
@@ -332,11 +326,11 @@ static int nct6694_usb_probe(struct usb_interface *iface,
ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
if (ret)
- goto err_ida;
+ goto err_irq_domain;
usb_set_intfdata(iface, nct6694);
- ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
+ ret = devm_mfd_add_devices(dev, 0, nct6694_devs, ARRAY_SIZE(nct6694_devs), NULL, 0, NULL);
if (ret)
goto err_mfd;
@@ -344,11 +338,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
err_mfd:
usb_kill_urb(nct6694->int_in_urb);
-err_ida:
- ida_destroy(&nct6694->wdt_ida);
- ida_destroy(&nct6694->canfd_ida);
- ida_destroy(&nct6694->i2c_ida);
- ida_destroy(&nct6694->gpio_ida);
+err_irq_domain:
irq_domain_remove(nct6694->domain);
err_urb:
usb_free_urb(nct6694->int_in_urb);
@@ -359,12 +349,7 @@ static void nct6694_usb_disconnect(struct usb_interface *iface)
{
struct nct6694 *nct6694 = usb_get_intfdata(iface);
- mfd_remove_devices(nct6694->dev);
usb_kill_urb(nct6694->int_in_urb);
- ida_destroy(&nct6694->wdt_ida);
- ida_destroy(&nct6694->canfd_ida);
- ida_destroy(&nct6694->i2c_ida);
- ida_destroy(&nct6694->gpio_ida);
irq_domain_remove(nct6694->domain);
usb_free_urb(nct6694->int_in_urb);
}
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
index e5f7f8849a73..29282c56430f 100644
--- a/drivers/net/can/usb/nct6694_canfd.c
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -8,7 +8,6 @@
#include <linux/can/dev.h>
#include <linux/can/rx-offload.h>
#include <linux/ethtool.h>
-#include <linux/idr.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/mfd/nct6694.h>
@@ -725,15 +724,13 @@ static int nct6694_canfd_probe(struct platform_device *pdev)
struct net_device *ndev;
int port, irq, ret, can_clk;
- port = ida_alloc(&nct6694->canfd_ida, GFP_KERNEL);
- if (port < 0)
- return port;
+ port = pdev->id;
irq = irq_create_mapping(nct6694->domain,
NCT6694_IRQ_CAN0 + port);
if (!irq) {
ret = -EINVAL;
- goto free_ida;
+ return ret;
}
ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
@@ -796,24 +793,19 @@ static int nct6694_canfd_probe(struct platform_device *pdev)
free_candev(ndev);
dispose_irq:
irq_dispose_mapping(irq);
-free_ida:
- ida_free(&nct6694->canfd_ida, port);
return ret;
}
static void nct6694_canfd_remove(struct platform_device *pdev)
{
struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
- struct nct6694 *nct6694 = priv->nct6694;
struct net_device *ndev = priv->ndev;
- int port = ndev->dev_port;
int irq = ndev->irq;
unregister_candev(ndev);
can_rx_offload_del(&priv->offload);
free_candev(ndev);
irq_dispose_mapping(irq);
- ida_free(&nct6694->canfd_ida, port);
}
static struct platform_driver nct6694_canfd_driver = {
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
index bc3689bd4b6b..2b4b804a1739 100644
--- a/drivers/watchdog/nct6694_wdt.c
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -5,7 +5,6 @@
* Copyright (C) 2025 Nuvoton Technology Corp.
*/
-#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
@@ -233,21 +232,12 @@ static const struct watchdog_ops nct6694_wdt_ops = {
.ping = nct6694_wdt_ping,
};
-static void nct6694_wdt_ida_free(void *d)
-{
- struct nct6694_wdt_data *data = d;
- struct nct6694 *nct6694 = data->nct6694;
-
- ida_free(&nct6694->wdt_ida, data->wdev_idx);
-}
-
static int nct6694_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
struct nct6694_wdt_data *data;
struct watchdog_device *wdev;
- int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -260,15 +250,7 @@ static int nct6694_wdt_probe(struct platform_device *pdev)
data->dev = dev;
data->nct6694 = nct6694;
-
- ret = ida_alloc(&nct6694->wdt_ida, GFP_KERNEL);
- if (ret < 0)
- return ret;
- data->wdev_idx = ret;
-
- ret = devm_add_action_or_reset(dev, nct6694_wdt_ida_free, data);
- if (ret)
- return ret;
+ data->wdev_idx = pdev->id;
wdev = &data->wdev;
wdev->info = &nct6694_wdt_info;
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 6eb9be2cd4a0..496da72949d9 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -8,6 +8,10 @@
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
#define NCT6694_VENDOR_ID 0x0416
#define NCT6694_PRODUCT_ID 0x200B
#define NCT6694_INT_IN_EP 0x81
@@ -82,10 +86,6 @@ union __packed nct6694_usb_msg {
struct nct6694 {
struct device *dev;
- struct ida gpio_ida;
- struct ida i2c_ida;
- struct ida canfd_ida;
- struct ida wdt_ida;
struct irq_domain *domain;
struct mutex access_lock;
spinlock_t irq_lock;
--
2.34.1
^ permalink raw reply related
* [PATCH v1 2/2] mfd: Add Host Interface (HIF) support for Nuvoton NCT6694
From: a0282524688 @ 2026-04-02 5:14 UTC (permalink / raw)
To: tmyu0, linusw, brgl, linux, andi.shyti, lee, mkl, mailhol,
alexandre.belloni, wim
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20260402051442.1426672-1-a0282524688@gmail.com>
From: Ming Yu <a0282524688@gmail.com>
The Nuvoton NCT6694 also provides a Host Interface (HIF) via eSPI
to the host to access its features.
Sub-devices can use the common functions nct6694_read_msg() and
nct6694_write_msg() to issue a command. They can also request
interrupts that will be called when the HIF device triggers a
shared memory interrupt.
To support multiple transports, the driver configuration is
updated to allow selecting between the USB and HIF interfaces.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/gpio/gpio-nct6694.c | 7 -
drivers/hwmon/nct6694-hwmon.c | 21 -
drivers/i2c/busses/i2c-nct6694.c | 7 -
drivers/mfd/Kconfig | 47 +-
drivers/mfd/Makefile | 3 +-
drivers/mfd/nct6694-hif.c | 649 ++++++++++++++++++++++++++++
drivers/mfd/nct6694.c | 97 +++--
drivers/net/can/usb/nct6694_canfd.c | 6 -
drivers/rtc/rtc-nct6694.c | 7 -
drivers/watchdog/nct6694_wdt.c | 7 -
include/linux/mfd/nct6694.h | 51 ++-
12 files changed, 787 insertions(+), 116 deletions(-)
create mode 100644 drivers/mfd/nct6694-hif.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c3fe46d7c4bc..7b6241faa6df 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18899,6 +18899,7 @@ S: Supported
F: drivers/gpio/gpio-nct6694.c
F: drivers/hwmon/nct6694-hwmon.c
F: drivers/i2c/busses/i2c-nct6694.c
+F: drivers/mfd/nct6694-hif.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/usb/nct6694_canfd.c
F: drivers/rtc/rtc-nct6694.c
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
index 3703a61209e6..a279510ece89 100644
--- a/drivers/gpio/gpio-nct6694.c
+++ b/drivers/gpio/gpio-nct6694.c
@@ -12,13 +12,6 @@
#include <linux/module.h>
#include <linux/platform_device.h>
-/*
- * USB command module type for NCT6694 GPIO controller.
- * This defines the module type used for communication with the NCT6694
- * GPIO controller over the USB interface.
- */
-#define NCT6694_GPIO_MOD 0xFF
-
#define NCT6694_GPIO_VER 0x90
#define NCT6694_GPIO_VALID 0x110
#define NCT6694_GPI_DATA 0x120
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
index 6dcf22ca5018..581451875f2c 100644
--- a/drivers/hwmon/nct6694-hwmon.c
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -15,13 +15,6 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
-/*
- * USB command module type for NCT6694 report channel
- * This defines the module type used for communication with the NCT6694
- * report channel over the USB interface.
- */
-#define NCT6694_RPT_MOD 0xFF
-
/* Report channel */
/*
* The report channel is used to report the status of the hardware monitor
@@ -38,13 +31,6 @@
#define NCT6694_TIN_STS(x) (0x6A + (x))
#define NCT6694_FIN_STS(x) (0x6E + (x))
-/*
- * USB command module type for NCT6694 HWMON controller.
- * This defines the module type used for communication with the NCT6694
- * HWMON controller over the USB interface.
- */
-#define NCT6694_HWMON_MOD 0x00
-
/* Command 00h - Hardware Monitor Control */
#define NCT6694_HWMON_CONTROL 0x00
#define NCT6694_HWMON_CONTROL_SEL 0x00
@@ -53,13 +39,6 @@
#define NCT6694_HWMON_ALARM 0x02
#define NCT6694_HWMON_ALARM_SEL 0x00
-/*
- * USB command module type for NCT6694 PWM controller.
- * This defines the module type used for communication with the NCT6694
- * PWM controller over the USB interface.
- */
-#define NCT6694_PWM_MOD 0x01
-
/* PWM Command - Manual Control */
#define NCT6694_PWM_CONTROL 0x01
#define NCT6694_PWM_CONTROL_SEL 0x00
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index 7d8ad997f6d2..7ee209a04d16 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -11,13 +11,6 @@
#include <linux/module.h>
#include <linux/platform_device.h>
-/*
- * USB command module type for NCT6694 I2C controller.
- * This defines the module type used for communication with the NCT6694
- * I2C controller over the USB interface.
- */
-#define NCT6694_I2C_MOD 0x03
-
/* Command 00h - I2C Deliver */
#define NCT6694_I2C_DELIVER 0x00
#define NCT6694_I2C_DELIVER_SEL 0x00
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..8a715ec2f79f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1164,19 +1164,46 @@ config MFD_MENF21BMC
will be called menf21bmc.
config MFD_NCT6694
- tristate "Nuvoton NCT6694 support"
+ tristate
select MFD_CORE
+ help
+ Core MFD support for the Nuvoton NCT6694 peripheral expander.
+ This provides the common APIs and shared structures used by all
+ interfaces (USB, HIF) to access the NCT6694 hardware features
+ such as GPIO, I2C, CAN-FD, Watchdog, ADC, PWM, and RTC.
+
+ It is selected automatically by the transport interface drivers.
+
+config MFD_NCT6694_HIF
+ tristate "Nuvoton NCT6694 HIF (eSPI) interface support"
+ depends on HAS_IOPORT && ACPI
+ select MFD_NCT6694
+ select REGMAP_MMIO
+ help
+ This enables support for the Nuvoton NCT6694 peripheral expander
+ connected via the Host Interface (HIF) using eSPI transport.
+
+ The transport driver uses Super-I/O mapping and shared memory to
+ communicate with the NCT6694 firmware. Enable this option if you
+ are using the NCT6694 over an eSPI interface on an ACPI platform.
+
+ To compile this driver as a module, choose M here: the module
+ will be called nct6694-hif.
+
+config MFD_NCT6694_USB
+ tristate "Nuvoton NCT6694 USB interface support"
+ select MFD_NCT6694
depends on USB
help
- This enables support for the Nuvoton USB device NCT6694, which shares
- peripherals.
- The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
- 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
- PWM, and RTC.
- This driver provides core APIs to access the NCT6694 hardware
- monitoring and control features.
- Additional drivers must be enabled to utilize the specific
- functionalities of the device.
+ This enables support for the Nuvoton NCT6694 peripheral expander
+ connected via the USB interface.
+
+ The transport driver uses USB bulk and interrupt transfers to
+ communicate with the NCT6694 firmware. Enable this option if you
+ are using the NCT6694 via a USB connection.
+
+ To compile this driver as a module, choose M here: the module
+ will be called nct6694.
config MFD_OCELOT
tristate "Microsemi Ocelot External Control Support"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..4cee9b74978c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -124,7 +124,8 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
-obj-$(CONFIG_MFD_NCT6694) += nct6694.o
+obj-$(CONFIG_MFD_NCT6694_HIF) += nct6694-hif.o
+obj-$(CONFIG_MFD_NCT6694_USB) += nct6694.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
diff --git a/drivers/mfd/nct6694-hif.c b/drivers/mfd/nct6694-hif.c
new file mode 100644
index 000000000000..a5953c951eb5
--- /dev/null
+++ b/drivers/mfd/nct6694-hif.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 host-interface (eSPI) transport driver.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/unaligned.h>
+
+#define DRVNAME "nct6694-hif"
+
+#define NCT6694_POLL_INTERVAL_US 10
+#define NCT6694_POLL_TIMEOUT_US 10000
+
+/*
+ * Super-I/O registers
+ */
+#define SIO_REG_LDSEL 0x07 /* Logical device select */
+#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
+#define SIO_REG_LD_SHM 0x0F /* Logical device shared memory control */
+
+#define SIO_REG_SHM_ENABLE 0x30 /* Enable shared memory */
+#define SIO_REG_SHM_BASE_ADDR 0x60 /* Shared memory base address (2 bytes) */
+#define SIO_REG_SHM_IRQ_NR 0x70 /* Shared memory interrupt number */
+
+#define SIO_REG_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
+#define SIO_REG_LOCK_KEY 0xAA /* Key to disable Super-I/O */
+
+#define SIO_NCT6694B_ID 0xD029
+#define SIO_NCT6694D_ID 0x5832
+
+/*
+ * Super-I/O Shared Memory Logical Device registers
+ */
+#define NCT6694_SHM_COFS_STS 0x2E
+#define NCT6694_SHM_COFS_STS_COFS4W BIT(7)
+
+#define NCT6694_SHM_COFS_CTL2 0x3B
+#define NCT6694_SHM_COFS_CTL2_COFS4W_IE BIT(3)
+
+#define NCT6694_SHM_INTR_STATUS 0x9C /* Interrupt status register (4 bytes) */
+
+enum nct6694_chips {
+ NCT6694B = 0,
+ NCT6694D,
+};
+
+enum nct6694_module_id {
+ NCT6694_GPIO0 = 0,
+ NCT6694_GPIO1,
+ NCT6694_GPIO2,
+ NCT6694_GPIO3,
+ NCT6694_GPIO4,
+ NCT6694_GPIO5,
+ NCT6694_GPIO6,
+ NCT6694_GPIO7,
+ NCT6694_GPIO8,
+ NCT6694_GPIO9,
+ NCT6694_GPIOA,
+ NCT6694_GPIOB,
+ NCT6694_GPIOC,
+ NCT6694_GPIOD,
+ NCT6694_GPIOE,
+ NCT6694_GPIOF,
+ NCT6694_I2C0,
+ NCT6694_I2C1,
+ NCT6694_I2C2,
+ NCT6694_I2C3,
+ NCT6694_I2C4,
+ NCT6694_I2C5,
+ NCT6694_CAN0,
+ NCT6694_CAN1,
+};
+
+struct __packed nct6694_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+ unsigned char *data;
+};
+
+struct nct6694_sio_data {
+ enum nct6694_chips chip;
+ int sioreg; /* Super-I/O index port */
+
+ /* Super-I/O access functions */
+ int (*sio_enter)(struct nct6694_sio_data *sio_data);
+ void (*sio_exit)(struct nct6694_sio_data *sio_data);
+ void (*sio_select)(struct nct6694_sio_data *sio_data, int ld);
+ int (*sio_inb)(struct nct6694_sio_data *sio_data, int reg);
+ int (*sio_inw)(struct nct6694_sio_data *sio_data, int reg);
+ void (*sio_outb)(struct nct6694_sio_data *sio_data, int reg, int val);
+};
+
+struct nct6694_hif_data {
+ struct regmap *regmap;
+ struct mutex msg_lock;
+ struct nct6694_sio_data *sio_data;
+ void __iomem *msg_base;
+ unsigned int shm_base;
+};
+
+static const char * const nct6694_chip_names[] = {
+ "NCT6694D",
+ "NCT6694B"
+};
+
+/*
+ * Super-I/O functions.
+ */
+static int superio_enter(struct nct6694_sio_data *sio_data)
+{
+ int ioreg = sio_data->sioreg;
+
+ /*
+ * Try to reserve <ioreg> and <ioreg + 1> for exclusive access.
+ */
+ if (!request_muxed_region(ioreg, 2, DRVNAME))
+ return -EBUSY;
+
+ outb(SIO_REG_UNLOCK_KEY, ioreg);
+ outb(SIO_REG_UNLOCK_KEY, ioreg);
+
+ return 0;
+}
+
+static void superio_exit(struct nct6694_sio_data *sio_data)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(SIO_REG_LOCK_KEY, ioreg);
+
+ release_region(ioreg, 2);
+}
+
+static void superio_select(struct nct6694_sio_data *sio_data, int ld)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(SIO_REG_LDSEL, ioreg);
+ outb(ld, ioreg + 1);
+}
+
+static int superio_inb(struct nct6694_sio_data *sio_data, int reg)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(reg, ioreg);
+ return inb(ioreg + 1);
+}
+
+static int superio_inw(struct nct6694_sio_data *sio_data, int reg)
+{
+ int ioreg = sio_data->sioreg;
+ int val;
+
+ outb(reg++, ioreg);
+ val = inb(ioreg + 1) << 8;
+ outb(reg, ioreg);
+ val |= inb(ioreg + 1);
+
+ return val;
+}
+
+static void superio_outb(struct nct6694_sio_data *sio_data, int reg, int val)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(reg, ioreg);
+ outb(val, ioreg + 1);
+}
+
+static int nct6694_sio_find(struct nct6694_sio_data *sio_data, u8 sioreg)
+{
+ int ret;
+ u16 devid;
+
+ sio_data->sioreg = sioreg;
+
+ ret = sio_data->sio_enter(sio_data);
+ if (ret)
+ return ret;
+
+ /* Check Chip ID */
+ devid = sio_data->sio_inw(sio_data, SIO_REG_DEVID);
+ switch (devid) {
+ case SIO_NCT6694B_ID:
+ sio_data->chip = NCT6694B;
+ break;
+ case SIO_NCT6694D_ID:
+ sio_data->chip = NCT6694D;
+ break;
+ default:
+ pr_err("Unsupported device 0x%04x\n", devid);
+ goto err;
+ }
+
+ pr_info("Found %s at %#x\n", nct6694_chip_names[sio_data->chip], sio_data->sioreg);
+
+ sio_data->sio_exit(sio_data);
+
+ return 0;
+
+err:
+ sio_data->sio_exit(sio_data);
+ return -ENODEV;
+}
+
+static const struct mfd_cell_acpi_match nct6694_acpi_match_gpio[] = {
+ { .adr = NCT6694_GPIO0 },
+ { .adr = NCT6694_GPIO1 },
+ { .adr = NCT6694_GPIO2 },
+ { .adr = NCT6694_GPIO3 },
+ { .adr = NCT6694_GPIO4 },
+ { .adr = NCT6694_GPIO5 },
+ { .adr = NCT6694_GPIO6 },
+ { .adr = NCT6694_GPIO7 },
+ { .adr = NCT6694_GPIO8 },
+ { .adr = NCT6694_GPIO9 },
+ { .adr = NCT6694_GPIOA },
+ { .adr = NCT6694_GPIOB },
+ { .adr = NCT6694_GPIOC },
+ { .adr = NCT6694_GPIOD },
+ { .adr = NCT6694_GPIOE },
+ { .adr = NCT6694_GPIOF },
+};
+
+static const struct mfd_cell_acpi_match nct6694_acpi_match_i2c[] = {
+ { .adr = NCT6694_I2C0 },
+ { .adr = NCT6694_I2C1 },
+ { .adr = NCT6694_I2C2 },
+ { .adr = NCT6694_I2C3 },
+ { .adr = NCT6694_I2C4 },
+ { .adr = NCT6694_I2C5 },
+};
+
+static const struct mfd_cell_acpi_match nct6694_acpi_match_can[] = {
+ { .adr = NCT6694_CAN0 },
+ { .adr = NCT6694_CAN1 },
+};
+
+static const struct mfd_cell nct6694_devs[] = {
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 0, &nct6694_acpi_match_gpio[0]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 1, &nct6694_acpi_match_gpio[1]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 2, &nct6694_acpi_match_gpio[2]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 3, &nct6694_acpi_match_gpio[3]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 4, &nct6694_acpi_match_gpio[4]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 5, &nct6694_acpi_match_gpio[5]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 6, &nct6694_acpi_match_gpio[6]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 7, &nct6694_acpi_match_gpio[7]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 8, &nct6694_acpi_match_gpio[8]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 9, &nct6694_acpi_match_gpio[9]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 10, &nct6694_acpi_match_gpio[10]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 11, &nct6694_acpi_match_gpio[11]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 12, &nct6694_acpi_match_gpio[12]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 13, &nct6694_acpi_match_gpio[13]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 14, &nct6694_acpi_match_gpio[14]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 15, &nct6694_acpi_match_gpio[15]),
+
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 0, &nct6694_acpi_match_i2c[0]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 1, &nct6694_acpi_match_i2c[1]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 2, &nct6694_acpi_match_i2c[2]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 3, &nct6694_acpi_match_i2c[3]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 4, &nct6694_acpi_match_i2c[4]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 5, &nct6694_acpi_match_i2c[5]),
+
+ MFD_CELL_ACPI("nct6694-canfd", NULL, NULL, 0, 0, &nct6694_acpi_match_can[0]),
+ MFD_CELL_ACPI("nct6694-canfd", NULL, NULL, 0, 1, &nct6694_acpi_match_can[1]),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return 0;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_err(nct6694->dev, "Command is not supported!\n");
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_warn(nct6694->dev, "Command received no response!\n");
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_warn(nct6694->dev, "Command timed out!\n");
+ break;
+ case NCT6694_PENDING:
+ dev_err(nct6694->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+static int nct6694_xfer_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ u8 hctrl, void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ void __iomem *hdr = hdata->msg_base + offsetof(struct nct6694_msg, cmd_header);
+ struct nct6694_cmd_header cmd = *cmd_hd;
+ struct nct6694_response_header resp;
+ u16 len = le16_to_cpu(cmd.len);
+ u8 status;
+ int ret;
+
+ guard(mutex)(&hdata->msg_lock);
+
+ /* Wait until the previous command is completed */
+ ret = readb_poll_timeout(hdr + offsetof(struct nct6694_cmd_header, hctrl),
+ status, status == 0, NCT6694_POLL_INTERVAL_US,
+ NCT6694_POLL_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ /*
+ * Write cmd header fields, but skip hctrl — writing to it triggers
+ * firmware command processing and must be deferred until data is ready.
+ */
+ memcpy_toio(hdr, &cmd, offsetof(struct nct6694_cmd_header, hctrl));
+ memcpy_toio(hdr + offsetof(struct nct6694_cmd_header, rsv2), &cmd.rsv2,
+ sizeof(cmd) - offsetof(struct nct6694_cmd_header, rsv2));
+
+ if (hctrl == NCT6694_HCTRL_SET && len)
+ memcpy_toio(hdata->msg_base + offsetof(struct nct6694_msg, data),
+ buf, len);
+
+ /* Write hctrl last to trigger command processing */
+ writeb(hctrl, hdr + offsetof(struct nct6694_cmd_header, hctrl));
+
+ ret = readb_poll_timeout(hdr + offsetof(struct nct6694_cmd_header, hctrl),
+ status, status == 0, NCT6694_POLL_INTERVAL_US,
+ NCT6694_POLL_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ memcpy_fromio(&resp, hdata->msg_base + offsetof(struct nct6694_msg, response_header),
+ sizeof(resp));
+
+ ret = nct6694_response_err_handling(nct6694, resp.sts);
+ if (ret)
+ return ret;
+
+ if (le16_to_cpu(resp.len))
+ memcpy_fromio(buf, hdata->msg_base + offsetof(struct nct6694_msg, data),
+ min(len, le16_to_cpu(resp.len)));
+
+ return 0;
+}
+
+/**
+ * nct6694_hif_read_msg() - Send a command and read response data via HIF
+ * @nct6694: NCT6694 device data
+ * @cmd_hd: command header
+ * @buf: buffer to store response data
+ *
+ * Return: 0 on success or negative errno on failure.
+ */
+static int nct6694_hif_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+
+ if (cmd_hd->mod == NCT6694_RPT_MOD)
+ return regmap_bulk_read(hdata->regmap,
+ le16_to_cpu(cmd_hd->offset),
+ buf, le16_to_cpu(cmd_hd->len));
+ return nct6694_xfer_msg(nct6694, cmd_hd, NCT6694_HCTRL_GET, buf);
+}
+
+/**
+ * nct6694_hif_write_msg() - Send a command with data payload via HIF
+ * @nct6694: NCT6694 device data
+ * @cmd_hd: command header
+ * @buf: buffer containing data to send
+ *
+ * Return: 0 on success or negative errno on failure.
+ */
+static int nct6694_hif_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+
+ if (cmd_hd->mod == NCT6694_RPT_MOD)
+ return regmap_bulk_write(hdata->regmap,
+ le16_to_cpu(cmd_hd->offset),
+ buf, le16_to_cpu(cmd_hd->len));
+ return nct6694_xfer_msg(nct6694, cmd_hd, NCT6694_HCTRL_SET, buf);
+}
+
+static const struct regmap_config nct6694_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_stride = 1,
+};
+
+static irqreturn_t nct6694_irq_handler(int irq, void *data)
+{
+ struct nct6694 *nct6694 = data;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ u8 reg_data[4];
+ u32 intr_status;
+ int ret;
+
+ /* Check interrupt status is set */
+ if (!(inb(hdata->shm_base + NCT6694_SHM_COFS_STS) & NCT6694_SHM_COFS_STS_COFS4W))
+ return IRQ_NONE;
+
+ /* Clear interrupt status */
+ outb(NCT6694_SHM_COFS_STS_COFS4W, hdata->shm_base + NCT6694_SHM_COFS_STS);
+
+ ret = regmap_bulk_read(hdata->regmap, NCT6694_SHM_INTR_STATUS,
+ reg_data, ARRAY_SIZE(reg_data));
+ if (ret)
+ return IRQ_NONE;
+
+ intr_status = get_unaligned_le32(reg_data);
+
+ while (intr_status) {
+ int irq = __ffs(intr_status);
+
+ generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+ intr_status &= ~BIT(irq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void nct6694_irq_release(void *data)
+{
+ struct nct6694 *nct6694 = data;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ unsigned char cofs_ctl2;
+
+ /* Disable SIRQ interrupt */
+ cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 &= ~NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+}
+
+static int nct6694_irq_init(struct nct6694 *nct6694, int irq)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ struct nct6694_sio_data *sio_data = hdata->sio_data;
+ unsigned char cofs_ctl2;
+
+ /* Set SIRQ number */
+ sio_data->sio_enter(sio_data);
+ sio_data->sio_select(sio_data, SIO_REG_LD_SHM);
+ if (!sio_data->sio_inb(sio_data, SIO_REG_SHM_ENABLE)) {
+ sio_data->sio_exit(sio_data);
+ return -EIO;
+ }
+ hdata->shm_base = sio_data->sio_inw(sio_data, SIO_REG_SHM_BASE_ADDR);
+
+ sio_data->sio_outb(sio_data, SIO_REG_SHM_IRQ_NR, irq);
+
+ sio_data->sio_exit(sio_data);
+
+ /* Enable SIRQ interrupt */
+ cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 |= NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+
+ return 0;
+}
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->irq_enable &= ~BIT(hwirq);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694-irq",
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+ .irq_enable = nct6694_irq_enable,
+ .irq_disable = nct6694_irq_disable,
+};
+
+static void nct6694_irq_domain_remove(void *data)
+{
+ struct nct6694 *nct6694 = data;
+
+ irq_domain_remove(nct6694->domain);
+}
+
+static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
+{
+ struct nct6694 *nct6694 = d->host_data;
+
+ irq_set_chip_data(irq, nct6694);
+ irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
+
+ return 0;
+}
+
+static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops nct6694_irq_domain_ops = {
+ .map = nct6694_irq_domain_map,
+ .unmap = nct6694_irq_domain_unmap,
+};
+
+static const u8 sio_addrs[] = { 0x2e, 0x4e };
+
+static int nct6694_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct nct6694_sio_data *sio_data;
+ struct nct6694_hif_data *hdata;
+ struct nct6694 *data;
+ void __iomem *rpt_base, *msg_base;
+ int ret, i, irq;
+
+ sio_data = devm_kzalloc(dev, sizeof(*sio_data), GFP_KERNEL);
+ if (!sio_data)
+ return -ENOMEM;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ hdata = devm_kzalloc(dev, sizeof(*hdata), GFP_KERNEL);
+ if (!hdata)
+ return -ENOMEM;
+
+ rpt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rpt_base))
+ return PTR_ERR(rpt_base);
+ msg_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(msg_base))
+ return PTR_ERR(msg_base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ sio_data->sio_enter = superio_enter;
+ sio_data->sio_exit = superio_exit;
+ sio_data->sio_select = superio_select;
+ sio_data->sio_inb = superio_inb;
+ sio_data->sio_inw = superio_inw;
+ sio_data->sio_outb = superio_outb;
+
+ for (i = 0; i < ARRAY_SIZE(sio_addrs); i++) {
+ ret = nct6694_sio_find(sio_data, sio_addrs[i]);
+ if (!ret)
+ break;
+ }
+ if (ret)
+ return ret;
+
+ hdata->sio_data = sio_data;
+ hdata->msg_base = msg_base;
+ hdata->regmap = devm_regmap_init_mmio(dev, rpt_base,
+ &nct6694_regmap_config);
+ if (IS_ERR(hdata->regmap))
+ return PTR_ERR(hdata->regmap);
+
+ data->dev = dev;
+ data->priv = hdata;
+ data->read_msg = nct6694_hif_read_msg;
+ data->write_msg = nct6694_hif_write_msg;
+
+ spin_lock_init(&data->irq_lock);
+
+ data->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
+ &nct6694_irq_domain_ops,
+ data);
+ if (!data->domain)
+ return -ENODEV;
+
+ ret = devm_add_action_or_reset(dev, nct6694_irq_domain_remove, data);
+ if (ret)
+ return ret;
+
+ ret = nct6694_irq_init(data, irq);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_irq_release, data);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(dev), data);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &hdata->msg_lock);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, data);
+
+ return devm_mfd_add_devices(dev, 0, nct6694_devs, ARRAY_SIZE(nct6694_devs), NULL, 0, NULL);
+}
+
+static const struct acpi_device_id nct6694_acpi_ids[] = {
+ { "NTN0538", 0 },
+ {}
+};
+
+static struct platform_driver nct6694_driver = {
+ .driver = {
+ .name = DRVNAME,
+ .acpi_match_table = nct6694_acpi_ids,
+ },
+ .probe = nct6694_probe,
+};
+module_platform_driver(nct6694_driver);
+
+MODULE_DESCRIPTION("Nuvoton NCT6694 host-interface transport driver");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 8ce2c4985aab..903a0a7f0694 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -21,6 +21,27 @@
#include <linux/spinlock.h>
#include <linux/usb.h>
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define NCT6694_INT_IN_EP 0x81
+#define NCT6694_BULK_IN_EP 0x02
+#define NCT6694_BULK_OUT_EP 0x03
+
+#define NCT6694_URB_TIMEOUT 1000
+
+union __packed nct6694_usb_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+};
+
+struct nct6694_usb_data {
+ struct mutex access_lock;
+ struct urb *int_in_urb;
+ struct usb_device *udev;
+ union nct6694_usb_msg *usb_msg;
+ __le32 *int_buffer;
+};
+
static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
@@ -57,7 +78,8 @@ static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_NAME("nct6694-rtc"),
};
-static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+static int nct6694_usb_err_handling(struct nct6694 *nct6694,
+ unsigned char err_status)
{
switch (err_status) {
case NCT6694_NO_ERROR:
@@ -82,7 +104,7 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
}
/**
- * nct6694_read_msg() - Read message from NCT6694 device
+ * nct6694_usb_read_msg() - Read message from NCT6694 device via USB
* @nct6694: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer to store the response data
@@ -93,13 +115,16 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
*
* Return: Negative value on error or 0 on success.
*/
-int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+static int nct6694_usb_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ union nct6694_usb_msg *msg = udata->usb_msg;
+ struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&udata->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
@@ -128,12 +153,11 @@ int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *c
return -EIO;
}
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_usb_err_handling(nct6694, msg->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_read_msg);
/**
- * nct6694_write_msg() - Write message to NCT6694 device
+ * nct6694_usb_write_msg() - Write message to NCT6694 device via USB
* @nct6694: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer containing the data to be sent
@@ -143,13 +167,16 @@ EXPORT_SYMBOL_GPL(nct6694_read_msg);
*
* Return: Negative value on error or 0 on success.
*/
-int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+static int nct6694_usb_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ union nct6694_usb_msg *msg = udata->usb_msg;
+ struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&udata->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
@@ -184,9 +211,8 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
return -EIO;
}
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_usb_err_handling(nct6694, msg->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_write_msg);
static void usb_int_callback(struct urb *urb)
{
@@ -276,6 +302,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
struct usb_endpoint_descriptor *int_endpoint;
struct usb_host_interface *interface;
struct device *dev = &iface->dev;
+ struct nct6694_usb_data *udata;
struct nct6694 *nct6694;
int ret;
@@ -283,18 +310,28 @@ static int nct6694_usb_probe(struct usb_interface *iface,
if (!nct6694)
return -ENOMEM;
- nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
- if (!nct6694->usb_msg)
+ udata = devm_kzalloc(dev, sizeof(*udata), GFP_KERNEL);
+ if (!udata)
+ return -ENOMEM;
+
+ udata->usb_msg = devm_kzalloc(dev, sizeof(*udata->usb_msg), GFP_KERNEL);
+ if (!udata->usb_msg)
return -ENOMEM;
- nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
- if (!nct6694->int_buffer)
+ udata->int_buffer = devm_kzalloc(dev, sizeof(*udata->int_buffer), GFP_KERNEL);
+ if (!udata->int_buffer)
return -ENOMEM;
- nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!nct6694->int_in_urb)
+ udata->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!udata->int_in_urb)
return -ENOMEM;
+ udata->udev = udev;
+
+ nct6694->priv = udata;
+ nct6694->read_msg = nct6694_usb_read_msg;
+ nct6694->write_msg = nct6694_usb_write_msg;
+
nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
&nct6694_irq_domain_ops,
nct6694);
@@ -304,11 +341,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
}
nct6694->dev = dev;
- nct6694->udev = udev;
spin_lock_init(&nct6694->irq_lock);
- ret = devm_mutex_init(dev, &nct6694->access_lock);
+ ret = devm_mutex_init(dev, &udata->access_lock);
if (ret)
goto err_irq_domain;
@@ -320,11 +356,11 @@ static int nct6694_usb_probe(struct usb_interface *iface,
goto err_irq_domain;
}
- usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
- nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
+ usb_fill_int_urb(udata->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
+ udata->int_buffer, sizeof(*udata->int_buffer), usb_int_callback,
nct6694, int_endpoint->bInterval);
- ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ ret = usb_submit_urb(udata->int_in_urb, GFP_KERNEL);
if (ret)
goto err_irq_domain;
@@ -337,21 +373,22 @@ static int nct6694_usb_probe(struct usb_interface *iface,
return 0;
err_mfd:
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(udata->int_in_urb);
err_irq_domain:
irq_domain_remove(nct6694->domain);
err_urb:
- usb_free_urb(nct6694->int_in_urb);
+ usb_free_urb(udata->int_in_urb);
return ret;
}
static void nct6694_usb_disconnect(struct usb_interface *iface)
{
struct nct6694 *nct6694 = usb_get_intfdata(iface);
+ struct nct6694_usb_data *udata = nct6694->priv;
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(udata->int_in_urb);
irq_domain_remove(nct6694->domain);
- usb_free_urb(nct6694->int_in_urb);
+ usb_free_urb(udata->int_in_urb);
}
static const struct usb_device_id nct6694_ids[] = {
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
index 29282c56430f..05db00455f63 100644
--- a/drivers/net/can/usb/nct6694_canfd.c
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -17,12 +17,6 @@
#define DEVICE_NAME "nct6694-canfd"
-/* USB command module type for NCT6694 CANfd controller.
- * This defines the module type used for communication with the NCT6694
- * CANfd controller over the USB interface.
- */
-#define NCT6694_CANFD_MOD 0x05
-
/* Command 00h - CAN Setting and Initialization */
#define NCT6694_CANFD_SETTING 0x00
#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1 BIT(0)
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
index 35401a0d9cf5..c06902f150c9 100644
--- a/drivers/rtc/rtc-nct6694.c
+++ b/drivers/rtc/rtc-nct6694.c
@@ -14,13 +14,6 @@
#include <linux/rtc.h>
#include <linux/slab.h>
-/*
- * USB command module type for NCT6694 RTC controller.
- * This defines the module type used for communication with the NCT6694
- * RTC controller over the USB interface.
- */
-#define NCT6694_RTC_MOD 0x08
-
/* Command 00h - RTC Time */
#define NCT6694_RTC_TIME 0x0000
#define NCT6694_RTC_TIME_SEL 0x00
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
index 2b4b804a1739..847d8f1d1830 100644
--- a/drivers/watchdog/nct6694_wdt.c
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -19,13 +19,6 @@
#define NCT6694_WDT_MAX_DEVS 2
-/*
- * USB command module type for NCT6694 WDT controller.
- * This defines the module type used for communication with the NCT6694
- * WDT controller over the USB interface.
- */
-#define NCT6694_WDT_MOD 0x07
-
/* Command 00h - WDT Setup */
#define NCT6694_WDT_SETUP 0x00
#define NCT6694_WDT_SETUP_SEL(idx) (idx ? 0x01 : 0x00)
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 496da72949d9..ff0814dc82d4 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -2,7 +2,8 @@
/*
* Copyright (C) 2025 Nuvoton Technology Corp.
*
- * Nuvoton NCT6694 USB transaction and data structure.
+ * Nuvoton NCT6694 core definitions shared by all transport drivers
+ * and sub-device drivers.
*/
#ifndef __MFD_NCT6694_H
@@ -12,16 +13,17 @@
#include <linux/spinlock.h>
#include <linux/types.h>
-#define NCT6694_VENDOR_ID 0x0416
-#define NCT6694_PRODUCT_ID 0x200B
-#define NCT6694_INT_IN_EP 0x81
-#define NCT6694_BULK_IN_EP 0x02
-#define NCT6694_BULK_OUT_EP 0x03
-
#define NCT6694_HCTRL_SET 0x40
#define NCT6694_HCTRL_GET 0x80
-#define NCT6694_URB_TIMEOUT 1000
+#define NCT6694_HWMON_MOD 0x00
+#define NCT6694_PWM_MOD 0x01
+#define NCT6694_I2C_MOD 0x03
+#define NCT6694_CANFD_MOD 0x05
+#define NCT6694_WDT_MOD 0x07
+#define NCT6694_RTC_MOD 0x08
+#define NCT6694_RPT_MOD 0xFF
+#define NCT6694_GPIO_MOD NCT6694_RPT_MOD
enum nct6694_irq_id {
NCT6694_IRQ_GPIO0 = 0,
@@ -79,24 +81,33 @@ struct __packed nct6694_response_header {
__le16 len;
};
-union __packed nct6694_usb_msg {
- struct nct6694_cmd_header cmd_header;
- struct nct6694_response_header response_header;
-};
-
struct nct6694 {
struct device *dev;
struct irq_domain *domain;
- struct mutex access_lock;
spinlock_t irq_lock;
- struct urb *int_in_urb;
- struct usb_device *udev;
- union nct6694_usb_msg *usb_msg;
- __le32 *int_buffer;
unsigned int irq_enable;
+
+ void *priv;
+ int (*read_msg)(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
+ int (*write_msg)(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
};
-int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
-int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+static inline int nct6694_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694->read_msg(nct6694, cmd_hd, buf);
+}
+
+static inline int nct6694_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694->write_msg(nct6694, cmd_hd, buf);
+}
#endif
--
2.34.1
^ permalink raw reply related
* Re: [PATCH net-next 3/3] net: usb: smsc95xx: suspend PHY during USB suspend
From: Parthiban.Veerasooran @ 2026-04-02 5:18 UTC (permalink / raw)
To: oneukum
Cc: netdev, linux-usb, piergiorgio.beruto, andrew, hkallweit1, linux,
davem, edumazet, kuba, pabeni, steve.glendinning, UNGLinuxDriver
In-Reply-To: <8fe179bd-417d-4624-9e35-04c75978dfc8@suse.com>
Hi Oliver,
On 01/04/26 10:54 pm, Oliver Neukum wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know
> the content is safe
>
> Hi,
>
> On 01.04.26 05:18, Parthiban.Veerasooran@microchip.com wrote:
>
>> Thank you for pointing it out. I agree with you. I didn’t note it
>> earlier since the issue did not occur during my testing. I will move the
>> phy_suspend() to the appropriate place.
>
> Thank you.
>
>>> And, as a question of principle: Why do you suspend the phy
>>> in suspend(), but take no action in resume()?
>>
>> In resume(), I did not call phy_resume() because the resume path already
>> invokes phy_init_hw(), which internally calls
>> phydev->drv->config_init(). This reinitializes and reconfigures the PHY.
>
> Thank you for the explanation. May I suggest that you add a comment
> to that effect to the driver with your patch? This needs to be pointed
> out. Your code as such is fine. It just needs a comment.
Thank you for the suggestion. I agree and I'll add an explanatory
comment in the driver and update the patch accordingly.
Best regards,
Parthiban V
>
> Regards
> Oliver
>
>
^ permalink raw reply
* Re: [PATCH bpf v5 1/2] bpf: tcp: Reject non-TCP skb in bpf_sk_assign_tcp_reqsk()
From: Martin KaFai Lau @ 2026-04-02 5:47 UTC (permalink / raw)
To: Jiayuan Chen
Cc: bpf, Kuniyuki Iwashima, Daniel Borkmann, John Fastabend,
Stanislav Fomichev, Alexei Starovoitov, Andrii Nakryiko,
Eduard Zingerman, Song Liu, Yonghong Song, KP Singh, Hao Luo,
Jiri Olsa, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <20260401110511.73355-2-jiayuan.chen@linux.dev>
On Wed, Apr 01, 2026 at 07:04:49PM +0800, Jiayuan Chen wrote:
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 78b548158fb0..f251744025e2 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -12248,11 +12248,23 @@ __bpf_kfunc int bpf_sk_assign_tcp_reqsk(struct __sk_buff *s, struct sock *sk,
>
> switch (skb->protocol) {
> case htons(ETH_P_IP):
> + if (!pskb_may_pull(skb, sizeof(struct iphdr)))
The ai-review [1] is correct. skb_header_pointer() should be used.
pskb_may_pull() could invalidate the skb->data[_end] pointers
in the bpf prog.
[1]: https://sashiko.dev/#/patchset/20260401110511.73355-1-jiayuan.chen%40linux.dev
It needs one more spin. Thanks.
pw-bot: cr
^ permalink raw reply
* Re: [PATCH v4 net-next 5/5] net: pcs: pcs-mtk-lynxi: deprecate "mediatek,pnswap"
From: Frank Wunderlich @ 2026-04-02 5:50 UTC (permalink / raw)
To: Vladimir Oltean
Cc: netdev, devicetree, linux-kernel, linux-mediatek, Daniel Golle,
Horatiu Vultur, Bj√∏rn Mork, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Eric Woudstra, Alexander Couzens, Chester A. Unal, DENG Qingfang,
Sean Wang, Felix Fietkau
In-Reply-To: <20260330190443.bol5vjfqqitz7kuo@skbuf>
Hi,
i tried using these properties in sgmiisys0 node (which should be mapped to mac0 and the mt7530 switch) without success [1].
it looks like these properties are not read somewhere.
the flow is
mtk_probe (eth driver)
if (MTK_HAS_CAPS(eth->soc->caps, MTK_SGMII)) {
err = mtk_sgmii_init(eth);
and there calling mtk_pcs_lynxi_create with the sgmiisys-node (for each mac, so imho mac0=sgmiisys0)
but handling the sgmiisys only as syscon, not a "real" pcs node [2].
but your new code calls phy_get_tx_polarity and should read out this properties, but from subnode "pcs", so next try was
&sgmiisys0 {
pcs {
rx-polarity = <PHY_POL_NORMAL>;
tx-polarity = <PHY_POL_INVERT>;
};
};
which results in completely strange behaviour (looks like sgmiisys1 is mapped to mac0, but based on code in mtk_sgmii_init 0=0 should be right):
[ 2.765218] SGMSYS_QPHY_WRAP_CTRL = 0x501, will write 0x500
[ 9.143849] SGMSYS_QPHY_WRAP_CTRL = 0x500, will write 0x501
but nevertheless i tried changing sgmiisys0 to sgmiisys1 and got the dame result as before
[ 2.713644] SGMSYS_QPHY_WRAP_CTRL = 0x501, will write 0x500
[ 9.061509] SGMSYS_QPHY_WRAP_CTRL = 0x500, will write 0x500
i can only change the second serdes with sgmiisys0, but not the first.
mapping between mac and sgmiisys in dts in mt7986a.dtsi [3] are like this:
eth: ethernet@15100000 {
compatible = "mediatek,mt7986-eth";
mediatek,sgmiisys = <&sgmiisys0>, <&sgmiisys1>;
...
};
ð {
status = "okay";
gmac0: mac@0 {
compatible = "mediatek,eth-mac";
...
};
gmac1: mac@1 {
compatible = "mediatek,eth-mac";
...
};
};
maybe it is time to revive the PCS framework discussion ([4]-[6])?
[1] https://github.com/frank-w/BPI-Router-Linux/commit/4846a7bb352fe5911136cba33813f099bac035fd
[2] https://elixir.bootlin.com/linux/v7.0-rc4/source/drivers/net/ethernet/mediatek/mtk_eth_soc.c#L5001
[3] https://elixir.bootlin.com/linux/v7.0-rc4/source/arch/arm64/boot/dts/mediatek/mt7986a.dtsi#L528
[4] * https://patchwork.kernel.org/project/netdevbpf/patch/20250610233134.3588011-4-sean.anderson@linux.dev/ (v6)
> pcs-framework itself had not yet got a response from netdev maintainer (only other parts)
[5] * https://patchwork.kernel.org/project/netdevbpf/patch/20250511201250.3789083-4-ansuelsmth@gmail.com/ (v4)
> discussion: https://lore.kernel.org/netdev/20250511201250.3789083-1-ansuelsmth@gmail.com/
[6] * https://patchwork.kernel.org/project/netdevbpf/patch/ba4e359584a6b3bc4b3470822c42186d5b0856f9.1721910728.git.daniel@makrotopia.org/
> discussion: https://patchwork.kernel.org/project/netdevbpf/patch/8aa905080bdb6760875d62cb3b2b41258837f80e.1702352117.git.daniel@makrotopia.org/
Am 30. März 2026 um 21:04 schrieb "Vladimir Oltean" <vladimir.oltean@nxp.com mailto:vladimir.oltean@nxp.com?to=%22Vladimir%20Oltean%22%20%3Cvladimir.oltean%40nxp.com%3E >:
>
> Hi Frank,
>
> On Mon, Mar 30, 2026 at 05:52:17PM +0000, Frank Wunderlich wrote:
>
> >
> > Hi Vladimir
> >
> > Thanks for the patch and sorry for my delay...i was away this weekend so i was not able to test.
> >
> > traffic works again (but there is only read now) and this is the result of your debug prints:
> >
> > root@bpi-r3:~# mailto:root@bpi-r3:~# dmesg | grep SGMSYS_QPHY_WRAP_CTRL
> > [ 2.706963] SGMSYS_QPHY_WRAP_CTRL = 0x501, intending to write 0x500
> > [ 9.134081] SGMSYS_QPHY_WRAP_CTRL = 0x500, intending to write 0x500
> >
> > R3/mt7986 has 2 MAC, and switch is on the first, so value will change, not sure why this is different.
> >
> > i have not found SGMSYS_QPHY_WRAP_CTRL or something related with polarity in ethernet/mac-
> > (drivers/net/ethernet/mediatek/mtk_eth_soc.c) or switch-driver (drivers/net/dsa/mt7530{,-mdio}.c)
> > in case they manipulate this register too (of course they should not). Also looked into the pcs-handling
> > in both drivers, but see nothing related to polarity. And looked for possible duplicate register const
> > definition (other name for 0xec).
> >
> This result means that your default QPHY_WRAP_CTRL register value has
> the SGMII_PN_SWAP_TX bit set. Whether that comes from U-Boot or hardware
> default or otherwise, it doesn't really matter. Curious that the
> SGMII_SW_RESET doesn't clear TX inversion, though. I guess you wouldn't
> have documentation that would suggest this setting is sticky?
>
> In Documentation/devicetree/bindings/net/pcs/mediatek,sgmiisys.yaml,
> it is not specified what happens when the "mediatek,pnswap" property is
> missing. I thought the most logical thing would be for the lane
> polarities to not be swapped - because how would you describe normal
> lane polarities otherwise? My bad for thinking the original vendor
> bindings were more sane than they were.
>
> The only way to describe the polarities that this SGMSYS block needs on
> a particular board is to use the newly introduced 'rx-polarity =
> <PHY_POL_NORMAL>' and 'tx-polarity = <PHY_POL_INVERT>'. Which I strongly
> recommend you to do, even if the attached patch should restore
> functionality with your current device tree.
>
regards Frank
^ permalink raw reply
* [PATCH net-next] net/mlx5: Use dma_wmb() for completion queue doorbell updates
From: lirongqing @ 2026-04-02 5:52 UTC (permalink / raw)
To: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch,
Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Alexei Starovoitov, Daniel Borkmann,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
Boris Pismenny, Richard Cochran, Cosmin Ratiu, Dragos Tatulea,
Carolina Jubran, Li RongQing, Kees Cook, Akiva Goldberger,
Simon Horman, netdev, linux-rdma, linux-kernel, bpf
From: Li RongQing <lirongqing@baidu.com>
dma_wmb() barriers are specifically for ordering writes to DMA
coherent memory that is accessible to both the CPU and DMA capable
devices.
The dma_wmb() barrier is lighter than wmb() on some architectures
because it only ensures ordering for DMA writes, not for all writes
including MMIO accesses.
In the MLX5 driver, completion queue (CQ) doorbell records are
allocated as DMA coherent memory via mlx5_dma_zalloc_coherent_node().
The CQ update pattern is:
1. Update CQ space (device reads via DMA)
2. Update doorbell record (device reads via DMA)
3. Memory barrier
4. Enable more CQEs
Since only DMA coherent memory accesses are involved (no MMIO accesses
follow), can safely use dma_wmb() instead of wmb().
This change improves performance slightly on architectures where
dma_wmb() is lighter than wmb().
Signed-off-by: Li RongQing <lirongqing@baidu.com>
---
drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/en_tx.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/fpga/conn.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/lib/aso.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/wc.c | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c
index 1b76647..7bd6dfc 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c
@@ -259,7 +259,7 @@ static bool mlx5e_ptp_poll_ts_cq(struct mlx5e_cq *cq, int napi_budget)
mlx5_cqwq_update_db_record(cqwq);
/* ensure cq space is freed before enabling more cqes */
- wmb();
+ dma_wmb();
while (metadata_buff_sz > 0)
mlx5e_ptp_metadata_fifo_push(&ptpsq->metadata_freelist,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c
index 80f9fc1..dde8856 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c
@@ -805,7 +805,7 @@ bool mlx5e_poll_xdpsq_cq(struct mlx5e_cq *cq)
mlx5_cqwq_update_db_record(&cq->wq);
/* ensure cq space is freed before enabling more cqes */
- wmb();
+ dma_wmb();
sq->cc = sqcc;
return (i == MLX5E_TX_CQ_POLL_BUDGET);
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c
index 268e208..f17e7f1 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c
@@ -2447,7 +2447,7 @@ int mlx5e_poll_rx_cq(struct mlx5e_cq *cq, int budget)
mlx5_cqwq_update_db_record(cqwq);
/* ensure cq space is freed before enabling more cqes */
- wmb();
+ dma_wmb();
return work_done;
}
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
index 9f02726..7ba319f 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
@@ -849,7 +849,7 @@ bool mlx5e_poll_tx_cq(struct mlx5e_cq *cq, int napi_budget)
mlx5_cqwq_update_db_record(&cq->wq);
/* ensure cq space is freed before enabling more cqes */
- wmb();
+ dma_wmb();
sq->dma_fifo_cc = dma_fifo_cc;
sq->cc = sqcc;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fpga/conn.c b/drivers/net/ethernet/mellanox/mlx5/core/fpga/conn.c
index 1f6bde5..1341874 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/fpga/conn.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/fpga/conn.c
@@ -384,7 +384,7 @@ static inline void mlx5_fpga_conn_cqes(struct mlx5_fpga_conn *conn,
mlx5_fpga_dbg(conn->fdev, "Re-arming CQ with cc# %u\n", conn->cq.wq.cc);
/* ensure cq space is freed before enabling more cqes */
- wmb();
+ dma_wmb();
mlx5_fpga_conn_arm_cq(conn);
}
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/aso.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/aso.c
index 614cd57..8f7a89a 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/lib/aso.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/aso.c
@@ -421,7 +421,7 @@ int mlx5_aso_poll_cq(struct mlx5_aso *aso, bool with_data)
mlx5_cqwq_update_db_record(&cq->wq);
/* ensure cq space is freed before enabling more cqes */
- wmb();
+ dma_wmb();
if (with_data)
aso->cc += MLX5_ASO_WQEBBS_DATA;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/wc.c b/drivers/net/ethernet/mellanox/mlx5/core/wc.c
index 7d3d4d7..1afbdd19 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/wc.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/wc.c
@@ -314,7 +314,7 @@ static void mlx5_wc_post_nop(struct mlx5_wc_sq *sq, unsigned int *offset,
/* ensure doorbell record is visible to device before ringing the
* doorbell
*/
- wmb();
+ dma_wmb();
mlx5_iowrite64_copy(sq, mmio_wqe, sizeof(mmio_wqe), *offset);
--
2.9.4
^ permalink raw reply related
* 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