* [PATCH net-next v5 2/2] selftests: net: add FOU multicast encapsulation resubmit test
From: Anton Danilov @ 2026-07-05 2:36 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, David S . Miller, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <cover.1783218197.git.littlesmilingcloud@gmail.com>
Add a selftest to verify that FOU-encapsulated packets addressed to a
multicast destination are correctly resubmitted to the inner protocol
handler (GRE) via the UDP multicast delivery path. Both IPv4 and IPv6
paths are tested.
The test creates two network namespaces connected by a veth pair with
a FOU/GRETAP (IPv4) and FOU/ip6gretap (IPv6) tunnel using multicast
remote addresses (239.0.0.1 and ff0e::1). Ping is sent through each
tunnel and received packets are counted on the receiver's tunnel
interface.
The veth pair is created directly inside the namespaces to avoid
possible name collisions with devices in the root namespace.
Static neighbor entries are configured on the sender because ARP/ND
replies from the receiver cannot traverse the unidirectional multicast
tunnel back to the sender.
The early demux optimization (net.ipv4.ip_early_demux, which controls
both IPv4 and IPv6) is disabled on the receiver to force packets
through __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver(), which
is the code path being tested.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
Assisted-by: Claude:claude-opus-4-6
---
tools/testing/selftests/net/Makefile | 1 +
.../testing/selftests/net/fou_mcast_encap.sh | 177 ++++++++++++++++++
2 files changed, 178 insertions(+)
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae07d..7e9ae937cffa 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -39,6 +39,7 @@ TEST_PROGS := \
fib_rule_tests.sh \
fib_tests.sh \
fin_ack_lat.sh \
+ fou_mcast_encap.sh \
fq_band_pktlimit.sh \
gre_gso.sh \
gre_ipv6_lladdr.sh \
diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh
new file mode 100755
index 000000000000..728513d55db4
--- /dev/null
+++ b/tools/testing/selftests/net/fou_mcast_encap.sh
@@ -0,0 +1,177 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test that UDP encapsulation (FOU) correctly handles packet resubmit
+# when packets are delivered via the multicast UDP delivery path.
+#
+# When a FOU-encapsulated packet arrives with a multicast destination IP,
+# __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver() must resubmit
+# it to the inner protocol handler (e.g., GRE) rather than consuming it.
+# This test verifies both IPv4 and IPv6 paths by creating a FOU/GRETAP
+# tunnel with a multicast remote address and sending ping through it.
+#
+# The early demux optimization can mask this issue by routing packets via
+# the unicast path (udp[6]_unicast_rcv_skb), so we disable it to force
+# packets through the multicast delivery function.
+
+source lib.sh
+
+NSENDER=""
+NRECV=""
+
+FOU_PORT4=4797
+FOU_PORT6=4798
+MCAST4=239.0.0.1
+MCAST6=ff0e::1
+
+TUN4_S=192.168.99.1
+TUN4_R=192.168.99.2
+TUN6_S=2001:db8:99::1
+TUN6_R=2001:db8:99::2
+
+cleanup() {
+ cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+setup_common() {
+ setup_ns NSENDER NRECV
+
+ # Create veth pair directly inside namespaces to avoid name
+ # collisions with devices in the root namespace.
+ ip link add veth_s netns "$NSENDER" type veth \
+ peer name veth_r netns "$NRECV"
+
+ ip -n "$NSENDER" link set veth_s up
+ ip -n "$NRECV" link set veth_r up
+
+ # Same sysctl controls early demux for both IPv4 and IPv6.
+ ip netns exec "$NRECV" sysctl -wq net.ipv4.ip_early_demux=0
+}
+
+setup_ipv4() {
+ ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s
+ ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r
+
+ # Join multicast group on receiver
+ ip -n "$NRECV" addr add "$MCAST4/32" dev veth_r autojoin
+
+ ip -n "$NSENDER" route add 239.0.0.0/8 dev veth_s
+ ip -n "$NRECV" route add 239.0.0.0/8 dev veth_r
+
+ # Sender: GRETAP with FOU encap (no FOU listener needed on TX side)
+ ip -n "$NSENDER" link add eoudp4 type gretap \
+ remote "$MCAST4" local 10.0.0.1 \
+ encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \
+ key "$MCAST4"
+ ip -n "$NSENDER" link set eoudp4 up
+ ip -n "$NSENDER" addr add "$TUN4_S/24" dev eoudp4
+
+ # Receiver: FOU listener + GRETAP
+ ip netns exec "$NRECV" ip fou add port "$FOU_PORT4" ipproto 47
+ ip -n "$NRECV" link add eoudp4 type gretap \
+ remote "$MCAST4" local 10.0.0.2 \
+ encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \
+ key "$MCAST4"
+ ip -n "$NRECV" link set eoudp4 up
+ ip -n "$NRECV" addr add "$TUN4_R/24" dev eoudp4
+
+ # Static neigh on sender: ARP replies cannot traverse the
+ # unidirectional multicast tunnel.
+ local recv_mac
+ recv_mac=$(ip -n "$NRECV" link show eoudp4 | awk '/ether/{print $2}')
+ ip -n "$NSENDER" neigh add "$TUN4_R" lladdr "$recv_mac" dev eoudp4
+}
+
+setup_ipv6() {
+ # Skip cleanly if IPv6 is not available in the running kernel.
+ [ -e /proc/sys/net/ipv6 ] || return "$ksft_skip"
+ modprobe -q fou6 || return "$ksft_skip"
+
+ ip -n "$NSENDER" addr add 2001:db8::1/64 dev veth_s nodad
+ ip -n "$NRECV" addr add 2001:db8::2/64 dev veth_r nodad
+
+ # Join multicast group on receiver
+ ip -n "$NRECV" addr add "$MCAST6/128" dev veth_r autojoin
+
+ ip -n "$NSENDER" -6 route add ff00::/8 dev veth_s
+ ip -n "$NRECV" -6 route add ff00::/8 dev veth_r
+
+ # Sender: ip6gretap with FOU encap
+ ip -n "$NSENDER" link add eoudp6 type ip6gretap \
+ remote "$MCAST6" local 2001:db8::1 \
+ encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \
+ key 42
+ ip -n "$NSENDER" link set eoudp6 up
+ ip -n "$NSENDER" addr add "$TUN6_S/64" dev eoudp6 nodad
+
+ # Receiver: FOU listener (IPv6) + ip6gretap
+ ip netns exec "$NRECV" ip fou add port "$FOU_PORT6" ipproto 47 -6
+ ip -n "$NRECV" link add eoudp6 type ip6gretap \
+ remote "$MCAST6" local 2001:db8::2 \
+ encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \
+ key 42
+ ip -n "$NRECV" link set eoudp6 up
+ ip -n "$NRECV" addr add "$TUN6_R/64" dev eoudp6 nodad
+
+ # Static neigh on sender: neighbor discovery cannot traverse the
+ # unidirectional multicast tunnel.
+ local recv_mac
+ recv_mac=$(ip -n "$NRECV" link show eoudp6 | awk '/ether/{print $2}')
+ ip -n "$NSENDER" neigh add "$TUN6_R" lladdr "$recv_mac" dev eoudp6
+}
+
+get_rx_packets() {
+ local dev="$1"
+
+ ip -n "$NRECV" -s link show "$dev" | awk '/RX:/{getline; print $2}'
+}
+
+run_ping_test() {
+ local family="$1"
+ local dev="$2"
+ local dst="$3"
+ local count=100
+ local rx_before rx_after rx_delta
+
+ # Warmup: let any initial broadcast/ND traffic settle
+ ip netns exec "$NSENDER" ping "$family" -c 1 -W 1 "$dst" \
+ >/dev/null 2>&1
+ sleep 1
+
+ rx_before=$(get_rx_packets "$dev")
+ ip netns exec "$NSENDER" ping "$family" -c $count -W 1 "$dst" \
+ >/dev/null 2>&1
+ sleep 1
+ rx_after=$(get_rx_packets "$dev")
+
+ rx_delta=$((rx_after - rx_before))
+
+ if [ "$rx_delta" -ge "$count" ]; then
+ echo "PASS: received $rx_delta/$count packets"
+ return "$ksft_pass"
+ elif [ "$rx_delta" -gt 0 ]; then
+ echo "FAIL: only $rx_delta/$count packets received"
+ return "$ksft_fail"
+ else
+ echo "FAIL: 0/$count packets received"
+ return "$ksft_fail"
+ fi
+}
+
+ret=0
+
+echo "TEST: FOU/GRETAP IPv4 multicast encapsulation resubmit"
+setup_common
+setup_ipv4
+run_ping_test -4 eoudp4 "$TUN4_R" || ret=$?
+
+echo "TEST: FOU/GRETAP IPv6 multicast encapsulation resubmit"
+if setup_ipv6; then
+ run_ping_test -6 eoudp6 "$TUN6_R" || ret=$?
+else
+ echo "SKIP: IPv6 unavailable"
+fi
+
+exit $ret
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v5 1/2] udp: fix encapsulation packet resubmit in multicast deliver
From: Anton Danilov @ 2026-07-05 2:36 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, David S . Miller, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <cover.1783218197.git.littlesmilingcloud@gmail.com>
When a UDP encapsulation socket (e.g., FOU) receives a multicast
packet, __udp4_lib_mcast_deliver() and __udp6_lib_mcast_deliver()
call consume_skb() when udp_queue_rcv_skb() returns a positive value.
A positive return value from udp_queue_rcv_skb() indicates that the
encap_rcv handler (e.g., fou_udp_recv) has consumed the UDP header
and wants the packet to be resubmitted to the IP protocol handler
for further processing (e.g., as a GRE packet).
The unicast paths handle this correctly by propagating the return
value up to ip_protocol_deliver_rcu() / ip6_protocol_deliver_rcu()
for resubmission. However, the multicast paths destroy the packet
via consume_skb() instead of resubmitting it, causing silent packet
loss.
This affects any UDP encapsulation (FOU, GUE) combined with multicast
destination addresses.
Fix this by returning the value from udp_queue_rcv_skb() when it is
positive, matching the behavior of the corresponding unicast paths.
Note the sign difference between IPv4 and IPv6:
- IPv4: udp_unicast_rcv_skb() returns -ret, and
ip_protocol_deliver_rcu() resubmits when ret < 0
(using -ret as the protocol number).
- IPv6: udp6_unicast_rcv_skb() returns ret, and
ip6_protocol_deliver_rcu() resubmits when ret > 0
(using ret as the nexthdr).
Both mcast paths now follow the same convention as their respective
unicast paths.
Suggested-by: Willem de Bruijn <willemb@google.com>
Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
Assisted-by: Claude:claude-opus-4-6
---
net/ipv4/udp.c | 6 ++++--
net/ipv6/udp.c | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 59248a59358c..d3ddcbfc8477 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -2476,6 +2476,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udp_hslot *hslot;
struct sk_buff *nskb;
bool use_hash2;
+ int ret;
hash2_any = 0;
hash2 = 0;
@@ -2520,8 +2521,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
}
if (first) {
- if (udp_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
+ ret = udp_queue_rcv_skb(first, skb);
+ if (ret > 0)
+ return -ret;
} else {
kfree_skb(skb);
__UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 392e18b97045..0910cc171776 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -949,6 +949,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udp_hslot *hslot;
struct sk_buff *nskb;
bool use_hash2;
+ int ret;
hash2_any = 0;
hash2 = 0;
@@ -998,8 +999,9 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
}
if (first) {
- if (udpv6_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
+ ret = udpv6_queue_rcv_skb(first, skb);
+ if (ret > 0)
+ return ret;
} else {
kfree_skb(skb);
__UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v5 0/2] udp: fix FOU/GUE over multicast
From: Anton Danilov @ 2026-07-05 2:36 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, David S . Miller, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
UDP encapsulation (FOU, GUE) has never worked correctly with multicast
destination addresses. When a FOU-encapsulated packet arrives at a
multicast address, it enters __udp4_lib_mcast_deliver() /
__udp6_lib_mcast_deliver() which call consume_skb() on packets that
need resubmission to the inner protocol handler, silently dropping
them instead.
The unicast delivery paths handle this correctly by propagating the
return value up to ip[6]_protocol_deliver_rcu() for resubmission, but
the multicast paths were never updated to support UDP encapsulation
resubmit.
This causes silent packet loss for FOU/GRETAP tunnels configured with
multicast remote addresses (both IPv4 and IPv6).
Reproducing the issue (IPv4):
ip netns add ns_a && ip netns add ns_b
ip link add veth0 netns ns_a type veth peer name veth1 netns ns_b
ip -n ns_a addr add 10.0.0.1/24 dev veth0 && ip -n ns_a link set veth0 up
ip -n ns_b addr add 10.0.0.2/24 dev veth1 && ip -n ns_b link set veth1 up
ip -n ns_a route add 239.0.0.0/8 dev veth0
ip -n ns_b route add 239.0.0.0/8 dev veth1
# Disable early demux to expose the issue (otherwise it's partially masked)
ip netns exec ns_b sysctl -w net.ipv4.ip_early_demux=0
# Join multicast group on receiver
ip -n ns_b addr add 239.0.0.1/32 dev veth1 autojoin
# Sender: GRETAP with FOU encap
ip -n ns_a link add eoudp0 type gretap \
remote 239.0.0.1 local 10.0.0.1 \
encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
ip -n ns_a link set eoudp0 up
ip -n ns_a addr add 192.168.99.1/24 dev eoudp0
# Receiver: FOU listener + GRETAP
ip netns exec ns_b ip fou add port 4797 ipproto 47
ip -n ns_b link add eoudp0 type gretap \
remote 239.0.0.1 local 10.0.0.2 \
encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
ip -n ns_b link set eoudp0 up
ip -n ns_b addr add 192.168.99.2/24 dev eoudp0
# Static neigh: ARP replies can't traverse unidirectional mcast tunnel
recv_mac=$(ip -n ns_b link show eoudp0 | awk '/ether/{print $2}')
ip -n ns_a neigh add 192.168.99.2 lladdr $recv_mac dev eoudp0
# Test: ping through the FOU/GRETAP tunnel
ip netns exec ns_a ping -c 100 192.168.99.2
# -> without this patch: 0 packets received on eoudp0
# -> with this patch: all packets received on eoudp0
IPv6 (using fou6 + ip6gretap) exhibits the same silent drop with a
different fix (see 1/2 for the sign-of-ret difference between
ip_protocol_deliver_rcu() and ip6_protocol_deliver_rcu()).
AI assistance (Claude, claude-opus-4-6) was used during root cause
analysis of the kernel source code (tracing the call chain from
udp[6]_queue_rcv_skb through encap_rcv to ip[6]_protocol_deliver_rcu,
comparing unicast/GSO/multicast paths) and during patch and selftest
authoring.
v5:
- Fix IPv6 patch: return ret, not -ret (Willem de Bruijn)
- selftest: add IPv6 test case
- selftest: create the veth pair inside the namespaces
(Willem de Bruijn)
v4: https://lore.kernel.org/netdev/cover.1782945956.git.littlesmilingcloud@gmail.com/
- Promoted from RFC to PATCH; no functional changes since v3.
v3 was posted as RFC and consequently dropped from patchwork,
which explains the lack of review feedback.
v3: https://lore.kernel.org/netdev/cover.1777934869.git.littlesmilingcloud@gmail.com/
- Use return -ret instead of calling ip_protocol_deliver_rcu()
directly, matching the unicast path and avoiding call stack
growth with nested encapsulations (Kuniyuki Iwashima)
- Only change the first-socket path; the clone loop is not
reachable for tunnel sockets (no SO_REUSEADDR/SO_REUSEPORT)
- Replace Python packet generator with ping through a properly
configured FOU/GRETAP tunnel in the selftest
- Add static neighbor entry (ARP replies cannot traverse the
unidirectional multicast tunnel)
v2: https://lore.kernel.org/netdev/ad_dal164gVmImWl@dau-home-pc/
- Moved inline Python packet generator into a separate helper
- Fixed author email typo in Signed-off-by
v1 (RFC): https://lore.kernel.org/netdev/ad7MsSJOuUU6EGwS@dau-home-pc/
Anton Danilov (2):
udp: fix encapsulation packet resubmit in multicast deliver
selftests: net: add FOU multicast encapsulation resubmit test
net/ipv4/udp.c | 6 +-
net/ipv6/udp.c | 6 +-
tools/testing/selftests/net/Makefile | 1 +
.../testing/selftests/net/fou_mcast_encap.sh | 177 ++++++++++++++++++
4 files changed, 186 insertions(+), 4 deletions(-)
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
--
2.47.3
^ permalink raw reply
* Re: [RFC] xdp: add device context to bpf_xdp_link_attach_failed tracepoint
From: Masashi Honma @ 2026-07-05 2:11 UTC (permalink / raw)
To: Leon Hwang
Cc: netdev, bpf, linux-trace-kernel, ast, daniel, kuba, hawk, andrii,
rostedt, mhiramat, edumazet, pabeni, linux-kernel
In-Reply-To: <216969a9-3584-4dc8-9e23-50fc18b31725@linux.dev>
2026年7月4日(土) 22:28 Leon Hwang <leon.hwang@linux.dev>:
> Probably, you can get the 'extack->_msg' by tracing dev_xdp_attach using
> kprobe+kretprobe or kprobe.session, if the extack is not NULL.
Thanks, that's a nice pointer -- dev_xdp_attach() has both the net_device
(so the ifindex, which lets us correlate a failure to a specific attach)
and the extack, and it avoids depending on the tracepoint you want to
retire.
The tradeoff is that dev_xdp_attach() is a static internal function, so a
probe on it can break across kernels (inlining/signature changes). For a
best-effort error message that's tolerable with a graceful fallback, but
it's a maintenance cost on our side.
Since this is ultimately just an error-message improvement, and your
in-band BPF_LINK_CREATE work would solve it cleanly for all link types, I
think we'd lean toward waiting for that rather than adding an internal
kprobe to Cilium. Do you have a rough timeline for the BPF_LINK_CREATE
series? That would help us decide whether a stopgap is worth it.
Regards,
Masashi Honma.
^ permalink raw reply
* Re: [PATCH 1/7] samples: rust_dma: use vertical import style
From: Guru Das Srinagesh @ 2026-07-05 1:15 UTC (permalink / raw)
To: Danilo Krummrich
Cc: Miguel Ojeda, rust-for-linux, linux-kernel, Abdiel Janulgue,
Daniel Almeida, Robin Murphy, Andreas Hindborg, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Alice Ryhl,
Trevor Gross, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Drew Fustini, Guo Ren, Fu Wei, Michal Wilczynski,
Uwe Kleine-König, Rafael J. Wysocki, Viresh Kumar,
Jens Axboe, FUJITA Tomonori, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Airlie, Simona Vetter, driver-core,
linux-riscv, linux-pwm, linux-pm, linux-block, netdev, nova-gpu,
dri-devel
In-Reply-To: <DJMAFSKM4K9W.19ED2PVPU6S3R@kernel.org>
On Tue, Jun 30, 2026 at 11:48:48AM +0200, Danilo Krummrich wrote:
> On Mon Jun 29, 2026 at 5:38 AM CEST, Guru Das Srinagesh wrote:
> > page, pci,
>
> Can you please also convert this one? Patch 7 also misses at least one case.
Will fix this and the missed ones in Patch 7 as well. Thanks for your review, Danilo.
^ permalink raw reply
* Re: [PATCH 3/7] cpufreq: rcpufreq_dt: use vertical import style
From: Guru Das Srinagesh @ 2026-07-05 1:01 UTC (permalink / raw)
To: Miguel Ojeda
Cc: Zhongqiu Han, Miguel Ojeda, rust-for-linux, linux-kernel,
Danilo Krummrich, Abdiel Janulgue, Daniel Almeida, Robin Murphy,
Andreas Hindborg, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Drew Fustini, Guo Ren, Fu Wei,
Michal Wilczynski, Uwe Kleine-König, Rafael J. Wysocki,
Viresh Kumar, Jens Axboe, FUJITA Tomonori, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, David Airlie, Simona Vetter,
driver-core, linux-riscv, linux-pwm, linux-pm, linux-block,
netdev, nova-gpu, dri-devel
In-Reply-To: <CANiq72=SQK7pd-fj+4MOb=E6=R-DHCcLcuCvN=us2E5o7Rcy2A@mail.gmail.com>
On Tue, Jun 30, 2026 at 10:35:00AM +0200, Miguel Ojeda wrote:
> On Mon, Jun 29, 2026 at 2:43 PM Zhongqiu Han
> <zhongqiu.han@oss.qualcomm.com> wrote:
> >
> > If the preferred style is to place each imported item on its own line,
> > shouldn't imports such as
> >
> > cpu, cpufreq,
> >
> > be formatted similarly as well?
>
> Indeed, good eyes.
>
> To do what we want, `rustfmt` needs the `//` at the end of that level
> too (in the future, it will be without the `//`), i.e. the patch
> probably passes `rustfmtcheck`, but it still needs to split that line
> and add the other `//`.
Hi Zhongqiu, Miguel:
Yes, I did run `make LLVM=1 rustfmtcheck` and it passed on this series. I will fix
the missed ones in v2.
While investigating this, I found that that adding this config `imports_layout =
"Vertical"` to the rustfmt options would fix all the imports automatically, including
the ones I missed. I ran it locally on the files touched in this series using rustfmt
nightly and it correctly fixed the imports as desired:
rustup run nightly rustfmt --unstable-features \
--config "imports_layout=Vertical" \
--config-path .rustfmt.toml <file>
But unfortunately, since `imports_layout` is an unstable option currently [1], it
cannot be used straightaway.
However, .rustfmt.toml already has a section of commented-out unstable options kept
as a reference for when they stabilize. Would it make sense to add `#imports_layout =
"Vertical"` there? If so, I can include it in v2.
[1]: https://github.com/rust-lang/rustfmt/issues/3361
^ permalink raw reply
* Re: [PATCH 0/7] rust: Use kernel style vertical imports in various drivers
From: Guru Das Srinagesh @ 2026-07-05 0:38 UTC (permalink / raw)
To: Andrew Lunn
Cc: Miguel Ojeda, rust-for-linux, linux-kernel, Danilo Krummrich,
Abdiel Janulgue, Daniel Almeida, Robin Murphy, Andreas Hindborg,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Alice Ryhl, Trevor Gross, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Drew Fustini, Guo Ren, Fu Wei, Michal Wilczynski,
Uwe Kleine-König, Rafael J. Wysocki, Viresh Kumar,
Jens Axboe, FUJITA Tomonori, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie, Simona Vetter, driver-core, linux-riscv, linux-pwm,
linux-pm, linux-block, netdev, nova-gpu, dri-devel
In-Reply-To: <c89a1bc8-6cc1-4e16-ac95-add389e45a2b@lunn.ch>
On Mon, Jun 29, 2026 at 04:06:36PM +0200, Andrew Lunn wrote:
> On Sun, Jun 28, 2026 at 08:38:14PM -0700, Guru Das Srinagesh wrote:
> > Came across a recent commit bc58905eb07 ("samples: rust_misc_device: use
> > vertical import style") and found a few more locations that could
> > benefit from this cleanup. No functional changes.
> >
> > Signed-off-by: Guru Das Srinagesh <linux@gurudas.dev>
> > ---
> > Guru Das Srinagesh (7):
> > samples: rust_dma: use vertical import style
> > pwm: th1520: use vertical import style
> > cpufreq: rcpufreq_dt: use vertical import style
> > block: rnull: use vertical import style
> > net: phy: ax88796b: use vertical import style
> > net: phy: qt2025: use vertical import style
> > drm/nova: use vertical import style
>
> You have multiple subsystems here, so you need to split this patch
> setup, per subsystem, and submit them separately. Maintainers only
> accept patchsets for their own subsystems.
>
> For netdev, please take a read of:
>
> https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
>
> You need to get the correct tree, and set the Subject: line correctly.
>
> Andrew
Hi Andrew,
Thanks for the feedback.
I was aware of the per-subsystem rule, but reasoned that since these changes are
purely about Rust import formatting coding style with no functional impact on any
subsystem, they might go through the rust-for-linux tree with acks from the
respective subsystem maintainers. The Rust coding style is independent of any
subsystem-specific guidelines.
Is that reasoning off-base, or is the right path to split these out per subsystem
regardless?
Miguel, could you please indicate if you have a preference here?
Thank you.
^ permalink raw reply
* [PATCH net] sctp: validate the body of a STALE_COOKIE error before reading it
From: Xiang Mei @ 2026-07-05 0:30 UTC (permalink / raw)
To: Marcelo Ricardo Leitner, Xin Long, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: linux-sctp, netdev, linux-kernel, bestswngs, Xiang Mei
sctp_sf_do_5_2_6_stale() reads the 32-bit Measure of Staleness that
follows the error header:
stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
without checking that the STALE_COOKIE cause actually carries that
4-byte body. sctp_walk_errors() in the caller only requires
err->length >= sizeof(struct sctp_errhdr), so a peer can send an 8-byte
ERROR chunk whose sole STALE_COOKIE cause has length == 4 and no body.
It passes sctp_chunk_length_valid() (>= 8) and the error walk, yet the
staleness read reaches past the validated cause.
When that is the only chunk in the packet the cause ends exactly at
skb_tail (sctp_inq_pop() discards only when chunk_end > skb_tail), so
the read stays in-bounds of the skb head slab object but past the packet
data. The value is folded into the COOKIE_PRESERVATIVE parameter of the
retransmitted INIT and reflected to the peer, leaking adjacent kernel
slab bytes.
Discard the chunk when the staleness field falls outside the validated
chunk data.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/sctp/sm_statefuns.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
index d23d935e128e..e4b4b63162cf 100644
--- a/net/sctp/sm_statefuns.c
+++ b/net/sctp/sm_statefuns.c
@@ -2592,6 +2592,9 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale(
err = (struct sctp_errhdr *)(chunk->skb->data);
+ if ((u8 *)err + sizeof(*err) + sizeof(__be32) > chunk->chunk_end)
+ return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
+
/* When calculating the time extension, an implementation
* SHOULD use the RTT information measured based on the
* previous COOKIE ECHO / ERROR exchange, and should add no
--
2.43.0
^ permalink raw reply related
* [PATCH] net: xscale: ixp4xx: add missing MODULE_DEVICE_TABLE()
From: Pengpeng Hou @ 2026-07-05 0:19 UTC (permalink / raw)
To: Linus Walleij, Imre Kaloz, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Pengpeng Hou, linux-arm-kernel, netdev, linux-kernel
The driver has a match table for the of bus wired into its driver
structure, but the table is not exported with MODULE_DEVICE_TABLE().
Add the missing MODULE_DEVICE_TABLE() entry so module alias information
is generated for automatic module loading.
This is a source-level fix. It does not claim dynamic hardware
reproduction; the evidence is the driver-owned match table, its use by
the driver registration structure, and the missing module alias
publication.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/net/ethernet/xscale/ixp4xx_eth.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/xscale/ixp4xx_eth.c b/drivers/net/ethernet/xscale/ixp4xx_eth.c
index b0faa0f1780d..3c6a300386c5 100644
--- a/drivers/net/ethernet/xscale/ixp4xx_eth.c
+++ b/drivers/net/ethernet/xscale/ixp4xx_eth.c
@@ -1606,6 +1606,7 @@ static const struct of_device_id ixp4xx_eth_of_match[] = {
},
{ },
};
+MODULE_DEVICE_TABLE(of, ixp4xx_eth_of_match);
static struct platform_driver ixp4xx_eth_driver = {
.driver = {
--
2.53.0
^ permalink raw reply related
* [PATCH] net: gianfar: fix use-after-free in gfar_enet_open on startup_gfar failure
From: Rosen Penev @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: Claudiu Manoil, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list
If startup_gfar() fails in gfar_enet_open(), the PHY remains connected
and IRQs remain requested. Since ndo_open returned an error, IFF_UP is
not set and ndo_stop (gfar_close) will not be called on unregister,
leaving the IRQ handlers registered against freed net_device memory
when the driver is removed.
Add proper error unwinding: phy_disconnect() and gfar_free_irq() before
returning the error.
Fixes: 80ec396cb6b5 ("gianfar: Don't free/request irqs on device reset")
Assisted-by: Opencode:Big-Pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/freescale/gianfar.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c
index 89215e1ddc2d..4b3a5eaadfb5 100644
--- a/drivers/net/ethernet/freescale/gianfar.c
+++ b/drivers/net/ethernet/freescale/gianfar.c
@@ -2878,8 +2878,11 @@ static int gfar_enet_open(struct net_device *dev)
return err;
err = startup_gfar(dev);
- if (err)
+ if (err) {
+ phy_disconnect(dev->phydev);
+ gfar_free_irq(priv);
return err;
+ }
return err;
}
--
2.55.0
^ permalink raw reply related
* [PATCH net-next 4/4] selftests: net: hsr: add PRP RedBox test
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
In-Reply-To: <20260704234704.4297-1-xiexinet@gmail.com>
Add a kselftest that builds a PRP RedBox (interlink) with a SAN behind the
interlink and a peer DANP, and checks bidirectional unicast across the
interlink, preservation of the SAN source MAC on the PRP network, and that
the proxy-announce supervision frame carries the RedBox-MAC TLV (Type 30)
terminated by an EOT marker. It reuses the hsr_common.sh / lib.sh helpers
and skips cleanly on a kernel or iproute2 without PRP interlink support.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
tools/testing/selftests/net/hsr/Makefile | 1 +
.../selftests/net/hsr/hsr_prp_redbox.sh | 96 +++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100755 tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326c..2150e487a 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -4,6 +4,7 @@ top_srcdir = ../../../../..
TEST_PROGS := \
hsr_ping.sh \
+ hsr_prp_redbox.sh \
hsr_redbox.sh \
link_faults.sh \
prp_ping.sh \
diff --git a/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
new file mode 100755
index 000000000..8ae36737b
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test a PRP RedBox (PRP-SAN): a SAN that sits behind the interlink port must
+# reach, and be reached by, a peer DANP on the PRP network with its own MAC
+# preserved on the wire, and the RedBox must announce the SAN with a RedBox-MAC
+# TLV (terminated by an EOT marker) in its PRP supervision frames.
+#
+# RB PRP RedBox: prp0 over rb_a/rb_b (LAN A/B) + interlink rb_il
+# PEER peer DANP : prp0 over pe_a/pe_b, 100.64.0.2
+# SAN SAN : san_il, own MAC, 100.64.0.51 (behind the interlink)
+
+ipv6=false
+
+source ./hsr_common.sh
+
+check_prerequisites
+
+if ! command -v tcpdump >/dev/null 2>&1; then
+ echo "SKIP: This test requires tcpdump"
+ exit $ksft_skip
+fi
+
+if ! ip link help hsr 2>&1 | grep -q interlink; then
+ echo "SKIP: iproute2 too old (no hsr interlink support)"
+ exit $ksft_skip
+fi
+
+setup_ns RB PEER SAN
+trap 'cleanup_ns "$RB" "$PEER" "$SAN"' EXIT
+
+ip link add rb_a netns "$RB" type veth peer name pe_a netns "$PEER"
+ip link add rb_b netns "$RB" type veth peer name pe_b netns "$PEER"
+ip link add rb_il netns "$RB" type veth peer name san_il netns "$SAN"
+
+ip -n "$RB" link set rb_a up
+ip -n "$RB" link set rb_b up
+ip -n "$RB" link set rb_il up
+ip -n "$PEER" link set pe_a up
+ip -n "$PEER" link set pe_b up
+ip -n "$SAN" link set san_il up
+ip -n "$SAN" addr add 100.64.0.51/24 dev san_il
+
+# Feature gate: PRP interlink (RedBox) creation. A kernel without PRP RedBox
+# support rejects this with -EINVAL, so SKIP rather than FAIL.
+if ! ip -n "$RB" link add name prp0 type hsr slave1 rb_a slave2 rb_b \
+ interlink rb_il proto 1 2>/dev/null; then
+ echo "SKIP: kernel without PRP RedBox (interlink) support"
+ exit $ksft_skip
+fi
+ip -n "$RB" link set prp0 up
+ip -n "$PEER" link add name prp0 type hsr slave1 pe_a slave2 pe_b proto 1
+ip -n "$PEER" link set prp0 up
+ip -n "$PEER" addr add 100.64.0.2/24 dev prp0
+sleep 1
+
+san_mac=$(ip -n "$SAN" -br link show san_il | awk '{print $3}')
+rb_mac=$(ip -n "$RB" -br link show rb_il | awk '{print $3}')
+
+# Bidirectional unicast across the interlink.
+do_ping "$PEER" 100.64.0.51
+do_ping "$SAN" 100.64.0.2
+stop_if_error "PRP RedBox bidirectional unicast failed"
+
+# The SAN source MAC must be preserved on the PRP network, not laundered to the
+# RedBox MAC: the peer resolves the SAN IP to the SAN's own MAC.
+neigh=$(ip -n "$PEER" neigh show 100.64.0.51 | awk '{print $5}')
+if [ "$neigh" != "$san_mac" ]; then
+ echo "SAN MAC preservation [ FAIL ]: peer resolved 100.64.0.51 to" \
+ "'$neigh', expected $san_mac" 1>&2
+ ret=1
+fi
+stop_if_error "SAN MAC not preserved on the PRP network"
+
+# The proxy-announce supervision frame must carry, in order, the life-check TLV
+# (type 0x14, len 6) + MacAddressA == SAN MAC + the RedBox-MAC TLV (type 0x1e,
+# len 6) + MacAddressRedBox == RedBox MAC + the EOT marker (0x0000).
+( ip netns exec "$SAN" ping -i 0.2 -q 100.64.0.2 >/dev/null 2>&1 & )
+cap=$(ip netns exec "$PEER" timeout 5 tcpdump -i pe_a -nn -x \
+ "ether proto 0x88fb and ether src $rb_mac" 2>/dev/null || true)
+ip netns exec "$SAN" pkill -f "ping -i 0.2" 2>/dev/null || true
+
+san_hex=$(echo "$san_mac" | tr -d ':')
+rb_hex=$(echo "$rb_mac" | tr -d ':')
+# Reassemble contiguous frame hex: drop the "0x0010:" offset labels and spaces.
+frame_hex=$(echo "$cap" | awk '/^[[:space:]]*0x[0-9a-f]+:/ {
+ sub(/^[[:space:]]*0x[0-9a-f]+:[[:space:]]*/, ""); gsub(/ /, ""); printf "%s", $0 }')
+if ! echo "$frame_hex" | grep -q "1406${san_hex}1e06${rb_hex}0000"; then
+ echo "supervision RedBox-MAC TLV [ FAIL ]: missing SAN MAC, Type-30" \
+ "payload, or EOT" 1>&2
+ ret=1
+fi
+stop_if_error "PRP RedBox supervision RedBox-MAC TLV/EOT check failed"
+
+echo "INFO: PRP RedBox (PRP-SAN) conformance checks passed"
+exit $ret
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 3/4] net: hsr: allow PRP RedBox (interlink) creation
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
In-Reply-To: <20260704234704.4297-1-xiexinet@gmail.com>
With the PRP interlink datapath, duplicate discard and supervision support
in place, a PRP device can act as a RedBox. Remove the rtnetlink rejection
of "type hsr ... interlink <dev> proto 1"; the feature is implemented
unconditionally by the preceding patches.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_netlink.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
index 8099f2069..88940e801 100644
--- a/net/hsr/hsr_netlink.c
+++ b/net/hsr/hsr_netlink.c
@@ -121,14 +121,8 @@ static int hsr_newlink(struct net_device *dev,
}
}
- if (proto == HSR_PROTOCOL_PRP) {
+ if (proto == HSR_PROTOCOL_PRP)
proto_version = PRP_V1;
- if (interlink) {
- NL_SET_ERR_MSG_MOD(extack,
- "Interlink only works with HSR");
- return -EINVAL;
- }
- }
return hsr_dev_finalize(dev, link, interlink, multicast_spec,
proto_version, extack);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 2/4] net: hsr: emit RedBox-MAC TLV in PRP RedBox supervision frames
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
In-Reply-To: <20260704234704.4297-1-xiexinet@gmail.com>
A PRP RedBox must announce the SANs it proxies so peers populate their
proxy node tables. The proxy-announce machinery (hsr_proxy_announce(),
armed via hsr->redbox) already iterates proxy_node_db under RCU and calls
send_sv_frame() once per SAN, but the PRP sender emitted neither the
announced SAN MAC nor the RedBox-MAC TLV that IEC 62439-3 requires.
Extend send_prp_supervision_frame() so that, for a proxy-announce
(identified by the interlink port, an O(1) test), the frame carries the
proxied SAN MAC as MacAddressA followed by the RedBox-MAC TLV (Type 30)
and an explicit End-of-TLV marker before padding.
hsr_get_node() must also accept the reinjected proxy-announce: a PRP
supervision frame is an untagged ETH_P_PRP frame (mac_len == ETH_HLEN, the
RCT is appended only on egress) sourced from macaddress_redbox, which is
never learned from data. Exempt only PRP supervision frames from the
hsr_ethhdr length guard; HSR (ETH_P_HSR) supervision is front-tagged and
keeps the original guard, so HSR malformed-frame filtering is unchanged.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_device.c | 33 ++++++++++++++++++++++++++++++---
net/hsr/hsr_framereg.c | 13 +++++++++++--
2 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index 5555b71ab..7cc21253c 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -372,10 +372,21 @@ static void send_prp_supervision_frame(struct hsr_port *master,
{
struct hsr_priv *hsr = master->hsr;
struct hsr_sup_payload *hsr_sp;
+ struct hsr_sup_tlv *hsr_stlv;
struct hsr_sup_tag *hsr_stag;
struct sk_buff *skb;
+ bool redbox_proxy;
+ int extra = 0;
+
+ redbox_proxy = hsr->redbox && master->type == HSR_PT_INTERLINK;
+
+ /* A proxy-announce carries a RedBox-MAC TLV and an EOT marker. */
+ if (redbox_proxy)
+ extra = sizeof(struct hsr_sup_tlv) +
+ sizeof(struct hsr_sup_payload) +
+ sizeof(struct hsr_sup_tlv);
- skb = hsr_init_skb(master, 0);
+ skb = hsr_init_skb(master, extra);
if (!skb) {
netdev_warn_once(master->dev, "PRP: Could not send supervision frame\n");
return;
@@ -393,9 +404,25 @@ static void send_prp_supervision_frame(struct hsr_port *master,
hsr_stag->tlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD;
hsr_stag->tlv.HSR_TLV_length = sizeof(struct hsr_sup_payload);
- /* Payload: MacAddressA */
+ /* Payload: MacAddressA, the announced node. */
hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
- ether_addr_copy(hsr_sp->macaddress_A, master->dev->dev_addr);
+ ether_addr_copy(hsr_sp->macaddress_A, addr);
+
+ /* Proxy-announce: append the RedBox-MAC TLV (Type 30) and an explicit
+ * EOT to terminate the TLV chain before zero padding.
+ */
+ if (redbox_proxy) {
+ hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv));
+ hsr_stlv->HSR_TLV_type = PRP_TLV_REDBOX_MAC;
+ hsr_stlv->HSR_TLV_length = sizeof(struct hsr_sup_payload);
+
+ hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
+ ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox);
+
+ hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv));
+ hsr_stlv->HSR_TLV_type = HSR_TLV_EOT;
+ hsr_stlv->HSR_TLV_length = 0;
+ }
if (skb_put_padto(skb, ETH_ZLEN)) {
spin_unlock_bh(&hsr->seqnr_lock);
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index 7a7c52a95..37ade2dde 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -293,8 +293,17 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
*/
if (ethhdr->h_proto == htons(ETH_P_PRP) ||
ethhdr->h_proto == htons(ETH_P_HSR)) {
- /* Check if skb contains hsr_ethhdr */
- if (skb->mac_len < sizeof(struct hsr_ethhdr))
+ bool prp_sup;
+
+ /* A PRP supervision frame is an untagged ETH_P_PRP frame
+ * (mac_len == ETH_HLEN); its RCT is appended only on egress.
+ * HSR (ETH_P_HSR) supervision is front-tagged and still must
+ * contain a struct hsr_ethhdr.
+ */
+ prp_sup = hsr->prot_version == PRP_V1 &&
+ ethhdr->h_proto == htons(ETH_P_PRP) && is_sup;
+
+ if (!prp_sup && skb->mac_len < sizeof(struct hsr_ethhdr))
return NULL;
} else {
rct = skb_get_PRP_rct(skb);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 1/4] net: hsr: add PRP interlink (RedBox) datapath and duplicate discard
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
In-Reply-To: <20260704234704.4297-1-xiexinet@gmail.com>
A PRP RedBox proxies SANs that sit behind an interlink port: their frames
must reach the PRP network with the SAN source MAC preserved, and PRP
unicast must be steered between the LAN and the SAN segment correctly.
Add the PRP interlink forwarding rules to prp_drop_frame() and give RedBox
nodes a second duplicate-discard slot so the two LAN copies of a frame
destined to a SAN collapse to a single delivery out the interlink.
The destination classification (is the unicast DA a PRP-network node or a
proxied SAN) is resolved once per frame in fill_frame_info(), gated to PRP
RedBox devices, and cached in struct hsr_frame_info, so prp_drop_frame()
stays O(1) and does not walk the node tables for every candidate egress
port in the softIRQ path. HSR RedBox frame classification is untouched.
Factor the LAN A/B duplicate test into prp_is_lan_dup() so the new PRP
interlink rules do not change hsr_drop_frame() behaviour, including the
NETIF_F_HW_HSR_FWD path which keeps using the LAN-duplicate test only.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_forward.c | 49 ++++++++++++++++++++++++++++++++++++------
net/hsr/hsr_framereg.c | 10 ++++++---
net/hsr/hsr_framereg.h | 2 ++
3 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a6..efcd0acef 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -440,12 +440,37 @@ static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,
return dev_queue_xmit(skb);
}
+static bool prp_is_lan_dup(struct hsr_frame_info *frame,
+ struct hsr_port *port)
+{
+ enum hsr_port_type rx = frame->port_rcv->type;
+
+ return (rx == HSR_PT_SLAVE_A && port->type == HSR_PT_SLAVE_B) ||
+ (rx == HSR_PT_SLAVE_B && port->type == HSR_PT_SLAVE_A);
+}
+
bool prp_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
{
- return ((frame->port_rcv->type == HSR_PT_SLAVE_A &&
- port->type == HSR_PT_SLAVE_B) ||
- (frame->port_rcv->type == HSR_PT_SLAVE_B &&
- port->type == HSR_PT_SLAVE_A));
+ enum hsr_port_type rx = frame->port_rcv->type;
+
+ /* Supervision frames are not delivered to a SAN on the interlink. */
+ if (frame->is_supervision && port->type == HSR_PT_INTERLINK)
+ return true;
+
+ if (prp_is_lan_dup(frame, port))
+ return true;
+
+ /* LAN to interlink: keep PRP-network unicast off the SAN segment. */
+ if ((rx == HSR_PT_SLAVE_A || rx == HSR_PT_SLAVE_B) &&
+ port->type == HSR_PT_INTERLINK)
+ return frame->dst_in_node_db;
+
+ /* Interlink to LAN: keep SAN-to-SAN unicast local. */
+ if ((port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) &&
+ rx == HSR_PT_INTERLINK)
+ return frame->dst_in_proxy_node_db;
+
+ return false;
}
bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
@@ -453,7 +478,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
struct sk_buff *skb;
if (port->dev->features & NETIF_F_HW_HSR_FWD)
- return prp_drop_frame(frame, port);
+ return prp_is_lan_dup(frame, port);
/* RedBox specific frames dropping policies
*
@@ -466,7 +491,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
* are addressed to interlink port (and are in the ProxyNodeTable).
*/
skb = frame->skb_hsr;
- if (skb && prp_drop_frame(frame, port) &&
+ if (skb && prp_is_lan_dup(frame, port) &&
is_unicast_ether_addr(eth_hdr(skb)->h_dest) &&
hsr_is_node_in_db(&port->hsr->proxy_node_db,
eth_hdr(skb)->h_dest)) {
@@ -706,6 +731,18 @@ static int fill_frame_info(struct hsr_frame_info *frame,
frame->is_vlan = false;
proto = ethhdr->h_proto;
+ /* PRP RedBox only: classify the unicast destination once so the
+ * per-egress-port decision in prp_drop_frame() stays O(1). HSR RedBox
+ * does its own classification and must not pay these node-table walks.
+ */
+ if (hsr->prot_version == PRP_V1 && hsr->redbox &&
+ is_unicast_ether_addr(ethhdr->h_dest)) {
+ frame->dst_in_node_db =
+ hsr_is_node_in_db(&hsr->node_db, ethhdr->h_dest);
+ frame->dst_in_proxy_node_db =
+ hsr_is_node_in_db(&hsr->proxy_node_db, ethhdr->h_dest);
+ }
+
if (proto == htons(ETH_P_8021Q))
frame->is_vlan = true;
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index e44929871..7a7c52a95 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -199,7 +199,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
spin_lock_init(&new_node->seq_out_lock);
if (hsr->prot_version == PRP_V1)
- new_node->seq_port_cnt = 1;
+ new_node->seq_port_cnt = hsr->redbox ? 2 : 1;
else
new_node->seq_port_cnt = HSR_PT_PORTS - 1;
@@ -649,9 +649,13 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
if (frame->port_rcv->type == HSR_PT_MASTER)
return 0;
- /* for PRP we should only forward frames from the slave ports
- * to the master port
+ /* RedBox: forward LAN frames out the interlink to a SAN, deduping the
+ * two LAN copies on a dedicated slot.
*/
+ if (port->type == HSR_PT_INTERLINK)
+ return hsr_check_duplicate(frame, 1);
+
+ /* For PRP only slave-to-master frames are forwarded. */
if (port->type != HSR_PT_MASTER)
return 1;
diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h
index c65ecb925..127a3fb64 100644
--- a/net/hsr/hsr_framereg.h
+++ b/net/hsr/hsr_framereg.h
@@ -27,6 +27,8 @@ struct hsr_frame_info {
bool is_local_dest;
bool is_local_exclusive;
bool is_from_san;
+ bool dst_in_node_db;
+ bool dst_in_proxy_node_db;
};
void hsr_del_self_node(struct hsr_priv *hsr);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 0/4] net: hsr: PRP RedBox (PRP-SAN) support
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
This series adds PRP RedBox support to the hsr driver: a PRP node that
proxies one or more SANs sitting behind an interlink port (IEC 62439-3,
PRP-SAN). HSR-SAN has been supported since commit 5055cccfc2d1 ("net: hsr:
Provide RedBox support (HSR-SAN)"); this extends the equivalent capability
to PRP, reusing the existing protocol-neutral proxy machinery
(proxy_node_db, hsr_proxy_announce(), hsr_prune_proxy_nodes()).
A SAN behind the interlink does bidirectional unicast with peers on the PRP
network, its source MAC is preserved on the wire, the PRP RCT is correct,
and the RedBox announces each proxied SAN with the RedBox-MAC TLV (Type 30)
in its supervision frames.
The series is bisect-safe: the datapath, duplicate discard and supervision
support are added first; the rtnetlink rejection of "type hsr ... interlink
<dev> proto 1" is removed only in patch 3, once the feature is complete.
Design notes:
- prp_drop_frame() does not walk the node tables. The destination
classification (PRP-network node vs proxied SAN) is resolved once per
frame in fill_frame_info() and cached in struct hsr_frame_info, so the
per egress-port drop decision is O(1) in the softIRQ path. The
classification is gated on PRP RedBox devices (prot_version == PRP_V1 &&
hsr->redbox), so HSR RedBox traffic is not affected.
- The LAN A/B duplicate test is factored into prp_is_lan_dup() so the new
PRP interlink rules in prp_drop_frame() do not change hsr_drop_frame()
behaviour, including the NETIF_F_HW_HSR_FWD path. This is software PRP
RedBox only; it adds no new hardware-offload contract.
- The supervision emitter uses pre-reserved tailroom (hsr_init_skb() +
skb_put()) on the existing GFP_ATOMIC path; no skb_linearize() or
pskb_expand_head(). The RedBox-MAC TLV is followed by an explicit EOT
(Type 0, Length 0); padding via skb_put_padto(ETH_ZLEN) and the 6-byte
PRP RCT remain at the absolute tail of the egress frame.
- The hsr_get_node() hsr_ethhdr length guard is relaxed only for PRP
supervision frames (prot_version == PRP_V1 && ETH_P_PRP && is_sup), which
are untagged with mac_len == ETH_HLEN. HSR (ETH_P_HSR) supervision is
front-tagged and keeps the original length requirement, so HSR
malformed-frame filtering is unchanged.
Testing (on a net-next v7.2-rc1 kernel built from this base, x86-64):
- checkpatch.pl --strict: patches 1-3 clean; patch 4 reports only the
expected "added file(s), does MAINTAINERS need updating?" note, which is
ignorable here -- MAINTAINERS already lists
tools/testing/selftests/net/hsr/ under HSR NETWORK PROTOCOL.
- git diff --check clean; the series git-am's onto the base commit.
- tools/testing/selftests/net/hsr/hsr_prp_redbox.sh: PASS on the patched
kernel (bidirectional unicast, SAN MAC preservation, RedBox-MAC TLV +
EOT in the proxy-announce).
- HSR regression on the same kernel: hsr_redbox.sh (HSR-SAN/RedBox),
hsr_ping.sh and prp_ping.sh all PASS, confirming the PRP changes do not
regress the existing HSR/PRP paths.
- netns checks: peer<->SAN 0% loss with no duplicates and a valid PRP RCT
on the wire; a silent SAN is pruned from the announce; zero driver
WARN/BUG/Oops/RCU-stall during the run.
Beyond the in-tree selftest, this exact series (applied to this base and
running as the net-next kernel on x86-64 hardware) was also validated with an
out-of-tree IEC 62439-3 conformance harness (supervision TLV chain, duplicate
discard, cross-LAN rejection, seqnr rollover, VLAN/multicast/GOOSE frame types
with the RCT verified at the absolute frame tail), and interoperability-tested
against a commercial PRP RedBox (Siemens SCALANCE X204RNA) over 100 Mbit/s
Fast Ethernet with NIC hardware (PTP) timestamping: a mid-stream single-LAN
outage of ~2 s at 10 kpps was bridged with zero lost and zero duplicate frames
(seamless PRP failover), and the duplicate-discard window held zero lost /
zero duplicates under netem asymmetric delay up to 100 ms (~1000 sequence
numbers in flight), 25% reorder, and 5% single-LAN loss. The failover and
impairment matrix was additionally repeated on a KASAN + lockdep + kmemleak
instrumented build of this kernel, including a 72-carrier-event link
flap-storm with deliberate double-LAN cuts: zero KASAN, lockdep, or kmemleak
findings. This out-of-tree testing is supplementary and not required to
evaluate the series.
Xin Xie (4):
net: hsr: add PRP interlink (RedBox) datapath and duplicate discard
net: hsr: emit RedBox-MAC TLV in PRP RedBox supervision frames
net: hsr: allow PRP RedBox (interlink) creation
selftests: net: hsr: add PRP RedBox test
net/hsr/hsr_device.c | 33 ++++++-
net/hsr/hsr_forward.c | 49 ++++++++--
net/hsr/hsr_framereg.c | 23 ++++-
net/hsr/hsr_framereg.h | 2 +
net/hsr/hsr_netlink.c | 8 +-
tools/testing/selftests/net/hsr/Makefile | 1 +
.../selftests/net/hsr/hsr_prp_redbox.sh | 96 +++++++++++++++++++
7 files changed, 191 insertions(+), 21 deletions(-)
create mode 100755 tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
base-commit: b73bc9ca3686b78b642fb35dcc1fdf874ecb74a1
--
2.53.0
^ permalink raw reply
* [PATCH net] vxlan: vnifilter: enforce exact length of GROUP/GROUP6 attributes
From: Xiang Mei @ 2026-07-04 22:22 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Roopa Prabhu
Cc: netdev, linux-kernel, Petr Machata, Andy Roulin, David Yang,
Kees Cook, Weiming Shi, Xiang Mei
The VXLAN VNI filter entry policy declares the GROUP/GROUP6 address
attributes as NLA_BINARY with only a maximum length, so validate_nla()
accepts a payload shorter than the address. The GROUP consumer reads it
with nla_get_in_addr(), an unconditional 4-byte load, so a short
attribute over-reads up to 3 bytes of uninitialised slab data, which are
stored into remote_ip and echoed back via RTM_GETTUNNEL, disclosing
kernel memory.
Switch both entries to NLA_POLICY_EXACT_LEN() so the validator rejects
any GROUP/GROUP6 that is not exactly 4 / 16 bytes; a valid address is
always sent at full width.
Fixes: f9c4bb0b245c ("vxlan: vni filtering support on collect metadata device")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
drivers/net/vxlan/vxlan_vnifilter.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c
index 3e76f4e21094..dd94085e0886 100644
--- a/drivers/net/vxlan/vxlan_vnifilter.c
+++ b/drivers/net/vxlan/vxlan_vnifilter.c
@@ -462,10 +462,8 @@ static int vxlan_vnifilter_dump(struct sk_buff *skb, struct netlink_callback *cb
static const struct nla_policy vni_filter_entry_policy[VXLAN_VNIFILTER_ENTRY_MAX + 1] = {
[VXLAN_VNIFILTER_ENTRY_START] = { .type = NLA_U32 },
[VXLAN_VNIFILTER_ENTRY_END] = { .type = NLA_U32 },
- [VXLAN_VNIFILTER_ENTRY_GROUP] = { .type = NLA_BINARY,
- .len = sizeof_field(struct iphdr, daddr) },
- [VXLAN_VNIFILTER_ENTRY_GROUP6] = { .type = NLA_BINARY,
- .len = sizeof(struct in6_addr) },
+ [VXLAN_VNIFILTER_ENTRY_GROUP] = NLA_POLICY_EXACT_LEN(sizeof_field(struct iphdr, daddr)),
+ [VXLAN_VNIFILTER_ENTRY_GROUP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
};
static const struct nla_policy vni_filter_policy[VXLAN_VNIFILTER_MAX + 1] = {
--
2.43.0
^ permalink raw reply related
* [PATCH] net: pcs: xpcs-plat: fix runtime PM initialization
From: Coia Prant @ 2026-07-04 21:48 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, Heiner Kallweit, Russell King, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Serge Semin,
linux-kernel, Coia Prant, stable
The driver calls `pm_runtime_set_active()` before runtime PM is enabled,
and before the clock is prepared and enabled.
This causes the clock to be unprepared/disabled later in the suspend
callback even though it was never prepared/enabled, resulting in warnings:
clk_csr already disabled
clk_csr already unprepared
Fix this by setting the initial runtime PM status to SUSPENDED instead
of ACTIVE.
The clock will be properly enabled when the device is first resumed
via runtime PM (e.g., during MDIO access).
Fixes: f6bb3e9d98c2 ("net: pcs: xpcs: Add Synopsys DW xPCS platform device driver")
Cc: stable@vger.kernel.org
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/net/pcs/pcs-xpcs-plat.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/pcs/pcs-xpcs-plat.c b/drivers/net/pcs/pcs-xpcs-plat.c
index f4b1b8246ce96..fb80773379df5 100644
--- a/drivers/net/pcs/pcs-xpcs-plat.c
+++ b/drivers/net/pcs/pcs-xpcs-plat.c
@@ -285,7 +285,7 @@ static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs)
return dev_err_probe(dev, PTR_ERR(pxpcs->cclk),
"Failed to get CSR clock\n");
- pm_runtime_set_active(dev);
+ pm_runtime_set_suspended(dev);
ret = devm_pm_runtime_enable(dev);
if (ret) {
dev_err(dev, "Failed to enable runtime-PM\n");
--
2.47.3
^ permalink raw reply related
* [PATCH ipsec] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full
From: Xiang Mei @ 2026-07-04 21:03 UTC (permalink / raw)
To: steffen.klassert, herbert, davem
Cc: nakam, edumazet, kuba, pabeni, horms, netdev, bestswngs,
Xiang Mei
The depth check in xfrm6_input_addr() is off by one:
if (1 + sp->len == XFRM_MAX_DEPTH)
goto drop;
...
sp->xvec[sp->len++] = x;
xfrm_input() can leave sp->len == XFRM_MAX_DEPTH, and the transport-mode
receive path re-enters IPv6 input via xfrm_trans_reinject() with that
secpath preserved. If the inner packet carries a destination-options HAO
option or a type-2 routing header, xfrm6_input_addr() is called with
sp->len == XFRM_MAX_DEPTH; the check (1 + 6 == 6) is false, so
sp->xvec[sp->len++] writes one slot past the 6-element xvec[]. The write
stays within the sec_path allocation (invisible to KASAN); UBSAN_BOUNDS
flags it and panics under panic_on_warn.
Use "sp->len >= XFRM_MAX_DEPTH", matching xfrm_input(). This also
restores one chain level the old check rejected at sp->len == 5.
UBSAN: array-index-out-of-bounds in net/ipv6/xfrm6_input.c:309:10
index 6 is out of range for type 'xfrm_state *[6]'
Fixes: 9473e1f631de ("[XFRM] MIPv6: Fix to input RO state correctly.")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/ipv6/xfrm6_input.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b5307..07edef258984 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -247,7 +247,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
goto drop;
}
- if (1 + sp->len == XFRM_MAX_DEPTH) {
+ if (sp->len >= XFRM_MAX_DEPTH) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINBUFFERERROR);
goto drop;
}
--
2.43.0
^ permalink raw reply related
* [PATCH net 3/3] ipv4: igmp: Fix potential memory leak in igmp_mod_timer()
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260704194346.4065071-1-edumazet@google.com>
When a timer is deleted and not re-armed in igmp_mod_timer(), the code
currently decrements the reference counter of the multicast list entry
@im using refcount_dec(&im->refcnt).
However, igmp_mod_timer() can be called from the RCU reader path (e.g., in
igmp_heard_query() via for_each_pmc_rcu()). If the group im was
concurrently removed from the list by ip_mc_dec_group(), its reference count
might have already been decremented to 1.
In this case, timer_delete() succeeds, and refcount_dec() decrements
the refcount from 1 to 0. Since refcount_dec() does not free the object
when it hits 0 (unlike ip_ma_put()), the im structure is leaked.
Fix this by using ip_ma_put(im) instead of refcount_dec(&im->refcnt).
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net/ipv4/igmp.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index f5f9763895641bf86bfcf9fd7fd7b06012fa4ece..2170b33ba147ce4990e3ee71ba4868e8696b00cb 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -266,6 +266,8 @@ static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
{
+ bool put = false;
+
spin_lock_bh(&im->lock);
im->unsolicit_count = 0;
if (timer_delete(&im->timer)) {
@@ -275,10 +277,13 @@ static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
spin_unlock_bh(&im->lock);
return;
}
- refcount_dec(&im->refcnt);
+ put = true;
}
igmp_start_timer(im, max_delay);
spin_unlock_bh(&im->lock);
+
+ if (put)
+ ip_ma_put(im);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH net 2/3] ipv6: mcast: Fix potential UAF in MLD delayed work
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260704194346.4065071-1-edumazet@google.com>
A race condition exists between device teardown and incoming MLD query
processing, leading to a Use-After-Free in the MLD delayed work.
During device destruction, the primary reference to inet6_dev is dropped,
which can drop its refcount to 0. The actual freeing of inet6_dev memory
is deferred via RCU.
Concurrently, the packet receive path runs under RCU read lock and obtains
the inet6_dev pointer. Because the memory is RCU-protected, CPU-0 can
safely dereference inet6_dev even if its refcount has hit 0.
However, if CPU-0 calls igmp6_event_query() and schedules delayed work, it
attempts to acquire a reference using in6_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the inet6_dev memory is still scheduled to be freed after the RCU
grace period, the device is freed while the work is still scheduled.
When the work runs, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in6_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not schedule the work.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/addrconf.h | 5 +++++
net/ipv6/mcast.c | 38 ++++++++++++++++++++++++++++----------
2 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 539bbbe54b14e8108ff7304d7a08bc605655cc31..8ced27a8229b6e0580f934be2223676cc123307b 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -446,6 +446,11 @@ static inline void in6_dev_hold(struct inet6_dev *idev)
refcount_inc(&idev->refcnt);
}
+static inline bool in6_dev_hold_safe(struct inet6_dev *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
/* called with rcu_read_lock held */
static inline bool ip6_ignore_linkdown(const struct net_device *dev)
{
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 04b811b3be978e36b0fd4ecd8312313d73ed2ed9..7c3f739cf7638452f311c64331c89253361b34f9 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -1083,8 +1083,10 @@ static void mld_gq_start_work(struct inet6_dev *idev)
mc_assert_locked(idev);
idev->mc_gq_running = 1;
- if (!mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_gq_stop_work(struct inet6_dev *idev)
@@ -1102,8 +1104,10 @@ static void mld_ifc_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_ifc_stop_work(struct inet6_dev *idev)
@@ -1121,8 +1125,10 @@ static void mld_dad_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_dad_stop_work(struct inet6_dev *idev)
@@ -1395,6 +1401,7 @@ static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
void igmp6_event_query(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
@@ -1402,11 +1409,16 @@ void igmp6_event_query(struct sk_buff *skb)
spin_lock_bh(&idev->mc_query_lock);
if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) {
__skb_queue_tail(&idev->mc_query_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
+ put = true;
+ }
skb = NULL;
}
spin_unlock_bh(&idev->mc_query_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
@@ -1570,6 +1582,7 @@ static void mld_query_work(struct work_struct *work)
void igmp6_event_report(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
@@ -1577,11 +1590,16 @@ void igmp6_event_report(struct sk_buff *skb)
spin_lock_bh(&idev->mc_report_lock);
if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) {
__skb_queue_tail(&idev->mc_report_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
+ put = true;
+ }
skb = NULL;
}
spin_unlock_bh(&idev->mc_report_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH net 1/3] ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet, Zero Day Initiative
In-Reply-To: <20260704194346.4065071-1-edumazet@google.com>
A race condition exists between device teardown (inetdev_destroy) and
incoming IGMP query processing (igmp_rcv), leading to a Use-After-Free
in the IGMP timer callback.
During device destruction, inetdev_destroy() drops the primary reference
to in_device, which can drop its refcount to 0. The actual freeing of
in_device memory is deferred via RCU (using call_rcu()).
Concurrently, igmp_rcv() runs under RCU read lock and obtains the
in_device pointer. Because the memory is RCU-protected, CPU-0 can safely
dereference in_device even if its refcount has hit 0.
However, if CPU-0 calls igmp_gq_start_timer() and re-arms the timer, it
attempts to acquire a reference using in_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the in_device memory is still scheduled to be freed after the RCU
grace period (as the free callback does not check the refcount again),
the device is freed while the timer is still armed. When the timer
expires, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not arm the timer.
A similar issue in IPv6 MLD is fixed in a subsequent patch.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Zero Day Initiative <zdi-disclosures@trendmicro.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/linux/inetdevice.h | 5 +++++
net/ipv4/igmp.c | 14 +++++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index dccbeb25f70141982160c776f3cc727296def2b7..6032eea2539a60d0476d85667bda3bfcad8f1425 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -293,6 +293,11 @@ static inline void in_dev_put(struct in_device *idev)
#define __in_dev_put(idev) refcount_dec(&(idev)->refcnt)
#define in_dev_hold(idev) refcount_inc(&(idev)->refcnt)
+static inline bool in_dev_hold_safe(struct in_device *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
#endif /* __KERNEL__ */
static __inline__ __be32 inet_make_mask(int logmask)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index b6337a47c1418589bf8ef31e00fd98b6187f5271..f5f9763895641bf86bfcf9fd7fd7b06012fa4ece 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -248,16 +248,20 @@ static void igmp_gq_start_timer(struct in_device *in_dev)
return;
in_dev->mr_gq_running = 1;
- if (!mod_timer(&in_dev->mr_gq_timer, exp))
- in_dev_hold(in_dev);
+ if (in_dev_hold_safe(in_dev)) {
+ if (mod_timer(&in_dev->mr_gq_timer, exp))
+ in_dev_put(in_dev);
+ }
}
static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
{
- int tv = get_random_u32_below(delay);
+ if (in_dev_hold_safe(in_dev)) {
+ int tv = get_random_u32_below(delay);
- if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
- in_dev_hold(in_dev);
+ if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2))
+ in_dev_put(in_dev);
+ }
}
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH net 0/3] ipv4/ipv6: Fix UAF and memory leak in IGMP/MLD
From: Eric Dumazet @ 2026-07-04 19:43 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
This series addresses two potential UAF vulnerabilities
and one memory leak in the IPv4 IGMP and IPv6 MLD subsystems.
The first two patches fix a UAF where the packet receive path races with
device teardown. If the device refcount has already hit 0 (but the memory
is still held by RCU), incoming IGMP/MLD packets trying to schedule delayed
work or timers would call refcount_inc() on the 0 refcount, triggering a
warning and eventually leading to a UAF when the work runs after the device
has been freed. This is fixed by introducing safe hold helpers using
refcount_inc_not_zero().
The third patch fixes a memory leak in IPv4 IGMP timer modification. When
a timer is deleted and not re-armed, the code dropped the group refcount
using refcount_dec(). However, if the group was concurrently removed from
the list, this decrement could drop the refcount to 0 without triggering
the cleanup/free path, leaking the group structure. This is fixed by using
ip_ma_put() instead, and deferring the put until after the lock is released.
Eric Dumazet (3):
ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
ipv6: mcast: Fix potential UAF in MLD delayed work
ipv4: igmp: Fix potential memory leak in igmp_mod_timer()
include/linux/inetdevice.h | 5 +++++
include/net/addrconf.h | 5 +++++
net/ipv4/igmp.c | 21 +++++++++++++++------
net/ipv6/mcast.c | 38 ++++++++++++++++++++++++++++----------
4 files changed, 53 insertions(+), 16 deletions(-)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply
* [PATCH net-next v7 5/5] net: dsa: ks8995: Delete surplus driver
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij, Nicolai Buchwitz
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The Microchip ksz driver now handles all switches that the
old driver was handling, but better.
Delete the old driver, but leave a stub behind in Kconfig
so new users will be automatically phased over to the new
symbol when migrating their configs.
The old driver supports platform data (board file)
instantiation, but nothing in the kernel tree makes use
of this legacy mechanism so it is fine to delete.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/Kconfig | 10 +-
drivers/net/dsa/Makefile | 1 -
drivers/net/dsa/ks8995.c | 857 -----------------------------------------------
3 files changed, 6 insertions(+), 862 deletions(-)
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index e704ab702c18..360bacc52c3a 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -100,11 +100,13 @@ config NET_DSA_RZN1_A5PSW
config NET_DSA_KS8995
tristate "Micrel KS8995 family 5-ports 10/100 Ethernet switches"
depends on SPI
- depends on !NET_DSA_MICROCHIP_KSZ_SPI
- select NET_DSA_TAG_NONE
+ select NET_DSA_MICROCHIP_KSZ_COMMON
+ select NET_DSA_MICROCHIP_KSZ_SPI
help
- This driver supports the Micrel KS8995 family of 10/100 Mbit ethernet
- switches, managed over SPI.
+ This is a transitional option for users who had NET_DSA_KS8995
+ enabled. It automatically enables the new NET_DSA_MICROCHIP_KSZ_SPI
+ driver which supersedes it. This option will be removed in a future
+ kernel release.
config NET_DSA_SMSC_LAN9303
tristate
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index d2975badffc0..6ae16e1835f8 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -2,7 +2,6 @@
obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm-sf2.o
bcm-sf2-objs := bcm_sf2.o bcm_sf2_cfp.o
obj-$(CONFIG_NET_DSA_LOOP) += dsa_loop.o
-obj-$(CONFIG_NET_DSA_KS8995) += ks8995.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
obj-$(CONFIG_NET_DSA_MT7530_MMIO) += mt7530-mmio.o
diff --git a/drivers/net/dsa/ks8995.c b/drivers/net/dsa/ks8995.c
deleted file mode 100644
index 77d8b842693c..000000000000
--- a/drivers/net/dsa/ks8995.c
+++ /dev/null
@@ -1,857 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches
- *
- * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org>
- * Copyright (C) 2025 Linus Walleij <linus.walleij@linaro.org>
- *
- * This file was based on: drivers/spi/at25.c
- * Copyright (C) 2006 David Brownell
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/bits.h>
-#include <linux/if_bridge.h>
-#include <linux/if_vlan.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/gpio/consumer.h>
-#include <linux/of.h>
-#include <linux/spi/spi.h>
-#include <net/dsa.h>
-
-#define DRV_VERSION "0.1.1"
-#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver"
-
-/* ------------------------------------------------------------------------ */
-
-#define KS8995_REG_ID0 0x00 /* Chip ID0 */
-#define KS8995_REG_ID1 0x01 /* Chip ID1 */
-
-#define KS8995_REG_GC0 0x02 /* Global Control 0 */
-
-#define KS8995_GC0_P5_PHY BIT(3) /* Port 5 PHY enabled */
-
-#define KS8995_REG_GC1 0x03 /* Global Control 1 */
-#define KS8995_REG_GC2 0x04 /* Global Control 2 */
-
-#define KS8995_GC2_HUGE BIT(2) /* Huge packet support */
-#define KS8995_GC2_LEGAL BIT(1) /* Legal size override */
-
-#define KS8995_REG_GC3 0x05 /* Global Control 3 */
-#define KS8995_REG_GC4 0x06 /* Global Control 4 */
-
-#define KS8995_GC4_10BT BIT(4) /* Force switch to 10Mbit */
-#define KS8995_GC4_MII_FLOW BIT(5) /* MII full-duplex flow control enable */
-#define KS8995_GC4_MII_HD BIT(6) /* MII half-duplex mode enable */
-
-#define KS8995_REG_GC5 0x07 /* Global Control 5 */
-#define KS8995_REG_GC6 0x08 /* Global Control 6 */
-#define KS8995_REG_GC7 0x09 /* Global Control 7 */
-#define KS8995_REG_GC8 0x0a /* Global Control 8 */
-#define KS8995_REG_GC9 0x0b /* Global Control 9 */
-
-#define KS8995_GC9_SPECIAL BIT(0) /* Special tagging mode (DSA) */
-
-/* In DSA the ports 1-4 are numbered 0-3 and the CPU port is port 4 */
-#define KS8995_REG_PC(p, r) (0x10 + (0x10 * (p)) + (r)) /* Port Control */
-#define KS8995_REG_PS(p, r) (0x1e + (0x10 * (p)) + (r)) /* Port Status */
-
-#define KS8995_REG_PC0 0x00 /* Port Control 0 */
-#define KS8995_REG_PC1 0x01 /* Port Control 1 */
-#define KS8995_REG_PC2 0x02 /* Port Control 2 */
-#define KS8995_REG_PC3 0x03 /* Port Control 3 */
-#define KS8995_REG_PC4 0x04 /* Port Control 4 */
-#define KS8995_REG_PC5 0x05 /* Port Control 5 */
-#define KS8995_REG_PC6 0x06 /* Port Control 6 */
-#define KS8995_REG_PC7 0x07 /* Port Control 7 */
-#define KS8995_REG_PC8 0x08 /* Port Control 8 */
-#define KS8995_REG_PC9 0x09 /* Port Control 9 */
-#define KS8995_REG_PC10 0x0a /* Port Control 10 */
-#define KS8995_REG_PC11 0x0b /* Port Control 11 */
-#define KS8995_REG_PC12 0x0c /* Port Control 12 */
-#define KS8995_REG_PC13 0x0d /* Port Control 13 */
-
-#define KS8995_PC0_TAG_INS BIT(2) /* Enable tag insertion on port */
-#define KS8995_PC0_TAG_REM BIT(1) /* Enable tag removal on port */
-#define KS8995_PC0_PRIO_EN BIT(0) /* Enable priority handling */
-
-#define KS8995_PC2_TXEN BIT(2) /* Enable TX on port */
-#define KS8995_PC2_RXEN BIT(1) /* Enable RX on port */
-#define KS8995_PC2_LEARN_DIS BIT(0) /* Disable learning on port */
-
-#define KS8995_PC13_TXDIS BIT(6) /* Disable transmitter */
-#define KS8995_PC13_PWDN BIT(3) /* Power down */
-
-#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */
-#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */
-#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */
-#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */
-#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */
-#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */
-#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */
-#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */
-
-#define KS8995_REG_MAC0 0x68 /* MAC address 0 */
-#define KS8995_REG_MAC1 0x69 /* MAC address 1 */
-#define KS8995_REG_MAC2 0x6a /* MAC address 2 */
-#define KS8995_REG_MAC3 0x6b /* MAC address 3 */
-#define KS8995_REG_MAC4 0x6c /* MAC address 4 */
-#define KS8995_REG_MAC5 0x6d /* MAC address 5 */
-
-#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */
-#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */
-#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */
-#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */
-#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */
-#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */
-#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */
-#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */
-#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */
-#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */
-
-#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */
-
-#define KS8995_REGS_SIZE 0x80
-#define KSZ8864_REGS_SIZE 0x100
-#define KSZ8795_REGS_SIZE 0x100
-
-#define ID1_CHIPID_M 0xf
-#define ID1_CHIPID_S 4
-#define ID1_REVISION_M 0x7
-#define ID1_REVISION_S 1
-#define ID1_START_SW 1 /* start the switch */
-
-#define FAMILY_KS8995 0x95
-#define FAMILY_KSZ8795 0x87
-#define CHIPID_M 0
-#define KS8995_CHIP_ID 0x00
-#define KSZ8864_CHIP_ID 0x01
-#define KSZ8795_CHIP_ID 0x09
-
-#define KS8995_CMD_WRITE 0x02U
-#define KS8995_CMD_READ 0x03U
-
-#define KS8995_CPU_PORT 4
-#define KS8995_NUM_PORTS 5 /* 5 ports including the CPU port */
-#define KS8995_RESET_DELAY 10 /* usec */
-
-enum ks8995_chip_variant {
- ks8995,
- ksz8864,
- ksz8795,
- max_variant
-};
-
-struct ks8995_chip_params {
- char *name;
- int family_id;
- int chip_id;
- int regs_size;
- int addr_width;
- int addr_shift;
-};
-
-static const struct ks8995_chip_params ks8995_chip[] = {
- [ks8995] = {
- .name = "KS8995MA",
- .family_id = FAMILY_KS8995,
- .chip_id = KS8995_CHIP_ID,
- .regs_size = KS8995_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8864] = {
- .name = "KSZ8864RMN",
- .family_id = FAMILY_KS8995,
- .chip_id = KSZ8864_CHIP_ID,
- .regs_size = KSZ8864_REGS_SIZE,
- .addr_width = 8,
- .addr_shift = 0,
- },
- [ksz8795] = {
- .name = "KSZ8795CLX",
- .family_id = FAMILY_KSZ8795,
- .chip_id = KSZ8795_CHIP_ID,
- .regs_size = KSZ8795_REGS_SIZE,
- .addr_width = 12,
- .addr_shift = 1,
- },
-};
-
-struct ks8995_switch {
- struct spi_device *spi;
- struct device *dev;
- struct dsa_switch *ds;
- struct mutex lock;
- struct gpio_desc *reset_gpio;
- struct bin_attribute regs_attr;
- const struct ks8995_chip_params *chip;
- int revision_id;
- unsigned int max_mtu[KS8995_NUM_PORTS];
-};
-
-static const struct spi_device_id ks8995_id[] = {
- {"ks8995", ks8995},
- {"ksz8864", ksz8864},
- {"ksz8795", ksz8795},
- { }
-};
-MODULE_DEVICE_TABLE(spi, ks8995_id);
-
-static const struct of_device_id ks8995_spi_of_match[] = {
- { .compatible = "micrel,ks8995" },
- { .compatible = "micrel,ksz8864" },
- { .compatible = "micrel,ksz8795" },
- { },
-};
-MODULE_DEVICE_TABLE(of, ks8995_spi_of_match);
-
-static inline u8 get_chip_id(u8 val)
-{
- return (val >> ID1_CHIPID_S) & ID1_CHIPID_M;
-}
-
-static inline u8 get_chip_rev(u8 val)
-{
- return (val >> ID1_REVISION_S) & ID1_REVISION_M;
-}
-
-/* create_spi_cmd - create a chip specific SPI command header
- * @ks: pointer to switch instance
- * @cmd: SPI command for switch
- * @address: register address for command
- *
- * Different chip families use different bit pattern to address the switches
- * registers:
- *
- * KS8995: 8bit command + 8bit address
- * KSZ8795: 3bit command + 12bit address + 1bit TR (?)
- */
-static inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd,
- unsigned address)
-{
- u16 result = cmd;
-
- /* make room for address (incl. address shift) */
- result <<= ks->chip->addr_width + ks->chip->addr_shift;
- /* add address */
- result |= address << ks->chip->addr_shift;
- /* SPI protocol needs big endian */
- return cpu_to_be16(result);
-}
-/* ------------------------------------------------------------------------ */
-static int ks8995_read(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].rx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static int ks8995_write(struct ks8995_switch *ks, char *buf,
- unsigned offset, size_t count)
-{
- __be16 cmd;
- struct spi_transfer t[2];
- struct spi_message m;
- int err;
-
- cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset);
- spi_message_init(&m);
-
- memset(&t, 0, sizeof(t));
-
- t[0].tx_buf = &cmd;
- t[0].len = sizeof(cmd);
- spi_message_add_tail(&t[0], &m);
-
- t[1].tx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
- mutex_lock(&ks->lock);
- err = spi_sync(ks->spi, &m);
- mutex_unlock(&ks->lock);
-
- return err ? err : count;
-}
-
-static inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf)
-{
- return ks8995_read(ks, buf, addr, 1) != 1;
-}
-
-static inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val)
-{
- char buf = val;
-
- return ks8995_write(ks, &buf, addr, 1) != 1;
-}
-
-/* ------------------------------------------------------------------------ */
-
-static int ks8995_stop(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 0);
-}
-
-static int ks8995_start(struct ks8995_switch *ks)
-{
- return ks8995_write_reg(ks, KS8995_REG_ID1, 1);
-}
-
-static int ks8995_reset(struct ks8995_switch *ks)
-{
- int err;
-
- err = ks8995_stop(ks);
- if (err)
- return err;
-
- udelay(KS8995_RESET_DELAY);
-
- return ks8995_start(ks);
-}
-
-/* ks8995_get_revision - get chip revision
- * @ks: pointer to switch instance
- *
- * Verify chip family and id and get chip revision.
- */
-static int ks8995_get_revision(struct ks8995_switch *ks)
-{
- int err;
- u8 id0, id1, ksz8864_id;
-
- /* read family id */
- err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify family id */
- if (id0 != ks->chip->family_id) {
- dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n",
- ks->chip->family_id, id0);
- err = -ENODEV;
- goto err_out;
- }
-
- switch (ks->chip->family_id) {
- case FAMILY_KS8995:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- /* verify chip id */
- if ((get_chip_id(id1) == CHIPID_M) &&
- (get_chip_id(id1) == ks->chip->chip_id)) {
- /* KS8995MA */
- ks->revision_id = get_chip_rev(id1);
- } else if (get_chip_id(id1) != CHIPID_M) {
- /* KSZ8864RMN */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if ((ksz8864_id & 0x80) &&
- (ks->chip->chip_id == KSZ8864_CHIP_ID)) {
- ks->revision_id = get_chip_rev(id1);
- }
-
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- case FAMILY_KSZ8795:
- /* try reading chip id at CHIP ID1 */
- err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1);
- if (err) {
- err = -EIO;
- goto err_out;
- }
-
- if (get_chip_id(id1) == ks->chip->chip_id) {
- ks->revision_id = get_chip_rev(id1);
- } else {
- dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n",
- id1);
- err = -ENODEV;
- }
- break;
- default:
- dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0);
- err = -ENODEV;
- break;
- }
-err_out:
- return err;
-}
-
-static int ks8995_check_config(struct ks8995_switch *ks)
-{
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC0, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC0\n");
- return ret;
- }
-
- dev_dbg(ks->dev, "port 5 PHY %senabled\n",
- (val & KS8995_GC0_P5_PHY) ? "" : "not ");
-
- val |= KS8995_GC0_P5_PHY;
- ret = ks8995_write_reg(ks, KS8995_REG_GC0, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC0\n");
-
- dev_dbg(ks->dev, "set KS8995_REG_GC0 to 0x%02x\n", val);
-
- return 0;
-}
-
-static void
-ks8995_mac_config(struct phylink_config *config, unsigned int mode,
- const struct phylink_link_state *state)
-{
-}
-
-static void
-ks8995_mac_link_up(struct phylink_config *config, struct phy_device *phydev,
- unsigned int mode, phy_interface_t interface,
- int speed, int duplex, bool tx_pause, bool rx_pause)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
- int ret;
- u8 val;
-
- /* Allow forcing the mode on the fixed CPU port, no autonegotiation.
- * We assume autonegotiation works on the PHY-facing ports.
- */
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link up on CPU port (%d)\n", port);
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC4, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC4\n");
- return;
- }
-
- /* Conjure port config */
- switch (speed) {
- case SPEED_10:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val |= KS8995_GC4_10BT;
- break;
- case SPEED_100:
- default:
- dev_dbg(ks->dev, "set switch MII to 100Mbit mode\n");
- val &= ~KS8995_GC4_10BT;
- break;
- }
-
- if (duplex == DUPLEX_HALF) {
- dev_dbg(ks->dev, "set switch MII to half duplex\n");
- val |= KS8995_GC4_MII_HD;
- } else {
- dev_dbg(ks->dev, "set switch MII to full duplex\n");
- val &= ~KS8995_GC4_MII_HD;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_GC4 to %02x\n", val);
-
- /* Enable the CPU port */
- ret = ks8995_write_reg(ks, KS8995_REG_GC4, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC4\n");
-}
-
-static void
-ks8995_mac_link_down(struct phylink_config *config, unsigned int mode,
- phy_interface_t interface)
-{
- struct dsa_port *dp = dsa_phylink_to_port(config);
- struct ks8995_switch *ks = dp->ds->priv;
- int port = dp->index;
-
- if (port != KS8995_CPU_PORT)
- return;
-
- dev_dbg(ks->dev, "MAC link down on CPU port (%d)\n", port);
-
- /* Disable the CPU port */
-}
-
-static const struct phylink_mac_ops ks8995_phylink_mac_ops = {
- .mac_config = ks8995_mac_config,
- .mac_link_up = ks8995_mac_link_up,
- .mac_link_down = ks8995_mac_link_down,
-};
-
-static enum
-dsa_tag_protocol ks8995_get_tag_protocol(struct dsa_switch *ds,
- int port,
- enum dsa_tag_protocol mp)
-{
- /* This switch actually uses the 6 byte KS8995 protocol */
- return DSA_TAG_PROTO_NONE;
-}
-
-static int ks8995_setup(struct dsa_switch *ds)
-{
- return 0;
-}
-
-static int ks8995_port_enable(struct dsa_switch *ds, int port,
- struct phy_device *phy)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "enable port %d\n", port);
-
- return 0;
-}
-
-static void ks8995_port_disable(struct dsa_switch *ds, int port)
-{
- struct ks8995_switch *ks = ds->priv;
-
- dev_dbg(ks->dev, "disable port %d\n", port);
-}
-
-static int ks8995_port_pre_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- /* We support enabling/disabling learning */
- if (flags.mask & ~(BR_LEARNING))
- return -EINVAL;
-
- return 0;
-}
-
-static int ks8995_port_bridge_flags(struct dsa_switch *ds, int port,
- struct switchdev_brport_flags flags,
- struct netlink_ext_ack *extack)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- if (flags.mask & BR_LEARNING) {
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
-
- if (flags.val & BR_LEARNING)
- val &= ~KS8995_PC2_LEARN_DIS;
- else
- val |= KS8995_PC2_LEARN_DIS;
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return ret;
- }
- }
-
- return 0;
-}
-
-static void ks8995_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
-{
- struct ks8995_switch *ks = ds->priv;
- int ret;
- u8 val;
-
- ret = ks8995_read_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- /* Set the bits for the different STP states in accordance with
- * the datasheet, pages 36-37 "Spanning tree support".
- */
- switch (state) {
- case BR_STATE_DISABLED:
- case BR_STATE_BLOCKING:
- case BR_STATE_LISTENING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val |= KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_LEARNING:
- val &= ~KS8995_PC2_TXEN;
- val &= ~KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- case BR_STATE_FORWARDING:
- val |= KS8995_PC2_TXEN;
- val |= KS8995_PC2_RXEN;
- val &= ~KS8995_PC2_LEARN_DIS;
- break;
- default:
- dev_err(ks->dev, "unknown bridge state requested\n");
- return;
- }
-
- ret = ks8995_write_reg(ks, KS8995_REG_PC(port, KS8995_REG_PC2), val);
- if (ret) {
- dev_err(ks->dev, "failed to write KS8995_REG_PC2 on port %d\n", port);
- return;
- }
-
- dev_dbg(ks->dev, "set KS8995_REG_PC2 for port %d to %02x\n", port, val);
-}
-
-static void ks8995_phylink_get_caps(struct dsa_switch *dsa, int port,
- struct phylink_config *config)
-{
- unsigned long *interfaces = config->supported_interfaces;
-
- if (port == KS8995_CPU_PORT)
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
-
- if (port <= 3) {
- /* Internal PHYs */
- __set_bit(PHY_INTERFACE_MODE_INTERNAL, interfaces);
- /* phylib default */
- __set_bit(PHY_INTERFACE_MODE_MII, interfaces);
- }
-
- config->mac_capabilities = MAC_SYM_PAUSE | MAC_10 | MAC_100;
-}
-
-/* Huge packet support up to 1916 byte packages "inclusive"
- * which means that tags are included. If the bit is not set
- * it is 1536 bytes "inclusive". We present the length without
- * tags or ethernet headers. The setting affects all ports.
- */
-static int ks8995_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
-{
- struct ks8995_switch *ks = ds->priv;
- unsigned int max_mtu;
- int ret;
- u8 val;
- int i;
-
- ks->max_mtu[port] = new_mtu;
-
- /* Roof out the MTU for the entire switch to the greatest
- * common denominator: the biggest set for any one port will
- * be the biggest MTU for the switch.
- */
- max_mtu = ETH_DATA_LEN;
- for (i = 0; i < KS8995_NUM_PORTS; i++) {
- if (ks->max_mtu[i] > max_mtu)
- max_mtu = ks->max_mtu[i];
- }
-
- /* Translate to layer 2 size.
- * Add ethernet and (possible) VLAN headers, and checksum to the size.
- * For ETH_DATA_LEN (1500 bytes) this will add up to 1522 bytes.
- */
- max_mtu += VLAN_ETH_HLEN;
- max_mtu += ETH_FCS_LEN;
-
- ret = ks8995_read_reg(ks, KS8995_REG_GC2, &val);
- if (ret) {
- dev_err(ks->dev, "failed to read KS8995_REG_GC2\n");
- return ret;
- }
-
- if (max_mtu <= 1522) {
- val &= ~KS8995_GC2_HUGE;
- val &= ~KS8995_GC2_LEGAL;
- } else if (max_mtu > 1522 && max_mtu <= 1536) {
- /* This accepts packets up to 1536 bytes */
- val &= ~KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- } else {
- /* This accepts packets up to 1916 bytes */
- val |= KS8995_GC2_HUGE;
- val |= KS8995_GC2_LEGAL;
- }
-
- dev_dbg(ks->dev, "new max MTU %d bytes (inclusive)\n", max_mtu);
-
- ret = ks8995_write_reg(ks, KS8995_REG_GC2, val);
- if (ret)
- dev_err(ks->dev, "failed to set KS8995_REG_GC2\n");
-
- return ret;
-}
-
-static int ks8995_get_max_mtu(struct dsa_switch *ds, int port)
-{
- return 1916 - ETH_HLEN - ETH_FCS_LEN;
-}
-
-static const struct dsa_switch_ops ks8995_ds_ops = {
- .get_tag_protocol = ks8995_get_tag_protocol,
- .setup = ks8995_setup,
- .port_pre_bridge_flags = ks8995_port_pre_bridge_flags,
- .port_bridge_flags = ks8995_port_bridge_flags,
- .port_enable = ks8995_port_enable,
- .port_disable = ks8995_port_disable,
- .port_stp_state_set = ks8995_port_stp_state_set,
- .port_change_mtu = ks8995_change_mtu,
- .port_max_mtu = ks8995_get_max_mtu,
- .phylink_get_caps = ks8995_phylink_get_caps,
-};
-
-/* ------------------------------------------------------------------------ */
-static int ks8995_probe(struct spi_device *spi)
-{
- struct ks8995_switch *ks;
- int err;
- int variant = spi_get_device_id(spi)->driver_data;
-
- if (variant >= max_variant) {
- dev_err(&spi->dev, "bad chip variant %d\n", variant);
- return -ENODEV;
- }
-
- ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL);
- if (!ks)
- return -ENOMEM;
-
- mutex_init(&ks->lock);
- ks->spi = spi;
- ks->dev = &spi->dev;
- ks->chip = &ks8995_chip[variant];
-
- ks->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
- GPIOD_OUT_HIGH);
- err = PTR_ERR_OR_ZERO(ks->reset_gpio);
- if (err) {
- dev_err(&spi->dev,
- "failed to get reset gpio: %d\n", err);
- return err;
- }
-
- err = gpiod_set_consumer_name(ks->reset_gpio, "switch-reset");
- if (err)
- return err;
-
- if (ks->reset_gpio) {
- /*
- * If a reset line was obtained, wait for 100us after
- * de-asserting RESET before accessing any registers, see
- * the KS8995MA datasheet, page 44.
- */
- gpiod_set_value_cansleep(ks->reset_gpio, 0);
- udelay(100);
- }
-
- spi_set_drvdata(spi, ks);
-
- spi->mode = SPI_MODE_0;
- spi->bits_per_word = 8;
- err = spi_setup(spi);
- if (err) {
- dev_err(&spi->dev, "spi_setup failed, err=%d\n", err);
- return err;
- }
-
- err = ks8995_get_revision(ks);
- if (err)
- return err;
-
- err = ks8995_reset(ks);
- if (err)
- return err;
-
- dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n",
- ks->chip->name, ks->chip->chip_id, ks->revision_id);
-
- err = ks8995_check_config(ks);
- if (err)
- return err;
-
- ks->ds = devm_kzalloc(&spi->dev, sizeof(*ks->ds), GFP_KERNEL);
- if (!ks->ds)
- return -ENOMEM;
-
- ks->ds->dev = &spi->dev;
- ks->ds->num_ports = KS8995_NUM_PORTS;
- ks->ds->ops = &ks8995_ds_ops;
- ks->ds->phylink_mac_ops = &ks8995_phylink_mac_ops;
- ks->ds->priv = ks;
-
- err = dsa_register_switch(ks->ds);
- if (err)
- return dev_err_probe(&spi->dev, err,
- "unable to register DSA switch\n");
-
- return 0;
-}
-
-static void ks8995_remove(struct spi_device *spi)
-{
- struct ks8995_switch *ks = spi_get_drvdata(spi);
-
- dsa_unregister_switch(ks->ds);
- /* assert reset */
- gpiod_set_value_cansleep(ks->reset_gpio, 1);
-}
-
-/* ------------------------------------------------------------------------ */
-static struct spi_driver ks8995_driver = {
- .driver = {
- .name = "spi-ks8995",
- .of_match_table = ks8995_spi_of_match,
- },
- .probe = ks8995_probe,
- .remove = ks8995_remove,
- .id_table = ks8995_id,
-};
-
-module_spi_driver(ks8995_driver);
-
-MODULE_DESCRIPTION(DRV_DESC);
-MODULE_VERSION(DRV_VERSION);
-MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>");
-MODULE_LICENSE("GPL v2");
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 4/5] net: dsa: microchip: Support Microchip KSZ8995XA / KS8995XA
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
This adds support for the Microchip KSZ8995XA also known as the
Micrel KS8995XA switch to the KSZ driver.
Notice: there are also KSZ8995E and KSZ8995MA. These are BOTH
different from the KSZ8995XA.
The helper macros are named ksz_is_ksz8995xa() to make it
possible to add E and MA support in the future.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/microchip/Kconfig | 1 +
drivers/net/dsa/microchip/ksz8.c | 187 +++++++++++++++++++++++-----
drivers/net/dsa/microchip/ksz8.h | 2 +
drivers/net/dsa/microchip/ksz8_reg.h | 7 ++
drivers/net/dsa/microchip/ksz_common.c | 54 ++++++--
drivers/net/dsa/microchip/ksz_common.h | 11 +-
drivers/net/dsa/microchip/ksz_spi.c | 18 ++-
include/linux/platform_data/microchip-ksz.h | 1 +
8 files changed, 239 insertions(+), 42 deletions(-)
diff --git a/drivers/net/dsa/microchip/Kconfig b/drivers/net/dsa/microchip/Kconfig
index c71d3fd5dfeb..75c9b2114afd 100644
--- a/drivers/net/dsa/microchip/Kconfig
+++ b/drivers/net/dsa/microchip/Kconfig
@@ -2,6 +2,7 @@
menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
tristate "Microchip KSZ8XXX/KSZ9XXX/LAN937X series switch support"
depends on NET_DSA
+ select NET_DSA_TAG_KS8995
select NET_DSA_TAG_KSZ
select NET_DSA_TAG_NONE
select NET_IEEE8021Q_HELPERS
diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
index 586916570a84..03a5981a6315 100644
--- a/drivers/net/dsa/microchip/ksz8.c
+++ b/drivers/net/dsa/microchip/ksz8.c
@@ -3,6 +3,7 @@
* Microchip KSZ8XXX series switch driver
*
* It supports the following switches:
+ * - KSZ8995XA (the oldest ancestor)
* - KSZ8463
* - KSZ8863, KSZ8873 aka KSZ88X3
* - KSZ8895, KSZ8864 aka KSZ8895 family
@@ -137,7 +138,17 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat
static int ksz8_reset_switch(struct ksz_device *dev)
{
- if (ksz_is_ksz88x3(dev)) {
+ if (ksz_is_ksz8995xa(dev)) {
+ unsigned int port;
+
+ /* The KSZ8995XA switch itself cannot be reset by software, but
+ * often boot loaders have fun with ports, so reset them. This
+ * reset bit is self-clearing.
+ */
+ for (port = 0; port < dev->info->port_cnt; port++)
+ ksz_port_cfg(dev, port, REG_PORT_STATUS_3,
+ PORT_PHY_SOFT_RESET, true);
+ } else if (ksz_is_ksz88x3(dev)) {
/* reset switch */
ksz_cfg(dev, KSZ8863_REG_SW_RESET,
KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);
@@ -161,8 +172,14 @@ static int ksz8_reset_switch(struct ksz_device *dev)
static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size)
{
u8 ctrl2 = 0;
+ unsigned int legalsz;
+
+ if (ksz_is_ksz8995xa(dev))
+ legalsz = KSZ8995XA_LEGAL_PACKET_SIZE;
+ else
+ legalsz = KSZ8_LEGAL_PACKET_SIZE;
- if (frame_size <= KSZ8_LEGAL_PACKET_SIZE)
+ if (frame_size <= legalsz)
ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE;
else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE)
ctrl2 |= KSZ8863_HUGE_PACKET_ENABLE;
@@ -207,6 +224,7 @@ static int ksz8_change_mtu(struct dsa_switch *ds, int port, int mtu)
case KSZ88X3_CHIP_ID:
case KSZ8864_CHIP_ID:
case KSZ8895_CHIP_ID:
+ case KSZ8995XA_CHIP_ID:
return ksz8863_change_mtu(dev, frame_size);
}
@@ -242,6 +260,10 @@ static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN;
reg_4q = P1CR1;
reg_2q = P1CR1 + 1;
+ } else if (ksz_is_ksz8995xa(dev)) {
+ /* This switch has no 4way split support */
+ mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
+ reg_2q = REG_PORT_CTRL_0;
} else {
mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
@@ -860,6 +882,10 @@ static int ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val)
if (reg_val & PORT_MDIX_STATUS)
*val |= KSZ886X_CTRL_MDIX_STAT;
+ /* KSZ8995XA has no fancy features in register 0xA */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, ®_val);
if (ret < 0)
return ret;
@@ -958,8 +984,10 @@ static int ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val)
if (ctrl & PORT_FORCE_FULL_DUPLEX)
*val |= BMCR_FULLDPLX;
- if (speed & PORT_HP_MDIX)
- *val |= KSZ886X_BMCR_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ if (speed & PORT_HP_MDIX)
+ *val |= KSZ886X_BMCR_HP_MDIX;
+ }
if (restart & PORT_FORCE_MDIX)
*val |= KSZ886X_BMCR_FORCE_MDI;
@@ -1054,6 +1082,9 @@ static int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
data |= LPA_LPACK;
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1);
if (ret)
return ret;
@@ -1197,13 +1228,15 @@ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
if (val & BMCR_RESET)
return 0;
- speed = 0;
- if (val & KSZ886X_BMCR_HP_MDIX)
- speed |= PORT_HP_MDIX;
+ if (!ksz_is_ksz8995xa(dev)) {
+ speed = 0;
+ if (val & KSZ886X_BMCR_HP_MDIX)
+ speed |= PORT_HP_MDIX;
- ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
- if (ret)
- return ret;
+ ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed);
+ if (ret)
+ return ret;
+ }
ctrl = 0;
if (ksz_is_ksz88x3(dev)) {
@@ -1313,11 +1346,17 @@ static int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
}
break;
case PHY_REG_LINK_MD:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
if (val & PHY_START_CABLE_DIAG)
ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true);
break;
case PHY_REG_PHY_CTRL:
+ if (ksz_is_ksz8995xa(dev))
+ return -EOPNOTSUPP;
+
ret = ksz8_w_phy_ctrl(dev, p, val);
if (ret)
return ret;
@@ -1562,7 +1601,7 @@ static int ksz8_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag,
{
struct ksz_device *dev = ds->priv;
- if (ksz_is_ksz88x3(dev))
+ if (ksz_is_ksz88x3(dev) || ksz_is_ksz8995xa(dev))
return -ENOTSUPP;
/* Discard packets with VID not enabled on the switch */
@@ -1780,12 +1819,14 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_port_queue_split(dev, port, dev->info->num_tx_queues);
- /* replace priority */
- offset = P_802_1P_CTRL;
- if (ksz_is_ksz8463(dev))
- offset = P1CR2;
- ksz_port_cfg(dev, port, offset,
- masks[PORT_802_1P_REMAPPING], false);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* replace priority */
+ offset = P_802_1P_CTRL;
+ if (ksz_is_ksz8463(dev))
+ offset = P1CR2;
+ ksz_port_cfg(dev, port, offset,
+ masks[PORT_802_1P_REMAPPING], false);
+ }
if (cpu_port)
member = dsa_user_ports(ds);
@@ -1794,6 +1835,19 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
ksz8_cfg_port_member(dev, port, member);
+ if (ksz_is_ksz8995xa(dev)) {
+ /*
+ * The KSZ8995XA has a special tag format in the front of the frame
+ * that need to be inserted by the CPU and then removed by each
+ * port. PORT_REMOVE_TAG simply means "remove tags coming from the
+ * CPU port" it does not affect ingress packets.
+ */
+ if (cpu_port)
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_INSERT_TAG, true);
+ else
+ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_REMOVE_TAG, true);
+ }
+
/* Disable all WoL options by default. Otherwise
* ksz_switch_macaddr_get/put logic will not work properly.
* CPU port 4 has no WoL functionality.
@@ -1810,7 +1864,12 @@ static int ksz8_dsa_port_setup(struct dsa_switch *ds, int port)
return 0;
ksz8_port_setup(dev, port, false);
- return ksz_dcb_init_port(dev, port);
+
+ /* TODO: Revisit this and attempt to enable DCB on the KS8995XA. */
+ if (!ksz_is_ksz8995xa(dev))
+ return ksz_dcb_init_port(dev, port);
+
+ return 0;
}
static void ksz88x3_config_rmii_clk(struct ksz_device *dev)
@@ -1841,7 +1900,9 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds)
masks = dev->info->masks;
regs = dev->info->regs;
- ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
+ /* KSZ8995XA uses a tag in the header instead of the tail */
+ if (!ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true);
ksz8_port_setup(dev, dev->cpu_port, true);
@@ -2043,6 +2104,10 @@ static int ksz8_enable_stp_addr(struct ksz_device *dev)
{
struct alu_struct alu;
+ /* KSZ8995XA lacks STP */
+ if (ksz_is_ksz8995xa(dev))
+ return 0;
+
/* Setup STP address for STP operation. */
memset(&alu, 0, sizeof(alu));
ether_addr_copy(alu.mac, eth_stp_addr);
@@ -2061,13 +2126,17 @@ static int ksz8_setup(struct dsa_switch *ds)
struct ksz_port *p;
const u16 *regs;
int i, ret;
+ u8 val;
regs = dev->info->regs;
- dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
- dev->info->num_vlans, GFP_KERNEL);
- if (!dev->vlan_cache)
- return -ENOMEM;
+ /* KSZ8995XA has no SW controlled VLAN handling */
+ if (!ksz_is_ksz8995xa(dev)) {
+ dev->vlan_cache = devm_kcalloc(dev->dev, sizeof(struct vlan_table),
+ dev->info->num_vlans, GFP_KERNEL);
+ if (!dev->vlan_cache)
+ return -ENOMEM;
+ }
ret = ksz8_reset_switch(dev);
if (ret) {
@@ -2126,9 +2195,10 @@ static int ksz8_setup(struct dsa_switch *ds)
* Make sure unicast VLAN boundary is set as default and
* enable no excessive collision drop.
*/
- ret = ksz_rmw8(dev, REG_SW_CTRL_2,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP,
- UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP);
+ val = NO_EXC_COLLISION_DROP;
+ if (!ksz_is_ksz8995xa(dev))
+ val |= UNICAST_VLAN_BOUNDARY;
+ ret = ksz_rmw8(dev, REG_SW_CTRL_2, val, val);
if (ret)
return ret;
@@ -2136,11 +2206,15 @@ static int ksz8_setup(struct dsa_switch *ds)
ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false);
- if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev))
+ if (ksz_is_ksz8995xa(dev))
+ ksz_cfg(dev, REG_SW_CTRL_9, SW_SPECIAL_TAG, true);
+ else if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev))
ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true);
- for (i = 0; i < (dev->info->num_vlans / 4); i++)
- ksz8_r_vlan_entries(dev, i);
+ if (!ksz_is_ksz8995xa(dev)) {
+ for (i = 0; i < (dev->info->num_vlans / 4); i++)
+ ksz8_r_vlan_entries(dev, i);
+ }
/* Make sure PME (WoL) is not enabled. If requested, it will
* be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs
@@ -2204,9 +2278,15 @@ static int ksz8_setup(struct dsa_switch *ds)
goto out_ptp_clock_unregister;
}
- ret = ksz_dcb_init(dev);
- if (ret)
- goto out_ptp_clock_unregister;
+ /* TODO: the KSZ8995XA does have TOS priority control registers albeit
+ * 7 instead of 15 and in a different location. Revisit this and attempt
+ * to enable DCB on the KS8995XA.
+ */
+ if (!ksz_is_ksz8995xa(dev)) {
+ ret = ksz_dcb_init(dev);
+ if (ret)
+ goto out_ptp_clock_unregister;
+ }
/* start switch */
regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL],
@@ -2388,6 +2468,13 @@ static int ksz8_switch_init(struct ksz_device *dev)
return 0;
}
+static enum dsa_tag_protocol ksz8995xa_get_tag_protocol(struct dsa_switch *ds,
+ int port,
+ enum dsa_tag_protocol mp)
+{
+ return DSA_TAG_PROTO_KS8995;
+}
+
static enum dsa_tag_protocol ksz8463_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mp)
@@ -2484,6 +2571,16 @@ const struct phylink_mac_ops ksz8_phylink_mac_ops = {
.mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
};
+/*
+ * The KS(Z)8995XA has no indirect access, meaning no MIB counters,
+ * no FDB access, and no VLAN handling.
+ */
+const struct ksz_dev_ops ksz8995xa_dev_ops = {
+ .get_port_addr = ksz8_get_port_addr,
+ .cfg_port_member = ksz8_cfg_port_member,
+ .init = ksz8_switch_init,
+};
+
const struct ksz_dev_ops ksz8463_dev_ops = {
.get_port_addr = ksz8463_get_port_addr,
.cfg_port_member = ksz8_cfg_port_member,
@@ -2523,6 +2620,32 @@ const struct ksz_dev_ops ksz88xx_dev_ops = {
.pme_pwrite8 = ksz8_pme_pwrite8,
};
+/*
+ * Restricted operations for KSZ8995XA, so many things are not supported
+ * by this old switch that we need diet DSA operations.
+ */
+const struct dsa_switch_ops ksz8995xa_switch_ops = {
+ .get_tag_protocol = ksz8995xa_get_tag_protocol,
+ .setup = ksz8_setup,
+ .teardown = ksz_teardown,
+ .phy_read = ksz8_phy_read16,
+ .phy_write = ksz8_phy_write16,
+ .phylink_get_caps = ksz8_phylink_get_caps,
+ .port_setup = ksz8_dsa_port_setup,
+ .port_bridge_join = ksz_port_bridge_join,
+ .port_bridge_leave = ksz_port_bridge_leave,
+ .port_set_mac_address = ksz_port_set_mac_address,
+ .port_stp_state_set = ksz_port_stp_state_set,
+ .port_pre_bridge_flags = ksz_port_pre_bridge_flags,
+ .port_bridge_flags = ksz_port_bridge_flags,
+ .port_fast_age = ksz8_flush_dyn_mac_table,
+ .port_mirror_add = ksz8_port_mirror_add,
+ .port_mirror_del = ksz8_port_mirror_del,
+ .port_change_mtu = ksz8_change_mtu,
+ .port_max_mtu = ksz_max_mtu,
+ /* TODO: add .port_get/set_apptrust() when we implement priority */
+};
+
const struct dsa_switch_ops ksz8463_switch_ops = {
.get_tag_protocol = ksz8463_get_tag_protocol,
.connect_tag_protocol = ksz8463_connect_tag_protocol,
diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h
index bc371cc26c6f..9b37c56c9617 100644
--- a/drivers/net/dsa/microchip/ksz8.h
+++ b/drivers/net/dsa/microchip/ksz8.h
@@ -12,11 +12,13 @@
#include <net/dsa.h>
#include "ksz_common.h"
+extern const struct ksz_dev_ops ksz8995xa_dev_ops;
extern const struct ksz_dev_ops ksz8463_dev_ops;
extern const struct ksz_dev_ops ksz87xx_dev_ops;
extern const struct ksz_dev_ops ksz88xx_dev_ops;
extern const struct phylink_mac_ops ksz88x3_phylink_mac_ops;
extern const struct phylink_mac_ops ksz8_phylink_mac_ops;
+extern const struct dsa_switch_ops ksz8995xa_switch_ops;
extern const struct dsa_switch_ops ksz8463_switch_ops;
extern const struct dsa_switch_ops ksz87xx_switch_ops;
extern const struct dsa_switch_ops ksz88xx_switch_ops;
diff --git a/drivers/net/dsa/microchip/ksz8_reg.h b/drivers/net/dsa/microchip/ksz8_reg.h
index 981ab441d9b7..c25897ccdc39 100644
--- a/drivers/net/dsa/microchip/ksz8_reg.h
+++ b/drivers/net/dsa/microchip/ksz8_reg.h
@@ -30,6 +30,11 @@
#define KSZ88X3_REG_FVID_AND_HOST_MODE 0xC6
#define KSZ88X3_PORT3_RMII_CLK_INTERNAL BIT(3)
+#define REG_SW_ID_0 0x00
+#define REG_SW_ID_1 0x01
+
+#define SW_START_SWITCH BIT(0)
+
#define REG_SW_CTRL_0 0x02
#define SW_NEW_BACKOFF BIT(7)
@@ -95,6 +100,8 @@
#define SW_LED_LINK_ACT_DUPLEX 2
#define SW_LED_LINK_DUPLEX 3
+#define SW_SPECIAL_TAG BIT(0) /* KSZ8995XA only */
+
#define REG_SW_CTRL_10 0x0C
#define SW_PASS_PAUSE BIT(0)
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index d1726778bb48..e9f85449517f 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -553,6 +553,20 @@ static const u8 ksz8895_shifts[] = {
[DYNAMIC_MAC_SRC_PORT] = 24,
};
+static const u16 ksz8995xa_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x68,
+ [P_FORCE_CTRL] = 0x0C,
+ [P_LINK_STATUS] = 0x0E,
+ [P_LOCAL_CTRL] = 0x0C,
+ [P_NEG_RESTART_CTRL] = 0x0D,
+ [P_REMOTE_STATUS] = 0x0E,
+ [P_SPEED_STATUS] = 0x09,
+ [P_STP_CTRL] = 0x02,
+ [S_START_CTRL] = 0x01,
+ [S_BROADCAST_CTRL] = 0x06,
+ [S_MULTICAST_CTRL] = 0x04,
+};
+
static const u16 ksz9477_regs[] = {
[REG_SW_MAC_ADDR] = 0x0302,
[P_STP_CTRL] = 0x0B04,
@@ -1484,6 +1498,21 @@ const struct ksz_chip_data ksz_switch_chips[] = {
.internal_phy = {true, true, true, true, false},
},
+ [KSZ8995XA] = {
+ .chip_id = KSZ8995XA_CHIP_ID, /* Also known as KS8995XA */
+ .dev_name = "KSZ8995XA",
+ .cpu_ports = 0x10, /* can be configured as cpu port */
+ .port_cnt = 5, /* total cpu and user ports */
+ .num_tx_queues = 2, /* low/hi priority queues, no more */
+ .num_ipms = 4,
+ .ops = &ksz8995xa_dev_ops,
+ .switch_ops = &ksz8995xa_switch_ops,
+ .phylink_mac_ops = &ksz88x3_phylink_mac_ops,
+ .regs = ksz8995xa_regs,
+ .supports_mii = {true, true, true, true, true},
+ .internal_phy = {true, true, true, true, false},
+ },
+
[KSZ9477] = {
.chip_id = KSZ9477_CHIP_ID,
.dev_name = "KSZ9477",
@@ -2811,6 +2840,10 @@ void ksz_init_mib_timer(struct ksz_device *dev)
{
int i;
+ /* KSZ8995XA lacks MiB features */
+ if (ksz_is_ksz8995xa(dev))
+ return;
+
INIT_DELAYED_WORK(&dev->mib_read, ksz_mib_read_work);
for (i = 0; i < dev->info->port_cnt; i++) {
@@ -2997,6 +3030,7 @@ int ksz_max_mtu(struct dsa_switch *ds, int port)
case KSZ88X3_CHIP_ID:
case KSZ8864_CHIP_ID:
case KSZ8895_CHIP_ID:
+ case KSZ8995XA_CHIP_ID:
return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN;
case KSZ8563_CHIP_ID:
case KSZ8567_CHIP_ID:
@@ -3213,11 +3247,15 @@ static int ksz_switch_detect(struct ksz_device *dev)
return -ENODEV;
break;
case KSZ8895_FAMILY_ID:
- if (id2 == KSZ8895_CHIP_ID_95 ||
- id2 == KSZ8895_CHIP_ID_95R)
+ if (id2 == KSZ8895_CHIP_ID_95XA) {
+ dev->chip_id = KSZ8995XA_CHIP_ID;
+ break;
+ } else if (id2 == KSZ8895_CHIP_ID_95 ||
+ id2 == KSZ8895_CHIP_ID_95R) {
dev->chip_id = KSZ8895_CHIP_ID;
- else
+ } else {
return -ENODEV;
+ }
ret = ksz_read8(dev, REG_KSZ8864_CHIP_ID, &id4);
if (ret)
return ret;
@@ -4530,11 +4568,13 @@ int ksz_switch_register(struct ksz_device *dev)
if (ret)
return ret;
- /* Read MIB counters every 30 seconds to avoid overflow. */
- dev->mib_read_interval = msecs_to_jiffies(5000);
+ if (!ksz_is_ksz8995xa(dev)) {
+ /* Read MIB counters every 30 seconds to avoid overflow. */
+ dev->mib_read_interval = msecs_to_jiffies(5000);
- /* Start the MIB timer. */
- schedule_delayed_work(&dev->mib_read, 0);
+ /* Start the MIB timer. */
+ schedule_delayed_work(&dev->mib_read, 0);
+ }
return ret;
}
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index b4a5673ba365..61fd5d059026 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -239,6 +239,7 @@ enum ksz_model {
KSZ88X3,
KSZ8864,
KSZ8895,
+ KSZ8995XA,
KSZ9477,
KSZ9896,
KSZ9897,
@@ -714,7 +715,13 @@ static inline bool ksz_is_ksz88x3(struct ksz_device *dev)
static inline bool ksz_is_8895_family(struct ksz_device *dev)
{
return dev->chip_id == KSZ8895_CHIP_ID ||
- dev->chip_id == KSZ8864_CHIP_ID;
+ dev->chip_id == KSZ8864_CHIP_ID ||
+ dev->chip_id == KSZ8995XA_CHIP_ID;
+}
+
+static inline bool ksz_is_ksz8995xa(struct ksz_device *dev)
+{
+ return dev->chip_id == KSZ8995XA_CHIP_ID;
}
static inline bool is_ksz8(struct ksz_device *dev)
@@ -785,6 +792,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ87_CHIP_ID_94 0x6
#define KSZ87_CHIP_ID_95 0x9
#define KSZ88_CHIP_ID_63 0x3
+#define KSZ8895_CHIP_ID_95XA 0x0
#define KSZ8895_CHIP_ID_95 0x4
#define KSZ8895_CHIP_ID_95R 0x6
@@ -848,6 +856,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
#define KSZ8863_HUGE_PACKET_SIZE 1916
#define KSZ8863_NORMAL_PACKET_SIZE 1536
#define KSZ8_LEGAL_PACKET_SIZE 1518
+#define KSZ8995XA_LEGAL_PACKET_SIZE 1522
#define KSZ9477_MAX_FRAME_SIZE 9000
#define KSZ8873_REG_GLOBAL_CTRL_12 0x0e
diff --git a/drivers/net/dsa/microchip/ksz_spi.c b/drivers/net/dsa/microchip/ksz_spi.c
index 77aecac32466..2c55014246e5 100644
--- a/drivers/net/dsa/microchip/ksz_spi.c
+++ b/drivers/net/dsa/microchip/ksz_spi.c
@@ -161,7 +161,8 @@ static int ksz_spi_probe(struct spi_device *spi)
chip->chip_id == KSZ8794_CHIP_ID ||
chip->chip_id == KSZ8765_CHIP_ID)
regmap_config = ksz8795_regmap_config;
- else if (chip->chip_id == KSZ8895_CHIP_ID ||
+ else if (chip->chip_id == KSZ8995XA_CHIP_ID ||
+ chip->chip_id == KSZ8895_CHIP_ID ||
chip->chip_id == KSZ8864_CHIP_ID)
regmap_config = ksz8863_regmap_config;
else
@@ -185,7 +186,10 @@ static int ksz_spi_probe(struct spi_device *spi)
dev->pdata = spi->dev.platform_data;
/* setup spi */
- spi->mode = SPI_MODE_3;
+ if (chip->chip_id == KSZ8995XA_CHIP_ID)
+ spi->mode = SPI_MODE_0;
+ else
+ spi->mode = SPI_MODE_3;
ret = spi_setup(spi);
if (ret)
return ret;
@@ -239,6 +243,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.compatible = "micrel,ksz8795",
.data = &ksz_switch_chips[KSZ8795]
},
+ {
+ .compatible = "micrel,ks8995",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz8463",
.data = &ksz_switch_chips[KSZ8463]
@@ -271,6 +279,10 @@ static const struct of_device_id ksz_dt_ids[] = {
.compatible = "microchip,ksz8895",
.data = &ksz_switch_chips[KSZ8895]
},
+ {
+ .compatible = "microchip,ksz8995xa",
+ .data = &ksz_switch_chips[KSZ8995XA]
+ },
{
.compatible = "microchip,ksz9477",
.data = &ksz_switch_chips[KSZ9477]
@@ -332,6 +344,7 @@ static const struct of_device_id ksz_dt_ids[] = {
MODULE_DEVICE_TABLE(of, ksz_dt_ids);
static const struct spi_device_id ksz_spi_ids[] = {
+ { "ks8995" },
{ "ksz8463" },
{ "ksz8765" },
{ "ksz8794" },
@@ -340,6 +353,7 @@ static const struct spi_device_id ksz_spi_ids[] = {
{ "ksz8864" },
{ "ksz8873" },
{ "ksz8895" },
+ { "ksz8995" },
{ "ksz9477" },
{ "ksz9896" },
{ "ksz9897" },
diff --git a/include/linux/platform_data/microchip-ksz.h b/include/linux/platform_data/microchip-ksz.h
index 028781ad4059..d8eddd21c3c7 100644
--- a/include/linux/platform_data/microchip-ksz.h
+++ b/include/linux/platform_data/microchip-ksz.h
@@ -31,6 +31,7 @@ enum ksz_chip_id {
KSZ88X3_CHIP_ID = 0x8830,
KSZ8864_CHIP_ID = 0x8864,
KSZ8895_CHIP_ID = 0x8895,
+ KSZ8995XA_CHIP_ID = 0x8995,
KSZ9477_CHIP_ID = 0x00947700,
KSZ9896_CHIP_ID = 0x00989600,
KSZ9897_CHIP_ID = 0x00989700,
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v7 3/5] net: dsa: tag_ks8995: Add the KS8995 tag handling
From: Linus Walleij @ 2026-07-04 19:39 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Marek Vasut,
Simon Horman, Russell King
Cc: netdev, Woojung Huh, devicetree, Linus Walleij
In-Reply-To: <20260704-ks8995-to-ksz8-v7-0-2af0eaa545a8@kernel.org>
The KS8995 100Mbit switch can do proper DSA per-port tagging
with the proper set-up. This adds the code to handle ingress
and egress KS8995 tags.
The tag is a modified 0x8100 ethertype tag where a bit in the
last nybble is set for each target port.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
MAINTAINERS | 1 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 6 +++
net/dsa/Makefile | 1 +
net/dsa/tag_ks8995.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 147 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 52f1a55eca99..6bd1baec7b44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17598,6 +17598,7 @@ F: Documentation/devicetree/bindings/net/dsa/microchip,lan937x.yaml
F: drivers/net/dsa/microchip/*
F: include/linux/dsa/ksz_common.h
F: include/linux/platform_data/microchip-ksz.h
+F: net/dsa/tag_ks8995.c
F: net/dsa/tag_ksz.c
MICROCHIP LAN743X ETHERNET DRIVER
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..abd159527782 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -59,6 +59,7 @@ struct tc_action;
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
#define DSA_TAG_PROTO_NETC_VALUE 33
+#define DSA_TAG_PROTO_KS8995_VALUE 34
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -95,6 +96,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
+ DSA_TAG_PROTO_KS8995 = DSA_TAG_PROTO_KS8995_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d5e725b90d78..bbdf324addac 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -119,6 +119,12 @@ config NET_DSA_TAG_MXL_GSW1XX
Say Y or M if you want to enable support for tagging frames for
MaxLinear GSW1xx switches.
+config NET_DSA_TAG_KS8995
+ tristate "Tag driver for Micrel KS8995 switch"
+ help
+ Say Y if you want to enable support for tagging frames for the
+ Micrel KS8995 switch.
+
config NET_DSA_TAG_KSZ
tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index b8c2667cd14a..a9c2a0569e9e 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o
obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
+obj-$(CONFIG_NET_DSA_TAG_KS8995) += tag_ks8995.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
diff --git a/net/dsa/tag_ks8995.c b/net/dsa/tag_ks8995.c
new file mode 100644
index 000000000000..5e81ce1ea91a
--- /dev/null
+++ b/net/dsa/tag_ks8995.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Linus Walleij <linusw@kernel.org>
+ */
+#include <linux/etherdevice.h>
+#include <linux/log2.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include "tag.h"
+
+/* The Micrel KS8995XA / Microchip KSZ8995XA Special Tag Packet ID (STPID)
+ * pushes its tag in a modified VLAN (802.1Q) tag.
+ * -----------------------------------------------------------
+ * | MAC DA | MAC SA | 2 bytes tag | 2 bytes TCI | EtherType |
+ * -----------------------------------------------------------
+ * The tag is: 0x8100 |= BIT(port), ports 0,1,2,3
+ */
+
+#define KS8995_NAME "ks8995"
+
+#define KS8995M_STPID_STD GENMASK(15, 4)
+#define KS8995M_STPID_PORTMASK GENMASK(3, 0)
+#define KS8995M_STPID(portmask) htons(ETH_P_8021Q | FIELD_PREP(KS8995M_STPID_PORTMASK, portmask))
+
+static struct sk_buff *ks8995_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vlan_ethhdr *hdr = vlan_eth_hdr(skb);
+ bool have_hwaccel_tag = false;
+ u16 tci = 0, portmask;
+
+ /* Prepare the special KS8995 tags */
+ portmask = dsa_xmit_port_mask(skb, dev);
+
+ if (skb_vlan_tag_present(skb) && skb->vlan_proto == htons(ETH_P_8021Q)) {
+ tci = skb_vlan_tag_get(skb);
+ __vlan_hwaccel_clear_tag(skb);
+ have_hwaccel_tag = true;
+ }
+
+ if (have_hwaccel_tag || hdr->h_vlan_proto != htons(ETH_P_8021Q)) {
+ skb = vlan_insert_tag(skb, KS8995M_STPID(portmask), tci);
+ /* vlan_insert_tag() drops the skb on failure */
+ if (!skb)
+ return NULL;
+ hdr = vlan_eth_hdr(skb);
+ netdev_dbg(dev, "%s: inserted VLAN TAG %04x TCI %04x\n",
+ __func__, hdr->h_vlan_proto, hdr->h_vlan_TCI);
+ } else {
+ /* VLAN tag already exists in skb head, modify it in place */
+ hdr = vlan_eth_hdr(skb);
+ hdr->h_vlan_proto = KS8995M_STPID(portmask);
+ netdev_dbg(dev, "%s: modified VLAN TAG %04x\n",
+ __func__, hdr->h_vlan_proto);
+ }
+
+ return skb;
+}
+
+static struct sk_buff *ks8995_rcv(struct sk_buff *skb, struct net_device *dev)
+{
+ int portmask;
+ u16 etype;
+
+ /* We are expecting all received packets to have a mangled VLAN
+ * TPID, so drop anything else. Because of the non-standard TPID,
+ * don't even bother looking for a tag in the hwaccel area.
+ *
+ * We have to inspect the ethertype directly because skb->protocol
+ * will contain garbage.
+ */
+ etype = ntohs(*(__be16 *)dsa_etype_header_pos_rx(skb));
+ if ((etype & KS8995M_STPID_STD) != ETH_P_8021Q) {
+ netdev_dbg(dev, "%s: dropped ethertype 0x%04x\n",
+ __func__, etype);
+ kfree_skb(skb);
+ return NULL;
+ }
+ netdev_dbg(dev, "%s: received ethertype %04x\n",
+ __func__, etype);
+
+ /* Move the custom DSA+VLAN tag into the hwaccel area and strip
+ * it from the skb head
+ */
+ skb = skb_vlan_untag(skb);
+ if (!skb) {
+ /* skb_vlan_untag drops the skb on failure */
+ netdev_err(dev, "%s: unable to untag skb\n", __func__);
+ return NULL;
+ }
+
+ portmask = FIELD_GET(KS8995M_STPID_PORTMASK, etype);
+ netdev_dbg(dev, "%s: etype %04x portmask %04x (%d)\n",
+ __func__, etype, portmask, ilog2(portmask));
+ skb->dev = dsa_conduit_find_user(dev, 0, ilog2(portmask));
+ if (!skb->dev) {
+ kfree_skb(skb);
+ return NULL;
+ }
+
+ /* Preserve the VLAN tag if it contains a non-zero VID which is not
+ * identical to 0x001, or PCP, and restore its TPID to the standard
+ * value.
+ *
+ * If this is just an ordinary inbound package the datasheet claims
+ * it will "replace null VID with ingress port VID", which means
+ * VID set to 1: 0x8101 0001 for port 0 or 0x8102 0001 for port 1.
+ * So in the DSA driver we will set the default port VID to 0 so
+ * we can properly detect non-VLAN frames.
+ */
+ if (!skb->vlan_tci) {
+ netdev_dbg(dev, "%s: clear VLAN tag from frame\n", __func__);
+ __vlan_hwaccel_clear_tag(skb);
+ } else {
+ skb->vlan_proto = htons(ETH_P_8021Q);
+ netdev_dbg(dev, "%s: vlan_tci = 0x%04x VLAN frame\n",
+ __func__, skb->vlan_tci);
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+
+ return skb;
+}
+
+static const struct dsa_device_ops ks8995_netdev_ops = {
+ .name = KS8995_NAME,
+ .proto = DSA_TAG_PROTO_KS8995,
+ .xmit = ks8995_xmit,
+ .rcv = ks8995_rcv,
+ .needed_headroom = VLAN_HLEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for Micrel KS8995 family of switches");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KS8995, KS8995_NAME);
+
+module_dsa_tag_driver(ks8995_netdev_ops);
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox