* Re: [RFC PATCH 3/3] coredump, net: remove `SOCK_COREDUMP`
From: Christian Brauner @ 2026-07-03 8:11 UTC (permalink / raw)
To: John Ericson
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
John Ericson, Cong Wang, Kuniyuki Iwashima, Simon Horman,
Christian Brauner, David Rheinsberg, Andy Lutomirski,
Sergei Zimmerman, netdev, linux-fsdevel, Mickaël Salaün,
Günther Noack, Paul Moore, linux-security-module,
linux-kernel
In-Reply-To: <20260703073948.2541875-4-John.Ericson@Obsidian.Systems>
> The utility of the refactors of the last two commits is demonstrated by
> fixing this glaring layer violation: the unix socket implementation
> knowing about the coredump socket caller.
>
> Before, when the only way to connect to a socket was via the UAPI
> `struct sockaddr_un`, the only way to implement the proper logic that
> the kernel needs to resolve the coredump socket path was via hacking it
> into the socket implementation.
>
> In addition to being quite ugly, this layer violation is not great for
> security. The intent is that `SOCK_COREDUMP` can only be used by the
> kernel, and to be clear, I have no reason to believe this is not
> correctly enforced today. But because of the many functions with flags
> arguments, this is not a locally-enforced invariant. Some change, at
> some point, could mess up, and allow user-provided flags to sneak in,
> and this strikes me as a mistake waiting to happen. At that point, a
> user could exploit this to connect to any socket it likes, bypassing
> permission checks.
>
> Now, with the two functions we've just previously factored out, we can
> fix the layering. The custom path lookup logic lives with the coredump
> caller, where it belongs. Once the right `struct path` is found
> (actually just the inode is needed), the coredump caller can resolve a
> `struct sock *` from it, and then directly connect to it. With this
> change, `SOCK_COREDUMP` is no longer needed at all, and can be deleted.
> The layer violation is gone, and the security footgun is gone with it.
>
> As an added bonus, remove `flags` parameters from a number of internal
> functions that no longer need them. They were just taking flags
> parameters for the sake of `SOCK_COREDUMP`, and so with that gone, those
> flag parameters are also no longer needed. If or when there is a new
> flag that motivates them, they can be added back.
>
> Tested that `coredump_socket_protocol_test` still passes.
>
> Signed-off-by: John Ericson <mail@JohnEricson.me>
>
> diff --git a/fs/coredump.c b/fs/coredump.c
> index e68a76ff92a3..e1452021218e 100644
> --- a/fs/coredump.c
> +++ b/fs/coredump.c
> @@ -14,6 +14,7 @@
> #include <linux/perf_event.h>
> #include <linux/highmem.h>
> #include <linux/spinlock.h>
> +#include <linux/cred.h>
> #include <linux/key.h>
> #include <linux/personality.h>
> #include <linux/binfmts.h>
> @@ -21,6 +22,7 @@
> #include <linux/sort.h>
> #include <linux/sched/coredump.h>
> #include <linux/sched/signal.h>
> +#include <linux/sched/task.h>
> #include <linux/sched/task_stack.h>
> #include <linux/utsname.h>
> #include <linux/pid_namespace.h>
> @@ -50,7 +52,6 @@
> #include <net/net_namespace.h>
> #include <net/sock.h>
> #include <uapi/linux/pidfd.h>
> -#include <uapi/linux/un.h>
> #include <uapi/linux/coredump.h>
>
> #include <linux/uaccess.h>
> @@ -668,17 +669,10 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)
> static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)
> {
> struct file *file __free(fput) = NULL;
> - struct sockaddr_un addr = {
> - .sun_family = AF_UNIX,
> - };
> - ssize_t addr_len;
> - int retval;
> + struct path root, path;
> struct socket *socket;
> -
> - addr_len = strscpy(addr.sun_path, cn->corename);
> - if (addr_len < 0)
> - return false;
> - addr_len += offsetof(struct sockaddr_un, sun_path) + 1;
> + struct sock *sk;
> + int retval;
>
> /*
> * It is possible that the userspace process which is supposed
> @@ -710,14 +704,37 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
> */
> pidfs_coredump(cprm);
>
> - retval = kernel_connect(socket, (struct sockaddr_unsized *)(&addr), addr_len,
> - O_NONBLOCK | SOCK_COREDUMP);
> + /*
> + * Resolve the socket path relative to init's root and with kernel
> + * credentials, and with symlinks, magic links and escaping the
> + * root all forbidden, so the dumping process cannot use its own
> + * filesystem view to redirect its core to an arbitrary socket.
> + */
> + task_lock(&init_task);
> + get_fs_root(init_task.fs, &root);
> + task_unlock(&init_task);
> +
> + scoped_with_kernel_creds()
> + retval = vfs_path_lookup(root.dentry, root.mnt, cn->corename,
> + LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS |
> + LOOKUP_NO_MAGICLINKS, &path);
> + path_put(&root);
> + if (retval)
> + return false;
> +
> + /* Connect directly to the socket bound there, by fd not by name. */
> + sk = unix_lookup_bsd_path(&path, SOCK_STREAM);
> + path_put(&path);
> + if (IS_ERR(sk))
> + return false;
>
> + retval = kernel_unix_connect_direct(sk, socket, O_NONBLOCK);
I don't think dragging the bowels of net/ into fs/coredump.c just for
the sake of getting a purely internal SOCK_COREDUMP flag out of the way
is the right way to go.
The two helpers also make no sense to me and force a bunch of cleanup on
the caller as well.
I suspect the saner thing to do would be to introduce a primitive for
connecting to an AF_UNIX path-based socket directly instead of this
weird dance here, no?
int kernel_unix_connect(const char *path, struct socket *socket, unsigned int o_flags, int type)
then my kthread changes would collapse this into:
scoped_with_init_fs()
retval = kernel_unix_connect(path, socket, O_NONBLOCK, SOCK_STREAM);
> + sock_put(sk);
> if (retval) {
> if (retval == -EAGAIN)
> - coredump_report_failure("Coredump socket %s receive queue full", addr.sun_path);
> + coredump_report_failure("Coredump socket %s receive queue full", cn->corename);
> else
> - coredump_report_failure("Coredump socket connection %s failed %d", addr.sun_path, retval);
> + coredump_report_failure("Coredump socket connection %s failed %d", cn->corename, retval);
> return false;
> }
>
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..3d6fbb6d2628 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -323,8 +323,7 @@ LSM_HOOK(int, 0, watch_key, struct key *key)
> #endif /* CONFIG_SECURITY && CONFIG_KEY_NOTIFICATIONS */
>
> #if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH)
> -LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other,
> - int flags)
> +LSM_HOOK(int, 0, unix_find, const struct path *path, struct sock *other)
I'm not sure why we would go on altering all kinds of LSM hooks as well.
--
Christian Brauner <brauner@kernel.org>
^ permalink raw reply
* [PATCH net v2 1/3] af_unix: fix listen() succeeding on sockets in the wrong state
From: John Ericson @ 2026-07-03 8:14 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Kuniyuki Iwashima
Cc: John Ericson, Simon Horman, Christian Brauner, David Rheinsberg,
Cong Wang, John Ericson, Sergei Zimmerman, netdev, linux-kernel
From: John Ericson <mail@johnericson.me>
Commit fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for
reaped sk->sk_peer_pid") inserted a `prepare_peercred()` call between
`err = -EINVAL` and the socket-state check in `unix_listen()`. Since
`prepare_peercred()` leaves `err` at 0 on success, `listen()` on an
AF_UNIX socket that is not in `TCP_CLOSE` or `TCP_LISTEN` state (e.g.
one that is already connected) now silently returns success without
doing anything, instead of failing with `EINVAL` as it did before.
Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
Assisted-by: Claude:claude-fable-5
Signed-off-by: John Ericson <mail@johnericson.me>
---
net/unix/af_unix.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..10ed9421e43a 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -823,6 +823,7 @@ static int unix_listen(struct socket *sock, int backlog)
if (err)
goto out;
unix_state_lock(sk);
+ err = -EINVAL;
if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN)
goto out_unlock;
if (backlog > sk->sk_max_ack_backlog)
--
2.54.0
^ permalink raw reply related
* [PATCH net v2 2/3] selftests/net/af_unix: test listen() rejects wrong socket states
From: John Ericson @ 2026-07-03 8:14 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Kuniyuki Iwashima
Cc: John Ericson, Simon Horman, Christian Brauner, David Rheinsberg,
Cong Wang, John Ericson, Sergei Zimmerman, netdev, linux-kernel
In-Reply-To: <20260703081416.2583118-1-John.Ericson@Obsidian.Systems>
From: John Ericson <mail@johnericson.me>
Add a regression test for the `unix_listen()` state check. The key case
is `listen()` on a bound socket that has already been connected: it is
no longer in `TCP_CLOSE` or `TCP_LISTEN`, so it must fail with `EINVAL`.
A `prepare_peercred()` call slipped in ahead of that check once left
`err` at 0 and made `listen()` silently succeed there instead; this
guards against a repeat.
The neighbouring outcomes are covered too so they cannot regress the
same way: a bound socket in `TCP_CLOSE` listens fine, re-`listen()`ing a
socket already in `TCP_LISTEN` is allowed, and an unbound socket fails
with `EINVAL`.
Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: John Ericson <mail@johnericson.me>
---
.../testing/selftests/net/af_unix/.gitignore | 1 +
tools/testing/selftests/net/af_unix/Makefile | 1 +
.../selftests/net/af_unix/unix_listen.c | 126 ++++++++++++++++++
3 files changed, 128 insertions(+)
create mode 100644 tools/testing/selftests/net/af_unix/unix_listen.c
diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 240b26740c9e..973176644103 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -6,3 +6,4 @@ scm_rights
so_peek_off
unix_connect
unix_connreset
+unix_listen
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 4c0375e28bbe..57d159803a3a 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -14,6 +14,7 @@ TEST_GEN_PROGS := \
so_peek_off \
unix_connect \
unix_connreset \
+ unix_listen \
# end of TEST_GEN_PROGS
include ../../lib.mk
diff --git a/tools/testing/selftests/net/af_unix/unix_listen.c b/tools/testing/selftests/net/af_unix/unix_listen.c
new file mode 100644
index 000000000000..7b5264c97f52
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/unix_listen.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the state checks in AF_UNIX listen().
+ *
+ * The central case is a regression test: listen() on a bound socket that
+ * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must
+ * fail with EINVAL. A prior change accidentally let it return success
+ * without doing anything, because a helper called in between reset the
+ * error code to 0. The neighbouring checks (unbound, already listening)
+ * are tested too so they cannot silently regress the same way.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "kselftest_harness.h"
+
+/* Fill @addr with an abstract-namespace address named @name. */
+static socklen_t unix_abstract(struct sockaddr_un *addr, const char *name)
+{
+ size_t len = strlen(name);
+
+ memset(addr, 0, sizeof(*addr));
+ addr->sun_family = AF_UNIX;
+ /* Leading NUL selects the abstract namespace (no filesystem entry). */
+ memcpy(addr->sun_path + 1, name, len);
+
+ return offsetof(struct sockaddr_un, sun_path) + 1 + len;
+}
+
+FIXTURE(unix_listen)
+{
+ int sk; /* socket under test */
+ int server; /* a listening peer, when a test needs one */
+ struct sockaddr_un addr, srv_addr;
+ socklen_t addrlen, srv_addrlen;
+};
+
+FIXTURE_SETUP(unix_listen)
+{
+ self->sk = -1;
+ self->server = -1;
+ self->addrlen = unix_abstract(&self->addr, "unix_listen.sk");
+ self->srv_addrlen = unix_abstract(&self->srv_addr, "unix_listen.srv");
+}
+
+FIXTURE_TEARDOWN(unix_listen)
+{
+ if (self->sk >= 0)
+ close(self->sk);
+ if (self->server >= 0)
+ close(self->server);
+ /* Abstract addresses are released automatically on close. */
+}
+
+/* A bound socket in TCP_CLOSE is the normal, allowed case. */
+TEST_F(unix_listen, bound_is_ok)
+{
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+ ASSERT_EQ(0, bind(self->sk, (struct sockaddr *)&self->addr,
+ self->addrlen));
+
+ EXPECT_EQ(0, listen(self->sk, 8));
+}
+
+/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */
+TEST_F(unix_listen, relisten_is_ok)
+{
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+ ASSERT_EQ(0, bind(self->sk, (struct sockaddr *)&self->addr,
+ self->addrlen));
+ ASSERT_EQ(0, listen(self->sk, 8));
+
+ EXPECT_EQ(0, listen(self->sk, 16));
+}
+
+/* listen() on an unbound socket fails: there is nothing to listen on. */
+TEST_F(unix_listen, unbound_is_einval)
+{
+ int ret;
+
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+
+ ret = listen(self->sk, 8);
+ EXPECT_EQ(-1, ret);
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * The regression: a bound socket that has already been connected is not in
+ * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather
+ * than quietly succeeding.
+ */
+TEST_F(unix_listen, connected_is_einval)
+{
+ int ret;
+
+ self->server = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->server);
+ ASSERT_EQ(0, bind(self->server, (struct sockaddr *)&self->srv_addr,
+ self->srv_addrlen));
+ ASSERT_EQ(0, listen(self->server, 8));
+
+ self->sk = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, self->sk);
+ /* Bind first so the unbound check does not mask the state check. */
+ ASSERT_EQ(0, bind(self->sk, (struct sockaddr *)&self->addr,
+ self->addrlen));
+ ASSERT_EQ(0, connect(self->sk, (struct sockaddr *)&self->srv_addr,
+ self->srv_addrlen));
+
+ ret = listen(self->sk, 8);
+ EXPECT_EQ(-1, ret);
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_HARNESS_MAIN
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v2 3/3] af_unix: Clean up error handling in unix_listen()
From: John Ericson @ 2026-07-03 8:14 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Kuniyuki Iwashima
Cc: John Ericson, Simon Horman, Christian Brauner, David Rheinsberg,
Cong Wang, John Ericson, Sergei Zimmerman, netdev, linux-kernel
In-Reply-To: <20260703081416.2583118-1-John.Ericson@Obsidian.Systems>
From: John Ericson <mail@johnericson.me>
To avoid the sort of bug that was just fixed going forward, switch to a
style where `err = -E...;` instead happens right before the `goto`.
(`err = other_function(...);` is not changed.) Then there is no
spooky-action-at-a-distance between the `err` initialization and the
`goto`, something which is easier to slip by code review.
Assisted-by: Claude:claude-fable-5
Signed-off-by: John Ericson <mail@johnericson.me>
---
net/unix/af_unix.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 10ed9421e43a..7878b27bbaf8 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -813,19 +813,22 @@ static int unix_listen(struct socket *sock, int backlog)
struct unix_sock *u = unix_sk(sk);
struct unix_peercred peercred = {};
- err = -EOPNOTSUPP;
- if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET)
+ if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET) {
+ err = -EOPNOTSUPP;
goto out; /* Only stream/seqpacket sockets accept */
- err = -EINVAL;
- if (!READ_ONCE(u->addr))
+ }
+ if (!READ_ONCE(u->addr)) {
+ err = -EINVAL;
goto out; /* No listens on an unbound socket */
+ }
err = prepare_peercred(&peercred);
if (err)
goto out;
unix_state_lock(sk);
- err = -EINVAL;
- if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN)
+ if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN) {
+ err = -EINVAL;
goto out_unlock;
+ }
if (backlog > sk->sk_max_ack_backlog)
wake_up_interruptible_all(&u->peer_wait);
sk->sk_max_ack_backlog = backlog;
--
2.54.0
^ permalink raw reply related
* [PATCH] wanxl: Remove pci_map_single_debug()
From: Bence Csokas @ 2026-07-03 8:21 UTC (permalink / raw)
To: Krzysztof Halasa, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
Cc: Bence Csokas
Since commit 24dd377a76b0 ("wan: wanxl: switch from 'pci_' to 'dma_' API")
this has been dead code anyways. The pci_map_single() function it attempts
to redefine has been removed in 7968778914e5 ("PCI: Remove the deprecated
"pci-dma-compat.h" API").
Signed-off-by: Bence Csokas <bence.csokas@arm.com>
---
drivers/net/wan/wanxl.c | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/drivers/net/wan/wanxl.c b/drivers/net/wan/wanxl.c
index d4da88c77112..1540ce0a499d 100644
--- a/drivers/net/wan/wanxl.c
+++ b/drivers/net/wan/wanxl.c
@@ -88,22 +88,6 @@ static inline port_status_t *get_status(struct port *port)
return &port->card->status->port_status[port->node];
}
-#ifdef DEBUG_PCI
-static inline dma_addr_t pci_map_single_debug(struct pci_dev *pdev, void *ptr,
- size_t size, int direction)
-{
- dma_addr_t addr = dma_map_single(&pdev->dev, ptr, size, direction);
-
- if (addr + size > 0x100000000LL)
- pr_crit("%s: pci_map_single() returned memory at 0x%llx!\n",
- pci_name(pdev), (unsigned long long)addr);
- return addr;
-}
-
-#undef pci_map_single
-#define pci_map_single pci_map_single_debug
-#endif
-
/* Cable and/or personality module change interrupt service */
static inline void wanxl_cable_intr(struct port *port)
{
---
base-commit: 51512e22efe813d8223de27f6fd02a8a48ea2323
change-id: 20260427-wanxl-cleanup-7e59607dc4f9
Best regards,
--
Bence Csokas <bence.csokas@arm.com>
^ permalink raw reply related
* Re: [PATCH net] gue: validate REMCSUM private option length
From: patchwork-bot+netdevbpf @ 2026-07-03 8:35 UTC (permalink / raw)
To: Qihang; +Cc: netdev, edumazet, kuba, davem, pabeni
In-Reply-To: <20260701022617.53171-1-q.h.hack.winter@gmail.com>
Hello:
This patch was applied to netdev/net.git (main)
by David S. Miller <davem@davemloft.net>:
On Wed, 1 Jul 2026 10:26:17 +0800 you wrote:
> GUE private flags can indicate that remote checksum offload metadata is
> present. The private flags field itself is accounted for by
> guehdr_flags_len(), but guehdr_priv_flags_len() currently returns 0 even
> when GUE_PFLAG_REMCSUM is set.
>
> This lets a packet with only the private flags field pass
> validate_gue_flags(), after which gue_remcsum() and gue_gro_remcsum()
> read the missing REMCSUM start/offset fields from the following bytes.
>
> [...]
Here is the summary with links:
- [net] gue: validate REMCSUM private option length
https://git.kernel.org/netdev/net/c/d335dcc6f521
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH net] macsec: don't read an unset MAC header in macsec_encrypt()
From: Daehyeon Ko @ 2026-07-03 8:36 UTC (permalink / raw)
To: netdev
Cc: Sabrina Dubroca, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-kernel, Daehyeon Ko, stable
macsec_encrypt() reads the Ethernet header via eth_hdr(skb)
(skb->head + skb->mac_header) to memmove() the 12 source/destination MAC
bytes forward and make room for the SecTAG.
On the AF_PACKET SOCK_RAW + PACKET_QDISC_BYPASS transmit path the skb
reaches the macsec ndo_start_xmit() with the MAC header unset, so
eth_hdr(skb) resolves to skb->head + (u16)~0 and the read is out of
bounds: a 12-byte heap over-read that is also emitted on the wire as the
frame's outer source/destination MAC. KASAN reports a slab-out-of-bounds
read in macsec_start_xmit() on 6.0; on current mainline a CONFIG_DEBUG_NET
build flags it as an unset mac header in skb_mac_header().
On the TX path the L2 header is at skb->data, so use skb_eth_hdr(), added
by commit 96cc4b69581d ("macvlan: do not assume mac_header is set in
macvlan_broadcast()") for exactly this purpose.
Fixes: c09440f7dcb3 ("macsec: introduce IEEE 802.1AE driver")
Cc: stable@vger.kernel.org
Signed-off-by: Daehyeon Ko <4ncienth@gmail.com>
---
drivers/net/macsec.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c
index fb009120a924..dd89282f0179 100644
--- a/drivers/net/macsec.c
+++ b/drivers/net/macsec.c
@@ -646,7 +646,7 @@ static struct sk_buff *macsec_encrypt(struct sk_buff *skb,
}
unprotected_len = skb->len;
- eth = eth_hdr(skb);
+ eth = skb_eth_hdr(skb);
sci_present = macsec_send_sci(secy);
hh = skb_push(skb, macsec_extra_len(sci_present));
memmove(hh, eth, 2 * ETH_ALEN);
--
2.54.0
^ permalink raw reply related
* [PATCH net] gtp: parse extension headers before reading inner protocol
From: Zhixing Chen @ 2026-07-03 8:42 UTC (permalink / raw)
To: Pablo Neira Ayuso, Harald Welte
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, osmocom-net-gprs, netdev, Zhixing Chen
GTPv1-U packets may carry a chain of extension headers before the inner
IP packet. The receive path already parses and skips these extension
headers, but it currently reads the inner protocol before doing so.
As a result, the first extension header byte is interpreted as the inner
IP version. Packets with extension headers are then dropped before PDP
lookup.
Parse the extension header chain before calling gtp_inner_proto(), so the
inner protocol is read from the actual inner IP header.
Signed-off-by: Zhixing Chen <running910@gmail.com>
---
I noticed this while running a few GTP tunnel tests with a veth pair and a
peer network namespace.
The commands below only set up a small GTPv1-U demo and verify that the
plain tunnel works as expected:
ip link add vroot type veth peer name vpeer
ip a a 172.0.0.1/24 dev vroot
ip link set vroot up
ip a a 172.99.0.1/32 dev lo
gtp-link add gtp0 ip 172.0.0.1 &
gtp-tunnel add gtp0 v1 200 100 172.99.0.2 172.0.0.2
ip r a 172.99.0.2/32 dev gtp0
ip link set gtp0 mtu 1500
ip netns add nspeer
ip link set vpeer netns nspeer
ip netns exec nspeer ip a a 172.0.0.2/24 dev vpeer
ip netns exec nspeer ip link set vpeer up
ip netns exec nspeer ip a a 172.99.0.2/32 dev lo
ip netns exec nspeer ip link set lo up
ip netns exec nspeer gtp-link add gtp1 ip 172.0.0.2 &
ip netns exec nspeer gtp-tunnel add gtp1 v1 100 200 172.99.0.1 172.0.0.1
ip netns exec nspeer ip r a 172.99.0.1/32 dev gtp1
ip netns exec nspeer ip link set gtp1 mtu 1500
With this setup, plain traffic between 172.99.0.1 and 172.99.0.2 goes
through the GTP tunnel.
After that, I used a small sender in the peer namespace to build two
UDP/2152 packets for the root namespace GTP endpoint. Both packets use TEID
200 and carry an inner UDP packet from 172.99.0.2:12345 to
172.99.0.1:9999. The first packet is a plain GTPv1-U T-PDU. The second
packet carries the same inner UDP packet after a GTP extension header.
Before this fix, a receiver bound to 172.99.0.1:9999 only receives the
plain packet:
root@vm:/# python gtp_nsroot_recv.py
listening on 172.99.0.1:9999
received #1 from ('172.99.0.2', 12345): b'plain'
After this fix, it receives both packets:
root@vm:/# python gtp_nsroot_recv.py
listening on 172.99.0.1:9999
received #1 from ('172.99.0.2', 12345): b'plain'
received #2 from ('172.99.0.2', 12345): b'extension'
---
drivers/net/gtp.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index a60ef32b35b8..4a8b00548673 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -826,6 +826,10 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
if (!pskb_may_pull(skb, hdrlen))
return -1;
+ if (gtp1->flags & GTP1_F_EXTHDR &&
+ gtp_parse_exthdrs(skb, &hdrlen) < 0)
+ return -1;
+
if (gtp_inner_proto(skb, hdrlen, &inner_proto) < 0) {
netdev_dbg(gtp->dev, "GTP packet does not encapsulate an IP packet\n");
return -1;
@@ -840,10 +844,6 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
return 1;
}
- if (gtp1->flags & GTP1_F_EXTHDR &&
- gtp_parse_exthdrs(skb, &hdrlen) < 0)
- return -1;
-
return gtp_rx(pctx, skb, hdrlen, gtp->role, inner_proto);
}
--
2.34.1
^ permalink raw reply related
* Re: [PATCH net] net/stmmac: Set Rx queue page_pool to NULL when freeing DMA resources
From: Jakub Raczynski @ 2026-07-03 8:43 UTC (permalink / raw)
To: Paolo Abeni
Cc: netdev, andrew+netdev, davem, edumazet, kuba, mcoquelin.stm32,
linux-kernel, k.tegowski, k.domagalski, yashwant.v
In-Reply-To: <20260703072651.110614-1-pabeni@redhat.com>
[-- Attachment #1: Type: text/plain, Size: 2140 bytes --]
For starters I would like to comment that I am sending this fix because this
actually happened in our environment, so this is not some fake AI report,
which I would credit with Reported-by if it was.
Sadly, no panic logs available since it happened long ago and this fix has
been for a while in our code.
Comments for AI review below
On Fri, Jul 03, 2026 at 09:26:51AM +0200, Paolo Abeni wrote:
> From: AI Reviewer <ai@example.com>
>
> - read rx_q->buf_pool[i] in dma_free_rx_skbufs() ->
> stmmac_free_rx_buffer() from already-freed slab memory:
>
> static void stmmac_free_rx_buffer(...)
> {
> struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
> if (buf->page)
> page_pool_put_full_page(rx_q->page_pool, buf->page, false);
> ...
> }
Comment invalid, I love how AI ignored literally next line, a bit more code:
>> static void stmmac_free_rx_buffer(struct stmmac_priv *priv,
>> struct stmmac_rx_queue *rx_q,
>> int i)
>>{
>> struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i];
>>
>> if (buf->page)
>> page_pool_put_full_page(rx_q->page_pool, buf->page, false);
>> buf->page = NULL;
>
> - call dma_free_coherent() a second time on the stale
> rx_q->dma_rx/dma_erx and rx_q->dma_rx_phy?
That is completely separate issue and fix is not what AI suggets.
Because if we zero rx_q->dma_erx or rx_q->dma_rx, dma_free_coherent() will run
regardless on NULL pointer then. We probably should lock dma_free_coherent()
behind simple NULL check and zero it, but this will be separate patch if so.
>
> - call kfree() a second time on the stale rx_q->buf_pool?
Invalid, kfree() does have 'if (unlikely(ZERO_OR_NULL_PTR(object)))' check
>
> Would also nulling rx_q->buf_pool, rx_q->dma_rx, rx_q->dma_erx, and
> rx_q->dma_rx_phy in __free_dma_rx_desc_resources() (or having the alloc
> path defensively reset them before any early return) be appropriate to
> cover the same failure mode the commit message describes?
"Would this be appropriate to cover same failures in same commit"
- Nah
"[...] in same patchset
- Maybe
BR
Jakub Raczynski
[-- Attachment #2: Type: text/plain, Size: 0 bytes --]
^ permalink raw reply
* Re: [PATCH net] gtp: parse extension headers before reading inner protocol
From: Pablo Neira Ayuso @ 2026-07-03 8:54 UTC (permalink / raw)
To: Zhixing Chen
Cc: Harald Welte, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, osmocom-net-gprs, netdev
In-Reply-To: <20260703084244.59077-1-running910@gmail.com>
On Fri, Jul 03, 2026 at 04:42:44PM +0800, Zhixing Chen wrote:
> GTPv1-U packets may carry a chain of extension headers before the inner
> IP packet. The receive path already parses and skips these extension
> headers, but it currently reads the inner protocol before doing so.
>
> As a result, the first extension header byte is interpreted as the inner
> IP version. Packets with extension headers are then dropped before PDP
> lookup.
>
> Parse the extension header chain before calling gtp_inner_proto(), so the
> inner protocol is read from the actual inner IP header.
Missing Fixes: tag.
> Signed-off-by: Zhixing Chen <running910@gmail.com>
> ---
>
> I noticed this while running a few GTP tunnel tests with a veth pair and a
> peer network namespace.
>
> The commands below only set up a small GTPv1-U demo and verify that the
> plain tunnel works as expected:
>
> ip link add vroot type veth peer name vpeer
> ip a a 172.0.0.1/24 dev vroot
> ip link set vroot up
> ip a a 172.99.0.1/32 dev lo
> gtp-link add gtp0 ip 172.0.0.1 &
> gtp-tunnel add gtp0 v1 200 100 172.99.0.2 172.0.0.2
> ip r a 172.99.0.2/32 dev gtp0
> ip link set gtp0 mtu 1500
>
> ip netns add nspeer
> ip link set vpeer netns nspeer
> ip netns exec nspeer ip a a 172.0.0.2/24 dev vpeer
> ip netns exec nspeer ip link set vpeer up
> ip netns exec nspeer ip a a 172.99.0.2/32 dev lo
> ip netns exec nspeer ip link set lo up
>
> ip netns exec nspeer gtp-link add gtp1 ip 172.0.0.2 &
> ip netns exec nspeer gtp-tunnel add gtp1 v1 100 200 172.99.0.1 172.0.0.1
> ip netns exec nspeer ip r a 172.99.0.1/32 dev gtp1
> ip netns exec nspeer ip link set gtp1 mtu 1500
>
> With this setup, plain traffic between 172.99.0.1 and 172.99.0.2 goes
> through the GTP tunnel.
>
> After that, I used a small sender in the peer namespace to build two
> UDP/2152 packets for the root namespace GTP endpoint. Both packets use TEID
> 200 and carry an inner UDP packet from 172.99.0.2:12345 to
> 172.99.0.1:9999. The first packet is a plain GTPv1-U T-PDU. The second
> packet carries the same inner UDP packet after a GTP extension header.
>
> Before this fix, a receiver bound to 172.99.0.1:9999 only receives the
> plain packet:
>
> root@vm:/# python gtp_nsroot_recv.py
> listening on 172.99.0.1:9999
> received #1 from ('172.99.0.2', 12345): b'plain'
>
> After this fix, it receives both packets:
>
> root@vm:/# python gtp_nsroot_recv.py
> listening on 172.99.0.1:9999
> received #1 from ('172.99.0.2', 12345): b'plain'
> received #2 from ('172.99.0.2', 12345): b'extension'
>
> ---
> drivers/net/gtp.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
> index a60ef32b35b8..4a8b00548673 100644
> --- a/drivers/net/gtp.c
> +++ b/drivers/net/gtp.c
> @@ -826,6 +826,10 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
> if (!pskb_may_pull(skb, hdrlen))
> return -1;
>
> + if (gtp1->flags & GTP1_F_EXTHDR &&
> + gtp_parse_exthdrs(skb, &hdrlen) < 0)
> + return -1;
> +
> if (gtp_inner_proto(skb, hdrlen, &inner_proto) < 0) {
> netdev_dbg(gtp->dev, "GTP packet does not encapsulate an IP packet\n");
> return -1;
> @@ -840,10 +844,6 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
> return 1;
> }
>
> - if (gtp1->flags & GTP1_F_EXTHDR &&
> - gtp_parse_exthdrs(skb, &hdrlen) < 0)
> - return -1;
> -
> return gtp_rx(pctx, skb, hdrlen, gtp->role, inner_proto);
> }
>
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH net] bnge/bng_re: fix ring ID widths
From: Vikas Gupta @ 2026-07-03 8:59 UTC (permalink / raw)
To: Paolo Abeni
Cc: davem, edumazet, kuba, andrew+netdev, horms, netdev, linux-kernel,
linux-rdma, leonro, jgg, bhargava.marreddy, rahul-rg.gupta,
vsrama-krishna.nemani, rajashekar.hudumula, ajit.khaparde,
Siva Reddy Kallam, Dharmender Garg,
Yendapally Reddy Dhananjaya Reddy
In-Reply-To: <55e4bc6f-f393-4e76-9220-a818b28c585b@redhat.com>
[-- Attachment #1: Type: text/plain, Size: 1256 bytes --]
On Fri, Jul 3, 2026 at 1:03 PM Paolo Abeni <pabeni@redhat.com> wrote:
>
> On 6/30/26 12:15 PM, Vikas Gupta wrote:
> > diff --git a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
> > index 341c7f81ed09..bb0c79a1ee60 100644
> > --- a/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
> > +++ b/drivers/net/ethernet/broadcom/bnge/bnge_rmem.h
> > @@ -184,7 +184,7 @@ struct bnge_ctx_mem_info {
> > struct bnge_ring_struct {
> > struct bnge_ring_mem_info ring_mem;
> >
> > - u16 fw_ring_id;
> > + u32 fw_ring_id;
>
> Sashiko gemini has a few concerns about the id size increases:
>
> https://sashiko.dev/#/patchset/20260630101554.1221733-1-vikas.gupta%40broadcom.com
>
> please have a look.
Sashiko's concern about backward compatibility is valid, however, the
driver is not expected to work with older firmware because the Thor
Ultra device has not been deployed yet.
The concern about overlapping bits is also valid, but the firmware
uses a ring ID with a maximum width of 18 bits, so no overlap occurs.
Nevertheless, I will add a mask for xid to improve code clarity and
readability.
Thanks,
Vikas
>
>
> /P
>
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5465 bytes --]
^ permalink raw reply
* Re: [RFC PATCH 3/3] coredump, net: remove `SOCK_COREDUMP`
From: John Ericson @ 2026-07-03 9:08 UTC (permalink / raw)
To: Christian Brauner, John Ericson
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Cong Wang, Kuniyuki Iwashima, Simon Horman, David Rheinsberg,
Andy Lutomirski, Sergei Zimmerman, network dev, linux-fsdevel,
Mickaël Salaün, Günther Noack, Paul Moore,
linux-security-module, LKML
In-Reply-To: <20260703-umgehen-auftun-anlagen-826bf59dfcb5@brauner>
On Fri, Jul 3, 2026, at 4:11 AM, Christian Brauner wrote:
> I don't think dragging the bowels of net/ into fs/coredump.c just for
> the sake of getting a purely internal SOCK_COREDUMP flag out of the way
> is the right way to go.
>
> I suspect the saner thing to do would be to introduce a primitive for
> connecting to an AF_UNIX path-based socket directly instead of this
> weird dance here, no?
>
> int kernel_unix_connect(const char *path, struct socket *socket, unsigned int o_flags, int type)
>
> then my kthread changes would collapse this into:
>
> scoped_with_init_fs()
> retval = kernel_unix_connect(path, socket, O_NONBLOCK, SOCK_STREAM);
That works for this case, but the destination I am trying to eventually
reach is being allowed (in userspace) to connect to unbound sockets by
their fd --- no path, no abstract socket name.
unix_connectat(int fd, const char *path, struct socket *socket,
unsigned int at_flags, unsigned int o_flags,
int type)
is a mouthful, but it would work. Still, there is a subtlety about the
retry logic. When one does something like:
> connect(..."/dev/fd/N",...)
it will repeatedly re-lookup "/dev/fd/N" until the timeout is reached. I
consider that pretty terrible --- the rest of the program could race to
change what that file descriptor (number) refers to. Therefore, I think
table stakes to make a good `unix_connectat` is to make the
`AT_EMPTY_PATH` case not re-lookup the socket.
(making a sockfs file descriptor work is separate, I already have the
patch for that, I can include it.)
> The two helpers also make no sense to me and force a bunch of cleanup on
> the caller as well.
I am fine with `unix_connectat`, but just for reference, me breaking up
the steps is because I generally like the decomposition of `fverb`
rather than `verbat` system calls, since the latter would typically be
used with `openat` or so. It seems the `kernel_*` "syscalls" generally
take `struct path` rather than strings, which seems good and in the
spirit of that decomposition, but then even `struct path` is overkill to
refer to a socket. So putting all that together, the final composition I
had was:
1. string path (from /proc/...) -> `struct path`
2. `struct path` -> `struct sock *`
3. connect to `struct sock *`
So I quite liked those 3 orthogonal knobs, vs an all-singing-all-dancing
`unix_connectat`. (I suppose making it `struct socket *` would make it
slightly less internals-y?) But again, anything that puts us on track to
being able to connect to an unbound socket without procfs is good enough
for me.
> I'm not sure why we would go on altering all kinds of LSM hooks as well.
That's in the commit message, it is because without `SOCK_COREDUMP`
those are all dead code. Instead of removing them in this commit, I can
just keep those flags, or remove them in a separate commit. Fine
with any of those.
John
^ permalink raw reply
* [PATCH net-next v8 0/3] airoha: add the capability to configure GDM3/GDM4 as WAN/LAN on demand
From: Lorenzo Bianconi @ 2026-07-03 9:19 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Alexander Lobakin, linux-arm-kernel, linux-mediatek,
netdev, Madhur Agrawal
Add the capability to configure GDM3/GDM4 as WAN/LAN on demand when QoS
offload is created or destroyed.
Make dev->qdma an RCU pointer so the TX path can safely dereference it
without holding RTNL.
Introduce airoha_qdma_start() and airoha_qdma_stop() helpers.
---
Changes in v8:
- Rebase on top of next-next to fix conflicts.
- Link to v7: https://lore.kernel.org/r/20260701-airoha-ethtool-priv_flags-v7-0-b4153bd44428@kernel.org
Changes in v7:
- Fix ETS stats accounting in patch 2/3
- Reset ETS stats accounting in airoha_dev_set_qdma().
- Link to v6: https://lore.kernel.org/r/20260629-airoha-ethtool-priv_flags-v6-0-86bc600d31bc@kernel.org
Changes in v6:
- Rebase on top of next-next
- Add patch 1/3: "rename airoha_priv_flags to airoha_dev_flags"
- Drop patch 2/3: "refactor QDMA start/stop into reusable helpers"
- Link to v5: https://lore.kernel.org/r/20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1@kernel.org
Changes in v5:
- Add patch 1/3: use int instead of atomic_t for qdma users counter
- Protect dev->flags with flow_offload_mutex mutex.
- Introduce AIROHA_PRIV_F_QOS in order to handle better WAN/LAN
switching.
- Link to v4: https://lore.kernel.org/r/20260610-airoha-ethtool-priv_flags-v4-0-60e89cf28fea@kernel.org
Changes in v4:
- Move back QDMA TX/RX DMA enable to airoha_dev_open()/airoha_dev_stop().
- Configure GDM3/4 as WAN if GDM2 is not available in ndo_init()
callback.
- Protect qdma pointer in airoha_gdm_dev struct using RCU.
- Rely on rtnl_dereference() to access qdma pointer in the control path.
- Add airoha_qdma_start() and airoha_qdma_stop() utility routines in
patch 1/2
- Link to v3: https://lore.kernel.org/r/20260608-airoha-ethtool-priv_flags-v3-1-3e8e3dc3f715@kernel.org
Changes in v3:
- Do not introduce ethtool private flags support to configure LAN/WAN
for GDM3/4 and rely on tc qdisc offload for it instead.
- Set GDM3/4 ports as LAN by default.
- Move QDMA TX/RX DMA enable from airoha_dev_open() to airoha_probe()
and the corresponding disable from airoha_dev_stop() to airoha_qdma_cleanup().
- Link to v2: https://lore.kernel.org/r/20260607-airoha-ethtool-priv_flags-v2-1-742c7aa1e182@kernel.org
Changes in v2:
- Rework airoha_dev_set_wan_flag routine
- Enable GDM_STRIP_CRC_MASK in airoha_disable_gdm2_loopback()
- Do not always reset REG_SRC_PORT_FC_MAP6 in
airoha_disable_gdm2_loopback() but use the same condition used in
airoha_enable_gdm2_loopback().
- Link to v1: https://lore.kernel.org/r/20260606-airoha-ethtool-priv_flags-v1-1-401b2c9fe9f1@kernel.org
---
Lorenzo Bianconi (3):
net: airoha: rename airoha_priv_flags to airoha_dev_flags
net: airoha: fix ETS QoS stats counter underflow and cross-channel corruption
net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
drivers/net/ethernet/airoha/airoha_eth.c | 253 ++++++++++++++++++++++++++----
drivers/net/ethernet/airoha/airoha_eth.h | 26 ++-
drivers/net/ethernet/airoha/airoha_ppe.c | 9 +-
drivers/net/ethernet/airoha/airoha_regs.h | 1 +
4 files changed, 246 insertions(+), 43 deletions(-)
---
base-commit: 6fb33632323a396c9dc2bb9bea483e013e547d57
change-id: 20260606-airoha-ethtool-priv_flags-b6aa70caa780
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply
* [PATCH net-next v8 1/3] net: airoha: rename airoha_priv_flags to airoha_dev_flags
From: Lorenzo Bianconi @ 2026-07-03 9:19 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Alexander Lobakin, linux-arm-kernel, linux-mediatek,
netdev
In-Reply-To: <20260703-airoha-ethtool-priv_flags-v8-0-015ba5ac89ee@kernel.org>
Rename the airoha_priv_flags enum to airoha_dev_flags and the
AIROHA_PRIV_F_WAN flag to AIROHA_DEV_F_WAN. The "priv_flags" naming
dates back to an earlier design that used ethtool private flags; since
this series switched to tc qdisc offload for LAN/WAN configuration,
align the naming to reflect that these are per-device flags rather than
ethtool private flags. No functional change.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
drivers/net/ethernet/airoha/airoha_eth.h | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 59001fd4b6f7..41c1a0ffbdd8 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2039,7 +2039,7 @@ static int airoha_dev_init(struct net_device *netdev)
fallthrough;
case AIROHA_GDM2_IDX:
/* GDM2 is always used as wan */
- dev->flags |= AIROHA_PRIV_F_WAN;
+ dev->flags |= AIROHA_DEV_F_WAN;
break;
default:
break;
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index f6d01a8e8da1..bf1c249255bd 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -543,8 +543,8 @@ struct airoha_qdma {
DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
};
-enum airoha_priv_flags {
- AIROHA_PRIV_F_WAN = BIT(0),
+enum airoha_dev_flags {
+ AIROHA_DEV_F_WAN = BIT(0),
};
struct airoha_gdm_dev {
@@ -667,7 +667,7 @@ static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid)
static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
{
- return !(dev->flags & AIROHA_PRIV_F_WAN);
+ return !(dev->flags & AIROHA_DEV_F_WAN);
}
static inline bool airoha_is_7581(struct airoha_eth *eth)
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v8 2/3] net: airoha: fix ETS QoS stats counter underflow and cross-channel corruption
From: Lorenzo Bianconi @ 2026-07-03 9:19 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Alexander Lobakin, linux-arm-kernel, linux-mediatek,
netdev
In-Reply-To: <20260703-airoha-ethtool-priv_flags-v8-0-015ba5ac89ee@kernel.org>
airoha_qdma_get_tx_ets_stats() has two bugs:
- The hardware counters read via airoha_qdma_rr() are 32-bit values
but are stored in u64 locals and subtracted from u64 baselines. When
a 32-bit hardware counter wraps around, the subtraction produces a
large underflow value passed to _bstats_update().
- The baseline counters (cpu_tx_packets, fwd_tx_packets) are stored as
single per-device fields, but airoha_qdma_get_tx_ets_stats() is
called with different channel values (0-3). Each call reads a
different channel's hardware counter but overwrites the same
baseline, corrupting the delta computation for other channels.
Fix both by:
- Narrowing the counter locals and baselines to u32 so that 32-bit
unsigned subtraction handles wrap-around naturally.
- Grouping the baselines into a per-channel qos_stats array so each
channel tracks its own previous counter value independently.
- Splitting the delta addition into two statements so the first u32
delta is widened to u64 on assignment and the second is added in
u64 arithmetic, preventing overflow when both deltas are large.
Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 18 +++++++++++-------
drivers/net/ethernet/airoha/airoha_eth.h | 7 ++++---
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 41c1a0ffbdd8..aaf2a4717d12 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2482,16 +2482,20 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_qdma *qdma = dev->qdma;
+ u32 cpu_tx_packets, fwd_tx_packets;
+ u64 tx_packets;
- u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
- u64 fwd_tx_packets = airoha_qdma_rr(qdma,
- REG_CNTR_VAL((channel << 1) + 1));
- u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
- (fwd_tx_packets - dev->fwd_tx_packets);
+ cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
+ fwd_tx_packets = airoha_qdma_rr(qdma,
+ REG_CNTR_VAL((channel << 1) + 1));
+ tx_packets = (u32)(cpu_tx_packets -
+ dev->qos_stats[channel].cpu_tx_packets);
+ tx_packets += (u32)(fwd_tx_packets -
+ dev->qos_stats[channel].fwd_tx_packets);
_bstats_update(opt->stats.bstats, 0, tx_packets);
- dev->cpu_tx_packets = cpu_tx_packets;
- dev->fwd_tx_packets = fwd_tx_packets;
+ dev->qos_stats[channel].cpu_tx_packets = cpu_tx_packets;
+ dev->qos_stats[channel].fwd_tx_packets = fwd_tx_packets;
return 0;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index bf1c249255bd..bf44be9f0954 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -553,9 +553,10 @@ struct airoha_gdm_dev {
struct airoha_eth *eth;
DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
- /* qos stats counters */
- u64 cpu_tx_packets;
- u64 fwd_tx_packets;
+ struct {
+ u32 cpu_tx_packets;
+ u32 fwd_tx_packets;
+ } qos_stats[AIROHA_NUM_QOS_CHANNELS];
u32 flags;
int nbq;
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v8 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
From: Lorenzo Bianconi @ 2026-07-03 9:19 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: Simon Horman, Alexander Lobakin, linux-arm-kernel, linux-mediatek,
netdev, Madhur Agrawal
In-Reply-To: <20260703-airoha-ethtool-priv_flags-v8-0-015ba5ac89ee@kernel.org>
GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware
QoS offload to function. Without it, HTB and ETS offload on these ports
do not work.
Previously, GDM3/GDM4 ports were automatically configured as WAN with
GDM2 loopback enabled during ndo_init(). Add the capability to configure
GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed.
Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting
HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables
GDM2 loopback, with proper rollback on failure. Introduce the
AIROHA_DEV_F_QOS flag to track whether a device has an active HTB
qdisc; clear it on TC_HTB_DESTROY. The device keeps its WAN role after
qdisc teardown so that its configuration is preserved until another
device explicitly needs the WAN role for QoS offload.
If another GDM3/GDM4 device already holds the WAN role without an active
QoS qdisc, demote it to LAN before promoting the requesting device. Skip
the demotion when the requesting device is itself already the WAN device.
Since airoha_dev_set_qdma() can now be called on a running device to
migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX
path can safely dereference it without holding RTNL.
Hold flow_offload_mutex in airoha_enable_qos_for_gdm34() and
airoha_disable_qos_for_gdm34() around the dev->flags update,
airoha_dev_set_qdma() and GDM2 loopback configuration, serializing
against concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER
offload path.
Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected()
with a lockdep condition accepting either rtnl_lock or flow_offload_mutex,
and use it across all control-path dereferences of the RCU-protected
dev->qdma pointer.
Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback.
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 233 ++++++++++++++++++++++++++----
drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 9 +-
drivers/net/ethernet/airoha/airoha_regs.h | 1 +
4 files changed, 227 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index aaf2a4717d12..fe2e2af67173 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -934,7 +934,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
if (!dev)
continue;
- if (dev->qdma != qdma)
+ if (rcu_access_pointer(dev->qdma) != qdma)
continue;
netdev = netdev_from_priv(dev);
@@ -1866,8 +1866,8 @@ static int airoha_dev_open(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- struct airoha_qdma *qdma = dev->qdma;
u32 pse_port = FE_PSE_PORT_PPE1;
+ struct airoha_qdma *qdma;
int err;
netif_tx_start_all_queues(netdev);
@@ -1875,6 +1875,7 @@ static int airoha_dev_open(struct net_device *netdev)
if (err)
return err;
+ qdma = airoha_qdma_deref(dev);
if (netdev_uses_dsa(netdev))
airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
@@ -1898,7 +1899,6 @@ static int airoha_dev_stop(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
- struct airoha_qdma *qdma = dev->qdma;
netif_tx_disable(netdev);
airoha_set_vip_for_gdm_port(dev, false);
@@ -1906,7 +1906,7 @@ static int airoha_dev_stop(struct net_device *netdev)
if (--port->users)
airoha_ppe_set_xmit_frame_size(dev);
else
- airoha_set_gdm_port_fwd_cfg(qdma->eth,
+ airoha_set_gdm_port_fwd_cfg(dev->eth,
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
return 0;
@@ -1989,6 +1989,53 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
return 0;
}
+static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
+{
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int i, src_port;
+ u32 pse_port;
+
+ src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
+ if (src_port < 0)
+ return src_port;
+
+ airoha_fe_clear(eth,
+ REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
+ SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
+
+ airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ GDM_STRIP_CRC_MASK);
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ FE_PSE_PORT_DROP);
+ airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
+ LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
+ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
+ : FE_PSE_PORT_PPE1;
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ pse_port);
+
+ airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
+ FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
+
+ for (i = 0; i < eth->soc->num_ppe; i++)
+ airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX),
+ DFT_CPORT_MASK(AIROHA_GDM2_IDX));
+
+ /* Enable VIP and IFC for GDM2 */
+ airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
+ airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
+
+ if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+ u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
+
+ airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
+ FC_MAP6_DEF_VALUE & mask);
+ }
+
+ return 0;
+}
+
static struct airoha_gdm_dev *
airoha_get_wan_gdm_dev(struct airoha_eth *eth)
{
@@ -2015,15 +2062,35 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth)
static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
{
struct net_device *netdev = netdev_from_priv(dev);
+ struct airoha_qdma *cur_qdma, *qdma;
struct airoha_eth *eth = dev->eth;
- int ppe_id;
+ int i, ppe_id;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
- dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
- netdev->irq = dev->qdma->irq_banks[0].irq;
+ qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
+ cur_qdma = airoha_qdma_deref(dev);
+
+ rcu_assign_pointer(dev->qdma, qdma);
+ netdev->irq = qdma->irq_banks[0].irq;
ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
+
+ if (!cur_qdma)
+ return;
+
+ /* Seed qos_stats baselines with the new QDMA block's current
+ * counter values to avoid a spurious spike on the first stats
+ * query after migration.
+ */
+ for (i = 0; i < ARRAY_SIZE(dev->qos_stats); i++) {
+ dev->qos_stats[i].cpu_tx_packets =
+ airoha_qdma_rr(qdma, REG_CNTR_VAL(i << 1));
+ dev->qos_stats[i].fwd_tx_packets =
+ airoha_qdma_rr(qdma, REG_CNTR_VAL((i << 1) + 1));
+ }
+ synchronize_rcu();
+ netif_tx_wake_all_queues(netdev);
}
static int airoha_dev_init(struct net_device *netdev)
@@ -2178,9 +2245,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_qdma *qdma = dev->qdma;
u32 nr_frags, tag, msg0, msg1, len;
struct airoha_queue_entry *e;
+ struct airoha_qdma *qdma;
struct netdev_queue *txq;
struct airoha_queue *q;
LIST_HEAD(tx_list);
@@ -2189,6 +2256,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
u16 index;
u8 fport;
+ rcu_read_lock();
+ qdma = rcu_dereference(dev->qdma);
qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
tag = airoha_get_dsa_tag(skb, netdev);
@@ -2238,6 +2307,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
netif_tx_stop_queue(txq);
q->txq_stopped = true;
spin_unlock_bh(&q->lock);
+ rcu_read_unlock();
+
return NETDEV_TX_BUSY;
}
@@ -2303,6 +2374,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
spin_unlock_bh(&q->lock);
+ rcu_read_unlock();
return NETDEV_TX_OK;
@@ -2315,6 +2387,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
error:
dev_kfree_skb_any(skb);
netdev->stats.tx_dropped++;
+ rcu_read_unlock();
return NETDEV_TX_OK;
}
@@ -2394,17 +2467,19 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
const u16 *weights, u8 n_weights)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_qdma *qdma;
int i;
+ qdma = airoha_qdma_deref(dev);
for (i = 0; i < AIROHA_NUM_QOS_QUEUES; i++)
- airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
+ airoha_qdma_clear(qdma, REG_QUEUE_CLOSE_CFG(channel),
TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
for (i = 0; i < n_weights; i++) {
u32 status;
int err;
- airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
+ airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG,
TWRR_RW_CMD_MASK |
FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
@@ -2412,12 +2487,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
err = read_poll_timeout(airoha_qdma_rr, status,
status & TWRR_RW_CMD_DONE,
USEC_PER_MSEC, 10 * USEC_PER_MSEC,
- true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
+ true, qdma, REG_TXWRR_WEIGHT_CFG);
if (err)
return err;
}
- airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+ airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3),
CHAN_QOS_MODE_MASK(channel),
__field_prep(CHAN_QOS_MODE_MASK(channel), mode));
@@ -2481,10 +2556,11 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
struct tc_ets_qopt_offload *opt)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_qdma *qdma = dev->qdma;
u32 cpu_tx_packets, fwd_tx_packets;
+ struct airoha_qdma *qdma;
u64 tx_packets;
+ qdma = airoha_qdma_deref(dev);
cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
fwd_tx_packets = airoha_qdma_rr(qdma,
REG_CNTR_VAL((channel << 1) + 1));
@@ -2751,16 +2827,18 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
u32 bucket_size)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_qdma *qdma;
int i, err;
+ qdma = airoha_qdma_deref(dev);
for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
- err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
+ err = airoha_qdma_set_trtcm_config(qdma, channel,
REG_EGRESS_TRTCM_CFG, i,
!!rate, TRTCM_METER_MODE);
if (err)
return err;
- err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
+ err = airoha_qdma_set_trtcm_token_bucket(qdma, channel,
REG_EGRESS_TRTCM_CFG,
i, rate, bucket_size);
if (err)
@@ -2796,11 +2874,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
int err, num_tx_queues = AIROHA_NUM_TX_RING + channel + 1;
struct airoha_gdm_dev *dev = netdev_priv(netdev);
- struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_qdma *qdma;
/* Here we need to check the requested QDMA channel is not already
* in use by another net_device running on the same QDMA block.
*/
+ qdma = airoha_qdma_deref(dev);
if (test_and_set_bit(channel, qdma->qos_channel_map)) {
NL_SET_ERR_MSG_MOD(opt->extack,
"qdma qos channel already in use");
@@ -2836,7 +2915,7 @@ static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
u32 rate, u32 bucket_size,
enum trtcm_unit_type unit_type)
{
- struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
int i;
for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
@@ -3011,10 +3090,11 @@ static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
int num_tx_queues = AIROHA_NUM_TX_RING;
- struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_qdma *qdma;
airoha_qdma_set_tx_rate_limit(netdev, queue, 0, 0);
+ qdma = airoha_qdma_deref(dev);
clear_bit(queue, qdma->qos_channel_map);
clear_bit(queue, dev->qos_sq_bmap);
@@ -3040,6 +3120,98 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
return 0;
}
+static void airoha_disable_qos_for_gdm34(struct net_device *netdev)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ int err;
+
+ if (port->id != AIROHA_GDM3_IDX &&
+ port->id != AIROHA_GDM4_IDX)
+ return;
+
+ err = airoha_disable_gdm2_loopback(dev);
+ if (err)
+ netdev_warn(netdev,
+ "failed disabling GDM2 loopback: %d\n", err);
+
+ dev->flags &= ~AIROHA_DEV_F_WAN;
+ airoha_dev_set_qdma(dev);
+
+ airoha_set_macaddr(dev, netdev->dev_addr);
+ airoha_ppe_set_xmit_frame_size(dev);
+
+ if (netif_running(netdev))
+ airoha_set_gdm_port_fwd_cfg(dev->eth,
+ REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_PPE1);
+}
+
+static int airoha_enable_qos_for_gdm34(struct net_device *netdev,
+ struct netlink_ext_ack *extack)
+{
+ struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int err = -EBUSY;
+
+ if (port->id != AIROHA_GDM3_IDX &&
+ port->id != AIROHA_GDM4_IDX) {
+ /* HW QoS is always supported by GDM1 and GDM2 */
+ return 0;
+ }
+
+ if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */
+ return 0;
+
+ mutex_lock(&flow_offload_mutex);
+
+ wan_dev = airoha_get_wan_gdm_dev(eth);
+ if (wan_dev) {
+ if ((wan_dev->flags & AIROHA_DEV_F_QOS) ||
+ wan_dev->port->id == AIROHA_GDM2_IDX) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "QoS configured for WAN device");
+ goto error_unlock;
+ }
+ airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
+ }
+
+ dev->flags |= AIROHA_DEV_F_WAN;
+ airoha_dev_set_qdma(dev);
+ err = airoha_enable_gdm2_loopback(dev);
+ if (err)
+ goto error_disable_wan;
+
+ err = airoha_set_macaddr(dev, netdev->dev_addr);
+ if (err)
+ goto error_disable_loopback;
+
+ airoha_dev_set_xmit_frame_size(netdev);
+ if (netif_running(netdev)) {
+ u32 pse_port;
+
+ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
+ : FE_PSE_PORT_PPE1;
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
+ pse_port);
+ }
+
+ mutex_unlock(&flow_offload_mutex);
+
+ return 0;
+
+error_disable_loopback:
+ airoha_disable_gdm2_loopback(dev);
+error_disable_wan:
+ dev->flags &= ~AIROHA_DEV_F_WAN;
+ airoha_dev_set_qdma(dev);
+error_unlock:
+ mutex_unlock(&flow_offload_mutex);
+
+ return err;
+}
+
static int airoha_tc_htb_destroy(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
@@ -3048,6 +3220,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev)
for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
airoha_tc_remove_htb_queue(netdev, q);
+ dev->flags &= ~AIROHA_DEV_F_QOS;
+
return 0;
}
@@ -3067,24 +3241,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
return 0;
}
-static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
+static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
struct tc_htb_qopt_offload *opt)
{
switch (opt->command) {
- case TC_HTB_CREATE:
+ case TC_HTB_CREATE: {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ int err;
+
+ err = airoha_enable_qos_for_gdm34(netdev, opt->extack);
+ if (err)
+ return err;
+
+ dev->flags |= AIROHA_DEV_F_QOS;
break;
+ }
case TC_HTB_DESTROY:
- return airoha_tc_htb_destroy(dev);
+ return airoha_tc_htb_destroy(netdev);
case TC_HTB_NODE_MODIFY:
- return airoha_tc_htb_modify_queue(dev, opt);
+ return airoha_tc_htb_modify_queue(netdev, opt);
case TC_HTB_LEAF_ALLOC_QUEUE:
- return airoha_tc_htb_alloc_leaf_queue(dev, opt);
+ return airoha_tc_htb_alloc_leaf_queue(netdev, opt);
case TC_HTB_LEAF_DEL:
case TC_HTB_LEAF_DEL_LAST:
case TC_HTB_LEAF_DEL_LAST_FORCE:
- return airoha_tc_htb_delete_leaf_queue(dev, opt);
+ return airoha_tc_htb_delete_leaf_queue(netdev, opt);
case TC_HTB_LEAF_QUERY_QUEUE:
- return airoha_tc_get_htb_get_leaf_queue(dev, opt);
+ return airoha_tc_get_htb_get_leaf_queue(netdev, opt);
default:
return -EOPNOTSUPP;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index bf44be9f0954..4d03ebae1a2d 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -545,11 +545,12 @@ struct airoha_qdma {
enum airoha_dev_flags {
AIROHA_DEV_F_WAN = BIT(0),
+ AIROHA_DEV_F_QOS = BIT(1),
};
struct airoha_gdm_dev {
+ struct airoha_qdma __rcu *qdma;
struct airoha_gdm_port *port;
- struct airoha_qdma *qdma;
struct airoha_eth *eth;
DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
@@ -685,6 +686,16 @@ int airoha_get_fe_port(struct airoha_gdm_dev *dev);
bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
struct airoha_gdm_dev *dev);
+extern struct mutex flow_offload_mutex;
+
+static inline struct airoha_qdma *
+airoha_qdma_deref(struct airoha_gdm_dev *dev)
+{
+ return rcu_dereference_protected(dev->qdma,
+ lockdep_rtnl_is_held() ||
+ lockdep_is_held(&flow_offload_mutex));
+}
+
void airoha_ppe_set_xmit_frame_size(struct airoha_gdm_dev *dev);
void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index e7c78293002a..78378a6262f9 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -15,7 +15,10 @@
#include "airoha_regs.h"
#include "airoha_eth.h"
-static DEFINE_MUTEX(flow_offload_mutex);
+/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port
+ * configuration.
+ */
+DEFINE_MUTEX(flow_offload_mutex);
static DEFINE_SPINLOCK(ppe_lock);
static const struct rhashtable_params airoha_flow_table_params = {
@@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
{
- struct airoha_qdma *qdma = dev->qdma;
- struct airoha_eth *eth = qdma->eth;
+ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ struct airoha_eth *eth = dev->eth;
u8 qdma_id = qdma - ð->qdma[0];
u32 fe_cpu_port;
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 6fed63d013b4..442b48c9b991 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -375,6 +375,7 @@
#define REG_SRC_PORT_FC_MAP6 0x2298
#define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
+#define FC_MAP6_DEF_VALUE 0x1b1a1918
#define REG_WAN_MTU0 0x2300
#define WAN_MTU1_MASK GENMASK(29, 16)
--
2.55.0
^ permalink raw reply related
* Re: [PATCH net-next v6 3/5] dpll: zl3073x: use per-operation poll timeouts
From: Petr Oros @ 2026-07-03 9:20 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Prathosh Satish, Simon Horman,
Vadim Fedorenko, linux-kernel
In-Reply-To: <20260630125536.720717-4-ivecera@redhat.com>
On 6/30/26 14:55, Ivan Vecera wrote:
> Replace the single 2s timeout in zl3073x_poll_zero_u8() with a
> per-caller timeout parameter. Different HW operations have different
> expected completion times so using per-operation timeouts improves
> error detection. The timeout values are based on proprietary source
> code provided by Microchip and own measurement.
>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> drivers/dpll/zl3073x/chan.c | 6 ++++--
> drivers/dpll/zl3073x/core.c | 29 +++++++++++++++++------------
> drivers/dpll/zl3073x/core.h | 10 +++++++++-
> 3 files changed, 30 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
> index 2fe3c3da84bb5..677a920c16254 100644
> --- a/drivers/dpll/zl3073x/chan.c
> +++ b/drivers/dpll/zl3073x/chan.c
> @@ -33,7 +33,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
>
> /* Read df_offset vs tracked reference */
> rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
> - ZL_DPLL_DF_READ_SEM);
> + ZL_DPLL_DF_READ_SEM,
> + ZL_POLL_DF_READ_TIMEOUT_US);
> if (rc)
> return rc;
>
> @@ -43,7 +44,8 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
> return rc;
>
> rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
> - ZL_DPLL_DF_READ_SEM);
> + ZL_DPLL_DF_READ_SEM,
> + ZL_POLL_DF_READ_TIMEOUT_US);
> if (rc)
> return rc;
>
> diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
> index 8e6416a4741de..0b2050aa2ed92 100644
> --- a/drivers/dpll/zl3073x/core.c
> +++ b/drivers/dpll/zl3073x/core.c
> @@ -311,17 +311,17 @@ int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
> * @zldev: zl3073x device pointer
> * @reg: register to poll (has to be 8bit register)
> * @mask: bit mask for polling
> + * @timeout_us: timeout in microseconds
> *
> * Waits for bits specified by @mask in register @reg value to be cleared
> * by the device.
> *
> * Returns: 0 on success, <0 on error
> */
> -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
> +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
> + u8 mask, unsigned int timeout_us)
> {
> - /* Register polling sleep & timeout */
> -#define ZL_POLL_SLEEP_US 10
> -#define ZL_POLL_TIMEOUT_US 2000000
> +#define ZL_POLL_SLEEP_US 10
> unsigned int val;
>
> /* Check the register is 8bit */
> @@ -335,7 +335,7 @@ int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
> reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
>
> return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
> - ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
> + ZL_POLL_SLEEP_US, timeout_us);
> }
>
> int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
> @@ -354,7 +354,8 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
> return rc;
>
> /* Wait for the operation to actually finish */
> - return zl3073x_poll_zero_u8(zldev, op_reg, op_val);
> + return zl3073x_poll_zero_u8(zldev, op_reg, op_val,
> + ZL_POLL_MB_TIMEOUT_US);
> }
>
> /**
> @@ -377,8 +378,8 @@ zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op)
> return rc;
>
> /* Poll for completion - pending bit cleared */
> - return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP,
> - ZL_HWREG_OP_PENDING);
> + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, ZL_HWREG_OP_PENDING,
> + ZL_POLL_HWREG_TIMEOUT_US);
> }
>
> /**
> @@ -609,7 +610,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
> * to be zero to ensure that the measured data are coherent.
> */
> rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
> - ZL_REF_PHASE_ERR_READ_RQST_RD);
> + ZL_REF_PHASE_ERR_READ_RQST_RD,
> + ZL_POLL_PHASE_ERR_TIMEOUT_US);
> if (rc)
> return rc;
>
> @@ -628,7 +630,8 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
>
> /* Wait for finish */
> return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_PHASE_ERR_READ_RQST,
> - ZL_REF_PHASE_ERR_READ_RQST_RD);
> + ZL_REF_PHASE_ERR_READ_RQST_RD,
> + ZL_POLL_PHASE_ERR_TIMEOUT_US);
> }
>
> /**
> @@ -648,7 +651,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
>
> /* Wait for previous measurement to finish */
> rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
> - ZL_REF_FREQ_MEAS_CTRL);
> + ZL_REF_FREQ_MEAS_CTRL,
> + ZL_POLL_FREQ_MEAS_TIMEOUT_US);
> if (rc)
> return rc;
>
> @@ -669,7 +673,8 @@ zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
>
> /* Wait for finish */
> return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
> - ZL_REF_FREQ_MEAS_CTRL);
> + ZL_REF_FREQ_MEAS_CTRL,
> + ZL_POLL_FREQ_MEAS_TIMEOUT_US);
> }
>
> /**
> diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
> index addba378b0df4..6b55a05a222ed 100644
> --- a/drivers/dpll/zl3073x/core.h
> +++ b/drivers/dpll/zl3073x/core.h
> @@ -7,6 +7,7 @@
> #include <linux/kthread.h>
> #include <linux/list.h>
> #include <linux/mutex.h>
> +#include <linux/time64.h>
> #include <linux/types.h>
>
> #include "chan.h"
> @@ -19,6 +20,12 @@ struct device;
> struct regmap;
> struct zl3073x_dpll;
>
> +/* Per-operation poll timeouts */
> +#define ZL_POLL_DF_READ_TIMEOUT_US (25 * USEC_PER_MSEC)
> +#define ZL_POLL_FREQ_MEAS_TIMEOUT_US (50 * USEC_PER_MSEC)
> +#define ZL_POLL_HWREG_TIMEOUT_US (50 * USEC_PER_MSEC)
> +#define ZL_POLL_MB_TIMEOUT_US (30 * USEC_PER_MSEC)
> +#define ZL_POLL_PHASE_ERR_TIMEOUT_US (50 * USEC_PER_MSEC)
>
> enum zl3073x_flags {
> ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
> @@ -127,7 +134,8 @@ struct zl3073x_hwreg_seq_item {
>
> int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val,
> unsigned int mask_reg, u16 mask_val);
> -int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask);
> +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg,
> + u8 mask, unsigned int timeout_us);
> int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val);
> int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val);
> int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val);
Reviewed-by: Petr Oros <poros@redhat.com>
^ permalink raw reply
* Re: [PATCH net-next v6 4/5] dpll: zl3073x: add per-DPLL serialization lock
From: Petr Oros @ 2026-07-03 9:21 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Michal Schmidt,
Paolo Abeni, Pasi Vaananen, Prathosh Satish, Simon Horman,
Vadim Fedorenko, linux-kernel
In-Reply-To: <20260630125536.720717-5-ivecera@redhat.com>
On 6/30/26 14:55, Ivan Vecera wrote:
> Add a per-DPLL mutex that serializes all operations on a given DPLL
> channel across DPLL netlink callbacks, the periodic kthread worker,
> and (in subsequent patches) PTP clock callbacks.
>
> All DPLL pin and device callbacks that access mutable state take the
> lock as the first operation. The periodic worker holds it for the
> entire check cycle of each channel, deferring change notifications
> until after the lock is released to avoid ABBA deadlock with
> dpll_lock. This establishes the lock ordering:
> dpll_lock (subsystem, outer) -> zldpll->lock (driver, inner).
>
> Move zl3073x_chan_state_update() from the per-device
> zl3073x_dev_chan_states_update() loop into the per-DPLL
> zl3073x_dpll_changes_check() so it runs under zldpll->lock.
> This serializes df_offset writes with all readers and
> eliminates the need for separate df_offset synchronization.
>
> Change pin->freq_offset from atomic64_t to plain s64 since all
> readers and writers are now serialized by zldpll->lock, making
> atomic access unnecessary.
>
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
> drivers/dpll/zl3073x/core.c | 19 +---
> drivers/dpll/zl3073x/core.h | 2 +-
> drivers/dpll/zl3073x/dpll.c | 189 +++++++++++++++++++++++++++---------
> drivers/dpll/zl3073x/dpll.h | 2 +
> 4 files changed, 149 insertions(+), 63 deletions(-)
>
> diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
> index 0b2050aa2ed92..7f5afaaae6342 100644
> --- a/drivers/dpll/zl3073x/core.c
> +++ b/drivers/dpll/zl3073x/core.c
> @@ -567,19 +567,7 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
> }
> }
>
> -static void
> -zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev)
> -{
> - int i, rc;
>
> - for (i = 0; i < zldev->info->num_channels; i++) {
> - rc = zl3073x_chan_state_update(zldev, i);
> - if (rc)
> - dev_warn(zldev->dev,
> - "Failed to get DPLL%u state: %pe\n", i,
> - ERR_PTR(rc));
> - }
> -}
>
> /**
> * zl3073x_ref_phase_offsets_update - update reference phase offsets
> @@ -720,9 +708,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
> /* Update input references' states */
> zl3073x_dev_ref_states_update(zldev);
>
> - /* Update DPLL channels' states */
> - zl3073x_dev_chan_states_update(zldev);
> -
> /* Update DPLL-to-connected-ref phase offsets registers */
> rc = zl3073x_ref_phase_offsets_update(zldev, -1);
> if (rc)
> @@ -732,7 +717,7 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
> /* Update measured input reference frequencies if frequency
> * monitoring is enabled.
> */
> - if (zldev->freq_monitor) {
> + if (READ_ONCE(zldev->freq_monitor)) {
> rc = zl3073x_ref_freq_meas_update(zldev);
> if (rc)
> dev_warn(zldev->dev,
> @@ -768,7 +753,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
> return rc;
>
> /* Save the new factor */
> - zldev->phase_avg_factor = factor;
> + WRITE_ONCE(zldev->phase_avg_factor, factor);
>
> return 0;
> }
> diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
> index 6b55a05a222ed..78dc208f3eea2 100644
> --- a/drivers/dpll/zl3073x/core.h
> +++ b/drivers/dpll/zl3073x/core.h
> @@ -101,7 +101,7 @@ void zl3073x_dev_stop(struct zl3073x_dev *zldev);
>
> static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev)
> {
> - return zldev->phase_avg_factor;
> + return READ_ONCE(zldev->phase_avg_factor);
> }
>
> int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor);
> diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
> index 5e58ded5734d7..87e8b7c865485 100644
> --- a/drivers/dpll/zl3073x/dpll.c
> +++ b/drivers/dpll/zl3073x/dpll.c
> @@ -1,6 +1,5 @@
> // SPDX-License-Identifier: GPL-2.0-only
>
> -#include <linux/atomic.h>
> #include <linux/bits.h>
> #include <linux/bitfield.h>
> #include <linux/bug.h>
> @@ -58,7 +57,7 @@ struct zl3073x_dpll_pin {
> s32 phase_gran;
> enum dpll_pin_operstate operstate;
> s64 phase_offset;
> - atomic64_t freq_offset;
> + s64 freq_offset;
> u32 measured_freq;
> };
>
> @@ -134,6 +133,8 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
> const struct zl3073x_ref *ref;
> u8 ref_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> ref = zl3073x_ref_state_get(zldev, ref_id);
>
> @@ -170,6 +171,8 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_ref ref;
> u8 ref_id, sync_mode;
>
> + guard(mutex)(&zldpll->lock);
> +
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> ref = *zl3073x_ref_state_get(zldev, ref_id);
>
> @@ -205,6 +208,8 @@ zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
> const struct zl3073x_ref *ref;
> u8 ref_id, mode, pair;
>
> + guard(mutex)(&zldpll->lock);
> +
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> ref = zl3073x_ref_state_get(zldev, ref_id);
> mode = zl3073x_ref_sync_mode_get(ref);
> @@ -236,6 +241,8 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_ref ref;
> int rc;
>
> + guard(mutex)(&zldpll->lock);
> +
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id);
> ref = *zl3073x_ref_state_get(zldev, ref_id);
> @@ -299,12 +306,15 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
> struct dpll_ffo_param *ffo,
> struct netlink_ext_ack *extack)
> {
> + struct zl3073x_dpll *zldpll = dpll_priv;
> struct zl3073x_dpll_pin *pin = pin_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
> return -ENODATA;
>
> - ffo->ffo = atomic64_read(&pin->freq_offset);
> + ffo->ffo = pin->freq_offset;
>
> return 0;
> }
> @@ -316,8 +326,11 @@ zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin,
> void *dpll_priv, u64 *measured_freq,
> struct netlink_ext_ack *extack)
> {
> + struct zl3073x_dpll *zldpll = dpll_priv;
> struct zl3073x_dpll_pin *pin = pin_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> *measured_freq = pin->measured_freq;
> *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER;
>
> @@ -335,6 +348,8 @@ zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
> struct zl3073x_dpll_pin *pin = pin_priv;
> u8 ref_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> *frequency = zl3073x_dev_ref_freq_get(zldpll->dev, ref_id);
>
> @@ -354,6 +369,8 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_ref ref;
> u8 ref_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> /* Get reference state */
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> ref = *zl3073x_ref_state_get(zldev, ref_id);
> @@ -402,6 +419,8 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
> u8 conn_id, ref_id;
> s64 ref_phase;
>
> + guard(mutex)(&zldpll->lock);
> +
> /* Get currently connected reference */
> conn_id = zl3073x_dpll_connected_ref_get(zldpll);
>
> @@ -459,6 +478,8 @@ zl3073x_dpll_input_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
> s64 phase_comp;
> u8 ref_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> /* Read reference configuration */
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> ref = zl3073x_ref_state_get(zldev, ref_id);
> @@ -491,6 +512,8 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_ref ref;
> u8 ref_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> /* Read reference configuration */
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> ref = *zl3073x_ref_state_get(zldev, ref_id);
> @@ -524,6 +547,8 @@ zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin,
> const struct zl3073x_ref *ref;
> u8 ref_id;
>
> + lockdep_assert_held(&zldpll->lock);
> +
> ref_id = zl3073x_input_pin_ref_get(pin->id);
>
> /* Check if this pin is the currently locked reference */
> @@ -557,6 +582,8 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
> const struct zl3073x_chan *chan;
> u8 mode, ref;
>
> + guard(mutex)(&zldpll->lock);
> +
> chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
> ref = zl3073x_input_pin_ref_get(pin->id);
> mode = zl3073x_chan_mode_get(chan);
> @@ -590,8 +617,11 @@ zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
> enum dpll_pin_operstate *operstate,
> struct netlink_ext_ack *extack)
> {
> + struct zl3073x_dpll *zldpll = dpll_priv;
> struct zl3073x_dpll_pin *pin = pin_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> return zl3073x_dpll_ref_operstate_get(pin, operstate);
> }
>
> @@ -607,7 +637,9 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_dpll_pin *pin = pin_priv;
> struct zl3073x_chan chan;
> u8 mode, ref;
> - int rc;
> + int rc = 0;
> +
> + mutex_lock(&zldpll->lock);
>
> chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
> ref = zl3073x_input_pin_ref_get(pin->id);
> @@ -649,13 +681,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
> case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
> if (state == DPLL_PIN_STATE_SELECTABLE) {
> if (zl3073x_chan_ref_is_selectable(&chan, ref))
> - return 0; /* Pin is already selectable */
> + goto unlock; /* Pin is already selectable */
>
> /* Restore pin priority in HW */
> zl3073x_chan_ref_prio_set(&chan, ref, pin->prio);
> } else if (state == DPLL_PIN_STATE_DISCONNECTED) {
> if (!zl3073x_chan_ref_is_selectable(&chan, ref))
> - return 0; /* Pin is already disconnected */
> + goto unlock; /* Pin is already disconnected */
>
> /* Set pin priority to none in HW */
> zl3073x_chan_ref_prio_set(&chan, ref,
> @@ -668,18 +700,20 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
> /* In other modes we cannot change input reference */
> NL_SET_ERR_MSG(extack,
> "Pin state cannot be changed in current mode");
> - return -EOPNOTSUPP;
> + rc = -EOPNOTSUPP;
> + goto unlock;
> }
>
> /* Commit DPLL channel changes */
> rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
> - if (rc)
> - return rc;
> + goto unlock;
>
> - return 0;
> invalid_state:
> NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
> - return -EINVAL;
> + rc = -EINVAL;
> +unlock:
> + mutex_unlock(&zldpll->lock);
> + return rc;
> }
>
> static int
> @@ -687,8 +721,11 @@ zl3073x_dpll_input_pin_prio_get(const struct dpll_pin *dpll_pin, void *pin_priv,
> const struct dpll_device *dpll, void *dpll_priv,
> u32 *prio, struct netlink_ext_ack *extack)
> {
> + struct zl3073x_dpll *zldpll = dpll_priv;
> struct zl3073x_dpll_pin *pin = pin_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> *prio = pin->prio;
>
> return 0;
> @@ -705,6 +742,8 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
> u8 ref;
> int rc;
>
> + guard(mutex)(&zldpll->lock);
> +
> if (prio > ZL_DPLL_REF_PRIO_MAX)
> return -EINVAL;
>
> @@ -740,6 +779,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
> u32 synth_freq, out_freq;
> u8 out_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> out_id = zl3073x_output_pin_out_get(pin->id);
> out = zl3073x_out_state_get(zldev, out_id);
>
> @@ -797,6 +838,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
> u32 synth_freq;
> u8 out_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> out_id = zl3073x_output_pin_out_get(pin->id);
> out = *zl3073x_out_state_get(zldev, out_id);
>
> @@ -817,7 +860,7 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
>
> /* If esync is being disabled just write mailbox and finish */
> if (!freq)
> - goto write_mailbox;
> + return zl3073x_out_state_set(zldev, out_id, &out);
>
> /* Get attached synth frequency */
> synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(&out));
> @@ -834,7 +877,6 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
> */
> out.esync_n_width = out.div / 2;
>
> -write_mailbox:
> /* Commit output configuration */
> return zl3073x_out_state_set(zldev, out_id, &out);
> }
> @@ -849,6 +891,8 @@ zl3073x_dpll_output_pin_frequency_get(const struct dpll_pin *dpll_pin,
> struct zl3073x_dpll *zldpll = dpll_priv;
> struct zl3073x_dpll_pin *pin = pin_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> *frequency = zl3073x_dev_output_pin_freq_get(zldpll->dev, pin->id);
>
> return 0;
> @@ -869,6 +913,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_out out;
> u8 out_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> out_id = zl3073x_output_pin_out_get(pin->id);
> out = *zl3073x_out_state_get(zldev, out_id);
>
> @@ -942,6 +988,8 @@ zl3073x_dpll_output_pin_phase_adjust_get(const struct dpll_pin *dpll_pin,
> const struct zl3073x_out *out;
> u8 out_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> out_id = zl3073x_output_pin_out_get(pin->id);
> out = zl3073x_out_state_get(zldev, out_id);
>
> @@ -965,6 +1013,8 @@ zl3073x_dpll_output_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
> struct zl3073x_out out;
> u8 out_id;
>
> + guard(mutex)(&zldpll->lock);
> +
> out_id = zl3073x_output_pin_out_get(pin->id);
> out = *zl3073x_out_state_get(zldev, out_id);
>
> @@ -998,6 +1048,8 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
> u16 val;
> int rc;
>
> + guard(mutex)(&zldpll->lock);
> +
> rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val);
> if (rc)
> return rc;
> @@ -1009,14 +1061,13 @@ zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
> }
>
> static int
> -zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
> - enum dpll_lock_status *status,
> - enum dpll_lock_status_error *status_error,
> - struct netlink_ext_ack *extack)
> +__zl3073x_dpll_lock_status_get(struct zl3073x_dpll *zldpll,
> + enum dpll_lock_status *status)
> {
> - struct zl3073x_dpll *zldpll = dpll_priv;
> const struct zl3073x_chan *chan;
>
> + lockdep_assert_held(&zldpll->lock);
> +
> chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
>
> switch (zl3073x_chan_mode_get(chan)) {
> @@ -1052,6 +1103,19 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
> return 0;
> }
>
> +static int
> +zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
> + enum dpll_lock_status *status,
> + enum dpll_lock_status_error *status_error,
> + struct netlink_ext_ack *extack)
> +{
> + struct zl3073x_dpll *zldpll = dpll_priv;
> +
> + guard(mutex)(&zldpll->lock);
> +
> + return __zl3073x_dpll_lock_status_get(zldpll, status);
> +}
> +
> static int
> zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
> void *dpll_priv, unsigned long *modes,
> @@ -1060,6 +1124,8 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
> struct zl3073x_dpll *zldpll = dpll_priv;
> const struct zl3073x_chan *chan;
>
> + guard(mutex)(&zldpll->lock);
> +
> chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
>
> /* We support switching between automatic and manual mode, except in
> @@ -1082,6 +1148,8 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
> struct zl3073x_dpll *zldpll = dpll_priv;
> const struct zl3073x_chan *chan;
>
> + guard(mutex)(&zldpll->lock);
> +
> chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
>
> switch (zl3073x_chan_mode_get(chan)) {
> @@ -1138,8 +1206,8 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
> return rc;
> }
>
> - /* The averaging factor is common for all DPLL channels so after change
> - * we have to send a notification for other DPLL devices.
> + /* The averaging factor is common for all DPLL channels so after
> + * change we have to send a notification for other DPLL devices.
> */
> list_for_each_entry(item, &zldpll->dev->dplls, list) {
> struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
> @@ -1160,6 +1228,8 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
> u8 hw_mode, ref;
> int rc;
>
> + guard(mutex)(&zldpll->lock);
> +
> chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
> ref = zl3073x_chan_refsel_ref_get(&chan);
>
> @@ -1221,6 +1291,8 @@ zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll,
> {
> struct zl3073x_dpll *zldpll = dpll_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> if (zldpll->phase_monitor)
> *state = DPLL_FEATURE_STATE_ENABLE;
> else
> @@ -1237,6 +1309,8 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
> {
> struct zl3073x_dpll *zldpll = dpll_priv;
>
> + guard(mutex)(&zldpll->lock);
> +
> zldpll->phase_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
>
> return 0;
> @@ -1250,7 +1324,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
> {
> struct zl3073x_dpll *zldpll = dpll_priv;
>
> - if (zldpll->dev->freq_monitor)
> + if (READ_ONCE(zldpll->dev->freq_monitor))
> *state = DPLL_FEATURE_STATE_ENABLE;
> else
> *state = DPLL_FEATURE_STATE_DISABLE;
> @@ -1265,13 +1339,14 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
> struct netlink_ext_ack *extack)
> {
> struct zl3073x_dpll *item, *zldpll = dpll_priv;
> + struct zl3073x_dev *zldev = zldpll->dev;
>
> - zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
> + WRITE_ONCE(zldev->freq_monitor, state == DPLL_FEATURE_STATE_ENABLE);
>
> /* The frequency monitoring is common for all DPLL channels so after
> * change we have to send a notification for other DPLL devices.
> */
> - list_for_each_entry(item, &zldpll->dev->dplls, list) {
> + list_for_each_entry(item, &zldev->dplls, list) {
> struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
>
> if (item != zldpll && dpll_dev)
> @@ -1697,6 +1772,8 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
> u8 ref_id;
> int rc;
>
> + lockdep_assert_held(&zldpll->lock);
> +
> /* No phase offset if the ref monitor reports signal errors */
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> if (!zl3073x_dev_ref_is_status_ok(zldev, ref_id))
> @@ -1753,6 +1830,8 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
> const struct zl3073x_chan *chan;
> s64 ffo;
>
> + lockdep_assert_held(&zldpll->lock);
> +
> if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
> return false;
>
> @@ -1760,9 +1839,10 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
> ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
> 244140625, 36);
>
> - if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
> + if (pin->freq_offset != ffo) {
> dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
> pin->label, ffo);
> + pin->freq_offset = ffo;
> return true;
> }
>
> @@ -1787,7 +1867,9 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
> u8 ref_id;
> u32 freq;
>
> - if (!zldpll->dev->freq_monitor)
> + lockdep_assert_held(&zldpll->lock);
> +
> + if (!READ_ONCE(zldpll->dev->freq_monitor))
> return false;
>
> ref_id = zl3073x_input_pin_ref_get(pin->id);
> @@ -1817,27 +1899,37 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
> void
> zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
> {
> + DECLARE_BITMAP(changed_pins, ZL3073X_NUM_INPUT_PINS);
> struct zl3073x_dev *zldev = zldpll->dev;
> enum dpll_lock_status lock_status;
> struct device *dev = zldev->dev;
> struct zl3073x_dpll_pin *pin;
> + bool dev_changed = false;
> int rc;
>
> + bitmap_zero(changed_pins, ZL3073X_NUM_INPUT_PINS);
> +
> + mutex_lock(&zldpll->lock);
> +
> zldpll->check_count++;
>
> - /* Get current lock status for the DPLL */
> - rc = zl3073x_dpll_lock_status_get(zldpll->dpll_dev, zldpll,
> - &lock_status, NULL, NULL);
> + rc = zl3073x_chan_state_update(zldev, zldpll->id);
> + if (rc) {
> + dev_err(dev, "Failed to get DPLL%u state: %pe\n",
> + zldpll->id, ERR_PTR(rc));
> + goto unlock;
> + }
> +
> + rc = __zl3073x_dpll_lock_status_get(zldpll, &lock_status);
> if (rc) {
> dev_err(dev, "Failed to get DPLL%u lock status: %pe\n",
> zldpll->id, ERR_PTR(rc));
> - return;
> + goto unlock;
> }
>
> - /* If lock status was changed then notify DPLL core */
> if (zldpll->lock_status != lock_status) {
> zldpll->lock_status = lock_status;
> - dpll_device_change_ntf(zldpll->dpll_dev);
> + dev_changed = true;
> }
>
> /* Update phase offset latch registers for this DPLL if the phase
> @@ -1849,17 +1941,13 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
> dev_err(zldev->dev,
> "Failed to update phase offsets: %pe\n",
> ERR_PTR(rc));
> - return;
> + goto unlock;
> }
> }
>
> list_for_each_entry(pin, &zldpll->pins, list) {
> enum dpll_pin_operstate operstate;
> - bool pin_changed = false;
>
> - /* Output pins change checks are not necessary because output
> - * states are constant.
> - */
> if (!zl3073x_dpll_is_input_pin(pin))
> continue;
>
> @@ -1868,31 +1956,40 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
> dev_err(dev,
> "Failed to get %s on DPLL%u oper state: %pe\n",
> pin->label, zldpll->id, ERR_PTR(rc));
> - return;
> + goto unlock;
> }
>
> if (operstate != pin->operstate) {
> dev_dbg(dev, "%s oper state changed: %u->%u\n",
> pin->label, pin->operstate, operstate);
> pin->operstate = operstate;
> - pin_changed = true;
> + set_bit(pin->id, changed_pins);
> }
>
> - /* Check for phase offset, ffo, and measured freq change
> - * once per second.
> - */
> if (zldpll->check_count % 2 == 0) {
> if (zl3073x_dpll_pin_phase_offset_check(pin))
> - pin_changed = true;
> + set_bit(pin->id, changed_pins);
>
> if (zl3073x_dpll_pin_ffo_check(pin))
> - pin_changed = true;
> + set_bit(pin->id, changed_pins);
>
> if (zl3073x_dpll_pin_measured_freq_check(pin))
> - pin_changed = true;
> + set_bit(pin->id, changed_pins);
> }
> + }
>
> - if (pin_changed)
> +unlock:
> + mutex_unlock(&zldpll->lock);
> +
> + /* Send notifications outside the lock to avoid ABBA deadlock
> + * with dpll_lock taken by notification functions.
> + */
> + if (dev_changed)
> + dpll_device_change_ntf(zldpll->dpll_dev);
> +
> + list_for_each_entry(pin, &zldpll->pins, list) {
> + if (zl3073x_dpll_is_input_pin(pin) &&
> + test_bit(pin->id, changed_pins))
> dpll_pin_change_ntf(pin->dpll_pin);
> }
> }
> @@ -1949,6 +2046,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
>
> zldpll->dev = zldev;
> zldpll->id = ch;
> + mutex_init(&zldpll->lock);
> INIT_LIST_HEAD(&zldpll->pins);
>
> return zldpll;
> @@ -1965,6 +2063,7 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
> {
> WARN(zldpll->dpll_dev, "DPLL device is still registered\n");
>
> + mutex_destroy(&zldpll->lock);
> kfree(zldpll);
> }
>
> diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
> index 21adcc18e45e1..9f57c944a0077 100644
> --- a/drivers/dpll/zl3073x/dpll.h
> +++ b/drivers/dpll/zl3073x/dpll.h
> @@ -18,6 +18,7 @@
> * @ops: DPLL device operations for this instance
> * @dpll_dev: pointer to registered DPLL device
> * @tracker: tracking object for the acquired reference
> + * @lock: per-DPLL mutex serializing all operations
> * @lock_status: last saved DPLL lock status
> * @pins: list of pins
> */
> @@ -30,6 +31,7 @@ struct zl3073x_dpll {
> struct dpll_device_ops ops;
> struct dpll_device *dpll_dev;
> dpll_tracker tracker;
> + struct mutex lock;
> enum dpll_lock_status lock_status;
> struct list_head pins;
> };
Reviewed-by: Petr Oros <poros@redhat.com>
^ permalink raw reply
* RE: [PATCH v4 3/7] mtd: spi-nor: sfdp: expose the SFDP as a read-only NVMEM device
From: Takahiro.Kuwano @ 2026-07-03 9:21 UTC (permalink / raw)
To: manikandan.m, pratyush, mwalle, miquel.raynal, richard, vigneshr,
robh, krzk+dt, conor+dt, srini, nicolas.ferre, alexandre.belloni,
claudiu.beznea, linux, richardcochran, linusw, arnd, michael,
linux-mtd, devicetree, linux-kernel, linux-arm-kernel, netdev
In-Reply-To: <8f3b422e0b7749b2a47b14a585dd0a0c@infineon.com>
> Hi,
>
> > Register the cached SFDP as a read-only NVMEM device rooted at the
> > flash's "sfdp" child node, exposing it in on-flash byte order. This lets
> > NVMEM cells reference any SFDP data: a fixed-layout for parameters at a
> > known offset, or an nvmem-layout parser for vendor data whose location
> > must be discovered at runtime. The device is only registered when an
> > "sfdp" node is present in the device tree.
> >
> > Signed-off-by: Manikandan Muralidharan <manikandan.m@microchip.com>
>
> SFDP is exposed in sysfs (e.g., /sys/bus/spi/devices/spi0.0/spi-nor/sfdp).
> Why don't you just use this existing entry?
Sorry, sysfs is for userspace.
Please just disregard my previous comment.
Thanks,
Takahiro
^ permalink raw reply
* RE: [PATCH net v2] net: usb: lan78xx: disable VLAN filter in promiscuous mode
From: Thangaraj.S @ 2026-07-03 9:25 UTC (permalink / raw)
To: nb, enrico.pozzobon
Cc: UNGLinuxDriver, andrew+netdev, davem, edumazet, kuba, pabeni,
Woojung.Huh, netdev, linux-usb, linux-kernel
In-Reply-To: <aef78533a81c88a362885c24928a7c60@tipi-net.de>
Hi Nicolai,
> AFAIU the findings from Sashiko [1] on v1 are either false positives or pre-
> existing issues unrelated to this change, so LGTM.
>
> @Thangaraj / maintainers:
> The mails to Rengarajan are bouncing since a few series - does the
> MAINTAINERS entry need an update?
>
[Thangaraj Samynathan] Yes, Rengarajan has left Microchip. His name needs
to be removed from maintainer.
> Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
>
Thanks,
Thangaraj Samynathan - I53494
^ permalink raw reply
* [PATCH net-next v4 0/6] r8169: add support for phylink
From: javen @ 2026-07-03 9:24 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
This series patch adds support for phylink. RTL8116af is a fiber mode
card, link status and speed can not be read from standard phy reg. So
we read link status and speed from serdes reg by pcs. So as RTL8127atf.
Javen Xu (6):
r8169: add speed in private struct
r8169: add support for phylink
r8169: add support for RTL8116af
r8169: add ltr support for RTL8117 series
r8169: fix RTL8116af can not enter s0idle and c10
r8169: add phylink support for RTL8127atf
drivers/net/ethernet/realtek/Kconfig | 1 +
drivers/net/ethernet/realtek/r8169_main.c | 619 +++++++++++++++++-----
drivers/net/phy/realtek/realtek_main.c | 53 --
include/net/phy/realtek_phy.h | 7 -
4 files changed, 479 insertions(+), 201 deletions(-)
delete mode 100644 include/net/phy/realtek_phy.h
--
2.43.0
^ permalink raw reply
* [PATCH net-next v4 1/6] r8169: add speed in private struct
From: javen @ 2026-07-03 9:24 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260703092459.1124-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
This patch adds speed in private struct in order to decouple
from phydev in the following patch supporting for phylink.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- repalce current_speed with speed
Changes in v3:
- update tp->speed in rtl8169_set_link_ksettings()
Changes in v4:
- no changes
---
drivers/net/ethernet/realtek/r8169_main.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index ec4fc21fa21f..c60710f9bd21 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -750,6 +750,7 @@ struct rtl8169_private {
u32 irq_mask;
int irq;
struct clk *clk;
+ int speed;
struct {
DECLARE_BITMAP(flags, RTL_FLAG_MAX);
@@ -1673,16 +1674,14 @@ static void rtl8169_irq_mask_and_ack(struct rtl8169_private *tp)
rtl_pci_commit(tp);
}
-static void rtl_link_chg_patch(struct rtl8169_private *tp)
+static void rtl_link_chg_patch(struct rtl8169_private *tp, int speed)
{
- struct phy_device *phydev = tp->phydev;
-
if (tp->mac_version == RTL_GIGA_MAC_VER_34 ||
tp->mac_version == RTL_GIGA_MAC_VER_38) {
- if (phydev->speed == SPEED_1000) {
+ if (speed == SPEED_1000) {
rtl_eri_write(tp, 0x1bc, ERIAR_MASK_1111, 0x00000011);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x00000005);
- } else if (phydev->speed == SPEED_100) {
+ } else if (speed == SPEED_100) {
rtl_eri_write(tp, 0x1bc, ERIAR_MASK_1111, 0x0000001f);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x00000005);
} else {
@@ -1692,7 +1691,7 @@ static void rtl_link_chg_patch(struct rtl8169_private *tp)
rtl_reset_packet_filter(tp);
} else if (tp->mac_version == RTL_GIGA_MAC_VER_35 ||
tp->mac_version == RTL_GIGA_MAC_VER_36) {
- if (phydev->speed == SPEED_1000) {
+ if (speed == SPEED_1000) {
rtl_eri_write(tp, 0x1bc, ERIAR_MASK_1111, 0x00000011);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x00000005);
} else {
@@ -1700,7 +1699,7 @@ static void rtl_link_chg_patch(struct rtl8169_private *tp)
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_1111, 0x0000003f);
}
} else if (tp->mac_version == RTL_GIGA_MAC_VER_37) {
- if (phydev->speed == SPEED_10) {
+ if (speed == SPEED_10) {
rtl_eri_write(tp, 0x1d0, ERIAR_MASK_0011, 0x4d02);
rtl_eri_write(tp, 0x1dc, ERIAR_MASK_0011, 0x0060a);
} else {
@@ -2074,11 +2073,11 @@ rtl_coalesce_info(struct rtl8169_private *tp)
ci = rtl_coalesce_info_8168_8136;
/* if speed is unknown assume highest one */
- if (tp->phydev->speed == SPEED_UNKNOWN)
+ if (tp->speed == SPEED_UNKNOWN)
return ci;
for (; ci->speed; ci++) {
- if (tp->phydev->speed == ci->speed)
+ if (tp->speed == ci->speed)
return ci;
}
@@ -2236,7 +2235,7 @@ static void rtl_set_eee_txidle_timer(struct rtl8169_private *tp)
static unsigned int r8169_get_tx_lpi_timer_us(struct rtl8169_private *tp)
{
- unsigned int speed = tp->phydev->speed;
+ unsigned int speed = tp->speed;
unsigned int timer = tp->tx_lpi_timer;
if (!timer || speed == SPEED_UNKNOWN)
@@ -4968,8 +4967,9 @@ static void r8169_phylink_handler(struct net_device *ndev)
struct rtl8169_private *tp = netdev_priv(ndev);
struct device *d = tp_to_dev(tp);
+ tp->speed = tp->phydev->speed;
if (netif_carrier_ok(ndev)) {
- rtl_link_chg_patch(tp);
+ rtl_link_chg_patch(tp, tp->speed);
rtl_enable_tx_lpi(tp, tp->phydev->enable_tx_lpi);
pm_request_resume(d);
} else {
@@ -5667,6 +5667,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
ext_xid_str, xid);
tp->mac_version = chip->mac_version;
tp->fw_name = chip->fw_name;
+ tp->speed = SPEED_UNKNOWN;
/* Disable ASPM L1 as that cause random device stop working
* problems as well as full system hangs for some PCIe devices users.
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 5/6] r8169: fix RTL8116af can not enter s0idle and c10
From: javen @ 2026-07-03 9:24 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260703092459.1124-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
RTL8116AF is a multi-function device. Functions 2 to 7 are hidden from
the PCI core and return an all-ones response when their vendor ID is read,
so they are not enumerated as normal PCI functions.
However, these hidden functions can still affect platform power
management. If they are left in D0 or keep ASPM disabled, the platform may
fail to enter the low-power s0ix state and the CPU package may fail to
enter Package C10.
Put functions 2 to 7 into D3hot and enable ASPM on their PCIe link control
register. Since these functions are hidden, access their configuration
space through pci_bus_read_config_dword() / pci_bus_write_config_dword()
using the same slot and the target function numbers.
Ignore functions that return a PCI error response when reading their
configuration space.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes
Changes in v3:
- no changes
Changes in v4:
- add gate for rtl_lowpower_hidden_functions, only for RTL8116af
---
drivers/net/ethernet/realtek/r8169_main.c | 57 ++++++++++++++++++-----
1 file changed, 46 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 635131c51db9..0dd805f0d613 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -97,17 +97,20 @@
#define JUMBO_9K (9 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
#define JUMBO_16K (SZ_16K - VLAN_ETH_HLEN - ETH_FCS_LEN)
-#define OCP_SDS_ADDR_REG 0xEB10
-#define OCP_SDS_CMD_REG 0xEB0E
-#define OCP_SDS_DATA_REG 0xEB14
-#define SDS_CMD_READ 0x0001
-#define RTL_SDS_C22_BASE 0x40
-#define RTL_PKG_DETECT 0xdc00
-#define RTL_PKG_DETECT_MASK 0x0078
-#define RTL_PKG_DETECT_8116AF 0x0030
-#define RTL_INT_HW_ID 0xd006
-#define RTL_INT_HW_ID_MASK 0x00ff
-#define RTL_INT_HW_ID_8116AF 0x0000
+#define OCP_SDS_ADDR_REG 0xEB10
+#define OCP_SDS_CMD_REG 0xEB0E
+#define OCP_SDS_DATA_REG 0xEB14
+#define SDS_CMD_READ 0x0001
+#define RTL_SDS_C22_BASE 0x40
+#define RTL_PKG_DETECT 0xdc00
+#define RTL_PKG_DETECT_MASK 0x0078
+#define RTL_PKG_DETECT_8116AF 0x0030
+#define RTL_INT_HW_ID 0xd006
+#define RTL_INT_HW_ID_MASK 0x00ff
+#define RTL_INT_HW_ID_8116AF 0x0000
+#define RTL8116AF_FUNC_PM_CSR 0x80
+#define RTL8116AF_FUNC_EXP_LNKCTL 0x44
+#define RTL_PM_D3HOT GENMASK(1, 0)
static const struct rtl_chip_info {
u32 mask;
@@ -3779,6 +3782,35 @@ static void rtl_hw_start_8168ep_3(struct rtl8169_private *tp)
r8168_mac_ocp_modify(tp, 0xe860, 0x0000, 0x0080);
}
+static void rtl_lowpower_hidden_functions(struct pci_dev *pdev)
+{
+ unsigned int slot = PCI_SLOT(pdev->devfn);
+ struct pci_bus *bus = pdev->bus;
+ unsigned int devfn;
+ int func;
+ int ret;
+ u32 val;
+
+ for (func = 2; func < 8; func++) {
+ devfn = PCI_DEVFN(slot, func);
+
+ ret = pci_bus_read_config_dword(bus, devfn, RTL8116AF_FUNC_PM_CSR, &val);
+ if (!ret && !PCI_POSSIBLE_ERROR(val)) {
+ val &= ~PCI_PM_CTRL_PME_STATUS;
+ val &= ~(PCI_PM_CTRL_STATE_MASK | PCI_PM_CTRL_PME_ENABLE);
+ val |= (RTL_PM_D3HOT | PCI_PM_CTRL_PME_ENABLE);
+ pci_bus_write_config_dword(bus, devfn, RTL8116AF_FUNC_PM_CSR, val);
+ }
+
+ ret = pci_bus_read_config_dword(bus, devfn, RTL8116AF_FUNC_EXP_LNKCTL, &val);
+ if (!ret && !PCI_POSSIBLE_ERROR(val)) {
+ val &= ~((PCI_EXP_LNKSTA_LBMS | PCI_EXP_LNKSTA_LABS) << 16);
+ val |= PCI_EXP_LNKCTL_ASPMC;
+ pci_bus_write_config_dword(bus, devfn, RTL8116AF_FUNC_EXP_LNKCTL, val);
+ }
+ }
+}
+
static void rtl_hw_start_8117(struct rtl8169_private *tp)
{
static const struct ephy_info e_info_8117[] = {
@@ -3835,6 +3867,9 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
r8168_mac_ocp_write(tp, 0xc094, 0x0000);
r8168_mac_ocp_write(tp, 0xc09e, 0x0000);
+ if (rtl_is_8116af(tp))
+ rtl_lowpower_hidden_functions(tp->pci_dev);
+
/* firmware is for MAC only */
r8169_apply_firmware(tp);
}
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 6/6] r8169: add phylink support for RTL8127atf
From: javen @ 2026-07-03 9:24 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260703092459.1124-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
RTL8127 ATF is a 10G SFP mode of RTL8127. Like RTL8116af, it has no
internal phy, so phylink uses a pcs to get the link status from the
serdes registers instead of standard phy registers.
The interface mode is PHY_INTERFACE_MODE_10GBASER. rtl_mac_select_pcs()
returns tp->pcs for it, and the serdes is configured for 10G in
pcs_config() via r8127_sfp_init_10g(). Speed and duplex are hardcoded
to 10Gbps Full-Duplex.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v3:
- No changes. New file.
Changes in v4:
- remove DUMMY_PHY in driver/net/phy/realtek/realtek_main.c and related
function
---
drivers/net/ethernet/realtek/r8169_main.c | 80 ++++++++++-------------
drivers/net/phy/realtek/realtek_main.c | 53 ---------------
include/net/phy/realtek_phy.h | 7 --
3 files changed, 35 insertions(+), 105 deletions(-)
delete mode 100644 include/net/phy/realtek_phy.h
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 0dd805f0d613..26d96edb3c42 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -102,6 +102,7 @@
#define OCP_SDS_DATA_REG 0xEB14
#define SDS_CMD_READ 0x0001
#define RTL_SDS_C22_BASE 0x40
+#define RTL_SDS_C45_BASE 0x0080
#define RTL_PKG_DETECT 0xdc00
#define RTL_PKG_DETECT_MASK 0x0078
#define RTL_PKG_DETECT_8116AF 0x0030
@@ -1158,10 +1159,6 @@ static int r8168_phy_ocp_read(struct rtl8169_private *tp, u32 reg)
if (rtl_ocp_reg_failure(reg))
return 0;
- /* Return dummy MII_PHYSID2 in SFP mode to match SFP PHY driver */
- if (tp->sfp_mode == RTL_SFP_8127_ATF && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
- return PHY_ID_RTL_DUMMY_SFP & 0xffff;
-
RTL_W32(tp, GPHY_OCP, reg << 15);
return rtl_loop_wait_high(tp, &rtl_ocp_gphy_cond, 25, 10) ?
@@ -1247,12 +1244,6 @@ static void r8127_sfp_init_10g(struct rtl8169_private *tp)
r8168_phy_ocp_write(tp, 0xc804, (val & ~0x000f) | 0x000c);
}
-static void rtl_sfp_init(struct rtl8169_private *tp)
-{
- if (tp->mac_version == RTL_GIGA_MAC_VER_80)
- r8127_sfp_init_10g(tp);
-}
-
static void rtl_sfp_reset(struct rtl8169_private *tp)
{
if (tp->mac_version == RTL_GIGA_MAC_VER_80)
@@ -2442,30 +2433,8 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *cmd)
{
struct rtl8169_private *tp = netdev_priv(ndev);
- struct phy_device *phydev = tp->phydev;
- int duplex = cmd->base.duplex;
- int speed = cmd->base.speed;
-
- if (tp->sfp_mode != RTL_SFP_8127_ATF)
- return phylink_ethtool_ksettings_set(tp->phylink, cmd);
-
- if (cmd->base.autoneg != AUTONEG_DISABLE)
- return -EINVAL;
- if (!phy_check_valid(speed, duplex, phydev->supported))
- return -EINVAL;
-
- mutex_lock(&phydev->lock);
-
- phydev->autoneg = AUTONEG_DISABLE;
- phydev->speed = speed;
- phydev->duplex = duplex;
-
- rtl_sfp_init(tp);
-
- mutex_unlock(&phydev->lock);
-
- return 0;
+ return phylink_ethtool_ksettings_set(tp->phylink, cmd);
}
static int rtl8169_nway_reset(struct net_device *dev)
@@ -2614,9 +2583,6 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
tp->pci_dev->subsystem_device == 0xe000)
phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
- if (tp->sfp_mode == RTL_SFP_8127_ATF)
- rtl_sfp_init(tp);
-
/* We may have called phy_speed_down before */
phy_speed_up(tp->phydev);
@@ -5036,7 +5002,7 @@ static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
if (status & LinkChg) {
if (tp->phydev)
phy_mac_interrupt(tp->phydev);
- else if (tp->sfp_mode == RTL_SFP_8168_AF)
+ else if (tp->sfp_mode)
phylink_mac_change(tp->phylink,
!!(RTL_R8(tp, PHYstatus) & LinkStatus));
}
@@ -5767,7 +5733,8 @@ static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
if (!tp->pcs.ops)
return NULL;
- if (interface == PHY_INTERFACE_MODE_1000BASEX)
+ if (interface == PHY_INTERFACE_MODE_1000BASEX ||
+ interface == PHY_INTERFACE_MODE_10GBASER)
return &tp->pcs;
return NULL;
@@ -5797,12 +5764,28 @@ static void rtl8169_pcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
- u16 bmsr, lpa;
- bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
- lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
+ if (tp->sfp_mode == RTL_SFP_8127_ATF) {
+ u16 stat1;
+
+ stat1 = rtl8169_sds_read(tp, RTL_SDS_C45_BASE + MDIO_STAT1);
+
+ if (!(stat1 & MDIO_STAT1_LSTATUS))
+ stat1 = rtl8169_sds_read(tp, RTL_SDS_C45_BASE + MDIO_STAT1);
+
+ state->link = !!(stat1 & MDIO_STAT1_LSTATUS);
+ if (!state->link)
+ return;
- phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa);
+ state->duplex = DUPLEX_FULL;
+ state->speed = SPEED_10000;
+ } else {
+ u16 bmsr, lpa;
+
+ bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
+ lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa);
+ }
}
static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
@@ -5810,6 +5793,11 @@ static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
+ struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
+
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
+ r8127_sfp_init_10g(tp);
+
return 0;
}
@@ -5855,7 +5843,7 @@ static unsigned long rtl8169_get_lpi_caps(struct rtl8169_private *tp)
{
unsigned long caps = 0;
- if (!rtl_supports_eee(tp))
+ if (!rtl_supports_eee(tp) || tp->sfp_mode == RTL_SFP_8127_ATF)
return 0;
caps |= MAC_100FD | MAC_1000FD;
@@ -5899,7 +5887,9 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
tp->phylink_config.mac_capabilities |= MAC_1000FD;
break;
case RTL_SFP_8127_ATF:
- phy_mode = PHY_INTERFACE_MODE_INTERNAL;
+ tp->pcs.ops = &r8169_pcs_ops;
+ phy_mode = PHY_INTERFACE_MODE_10GBASER;
+ tp->phylink_config.default_an_inband = true;
tp->phylink_config.mac_capabilities |= MAC_10000FD;
break;
default:
@@ -6129,7 +6119,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (rc)
return rc;
- if (tp->sfp_mode != RTL_SFP_8168_AF) {
+ if (tp->sfp_mode == RTL_SFP_NONE) {
rc = r8169_mdio_register(tp);
if (rc) {
phylink_destroy(tp->phylink);
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index b65d0f5fa1a0..7f904de583e0 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -2646,45 +2646,6 @@ static irqreturn_t rtl8221b_handle_interrupt(struct phy_device *phydev)
return IRQ_HANDLED;
}
-static int rtlgen_sfp_get_features(struct phy_device *phydev)
-{
- linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
- phydev->supported);
-
- /* set default mode */
- phydev->speed = SPEED_10000;
- phydev->duplex = DUPLEX_FULL;
-
- phydev->port = PORT_FIBRE;
-
- return 0;
-}
-
-static int rtlgen_sfp_read_status(struct phy_device *phydev)
-{
- int val, err;
-
- err = genphy_update_link(phydev);
- if (err)
- return err;
-
- if (!phydev->link)
- return 0;
-
- val = phy_read(phydev, RTL_PHYSR);
- if (val < 0)
- return val;
-
- rtlgen_decode_physr(phydev, val);
-
- return 0;
-}
-
-static int rtlgen_sfp_config_aneg(struct phy_device *phydev)
-{
- return 0;
-}
-
static struct phy_driver realtek_drvs[] = {
{
PHY_ID_MATCH_EXACT(0x00008201),
@@ -2934,20 +2895,6 @@ static struct phy_driver realtek_drvs[] = {
.write_page = rtl821x_write_page,
.read_mmd = rtl822x_read_mmd,
.write_mmd = rtl822x_write_mmd,
- }, {
- PHY_ID_MATCH_EXACT(PHY_ID_RTL_DUMMY_SFP),
- .name = "Realtek SFP PHY Mode",
- .flags = PHY_IS_INTERNAL,
- .probe = rtl822x_probe,
- .get_features = rtlgen_sfp_get_features,
- .config_aneg = rtlgen_sfp_config_aneg,
- .read_status = rtlgen_sfp_read_status,
- .suspend = genphy_suspend,
- .resume = rtlgen_resume,
- .read_page = rtl821x_read_page,
- .write_page = rtl821x_write_page,
- .read_mmd = rtl822x_read_mmd,
- .write_mmd = rtl822x_write_mmd,
}, {
PHY_ID_MATCH_EXACT(0x001ccad0),
.name = "RTL8224 2.5Gbps PHY",
diff --git a/include/net/phy/realtek_phy.h b/include/net/phy/realtek_phy.h
deleted file mode 100644
index d683bc1b0659..000000000000
--- a/include/net/phy/realtek_phy.h
+++ /dev/null
@@ -1,7 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef _REALTEK_PHY_H
-#define _REALTEK_PHY_H
-
-#define PHY_ID_RTL_DUMMY_SFP 0x001ccbff
-
-#endif /* _REALTEK_PHY_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 3/6] r8169: add support for RTL8116af
From: javen @ 2026-07-03 9:24 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260703092459.1124-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
RTL8116af is sfp mode. Phylink uses pcs to get the link status from its
serdes reg, instead of standard phy reg. Speed and duplex are hardcoded
to 1000Mbps Full-Duplex. Also, RTL8116af doesn't have internal phy, so
we add some checks to ensure that tp->phydev is not empty when we need it.
In rtl_hw_start_8117(), the MAC calibration for register 0xd412 relies
on reading the internal PHY register 0x0c42. Since RTL8116af does not
have an internal PHY, this calibration step is intentionally bypassed.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- replace some magic numbers with macro
Changes in v3:
- change commit message
- add lock when we do rtl8169_sds_read
- use phylink_mii_c22_pcs_decode_state to get status
- add phylink_mac_change for RTL8116af for it doesn't have phy
Changes in v4:
- if tp->pcs.ops is not initial, just return NULL in rtl_mac_select_pcs
---
drivers/net/ethernet/realtek/r8169_main.c | 186 ++++++++++++++++++----
1 file changed, 153 insertions(+), 33 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 8f06dedc33d1..b5816550a4a6 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -97,6 +97,18 @@
#define JUMBO_9K (9 * SZ_1K - VLAN_ETH_HLEN - ETH_FCS_LEN)
#define JUMBO_16K (SZ_16K - VLAN_ETH_HLEN - ETH_FCS_LEN)
+#define OCP_SDS_ADDR_REG 0xEB10
+#define OCP_SDS_CMD_REG 0xEB0E
+#define OCP_SDS_DATA_REG 0xEB14
+#define SDS_CMD_READ 0x0001
+#define RTL_SDS_C22_BASE 0x40
+#define RTL_PKG_DETECT 0xdc00
+#define RTL_PKG_DETECT_MASK 0x0078
+#define RTL_PKG_DETECT_8116AF 0x0030
+#define RTL_INT_HW_ID 0xd006
+#define RTL_INT_HW_ID_MASK 0x00ff
+#define RTL_INT_HW_ID_8116AF 0x0000
+
static const struct rtl_chip_info {
u32 mask;
u32 val;
@@ -729,6 +741,12 @@ enum rtl_dash_type {
RTL_DASH_25_BP,
};
+enum rtl_sfp_mode {
+ RTL_SFP_NONE,
+ RTL_SFP_8168_AF,
+ RTL_SFP_8127_ATF,
+};
+
struct rtl8169_private {
void __iomem *mmio_addr; /* memory map physical address */
struct pci_dev *pci_dev;
@@ -737,6 +755,7 @@ struct rtl8169_private {
struct napi_struct napi;
enum mac_version mac_version;
enum rtl_dash_type dash_type;
+ enum rtl_sfp_mode sfp_mode;
u32 cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */
u32 cur_tx; /* Index into the Tx descriptor buffer of next Rx pkt. */
u32 dirty_tx;
@@ -764,7 +783,6 @@ struct rtl8169_private {
unsigned supports_gmii:1;
unsigned aspm_manageable:1;
unsigned dash_enabled:1;
- bool sfp_mode:1;
dma_addr_t counters_phys_addr;
struct rtl8169_counters *counters;
struct rtl8169_tc_offsets tc_offset;
@@ -778,6 +796,7 @@ struct rtl8169_private {
u32 ocp_base;
struct phylink *phylink;
struct phylink_config phylink_config;
+ struct phylink_pcs pcs;
struct ethtool_pauseparam saved_pause;
bool jumbo_pause_saved;
};
@@ -1135,7 +1154,7 @@ static int r8168_phy_ocp_read(struct rtl8169_private *tp, u32 reg)
return 0;
/* Return dummy MII_PHYSID2 in SFP mode to match SFP PHY driver */
- if (tp->sfp_mode && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
+ if (tp->sfp_mode == RTL_SFP_8127_ATF && reg == (OCP_STD_PHY_BASE + 2 * MII_PHYSID2))
return PHY_ID_RTL_DUMMY_SFP & 0xffff;
RTL_W32(tp, GPHY_OCP, reg << 15);
@@ -1289,6 +1308,15 @@ static void mac_mcu_write(struct rtl8169_private *tp, int reg, int value)
r8168_mac_ocp_write(tp, tp->ocp_base + reg, value);
}
+static bool rtl_is_8116af(struct rtl8169_private *tp)
+{
+ return tp->mac_version == RTL_GIGA_MAC_VER_52 &&
+ (r8168_mac_ocp_read(tp, RTL_PKG_DETECT) & RTL_PKG_DETECT_MASK) ==
+ RTL_PKG_DETECT_8116AF &&
+ (r8168_mac_ocp_read(tp, RTL_INT_HW_ID) & RTL_INT_HW_ID_MASK) ==
+ RTL_INT_HW_ID_8116AF;
+}
+
static int mac_mcu_read(struct rtl8169_private *tp, int reg)
{
return r8168_mac_ocp_read(tp, tp->ocp_base + reg);
@@ -1584,6 +1612,20 @@ static bool rtl_dash_is_enabled(struct rtl8169_private *tp)
}
}
+static enum rtl_sfp_mode rtl_get_sfp_mode(struct rtl8169_private *tp)
+{
+ if (rtl_is_8125(tp)) {
+ u16 data = r8168_mac_ocp_read(tp, RTL_INT_HW_ID);
+
+ if ((data & 0xff) == 0x07)
+ return RTL_SFP_8127_ATF;
+ } else if (rtl_is_8116af(tp)) {
+ return RTL_SFP_8168_AF;
+ }
+
+ return RTL_SFP_NONE;
+}
+
static enum rtl_dash_type rtl_get_dash_type(struct rtl8169_private *tp)
{
switch (tp->mac_version) {
@@ -2399,7 +2441,7 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
int duplex = cmd->base.duplex;
int speed = cmd->base.speed;
- if (!tp->sfp_mode)
+ if (tp->sfp_mode != RTL_SFP_8127_ATF)
return phylink_ethtool_ksettings_set(tp->phylink, cmd);
if (cmd->base.autoneg != AUTONEG_DISABLE)
@@ -2511,9 +2553,10 @@ void r8169_apply_firmware(struct rtl8169_private *tp)
tp->ocp_base = OCP_STD_PHY_BASE;
/* PHY soft reset may still be in progress */
- phy_read_poll_timeout(tp->phydev, MII_BMCR, val,
- !(val & BMCR_RESET),
- 50000, 600000, true);
+ if (tp->phydev)
+ phy_read_poll_timeout(tp->phydev, MII_BMCR, val,
+ !(val & BMCR_RESET),
+ 50000, 600000, true);
}
}
@@ -2550,6 +2593,8 @@ static void rtl_schedule_task(struct rtl8169_private *tp, enum rtl_flag flag)
static void rtl8169_init_phy(struct rtl8169_private *tp)
{
+ phy_init_hw(tp->phydev);
+ phy_resume(tp->phydev);
r8169_hw_phy_config(tp, tp->phydev, tp->mac_version);
if (tp->mac_version <= RTL_GIGA_MAC_VER_06) {
@@ -2564,7 +2609,7 @@ static void rtl8169_init_phy(struct rtl8169_private *tp)
tp->pci_dev->subsystem_device == 0xe000)
phy_write_paged(tp->phydev, 0x0001, 0x10, 0xf01b);
- if (tp->sfp_mode)
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
rtl_sfp_init(tp);
/* We may have called phy_speed_down before */
@@ -3751,12 +3796,14 @@ static void rtl_hw_start_8117(struct rtl8169_private *tp)
rtl_pcie_state_l2l3_disable(tp);
- rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
- if (rg_saw_cnt > 0) {
- u16 sw_cnt_1ms_ini;
+ if (tp->phydev) {
+ rg_saw_cnt = phy_read_paged(tp->phydev, 0x0c42, 0x13) & 0x3fff;
+ if (rg_saw_cnt > 0) {
+ u16 sw_cnt_1ms_ini;
- sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
- r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
+ sw_cnt_1ms_ini = (16000000 / rg_saw_cnt) & 0x0fff;
+ r8168_mac_ocp_modify(tp, 0xd412, 0x0fff, sw_cnt_1ms_ini);
+ }
}
r8168_mac_ocp_modify(tp, 0xe056, 0x00f0, 0x0000);
@@ -4932,8 +4979,13 @@ static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
goto out;
}
- if (status & LinkChg)
- phy_mac_interrupt(tp->phydev);
+ if (status & LinkChg) {
+ if (tp->phydev)
+ phy_mac_interrupt(tp->phydev);
+ else if (tp->sfp_mode == RTL_SFP_8168_AF)
+ phylink_mac_change(tp->phylink,
+ !!(RTL_R8(tp, PHYstatus) & LinkStatus));
+ }
rtl_irq_disable(tp);
napi_schedule(&tp->napi);
@@ -5045,7 +5097,7 @@ static void rtl8169_down(struct rtl8169_private *tp)
phylink_stop(tp->phylink);
/* Reset SerDes PHY to bring down fiber link */
- if (tp->sfp_mode)
+ if (tp->sfp_mode == RTL_SFP_8127_ATF)
rtl_sfp_reset(tp);
rtl8169_update_counters(tp);
@@ -5067,9 +5119,9 @@ static void rtl8169_up(struct rtl8169_private *tp)
rtl8168_driver_start(tp);
pci_set_master(tp->pci_dev);
- phy_init_hw(tp->phydev);
- phy_resume(tp->phydev);
- rtl8169_init_phy(tp);
+ if (tp->phydev)
+ rtl8169_init_phy(tp);
+
napi_enable(&tp->napi);
enable_work(&tp->wk.work);
rtl_reset_work(tp);
@@ -5147,9 +5199,11 @@ static int rtl_open(struct net_device *dev)
if (retval < 0)
goto err_release_fw_2;
- retval = r8169_phy_connect(tp);
- if (retval)
- goto err_free_irq;
+ if (tp->phydev) {
+ retval = r8169_phy_connect(tp);
+ if (retval)
+ goto err_free_irq;
+ }
rtl8169_up(tp);
rtl8169_init_counter_offsets(tp);
@@ -5654,6 +5708,14 @@ static void rtl_mac_link_up(struct phylink_config *config, struct phy_device *ph
static struct phylink_pcs *rtl_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
+ struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
+
+ if (!tp->pcs.ops)
+ return NULL;
+
+ if (interface == PHY_INTERFACE_MODE_1000BASEX)
+ return &tp->pcs;
+
return NULL;
}
@@ -5662,6 +5724,51 @@ static void rtl_mac_config(struct phylink_config *config, unsigned int mode,
{
}
+static u16 rtl8169_sds_read(struct rtl8169_private *tp, u16 sds_reg)
+{
+ unsigned long flags;
+ u16 val = 0;
+
+ raw_spin_lock_irqsave(&tp->mac_ocp_lock, flags);
+ __r8168_mac_ocp_write(tp, OCP_SDS_ADDR_REG, sds_reg);
+ __r8168_mac_ocp_write(tp, OCP_SDS_CMD_REG, SDS_CMD_READ);
+ val = __r8168_mac_ocp_read(tp, OCP_SDS_DATA_REG);
+ raw_spin_unlock_irqrestore(&tp->mac_ocp_lock, flags);
+
+ return val;
+}
+
+static void rtl8169_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct rtl8169_private *tp = container_of(pcs, struct rtl8169_private, pcs);
+ u16 bmsr, lpa;
+
+ bmsr = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_BMSR);
+ lpa = rtl8169_sds_read(tp, RTL_SDS_C22_BASE + MII_LPA);
+
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa);
+}
+
+static int rtl8169_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ return 0;
+}
+
+static int rtl8169_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
+ const struct phylink_link_state *state)
+{
+ return 0;
+}
+
+static void rtl8169_pcs_an_restart(struct phylink_pcs *pcs)
+{
+}
+
static void rtl_mac_disable_tx_lpi(struct phylink_config *config)
{
struct rtl8169_private *tp = container_of(config, struct rtl8169_private, phylink_config);
@@ -5712,6 +5819,13 @@ static unsigned long rtl8169_get_lpi_caps(struct rtl8169_private *tp)
return caps;
}
+static const struct phylink_pcs_ops r8169_pcs_ops = {
+ .pcs_validate = rtl8169_pcs_validate,
+ .pcs_get_state = rtl8169_pcs_get_state,
+ .pcs_config = rtl8169_pcs_config,
+ .pcs_an_restart = rtl8169_pcs_an_restart,
+};
+
static int rtl_init_phylink(struct rtl8169_private *tp)
{
struct phylink *pl;
@@ -5723,10 +5837,18 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
tp->phylink_config.lpi_capabilities = rtl8169_get_lpi_caps(tp);
tp->phylink_config.mac_capabilities |= MAC_ASYM_PAUSE | MAC_SYM_PAUSE;
- if (tp->sfp_mode) {
+ switch (tp->sfp_mode) {
+ case RTL_SFP_8168_AF:
+ tp->pcs.ops = &r8169_pcs_ops;
+ tp->phylink_config.default_an_inband = true;
+ phy_mode = PHY_INTERFACE_MODE_1000BASEX;
+ tp->phylink_config.mac_capabilities |= MAC_1000FD;
+ break;
+ case RTL_SFP_8127_ATF:
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
tp->phylink_config.mac_capabilities |= MAC_10000FD;
- } else {
+ break;
+ default:
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
tp->phylink_config.mac_capabilities |= MAC_10 | MAC_100;
@@ -5747,6 +5869,7 @@ static int rtl_init_phylink(struct rtl8169_private *tp)
PHY_INTERFACE_MODE_MII;
else
phy_mode = PHY_INTERFACE_MODE_INTERNAL;
+ break;
}
__set_bit(phy_mode, tp->phylink_config.supported_interfaces);
@@ -5844,12 +5967,7 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
}
tp->aspm_manageable = !rc;
- if (rtl_is_8125(tp)) {
- u16 data = r8168_mac_ocp_read(tp, 0xd006);
-
- if ((data & 0xff) == 0x07)
- tp->sfp_mode = true;
- }
+ tp->sfp_mode = rtl_get_sfp_mode(tp);
tp->dash_type = rtl_get_dash_type(tp);
tp->dash_enabled = rtl_dash_is_enabled(tp);
@@ -5957,10 +6075,12 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (rc)
return rc;
- rc = r8169_mdio_register(tp);
- if (rc) {
- phylink_destroy(tp->phylink);
- return rc;
+ if (tp->sfp_mode != RTL_SFP_8168_AF) {
+ rc = r8169_mdio_register(tp);
+ if (rc) {
+ phylink_destroy(tp->phylink);
+ return rc;
+ }
}
rc = register_netdev(dev);
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox