* RE: [PATCH net v2] tipc: clear sock->sk on the failed-insert path in tipc_sk_create()
From: Tung Quang Nguyen @ 2026-07-14 9:36 UTC (permalink / raw)
To: Daehyeon Ko
Cc: Jon Maloy, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, tipc-discussion@lists.sourceforge.net,
linux-kernel@vger.kernel.org, stable@vger.kernel.org,
netdev@vger.kernel.org
In-Reply-To: <20260713082342.3803379-1-4ncienth@gmail.com>
>Subject: [PATCH net v2] tipc: clear sock->sk on the failed-insert path in
>tipc_sk_create()
>
>When tipc_sk_create() fails to insert the new socket (tipc_sk_insert() returns
>non-zero), its error path frees the sk with sk_free() but leaves
>sock->sk pointing at the freed object:
>
> if (tipc_sk_insert(tsk)) {
> sk_free(sk);
> pr_warn("Socket create failed; port number exhausted\n");
> return -EINVAL;
> }
>
>This is harmless for plain socket(): the syscall layer clears sock->ops before
>releasing, so tipc_release() is never called. It is not harmless on the accept()
>path. tipc_accept() creates the pre-allocated child socket with
>tipc_sk_create(net, new_sock, 0, kern); on failure it leaves new_sock->sk
>dangling and new_sock->ops non-NULL, and do_accept() then fput()s the new
>file, so __sock_release() -> tipc_release() runs
>lock_sock(new_sock->sk) on the freed sk -- a use-after-free write of the sk_lock
>spinlock.
>
>tipc_release() already guards this exact "failed accept() releases a pre-allocated
>child" case with "if (sk == NULL) return 0;", but the guard is bypassed because
>tipc_sk_create() left sock->sk non-NULL
>(dangling) rather than NULL.
>
>Clear sock->sk on the failed-insert path so the existing tipc_release() NULL
>check fires and the use-after-free is avoided.
Reviewed-by: Tung Nguyen <tung.quang.nguyen@est.tech>
^ permalink raw reply
* Re: [PATCH 2/2] ptp: ptp_s390: Add missing facility check
From: Heiko Carstens @ 2026-07-14 9:39 UTC (permalink / raw)
To: Sven Schnelle
Cc: Richard Cochran, netdev, linux-s390, linux-kernel,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Vasily Gorbik, Alexander Gordeev
In-Reply-To: <20260714084921.3926697-3-svens@linux.ibm.com>
On Tue, Jul 14, 2026 at 10:49:21AM +0200, Sven Schnelle wrote:
> Only register the physical clock when facility 28 is installed.
>
> Fixes: 2d7de7a3010d ("s390/time: Add PtP driver")
> Signed-off-by: Sven Schnelle <svens@linux.ibm.com>
> Cc: stable@kernel.org
> ---
> drivers/ptp/ptp_s390.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/ptp/ptp_s390.c b/drivers/ptp/ptp_s390.c
> index 7299c3aae65f..3ea94648cdde 100644
> --- a/drivers/ptp/ptp_s390.c
> +++ b/drivers/ptp/ptp_s390.c
> @@ -108,6 +108,9 @@ static __init int ptp_s390_init(void)
> if (IS_ERR(ptp_stcke_clock))
> return PTR_ERR(ptp_stcke_clock);
>
> + if (!test_facility(28))
> + return 0;
> +
> ptp_qpt_clock = ptp_clock_register(&ptp_s390_qpt_info, NULL);
> if (IS_ERR(ptp_qpt_clock)) {
> ptp_clock_unregister(ptp_stcke_clock);
Wouldn't it make more sense to check if PTFF-QPT is available via PTFF-QAF
before registering the clock? This would also avoid the need of the first
patch - since then it is guaranteed that PTFF-QPT will always return with
condition code zero.
And... we wouldn't have the potential oddity to have a clock registered which
doesn't work.
^ permalink raw reply
* [PATCH net-next 1/9] net/ipv6: fix lookup for ::/0 (non-)subtree route
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
Assume a scenario with something like the following routes:
default via fe80::1 dev dummy0
2001:db8:1::/48 via fe80::10 dev dummy0
2001:db8:1::/48 from 2001:db8:1:2::/64 via fe80::12 dev dummy0
Now if a lookup happens for 2001:db8:1::2345, but with a source address
*not* covered by the third route, the expectation is to hit the second
one. Unfortunately, this was broken since the code, on failing the
lookup in the subtree, didn't consider the node itself which the subtree
is attached to, i.e. route #2 above.
The fix is simple, check if the subtree is attached to a node that is
itself a valid route before backtracking to less specific destination
prefixes.
This case is somewhat rare for several reasons. To begin with, subtree
routes are most commonly attached to the default destination.
Additionally, in the rare cases where a non-default destination prefix
is host to subtree routes, the fallback on not hitting any subtree route
is commonly a default route (or a subtree route on that).
(Note that this was working for the "::/0 from ::/0" case since the root
node is special-cased. The issue was discovered during RFC 6724 rule
5.5 testing, trying to find edge cases.)
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Lorenzo Colitti <lorenzo@google.com>
Cc: Patrick Rohr <prohr@google.com>
Cc: Maciej Żenczykowski <maze@google.com>
---
net/ipv6/ip6_fib.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index a130cdfaebfb..273f2bfc5286 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -1656,8 +1656,11 @@ static struct fib6_node *fib6_node_lookup_1(struct fib6_node *root,
struct fib6_node *sfn;
sfn = fib6_node_lookup_1(subtree,
args + 1);
- if (!sfn)
+ if (!sfn) {
+ if (fn->fn_flags & RTN_RTINFO)
+ return fn;
goto backtrack;
+ }
fn = sfn;
}
#endif
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 0/9] RFC 6724 rule 5.5 support
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest
Hi all,
this patchset implements RFC6724 rule 5.5. For the unaquainted:
Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
If SA or SA's prefix is assigned by the selected next-hop that will
be used to send to D and SB or SB's prefix is assigned by a different
next-hop, then prefer SA. Similarly, if SB or SB's prefix is
assigned by the next-hop that will be used to send to D and SA or
SA's prefix is assigned by a different next-hop, then prefer SB.
The way this is done is through IPv6 subtree routes. If a router
advertises some prefix in its RA/PIOs, source specific subtree routes
should be created for the default route (and RIOs) installed as a result
of processing that RA.
This may initially sound like a weird way to do it, but for one RFC8028
requires the subtree routes anyway, and also I did try the more obvious
approaches (explicitly tracking it, putting it on the address, putting
it on the neighbor entry) and all of them break in some scenarios.
I've put together a selftest, there's also a rather hacky test suite
created for an IETF hackathon: https://github.com/eqvinox/rule5p5-tests
(it's not specific to this patchset.) I've also been dogfooding these
patches on my personal devices for more than a year.
Rule 5.5 itself has extensive history at the IETF, including changing
from optional to mandatory in the recent 6724 update. It is immensely
useful (really: required) to make multihoming, renumbering and failover
work.
@Jakub you had previously asked me to resubmit the "prep" patches since
it was at a poor time (cf. Fri, Jul 25, 2025 at 05:39:58PM -0700).
(I had tried submitting the preparation bits on its own.)
@Paolo you had looked at the lookup fix:
On Tue, Nov 11, 2025 at 11:13:30AM +0100, Paolo Abeni wrote:
> The patch LGTM, and I agree this should go via net-next, given that it's
> really a corner case and I could miss nasty side-effects.
>
> It looks like you have some testing scenario handy: it would be great to
> include it as a paired self-test; could you please add it?
Cheers,
equi (David)
P.S.: I also happen to be around at netdevconf in Rome, in case anyone
happens to see this and have questions. Of course being at a conference
generally means not looking at random patch mails, so this is mostly
just in case you see the Subject lines or this cover letter. Apologies,
it wasn't possible for me to submit this ahead of the conference.
diffstat:
Documentation/networking/ipv6-addrsel.rst | 75 ++++++++++++++++++++++++++++++++
MAINTAINERS | 1 +
include/net/addrconf.h | 4 ++
include/net/ip6_route.h | 26 ------------
net/ipv6/Kconfig | 18 ++++++--
net/ipv6/addrconf.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
net/ipv6/ip6_fib.c | 5 ++-
net/ipv6/ip6_output.c | 26 +++++++++---
net/ipv6/route.c | 16 +++++--
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 1 +
tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 497 insertions(+), 51 deletions(-)
^ permalink raw reply
* [PATCH net-next 2/9] net/ipv6: flatten ip6_route_get_saddr
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
Inline ip6_route_get_saddr()'s functionality in rt6_fill_node(), to
prepare for replacing the former with a dst based function.
NB: the l3mdev handling introduced by 252442f2ae31 "ipv6: fix source
address selection with route leak" is dropped here - the l3mdev ifindex
was a constant 0 on this call site, so that code was in fact dead.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
net/ipv6/route.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index a1301334da48..704afe2ddea7 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -5850,9 +5850,19 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
if (nla_put_u32(skb, RTA_IIF, iif))
goto nla_put_failure;
} else if (dest) {
- struct in6_addr saddr_buf;
- if (ip6_route_get_saddr(net, rt, dest, 0, 0, &saddr_buf) == 0 &&
- nla_put_in6_addr(skb, RTA_PREFSRC, &saddr_buf))
+ struct in6_addr saddr_buf, *saddr = NULL;
+
+ if (rt->fib6_prefsrc.plen) {
+ saddr = &rt->fib6_prefsrc.addr;
+ } else {
+ struct net_device *dev = fib6_info_nh_dev(rt);
+
+ if (ipv6_dev_get_saddr(net, dev, dest, 0,
+ &saddr_buf) == 0)
+ saddr = &saddr_buf;
+ }
+
+ if (saddr && nla_put_in6_addr(skb, RTA_PREFSRC, saddr))
goto nla_put_failure;
}
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 3/9] net/ipv6: create ipv6_fl_get_saddr
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
This adds passing the relevant flow information as well as selected
nexthop into the source address selection code, to allow the RFC6724
rule 5.5 code to look at its details.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
include/net/addrconf.h | 4 ++++
net/ipv6/addrconf.c | 45 +++++++++++++++++++++++++++++++-----------
2 files changed, 38 insertions(+), 11 deletions(-)
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 8ced27a8229b..7bc0711b8717 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -142,6 +142,10 @@ struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net,
int ipv6_dev_get_saddr(struct net *net, const struct net_device *dev,
const struct in6_addr *daddr, unsigned int srcprefs,
struct in6_addr *saddr);
+int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst,
+ const struct net_device *dst_dev,
+ const struct sock *sk, unsigned int srcprefs,
+ struct flowi6 *fl6);
int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr,
u32 banned_flags);
bool inet_rcv_saddr_equal(const struct sock *sk, const struct sock *sk2,
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f1fe9ede1edb..0ba46ed518a9 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1542,7 +1542,9 @@ struct ipv6_saddr_score {
};
struct ipv6_saddr_dst {
- const struct in6_addr *addr;
+ const struct flowi6 *fl6;
+ const struct dst_entry *dst;
+ const struct sock *sk;
int ifindex;
int scope;
int label;
@@ -1619,7 +1621,7 @@ static int ipv6_get_saddr_eval(struct net *net,
break;
case IPV6_SADDR_RULE_LOCAL:
/* Rule 1: Prefer same address */
- ret = ipv6_addr_equal(&score->ifa->addr, dst->addr);
+ ret = ipv6_addr_equal(&score->ifa->addr, &dst->fl6->daddr);
break;
case IPV6_SADDR_RULE_SCOPE:
/* Rule 2: Prefer appropriate scope
@@ -1697,11 +1699,11 @@ static int ipv6_get_saddr_eval(struct net *net,
* non-ORCHID vs non-ORCHID
*/
ret = !(ipv6_addr_orchid(&score->ifa->addr) ^
- ipv6_addr_orchid(dst->addr));
+ ipv6_addr_orchid(&dst->fl6->daddr));
break;
case IPV6_SADDR_RULE_PREFIX:
/* Rule 8: Use longest matching prefix */
- ret = ipv6_addr_diff(&score->ifa->addr, dst->addr);
+ ret = ipv6_addr_diff(&score->ifa->addr, &dst->fl6->daddr);
if (ret > score->ifa->prefix_len)
ret = score->ifa->prefix_len;
score->matchlen = ret;
@@ -1819,9 +1821,10 @@ static int ipv6_get_saddr_master(struct net *net,
return hiscore_idx;
}
-int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
- const struct in6_addr *daddr, unsigned int prefs,
- struct in6_addr *saddr)
+int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst_entry,
+ const struct net_device *dst_dev,
+ const struct sock *sk, unsigned int prefs,
+ struct flowi6 *fl6)
{
struct ipv6_saddr_score scores[2], *hiscore;
struct ipv6_saddr_dst dst;
@@ -1832,11 +1835,13 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
int hiscore_idx = 0;
int ret = 0;
- dst_type = __ipv6_addr_type(daddr);
- dst.addr = daddr;
+ dst_type = __ipv6_addr_type(&fl6->daddr);
+ dst.fl6 = fl6;
+ dst.sk = sk;
+ dst.dst = dst_entry;
dst.ifindex = dst_dev ? dst_dev->ifindex : 0;
dst.scope = __ipv6_addr_src_scope(dst_type);
- dst.label = ipv6_addr_label(net, daddr, dst_type, dst.ifindex);
+ dst.label = ipv6_addr_label(net, &fl6->daddr, dst_type, dst.ifindex);
dst.prefs = prefs;
scores[hiscore_idx].rule = -1;
@@ -1911,11 +1916,29 @@ int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
if (!hiscore->ifa)
ret = -EADDRNOTAVAIL;
else
- *saddr = hiscore->ifa->addr;
+ fl6->saddr = hiscore->ifa->addr;
rcu_read_unlock();
return ret;
}
+EXPORT_SYMBOL(ipv6_fl_get_saddr);
+
+int ipv6_dev_get_saddr(struct net *net, const struct net_device *dst_dev,
+ const struct in6_addr *daddr, unsigned int prefs,
+ struct in6_addr *saddr)
+{
+ struct flowi6 fl6;
+ int ret;
+
+ memset(&fl6, 0, sizeof(fl6));
+ fl6.daddr = *daddr;
+
+ ret = ipv6_fl_get_saddr(net, NULL, dst_dev, NULL, prefs, &fl6);
+ if (!ret)
+ *saddr = fl6.saddr;
+
+ return ret;
+}
EXPORT_SYMBOL(ipv6_dev_get_saddr);
static int __ipv6_get_lladdr(struct inet6_dev *idev, struct in6_addr *addr,
--
2.53.0
^ permalink raw reply related
* RE: [PATCH net-next v10 4/5] net: wangxun: implement soft quiesce for PCIe error recovery
From: Jiawen Wu @ 2026-07-14 9:43 UTC (permalink / raw)
To: 'Simon Horman'
Cc: netdev, mengyuanlou, andrew+netdev, davem, edumazet, kuba, pabeni,
richardcochran, linux, aleksandr.loktionov, jacob.e.keller,
michal.swiatkowski, kees, joe, leitao, shaojijie, u.kleine-koenig,
weirongguang, larysa.zaremba, fabio.baltieri
In-Reply-To: <20260714091113.1815787-1-horms@kernel.org>
> > diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> > index c8a4822d38194..a65312c8078fa 100644
> > --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> > +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> [ ... ]
> > @@ -3383,5 +3383,23 @@ void wx_service_timer(struct timer_list *t)
> > }
> > EXPORT_SYMBOL(wx_service_timer);
> >
> > +void wx_soft_quiesce(struct wx *wx)
> > +{
> > + if (!netif_running(wx->netdev) ||
> > + test_and_set_bit(WX_STATE_DOWN, wx->state))
> > + return;
> > +
> > + pci_clear_master(wx->pdev);
> > + netif_tx_stop_all_queues(wx->netdev);
> > + netif_carrier_off(wx->netdev);
> > + netif_tx_disable(wx->netdev);
> > + wx_napi_disable_all(wx);
> > + wx_ptp_quiesce(wx);
> > +
> > + clear_bit(WX_FLAG_NEED_PF_RESET, wx->flags);
> > + timer_delete_sync(&wx->service_timer);
>
> [Severity: High]
> The commit message notes the service task is deliberately not cancelled here
> to avoid delaying PCIe error recovery. However, does omitting
> cancel_work_sync(&wx->service_task) open a race window with the subtasks
> that rely on the WX_STATE_DOWN flag?
>
> > +}
> > +EXPORT_SYMBOL(wx_soft_quiesce);
>
> > diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> > index a8773712cff82..a7bde03a98feb 100644
> > --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> > +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> [ ... ]
> > @@ -94,6 +94,10 @@ static void txgbe_module_detection_subtask(struct wx *wx)
> > {
> > int err;
> >
> > + if (test_bit(WX_STATE_DOWN, wx->state) ||
> > + test_bit(WX_STATE_RESETTING, wx->state))
> > + return;
> > +
>
> [Severity: High]
> Does this state check adequately protect against concurrent MMIO access
> during a PCIe reset?
>
> If the task passes this check, it subsequently sleeps for 200ms:
>
> txgbe_main.c:txgbe_module_detection_subtask() {
> ...
> if (!test_and_clear_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags))
> return;
>
> /* wait for SFF module ready */
> msleep(200);
>
> err = txgbe_identify_module(wx);
> ...
> }
>
> If wx_soft_quiesce() runs on another CPU during this msleep(), it will set
> WX_STATE_DOWN and begin a hardware reset.
>
> When the task wakes up from the sleep, will it call txgbe_identify_module()
> and perform MMIO reads (such as rd32(wx, WX_GPIO_EXT)) while the device is
> in reset?
>
> Could this time-of-check to time-of-use race lead to Master Aborts or
> fatal PCIe errors during AER recovery?
>
> > if (!test_and_clear_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags))
> > return;
>
This task is typically triggered during interface bring-up or optical module
insertion. It has a very low coincidence rate with the timing of pcie errors.
Nevertheless, as a defensive measure, I will add a re-check of WX_STATE_DOWN
immediately after the sleep returns.
^ permalink raw reply
* [PATCH net-next 8/9] selftests: net: RFC6724 rule 5.5 tests
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
This throws a couple of situations at the IPv6 source address selection
logic, primarily to check that RFC6724 rule 5.5 is applied as expected.
This requires `CONFIG_IPV6_SUBTREES=y`, so that's added to
selftests/net/config. (The patchset also changes the default on that,
but arguably better to be explicit.)
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
tools/testing/selftests/net/Makefile | 1 +
tools/testing/selftests/net/config | 1 +
.../net/ipv6_saddr_rfc6724rule5p5.py | 231 ++++++++++++++++++
3 files changed, 233 insertions(+)
create mode 100644 tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae07d..c07d2aebad69 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -53,6 +53,7 @@ TEST_PROGS := \
ipv6_flowlabel.sh \
ipv6_force_forwarding.sh \
ipv6_route_update_soft_lockup.sh \
+ ipv6_saddr_rfc6724rule5p5.py \
ipvtap_test.sh \
l2_tos_ttl_inherit.sh \
l2tp.sh \
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index e1ce35c2abbe..b0b66412b9f3 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -48,6 +48,7 @@ CONFIG_IPV6_ROUTER_PREF=y
CONFIG_IPV6_RPL_LWTUNNEL=y
CONFIG_IPV6_SEG6_LWTUNNEL=y
CONFIG_IPV6_SIT=y
+CONFIG_IPV6_SUBTREES=y
CONFIG_IPV6_VTI=y
CONFIG_IPVLAN=m
CONFIG_IPVTAP=m
diff --git a/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py b/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py
new file mode 100644
index 000000000000..682b39ba991d
--- /dev/null
+++ b/tools/testing/selftests/net/ipv6_saddr_rfc6724rule5p5.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2023-2026 David 'equinox' Lamparter
+"""
+RFC 6724 (IPv6 source address selection) rule 5.5 tests
+
+For reference:
+
+ Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
+ If SA or SA's prefix is assigned by the selected next-hop that will
+ be used to send to D and SB or SB's prefix is assigned by a different
+ next-hop, then prefer SA. Similarly, if SB or SB's prefix is
+ assigned by the next-hop that will be used to send to D and SA or
+ SA's prefix is assigned by a different next-hop, then prefer SB.
+
+(and since it provides the "counterpoint":)
+
+ Rule 8: Use longest matching prefix.
+ If CommonPrefixLen(SA, D) > CommonPrefixLen(SB, D), then prefer SA.
+ Similarly, if CommonPrefixLen(SB, D) > CommonPrefixLen(SA, D), then
+ prefer SB.
+
+Note rule 5.5 was originally optional but made mandatory by
+draft-ietf-6man-rfc6724-update (which at the point of creation of this test
+was already "done" at the IETF but waiting in the RFC editor queue due to a
+blocking dependency.)
+"""
+
+from socket import socket, AF_INET6, SOCK_DGRAM
+from functools import wraps
+from typing import Callable
+
+from lib.py import ksft_run, ksft_exit, ksft_eq
+from lib.py import NetNS, NetNSEnter
+from lib.py import ip
+
+
+def select_addr(dest):
+ """
+ connect() + getsockname() to figure out what was selected as source address
+ """
+ sock = socket(AF_INET6, SOCK_DGRAM, 0)
+ sock.connect((dest, 12345))
+ return sock.getsockname()[0]
+
+
+def in_netns(func: Callable[[], None]) -> Callable[[], None]:
+ """
+ python decorator to put test function in netns
+ """
+
+ @wraps(func)
+ def wrapped() -> None:
+ with NetNS() as testns:
+ with NetNSEnter(str(testns)):
+ func()
+
+ return wrapped
+
+
+@in_netns
+def test_basic() -> None:
+ """
+ Simple & most common case for RFC6724 rule 5.5: multiple default routes
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add default via fe80::2 dev veth0 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:10::1", "rule 5.5 > rule 8")
+
+ ip("-6 route del default via fe80::1 dev veth0 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:1000::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:1000::1", "rule 5.5 > rule 8")
+
+
+@in_netns
+def test_nh_obj() -> None:
+ """
+ Same as above, but with nexthop objects for the default route
+
+ NB: The kernel doesn't currently allow nexthop objects for subtree routes.
+ """
+
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+
+ # distinct nexthop objects are used, because what matters is the nexthop
+ # itself, not the nexthop object. To cover everything, make a group.
+ ip("nexthop add id 101 via fe80::1 dev veth0")
+ ip("nexthop add id 201 group 101")
+ ip("nexthop add id 102 via fe80::2 dev veth0")
+ ip("nexthop add id 202 group 102")
+
+ ip("-6 route add default nhid 201 metric 100")
+ ip("-6 route add default nhid 202 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:10::1", "rule 5.5 > rule 8")
+
+ ip("-6 route del default nhid 201 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:1000::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:1000::1", "rule 5.5 > rule 8")
+
+
+@in_netns
+def test_low_metric() -> None:
+ """
+ Check that subtree routes take effect even if they are higher metric
+
+ For checking that "router advertised prefix", metric is irrelevant. It
+ matters for the initial unspecific lookup to find a nexthop to begin with.
+ (The later source address check lookup doesn't change the nexthop, i.e.
+ the effects of metrics are already done.)
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add default via fe80::2 dev veth0 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0 metric 1000")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::3 dev veth0 metric 50")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ # rule 8 would result in the use of the :1001: address, but rule 5.5 applies before.
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:10::1", "rule 5.5 > rule 8")
+
+ ip("-6 route del default via fe80::1 dev veth0 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:1000::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:1001::"), "2001:db8:1000::1", "rule 5.5 > rule 8")
+
+
+@in_netns
+def test_no_subtree() -> None:
+ """
+ Ensure that matching on a non-subtree route doesn't trigger rule 5.5
+
+ (This was non-obviously broken in earlier versions of the implementations,
+ a non-subtree route would still match. Make sure it doesn't break again.)
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add default via fe80::2 dev veth0 metric 200")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(
+ select_addr("2001:db8:1001::"),
+ "2001:db8:10::1",
+ "rule 5.5 > rule 8, ignoring non-SADR",
+ )
+
+
+@in_netns
+def test_longer() -> None:
+ """
+ Check functionality for non-default destination.
+
+ This is expected to be very rare in actual practice, and doesn't do
+ backtracking (also refer to kernel docs.)
+ """
+ ip("link add type veth")
+ ip("link set veth0 up")
+ ip("link set veth1 up")
+ ip("addr add 2001:db8:10::1/64 dev veth0 nodad")
+ ip("addr add 2001:db8:1000::1/64 dev veth0 nodad")
+ ip("-6 route add default via fe80::1 dev veth0 metric 100")
+ ip("-6 route add 2001:db8:500::/48 via fe80::2 dev veth0 metric 100")
+ ip("-6 route add default from 2001:db8:10::/48 via fe80::1 dev veth0")
+ ip("-6 route add 2001:db8:500::/48 from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:500::"), "2001:db8:1000::1", "rule 5.5")
+ ksft_eq(select_addr("2001:db8:500:aaa::"), "2001:db8:1000::1", "rule 5.5")
+
+ ip("-6 route add 2001:db8:500:aaa::/64 via fe80::2 dev veth0 metric 100")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:500::"), "2001:db8:1000::1", "rule 5.5")
+ ksft_eq(select_addr("2001:db8:500:aaa::"), "2001:db8:10::1", "no backtracking")
+
+ ip("-6 route del 2001:db8:500::/48 from 2001:db8:1000::/48 via fe80::2 dev veth0")
+ ip("-6 route add default from 2001:db8:1000::/48 via fe80::2 dev veth0")
+
+ ksft_eq(select_addr("2001:db8:11::"), "2001:db8:10::1", "baseline pass")
+ ksft_eq(select_addr("2001:db8:500::"), "2001:db8:10::1", "no backtracking")
+ ksft_eq(select_addr("2001:db8:500:aaa::"), "2001:db8:10::1", "no backtracking")
+
+
+def main() -> None:
+ """
+ RFC6724 rule 5.5 test driver
+ """
+ ksft_run(
+ [
+ test_basic,
+ test_nh_obj,
+ test_low_metric,
+ test_no_subtree,
+ test_longer,
+ ]
+ )
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 9/9] net: document RFC6724 rule 5.5 implementation
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
RFC6724 rule 5.5 is anything but obvious, especially if trying to do it
well. (RFC8028 and its errata kinda proves the point.)
This documents what exactly the Linux kernel does for RFC6724 rule 5.5,
especially what the routing table needs to look like for it to work.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
Documentation/networking/ipv6-addrsel.rst | 75 +++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 76 insertions(+)
create mode 100644 Documentation/networking/ipv6-addrsel.rst
diff --git a/Documentation/networking/ipv6-addrsel.rst b/Documentation/networking/ipv6-addrsel.rst
new file mode 100644
index 000000000000..bed032e69570
--- /dev/null
+++ b/Documentation/networking/ipv6-addrsel.rst
@@ -0,0 +1,75 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================================
+IPv6 source address selection trivia
+====================================
+
+
+RFC6724 rule 5.5 support
+------------------------
+
+RFC6724 rule 5.5 is a very short paragraph in a complex RFC that has turned
+out quite tricky, but also immensely useful in multihoming scenarios. For
+reference, it says:
+
+::
+
+ Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
+ If SA or SA's prefix is assigned by the selected next-hop that will
+ be used to send to D and SB or SB's prefix is assigned by a different
+ next-hop, then prefer SA. Similarly, if SB or SB's prefix is
+ assigned by the next-hop that will be used to send to D and SA or
+ SA's prefix is assigned by a different next-hop, then prefer SB.
+
+The way this works on Linux is as follows:
+
+- prior to any source address selection happening, when receiving a RA, more
+ than the installation of a default route (or ::/128 route) needs to happen:
+ for each PIO, a source-specific (subtree) route is *additionally* installed.
+ The effect of this is that *after* a source address has been selected, one
+ of the routers that advertised it will remain in use (this is *not* RFC 6724
+ related, but rather RFC 8028.) At the same time, these extra routes serve
+ to remember which router advertised what.
+
+- per usual, a route lookup for the IPv6 destination address in consideration
+ is done first. This is passed around in kernel as a dst_entry.
+
+- the source address selection code iterates through the various rules in
+ RFC 6724.
+
+- if/when rule 5.5 is reached, first of all, there is a check if *any* source
+ specific routes exist in the routing table. If there are none, the entire
+ code for 5.5 is skipped because it cannot have any effect, but is not free
+ to execute (can involve multiple routing lookups.) **In applications that
+ use a lot of unbound (e.g. UDP) sockets, installing subtree routes should
+ therefore be avoided to not incur this cost on each source address selection
+ pass.** Alternatively, applications should bind their sockets to a specific
+ source address such that the selection code is never hit.
+
+- if subtree routes do exist, the source address selection code now repeats
+ the routing lookup done before source address selection is entered, except
+ with the source address under consideration filled in. This lookup will hit
+ the subtree routes that were installed (see first item), giving a fresh
+ dst_entry. If the new dst_entry matches the original dst_entry, that means
+ the original router has in fact sent RAs with PIOs for this source address,
+ so it is preferred. Otherwise it is not.
+
+
+There are a few caveats to consider:
+
+- the kernel currently does not create the subtree routes mentioned in the
+ first item. This is a separate work item, partially done at the time of
+ writing this. But this can equally well be performed in userspace processing
+ of RAs, e.g. NetworkManager or plain static configuration.
+
+- since addresses can also be acquired from DHCPv6, even RA/PIO combinations
+ that didn't result in the creation of any addresses (e.g. A=0) should have
+ subtree routes added. Those routes *may* be relevant for DHCPv6-generated
+ addresses.
+
+- the "announce check" lookup does not backtrack. Only the destination prefix
+ that provided the "unspecific" (::/128) match is checked for source prefixes
+ to see what routers advertised what. This means that for e.g. RIOs, subtree
+ routes also have to be created. (Backtracking for this case would further
+ increase the cost of source address selection, for a pretty rare situation
+ that has an easy fix/workaround.)
diff --git a/MAINTAINERS b/MAINTAINERS
index f3218abefd0c..4edf48362a07 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18912,6 +18912,7 @@ F: Documentation/netlink/specs/rt-addr.yaml
F: Documentation/netlink/specs/rt-neigh.yaml
F: Documentation/netlink/specs/rt-route.yaml
F: Documentation/netlink/specs/rt-rule.yaml
+F: Documentation/networking/ipv6-addrsel.rst
F: include/linux/inetdevice.h
F: include/linux/mroute*
F: include/net/addrconf.h
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 4/9] net/ipv6: use ipv6_fl_get_saddr in output
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
Flatten ip6_route_get_saddr() into ip6_dst_lookup_tail (which really
just means handling fib6_prefsrc), and then replace ipv6_dev_get_saddr
with ipv6_fl_get_saddr to pass down the flow information.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
net/ipv6/ip6_output.c | 25 +++++++++++++++++++------
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 368e4fa3b43c..03b8d42d9df9 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1143,27 +1143,40 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
int flags = 0;
/* The correct way to handle this would be to do
- * ip6_route_get_saddr, and then ip6_route_output; however,
+ * ipv6_fl_get_saddr, and then ip6_route_output; however,
* the route-specific preferred source forces the
- * ip6_route_output call _before_ ip6_route_get_saddr.
+ * ip6_route_output call _before_ ipv6_fl_get_saddr.
*
* In source specific routing (no src=any default route),
* ip6_route_output will fail given src=any saddr, though, so
* that's why we try it again later.
*/
if (ipv6_addr_any(&fl6->saddr)) {
+ struct net_device *l3mdev;
+ struct net_device *dev;
struct fib6_info *from;
struct rt6_info *rt;
+ bool same_vrf;
*dst = ip6_route_output(net, sk, fl6);
rt = (*dst)->error ? NULL : dst_rt6_info(*dst);
rcu_read_lock();
from = rt ? rcu_dereference(rt->from) : NULL;
- err = ip6_route_get_saddr(net, from, &fl6->daddr,
- sk ? READ_ONCE(inet6_sk(sk)->srcprefs) : 0,
- fl6->flowi6_l3mdev,
- &fl6->saddr);
+
+ l3mdev = dev_get_by_index_rcu(net, fl6->flowi6_l3mdev);
+ if (!from || !from->fib6_prefsrc.plen || l3mdev)
+ dev = from ? fib6_info_nh_dev(from) : NULL;
+ same_vrf = !l3mdev || l3mdev_master_dev_rcu(dev) == l3mdev;
+ if (from && from->fib6_prefsrc.plen && same_vrf) {
+ fl6->saddr = from->fib6_prefsrc.addr;
+ err = 0;
+ } else
+ err = ipv6_fl_get_saddr(net, *dst,
+ same_vrf ? dev : l3mdev, sk,
+ sk ? READ_ONCE(inet6_sk(sk)->srcprefs) : 0,
+ fl6);
+
rcu_read_unlock();
if (err)
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 5/9] net/ipv6: drop ip6_route_get_saddr
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
It's no longer used anywhere.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
include/net/ip6_route.h | 26 --------------------------
1 file changed, 26 deletions(-)
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 09ffe0f13ce7..fe13cb1d3257 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -140,32 +140,6 @@ void rt6_flush_exceptions(struct fib6_info *f6i);
void rt6_age_exceptions(struct fib6_info *f6i, struct fib6_gc_args *gc_args,
unsigned long now);
-static inline int ip6_route_get_saddr(struct net *net, struct fib6_info *f6i,
- const struct in6_addr *daddr,
- unsigned int prefs, int l3mdev_index,
- struct in6_addr *saddr)
-{
- struct net_device *l3mdev;
- struct net_device *dev;
- bool same_vrf;
- int err = 0;
-
- rcu_read_lock();
-
- l3mdev = dev_get_by_index_rcu(net, l3mdev_index);
- if (!f6i || !f6i->fib6_prefsrc.plen || l3mdev)
- dev = f6i ? fib6_info_nh_dev(f6i) : NULL;
- same_vrf = !l3mdev || l3mdev_master_dev_rcu(dev) == l3mdev;
- if (f6i && f6i->fib6_prefsrc.plen && same_vrf)
- *saddr = f6i->fib6_prefsrc.addr;
- else
- err = ipv6_dev_get_saddr(net, same_vrf ? dev : l3mdev, daddr, prefs, saddr);
-
- rcu_read_unlock();
-
- return err;
-}
-
struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,
const struct in6_addr *saddr, int oif,
const struct sk_buff *skb, int flags);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 6/9] net/ipv6: flip IPV6_SUBTREES default to Y
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
It's needed for RFC6724 rule 5.5, which as it turns out is more
important than was initially discernible.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
---
net/ipv6/Kconfig | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index c3806c6ac96f..39659e9b53a3 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -243,16 +243,26 @@ config IPV6_MULTIPLE_TABLES
config IPV6_SUBTREES
bool "IPv6: source address based routing"
depends on IPV6_MULTIPLE_TABLES
+ default y
help
Enable routing by source address or prefix.
The destination address is still the primary routing key, so mixing
normal and source prefix specific routes in the same routing table
- may sometimes lead to unintended routing behavior. This can be
- avoided by defining different routing tables for the normal and
- source prefix specific routes.
+ may sometimes lead to unintended routing behavior if the order of
+ lookups is misunderstood. To achieve full separation based on source
+ address, different routing tables should be defined for each source
+ prefix.
- If unsure, say N.
+ Subtrees specifically address scenarios where local connectivity
+ between source prefixes is shared and unrestricted, but some
+ destinations (e.g. the default route) have more fine-grained rules.
+
+ This feature is required to correctly support RFC6724 rule 5.5 in
+ source address selection. Rule 5.5 is a necessity for good
+ multihoming, renumbering and source prefix liveliness checks.
+
+ If unsure, say Y.
config IPV6_MROUTE
bool "IPv6: multicast routing"
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 7/9] net/ipv6: support RFC6724 rule 5.5 via subtrees
From: David 'equinox' Lamparter @ 2026-07-14 9:40 UTC (permalink / raw)
To: Paolo Abeni, Jakub Kicinski, Ido Schimmel
Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
Jonathan Corbet, Shuah Khan, Fernando Fernandez Mancera,
Lorenzo Colitti, Maciej Żenczykowski, Patrick Rohr, netdev,
linux-doc, linux-kselftest, David 'equinox' Lamparter,
Jen Linkova
In-Reply-To: <20260714094030.136317-1-equinox@diac24.net>
RFC6724 says:
Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
If SA or SA's prefix is assigned by the selected next-hop that will
be used to send to D and SB or SB's prefix is assigned by a different
next-hop, then prefer SA. Similarly, if SB or SB's prefix is
assigned by the next-hop that will be used to send to D and SA or
SA's prefix is assigned by a different next-hop, then prefer SB.
As it turns out, this behavior is immensely useful in handling
renumbering, multihoming, and source address liveliness checks.
That said, just implementing this as an one-shot in source address
selection is not particularly useful if output routing then meanders
between multiple nexthops. To actually make this work (and work well),
installing source-specific routes for prefixes seen in PIOs is a good
way to go.
To actually select a source address, there's still the route lookup with
an empty source as before, which will select some particular nexthop.
Now, for the various candidate source addresses, this does another
lookup and checks if we got the same nexthop, and prefers those source
addresses - rule 5.5 behavior.
Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
Cc: Lorenzo Colitti <lorenzo@google.com>
Cc: Patrick Rohr <prohr@google.com>
Cc: Maciej Żenczykowski <maze@google.com>
Cc: Jen Linkova <furry13@gmail.com>
---
net/ipv6/addrconf.c | 101 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 100 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 0ba46ed518a9..f5c04928aa14 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1522,6 +1522,7 @@ enum {
IPV6_SADDR_RULE_HOA,
#endif
IPV6_SADDR_RULE_OIF,
+ IPV6_SADDR_RULE_PIO_BY_NEXTHOP,
IPV6_SADDR_RULE_LABEL,
IPV6_SADDR_RULE_PRIVACY,
IPV6_SADDR_RULE_ORCHID,
@@ -1542,13 +1543,14 @@ struct ipv6_saddr_score {
};
struct ipv6_saddr_dst {
- const struct flowi6 *fl6;
+ struct flowi6 *fl6;
const struct dst_entry *dst;
const struct sock *sk;
int ifindex;
int scope;
int label;
unsigned int prefs;
+ struct net *net;
};
static inline int ipv6_saddr_preferred(int type)
@@ -1593,6 +1595,80 @@ static bool ipv6_allow_optimistic_dad(const struct net *net,
#endif
}
+/* "source address is preferable if the chosen nexthop advertised it as a PIO"
+ * => consider 'advertised as a PIO' to be 'the routes for the source's subtree
+ * include one with the same nexthop'
+ *
+ * NB: there is no backtracking in the subtree here, this is intentional -
+ * each prefix seen (and accepted) in PIOs creates essentially a "zone" which
+ * is our search scope.
+ */
+static int ipv6_saddr_rule5p5(struct ipv6_saddr_score *score,
+ struct ipv6_saddr_dst *saddr_dst)
+{
+ const struct rt6_info *rt, *cmp_rt;
+ struct dst_entry *cmp_dst;
+ struct fib6_info *f6i;
+ int ret = 0;
+
+ rt = container_of(saddr_dst->dst, struct rt6_info, dst);
+
+ /* fl6->saddr is ::, cf. check at the top of ipv6_common_get_saddr() */
+ saddr_dst->fl6->saddr = score->ifa->addr;
+ cmp_dst = ip6_route_output(saddr_dst->net, saddr_dst->sk,
+ saddr_dst->fl6);
+ memset(&saddr_dst->fl6->saddr, 0, sizeof(saddr_dst->fl6->saddr));
+
+ if (cmp_dst->error)
+ goto out_release_dst;
+
+ cmp_rt = container_of(cmp_dst, struct rt6_info, dst);
+
+ /* this must work if _any_ nexthop matches; the non-subtree best may
+ * not be in same order as subtree best
+ */
+ for (f6i = rcu_dereference(cmp_rt->from); f6i;
+ f6i = rcu_dereference(f6i->fib6_next)) {
+ struct fib6_nh *f6n = f6i->fib6_nh;
+ struct fib6_info *sibling;
+
+ /* non-subtree route: says nothing about router advertising this source */
+ if (f6i->fib6_src.plen == 0)
+ continue;
+
+ if (f6n->nh_common.nhc_dev != saddr_dst->dst->dev ||
+ f6n->nh_common.nhc_gw_family != AF_INET6)
+ continue;
+
+ if (ipv6_addr_equal(&f6n->nh_common.nhc_gw.ipv6,
+ &rt->rt6i_gateway)) {
+ ret = 1;
+ goto out_release_dst;
+ }
+
+ if (!f6i->fib6_nsiblings)
+ continue;
+
+ list_for_each_entry(sibling, &f6i->fib6_siblings, fib6_siblings) {
+ f6n = sibling->fib6_nh;
+
+ if (f6n->nh_common.nhc_dev != saddr_dst->dst->dev ||
+ f6n->nh_common.nhc_gw_family != AF_INET6)
+ continue;
+
+ if (ipv6_addr_equal(&f6n->nh_common.nhc_gw.ipv6,
+ &rt->rt6i_gateway)) {
+ ret = 1;
+ goto out_release_dst;
+ }
+ }
+ }
+
+out_release_dst:
+ dst_release(cmp_dst);
+ return ret;
+}
+
static int ipv6_get_saddr_eval(struct net *net,
struct ipv6_saddr_score *score,
struct ipv6_saddr_dst *dst,
@@ -1677,6 +1753,24 @@ static int ipv6_get_saddr_eval(struct net *net,
ret = (!dst->ifindex ||
dst->ifindex == score->ifa->idev->dev->ifindex);
break;
+ case IPV6_SADDR_RULE_PIO_BY_NEXTHOP:
+ /* Rule 5.5: Prefer sources advertised by chosen next-hop */
+
+ /* Without subtrees, the source address will make no difference
+ * in the ip6_route_output call in rule5p5. Therefore the rule
+ * 5.5 check becomes useless. This wouldn't result in any
+ * errors, but ip6_route_output isn't free, so if subtrees are
+ * disabled save some cycles by skipping this entirely.
+ *
+ * This is done through subtrees_enabled to have the code
+ * compiled regardless.
+ */
+ if (fib6_routes_require_src(net) && dst->dst
+ && !dst->dst->error)
+ ret = ipv6_saddr_rule5p5(score, dst);
+ else
+ ret = 1;
+ break;
case IPV6_SADDR_RULE_LABEL:
/* Rule 6: Prefer matching label */
ret = ipv6_addr_label(net,
@@ -1835,7 +1929,12 @@ int ipv6_fl_get_saddr(struct net *net, const struct dst_entry *dst_entry,
int hiscore_idx = 0;
int ret = 0;
+ /* we should never end up here with a non-empty saddr. */
+ if (WARN_ON_ONCE(!ipv6_addr_any(&fl6->saddr)))
+ return 0;
+
dst_type = __ipv6_addr_type(&fl6->daddr);
+ dst.net = net;
dst.fl6 = fl6;
dst.sk = sk;
dst.dst = dst_entry;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net-next v2] mptcp: sockopt: implement IPV6_TCLASS
From: David 'equinox' Lamparter @ 2026-07-14 9:48 UTC (permalink / raw)
To: Geliang Tang; +Cc: Matthieu Baerts, Mat Martineau, netdev, mptcp
In-Reply-To: <c3b017e1c96cdafce15012e737389108d7cbf8f6.camel@kernel.org>
On Tue, Jul 14, 2026 at 10:22:12AM +0800, Geliang Tang wrote:
> Thanks for your patch. However there is already a version under review
> on the MPTCP mailing list that adds IPV6_TCLASS [1] (as well as IP_TTL
> and IPV6_UNICAST_HOPS).
>
> https://patchwork.kernel.org/project/mptcp/patch/2617b8684039574734b8622936ef126d6a7cd519.1754986785.git.tanggeliang@kylinos.cn/
Darn, I looked for patches for this & didn't find any, but I've also
been carrying this locally for a bit.
Anything I can do to help get your patch moving?
-equi
^ permalink raw reply
* Re: [PATCH net-next v2] mptcp: sockopt: implement IPV6_TCLASS
From: Matthieu Baerts @ 2026-07-14 9:53 UTC (permalink / raw)
To: David 'equinox' Lamparter, Geliang Tang
Cc: Mat Martineau, netdev, mptcp
In-Reply-To: <alYF-78I4LUaePGo@eidolon.nox.tf>
Hi David, Geliang,
On 14/07/2026 11:48, David 'equinox' Lamparter wrote:
> On Tue, Jul 14, 2026 at 10:22:12AM +0800, Geliang Tang wrote:
>> Thanks for your patch. However there is already a version under review
>> on the MPTCP mailing list that adds IPV6_TCLASS [1] (as well as IP_TTL
>> and IPV6_UNICAST_HOPS).
>>
>> https://patchwork.kernel.org/project/mptcp/patch/2617b8684039574734b8622936ef126d6a7cd519.1754986785.git.tanggeliang@kylinos.cn/
>
> Darn, I looked for patches for this & didn't find any, but I've also
> been carrying this locally for a bit.
>
> Anything I can do to help get your patch moving?
I was hoping to do some refactoring around Geliang's code, but I didn't
manage to do that so far. I will to see if it is fine to apply Geliang's
patch (after netdevconf) and do the refactoring after.
@Geliang: do you mind rebasing this series, please? No hurry, I can also
check later on. (And sorry for the delay on that)
Cheers,
Matt
--
Sponsored by the NGI0 Core fund.
^ permalink raw reply
* Re: [PATCH net] nexthop: initialize extack in nh_res_bucket_migrate()
From: Ido Schimmel @ 2026-07-14 10:44 UTC (permalink / raw)
To: Xiang Mei (Microsoft)
Cc: David Ahern, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Petr Machata, netdev, linux-kernel,
AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <20260713221551.3344650-1-xmei5@asu.edu>
On Mon, Jul 13, 2026 at 10:15:51PM +0000, Xiang Mei (Microsoft) wrote:
> nh_res_bucket_migrate() passes an uninitialized netlink_ext_ack to
> call_nexthop_res_bucket_notifiers(). When
> nh_notifier_res_bucket_info_init() fails (e.g. the kzalloc returns
> -ENOMEM), the error is propagated back before any notifier sets
> extack._msg, and the error path formats the stale pointer with
> pr_err_ratelimited("%s\n", extack._msg). With CONFIG_INIT_STACK_NONE
> this dereferences uninitialized stack memory:
>
> Oops: general protection fault, probably for non-canonical address ...
> KASAN: maybe wild-memory-access in range [...]
> RIP: 0010:string (lib/vsprintf.c:730)
> vsnprintf (lib/vsprintf.c:2945)
> _printk (kernel/printk/printk.c:2504)
> nh_res_bucket_migrate (net/ipv4/nexthop.c:1816)
> nh_res_table_upkeep (net/ipv4/nexthop.c:1866)
> rtm_new_nexthop (net/ipv4/nexthop.c:3323)
> rtnetlink_rcv_msg (net/core/rtnetlink.c:7076)
> netlink_sendmsg (net/netlink/af_netlink.c:1900)
> Kernel panic - not syncing: Fatal exception
>
> Zero-initialize extack so _msg is NULL on error paths that never set it.
>
> Fixes: 7c37c7e00411 ("nexthop: Implement notifiers for resilient nexthop groups")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
It's very unlikely that nh_notifier_res_bucket_info_init() will fail. I
assume that fault injection was used.
remove_nh_grp_entry() also doesn't initialize extack, but
call_nexthop_notifiers() is using NL_SET_ERR_MSG(). Still, the same
problem can happen if a listener is returning an error without setting
extack. Please send a separate patch (targeted at net-next, no Fixes
tag) to make remove_nh_grp_entry() consistent with
nh_res_bucket_migrate().
^ permalink raw reply
* [RFC net-next 0/2] hsr: Use only one MAC address per node
From: Felix Maurer @ 2026-07-14 10:52 UTC (permalink / raw)
To: netdev, davem, edumazet, kuba, pabeni, horms
Cc: bigeasy, fmancera, liuhangbin, luka.gejak, xiaoliang.yang_1,
kexinsun, ssrane_b23, michael.bommarito, 2022090917019,
yury.norov, jvaclav, maoyixie.tju
Many places in the hsr module assumed that a single node could be using
multiple MAC addresses to communicate in the network. The standard is
explicit that PRP nodes should use the same MAC for frames on both
ports and commit b65999e7238e ("net: hsr: sync hw addr of slave2
according to slave1 hw addr on PRP") made this clear. For HSR, the
standard is less explicit. But after quite some discussions and reading
standards, Fernando, Sebastian, and I concluded that it never mentions
different MAC addresses for HSR either and instead often suggests using
equal addresses for both ports and this should be what hsr interfaces
do.
A short history of how this assumption formed in the kernel supports
this as well: the original HSRv0 code implemented IEC 62439-3:2010 where
node tables had two MAC addresses per node. It was an optional feature
to support an unspecified address substitution mechanism for _PRP_, also
referred to as PICS_SUBS. Note that we never even supported PRPv0 from
the :2010 standard. In IEC 62439-3:2012, the feature was explicitly
removed. But with two addresses per node in the node table and selftests
setting different addresses for both interfaces, the assumption emerged
that all nodes can have two addresses. In :2010, this was optional and
the standard is written so that a node not supporting PICS_SUBS could
just ignore it. Since 2012:, nodes must use the same address for both
ports in the ring.
To prevent misconfiguration and simplify the hsr code, this patchset
removes the notion of two different MAC addresses for one node in the
network entirely. The first patch sets equal addresses on both ports so
that we are not running in invalid configurations. I also updates the
selftest to not use/expect different addresses on the two ports. The
second patch removes MAC address B from the node table and thereby
eliminates a lot of code, including the node merging.
I am posting as an RFC for now mostly for two reasons: First, I want to
make sure it's generally accepted to have only a single MAC address per
node and give everyone the chance to speak up against this.
Second, I adapted the PRP address handling to HSR as well, i.e., to copy
the address from port A to port B and master, mostly because it is low
effort. I am not sure though if this is the best approach now that we
touch this part of the code anyways. I have two alternative ideas how
addresses should be handled:
1) Make the master control the port MAC addresses: when the hsr
interface is created, generate a MAC address and assign it to both
ports. Changing the address afterwards would also only go through the
hsr interface which would update both ports.
2) Don't change the MAC addresses of the port interfaces at all. Instead
behave similar to bridge where the ports keep their addresses and
traffic from the bridge/master gets its own MAC address assigned.
What do you think? Do you prefer any of these approaches?
Thanks,
Felix
Cc: Fernando Fernandez Mancera <fmancera@suse.de>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Felix Maurer (2):
hsr: Set equal MAC addresses on port A and B for HSR
hsr: Remove second MAC address from node table
net/hsr/hsr_debugfs.c | 9 +-
net/hsr/hsr_device.c | 23 +-
net/hsr/hsr_forward.c | 8 +-
net/hsr/hsr_framereg.c | 250 ++----------------
net/hsr/hsr_framereg.h | 14 +-
net/hsr/hsr_main.c | 18 +-
net/hsr/hsr_main.h | 5 +-
net/hsr/hsr_netlink.c | 18 +-
tools/testing/selftests/net/hsr/hsr_ping.sh | 33 +--
tools/testing/selftests/net/hsr/hsr_redbox.sh | 13 +-
.../testing/selftests/net/hsr/link_faults.sh | 26 --
11 files changed, 62 insertions(+), 355 deletions(-)
base-commit: f6f3b36c15ed44de1fbb44e645e4fae8c4a4453e
--
2.55.0
^ permalink raw reply
* [RFC net-next 1/2] hsr: Set equal MAC addresses on port A and B for HSR
From: Felix Maurer @ 2026-07-14 10:52 UTC (permalink / raw)
To: netdev, davem, edumazet, kuba, pabeni, horms
Cc: bigeasy, fmancera, liuhangbin, luka.gejak, xiaoliang.yang_1,
kexinsun, ssrane_b23, michael.bommarito, 2022090917019,
yury.norov, jvaclav, maoyixie.tju
In-Reply-To: <cover.1784024900.git.fmaurer@redhat.com>
All hsr interfaces are created with their MAC address set to the MAC
address of port A. Since commit b65999e7238e ("net: hsr: sync hw addr of
slave2 according to slave1 hw addr on PRP"), we are copying the MAC address
of port A to port B as well for PRP interfaces because for correct
operation, a node in a PRP network must use the same MAC address on both
ports. IOW, the frames sent over port A and port B must only differ in the
LAN ID in their PRP redundancy control trailer, i.e., must not differ in
their source MAC address.
The same applies to HSR: for correct operation of the ring, the frames sent
from port A and port B must not differ in their source MAC address.
Therefore, extend the address copying behavior from PRP to HSR interfaces.
In the selftests, we now set only the addresses of port A which is how HSR
is intended to be used. The address is copied over to port B anyways. With
only a single address per node, there is also no reason anymore to wait for
nodes in the table to get merged. While touching the MAC addresses in the
hsr_redbox test, I noticed that the bridge in ns3 and its ports have the
same addresses; fixing that as well.
Signed-off-by: Felix Maurer <fmaurer@redhat.com>
---
net/hsr/hsr_device.c | 6 ++--
net/hsr/hsr_main.c | 12 +++----
tools/testing/selftests/net/hsr/hsr_ping.sh | 33 +++----------------
tools/testing/selftests/net/hsr/hsr_redbox.sh | 13 +++-----
.../testing/selftests/net/hsr/link_faults.sh | 26 ---------------
5 files changed, 16 insertions(+), 74 deletions(-)
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index 5555b71ab19b..a52079eb2adb 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -795,10 +795,8 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2],
if (res)
goto err_unregister;
- if (protocol_version == PRP_V1) {
- eth_hw_addr_set(slave[1], slave[0]->dev_addr);
- call_netdevice_notifiers(NETDEV_CHANGEADDR, slave[1]);
- }
+ eth_hw_addr_set(slave[1], slave[0]->dev_addr);
+ call_netdevice_notifiers(NETDEV_CHANGEADDR, slave[1]);
if (interlink) {
res = hsr_add_port(hsr, interlink, HSR_PT_INTERLINK, extack);
diff --git a/net/hsr/hsr_main.c b/net/hsr/hsr_main.c
index 33951d9bd3c5..202cf2a3625f 100644
--- a/net/hsr/hsr_main.c
+++ b/net/hsr/hsr_main.c
@@ -79,13 +79,11 @@ static int hsr_netdev_notify(struct notifier_block *nb, unsigned long event,
call_netdevice_notifiers(NETDEV_CHANGEADDR,
master->dev);
- if (hsr->prot_version == PRP_V1) {
- port = hsr_port_get_hsr(hsr, HSR_PT_SLAVE_B);
- if (port) {
- eth_hw_addr_set(port->dev, dev->dev_addr);
- call_netdevice_notifiers(NETDEV_CHANGEADDR,
- port->dev);
- }
+ port = hsr_port_get_hsr(hsr, HSR_PT_SLAVE_B);
+ if (port) {
+ eth_hw_addr_set(port->dev, dev->dev_addr);
+ call_netdevice_notifiers(NETDEV_CHANGEADDR,
+ port->dev);
}
}
diff --git a/tools/testing/selftests/net/hsr/hsr_ping.sh b/tools/testing/selftests/net/hsr/hsr_ping.sh
index f4d685df4345..454c6fdfd834 100755
--- a/tools/testing/selftests/net/hsr/hsr_ping.sh
+++ b/tools/testing/selftests/net/hsr/hsr_ping.sh
@@ -51,25 +51,6 @@ do_ping_tests()
do_ping "$ns3" "dead:beef:$netid::2"
stop_if_error "Initial validation failed on IPv6."
-# Wait until supervisor all supervision frames have been processed and the node
-# entries have been merged. Otherwise duplicate frames will be observed which is
-# valid at this stage.
- echo "INFO: Wait for node table entries to be merged."
- WAIT=5
- while [ ${WAIT} -gt 0 ]
- do
- grep 00:00:00:00:00:00 /sys/kernel/debug/hsr/hsr*/node_table
- if [ $? -ne 0 ]
- then
- break
- fi
- sleep 1
- let "WAIT = WAIT - 1"
- done
-
-# Just a safety delay in case the above check didn't handle it.
- sleep 1
-
echo "INFO: Longer ping test."
do_ping_long "$ns1" "100.64.$netid.2"
do_ping_long "$ns1" "dead:beef:$netid::2"
@@ -110,6 +91,11 @@ setup_hsr_interfaces()
ip link add ns1eth2 netns "$ns1" type veth peer name ns3eth1 netns "$ns3"
ip link add ns3eth2 netns "$ns3" type veth peer name ns2eth2 netns "$ns2"
+ # MAC addresses will be copied from LAN A interfaces
+ ip -net "$ns1" link set address 00:11:22:00:01:01 dev ns1eth1
+ ip -net "$ns2" link set address 00:11:22:00:02:01 dev ns2eth1
+ ip -net "$ns3" link set address 00:11:22:00:03:01 dev ns3eth1
+
# HSRv0/1
ip -net "$ns1" link add name hsr1 type hsr slave1 ns1eth1 \
slave2 ns1eth2 supervision 45 version "$HSRv" proto 0
@@ -126,15 +112,6 @@ setup_hsr_interfaces()
ip -net "$ns3" addr add 100.64.0.3/24 dev hsr3
ip -net "$ns3" addr add dead:beef:0::3/64 dev hsr3 nodad
- ip -net "$ns1" link set address 00:11:22:00:01:01 dev ns1eth1
- ip -net "$ns1" link set address 00:11:22:00:01:02 dev ns1eth2
-
- ip -net "$ns2" link set address 00:11:22:00:02:01 dev ns2eth1
- ip -net "$ns2" link set address 00:11:22:00:02:02 dev ns2eth2
-
- ip -net "$ns3" link set address 00:11:22:00:03:01 dev ns3eth1
- ip -net "$ns3" link set address 00:11:22:00:03:02 dev ns3eth2
-
# All Links up
ip -net "$ns1" link set ns1eth1 up
ip -net "$ns1" link set ns1eth2 up
diff --git a/tools/testing/selftests/net/hsr/hsr_redbox.sh b/tools/testing/selftests/net/hsr/hsr_redbox.sh
index 998103502d5d..f945d7d82fa1 100755
--- a/tools/testing/selftests/net/hsr/hsr_redbox.sh
+++ b/tools/testing/selftests/net/hsr/hsr_redbox.sh
@@ -23,10 +23,6 @@ do_complete_ping_test()
do_ping "${ns5}" 100.64.0.1
stop_if_error "Initial validation failed."
- # Wait for MGNT HSR frames being received and nodes being
- # merged.
- sleep 5
-
echo "INFO: Longer ping test (HSR-SAN/RedBox)."
# Ping from SAN to hsr1 (via hsr2)
do_ping_long "${ns3}" 100.64.0.1
@@ -96,16 +92,15 @@ setup_hsr_interfaces()
ip -n "${ns4}" link set ns4eth1 up
ip -n "${ns5}" link set ns5eth1 up
+ # Setting MAC addresses to help with debugging
ip -net "$ns1" link set address 00:11:22:00:01:01 dev ns1eth1
- ip -net "$ns1" link set address 00:11:22:00:01:02 dev ns1eth2
ip -net "$ns2" link set address 00:11:22:00:02:01 dev ns2eth1
- ip -net "$ns2" link set address 00:11:22:00:02:02 dev ns2eth2
ip -net "$ns2" link set address 00:11:22:00:02:03 dev ns2eth3
- ip -net "$ns3" link set address 00:11:22:00:03:11 dev ns3eth1
- ip -net "$ns3" link set address 00:11:22:00:03:11 dev ns3eth2
- ip -net "$ns3" link set address 00:11:22:00:03:11 dev ns3eth3
+ ip -net "$ns3" link set address 00:11:22:00:03:01 dev ns3eth1
+ ip -net "$ns3" link set address 00:11:22:00:03:02 dev ns3eth2
+ ip -net "$ns3" link set address 00:11:22:00:03:03 dev ns3eth3
ip -net "$ns3" link set address 00:11:22:00:03:11 dev ns3br1
ip -net "$ns4" link set address 00:11:22:00:04:01 dev ns4eth1
diff --git a/tools/testing/selftests/net/hsr/link_faults.sh b/tools/testing/selftests/net/hsr/link_faults.sh
index be526281571c..c002390acc07 100755
--- a/tools/testing/selftests/net/hsr/link_faults.sh
+++ b/tools/testing/selftests/net/hsr/link_faults.sh
@@ -59,13 +59,8 @@ setup_hsr_topo()
# MAC addresses (not needed for HSR operation, but helps with debugging)
ip -net "$node1" link set address 00:11:22:00:01:01 dev vethA
- ip -net "$node1" link set address 00:11:22:00:01:02 dev vethB
-
ip -net "$node2" link set address 00:11:22:00:02:01 dev vethA
- ip -net "$node2" link set address 00:11:22:00:02:02 dev vethB
-
ip -net "$node3" link set address 00:11:22:00:03:01 dev vethA
- ip -net "$node3" link set address 00:11:22:00:03:02 dev vethB
# HSR interfaces
ip -net "$node1" link add name hsr1 type hsr proto 0 version "$ver" \
@@ -134,35 +129,14 @@ setup_prp_topo()
ip -net "$node2" link set prp2 up
}
-wait_for_hsr_node_table()
-{
- log_info "Wait for node table entries to be merged."
- WAIT=5
- while [ "${WAIT}" -gt 0 ]; do
- nts=$(cat /sys/kernel/debug/hsr/hsr*/node_table)
-
- # We need entries in the node tables, and they need to be merged
- if (echo "$nts" | grep -qE "^([0-9a-f]{2}:){5}") && \
- ! (echo "$nts" | grep -q "00:00:00:00:00:00"); then
- return
- fi
-
- sleep 1
- ((WAIT--))
- done
- check_err 1 "Failed to wait for merged node table entries"
-}
-
setup_topo()
{
local proto="$1"
if [ "$proto" = "HSRv0" ]; then
setup_hsr_topo 0
- wait_for_hsr_node_table
elif [ "$proto" = "HSRv1" ]; then
setup_hsr_topo 1
- wait_for_hsr_node_table
elif [ "$proto" = "PRP" ]; then
setup_prp_topo
else
--
2.55.0
^ permalink raw reply related
* [RFC net-next 2/2] hsr: Remove second MAC address from node table
From: Felix Maurer @ 2026-07-14 10:52 UTC (permalink / raw)
To: netdev, davem, edumazet, kuba, pabeni, horms
Cc: bigeasy, fmancera, liuhangbin, luka.gejak, xiaoliang.yang_1,
kexinsun, ssrane_b23, michael.bommarito, 2022090917019,
yury.norov, jvaclav, maoyixie.tju
In-Reply-To: <cover.1784024900.git.fmaurer@redhat.com>
Since its creation, the hsr module supported two MAC addresses per node in
the node table. Presumably, this was initially implemented to support an
address substitution mechanism for PRP. The IEC 62439-3:2010 calls this
property PICS_SUBS and it is optional to implement:
"Some applications require different MAC addresses on the redundant ports,
and these MAC addresses may be different from the default MAC address of
that node. This involves address substitution mechanisms which are not
specified in this International Standard. However, the basic protocol and
the frame format are prepared for such extension." (IEC 62439-3:2010,
4.1.7)
Note that this is from IEC 62439-3:2010, which standardizes PRPv0 which we
do not support at all. IEC 62439-3:2012 explicitly removed this address
substitution mechanism completely, leaving only a single MAC address per
node in the node table.
While the initial implementation aimed at supporting PICS_SUBS, this
developed into a situation where a lot of the code in the hsr module
assumed that nodes can/should have different MAC addresses on their two
ports. This is not the case.
Therefore, remove all references to MAC address B and concepts depending on
it (like the addr_B_port, i.e., the port where the second MAC address
should be used; and notably the address A terminology). This allows to get
rid of the node merging code as well. As the supervision frame parsing was
mostly superficial afterwards and we don't do anything with the data, it is
removed as well. Proper parsing of supervision frames can be added at a
later point again (probably after reworking the whole rx path a bit).
Signed-off-by: Felix Maurer <fmaurer@redhat.com>
---
net/hsr/hsr_debugfs.c | 9 +-
net/hsr/hsr_device.c | 17 ++-
net/hsr/hsr_forward.c | 8 +-
net/hsr/hsr_framereg.c | 250 +++++------------------------------------
net/hsr/hsr_framereg.h | 14 +--
net/hsr/hsr_main.c | 6 +-
net/hsr/hsr_main.h | 5 +-
net/hsr/hsr_netlink.c | 18 +--
8 files changed, 46 insertions(+), 281 deletions(-)
diff --git a/net/hsr/hsr_debugfs.c b/net/hsr/hsr_debugfs.c
index 5b2cfac3b2ba..c6dcc9cdddee 100644
--- a/net/hsr/hsr_debugfs.c
+++ b/net/hsr/hsr_debugfs.c
@@ -23,8 +23,7 @@ hsr_node_table_show(struct seq_file *sfp, void *data)
seq_printf(sfp, "Node Table entries for (%s) device\n",
(priv->prot_version == PRP_V1 ? "PRP" : "HSR"));
- seq_puts(sfp, "MAC-Address-A, MAC-Address-B, time_in[A], ");
- seq_puts(sfp, "time_in[B], Address-B port, ");
+ seq_puts(sfp, "MAC-Address-A, time_in[A], time_in[B], ");
if (priv->prot_version == PRP_V1)
seq_puts(sfp, "SAN-A, SAN-B, DAN-P\n");
else
@@ -33,13 +32,11 @@ hsr_node_table_show(struct seq_file *sfp, void *data)
rcu_read_lock();
list_for_each_entry_rcu(node, &priv->node_db, mac_list) {
/* skip self node */
- if (hsr_addr_is_self(priv, node->macaddress_A))
+ if (hsr_addr_is_self(priv, node->macaddress))
continue;
- seq_printf(sfp, "%pM ", &node->macaddress_A[0]);
- seq_printf(sfp, "%pM ", &node->macaddress_B[0]);
+ seq_printf(sfp, "%pM ", &node->macaddress[0]);
seq_printf(sfp, "%10lx, ", node->time_in[HSR_PT_SLAVE_A]);
seq_printf(sfp, "%10lx, ", node->time_in[HSR_PT_SLAVE_B]);
- seq_printf(sfp, "%14x, ", node->addr_B_port);
if (priv->prot_version == PRP_V1)
seq_printf(sfp, "%5x, %5x, %5x\n",
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index a52079eb2adb..35457016a841 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -341,9 +341,9 @@ static void send_hsr_supervision_frame(struct hsr_port *port,
hsr_stag->tlv.HSR_TLV_length = hsr->prot_version ?
sizeof(struct hsr_sup_payload) : 12;
- /* Payload: MacAddressA / SAN MAC from ProxyNodeTable */
+ /* Payload: MacAddress / SAN MAC from ProxyNodeTable */
hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
- ether_addr_copy(hsr_sp->macaddress_A, addr);
+ ether_addr_copy(hsr_sp->macaddress, addr);
if (hsr->redbox &&
hsr_is_node_in_db(&hsr->proxy_node_db, addr)) {
@@ -353,7 +353,7 @@ static void send_hsr_supervision_frame(struct hsr_port *port,
/* Payload: MacAddressRedBox */
hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
- ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox);
+ ether_addr_copy(hsr_sp->macaddress, hsr->macaddress_redbox);
}
if (skb_put_padto(skb, ETH_ZLEN)) {
@@ -393,9 +393,9 @@ 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: MacAddress */
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, master->dev->dev_addr);
if (skb_put_padto(skb, ETH_ZLEN)) {
spin_unlock_bh(&hsr->seqnr_lock);
@@ -445,10 +445,10 @@ static void hsr_proxy_announce(struct timer_list *t)
goto done;
list_for_each_entry_rcu(node, &hsr->proxy_node_db, mac_list) {
- if (hsr_addr_is_redbox(hsr, node->macaddress_A))
+ if (hsr_addr_is_redbox(hsr, node->macaddress))
continue;
hsr->proto_ops->send_sv_frame(interlink, &interval,
- node->macaddress_A);
+ node->macaddress);
}
if (is_admin_up(interlink->dev)) {
@@ -745,8 +745,7 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2],
}
/* Make sure we recognize frames from ourselves in hsr_handle_frame() */
- res = hsr_create_self_node(hsr, hsr_dev->dev_addr,
- slave[1]->dev_addr);
+ res = hsr_create_self_node(hsr, hsr_dev->dev_addr);
if (res < 0)
return res;
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a65c1..96dd01b9ee82 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -28,9 +28,6 @@ struct hsr_node;
* 2) Use the LifeCheck frames to detect ring breaks. I.e. if no LifeCheck
* frame is received from a particular node, we know something is wrong.
* We just register these (as with normal frames) and throw them away.
- *
- * 3) Allow different MAC addresses for the two slave interfaces, using the
- * MacAddressA field.
*/
static bool is_supervision_frame(struct hsr_priv *hsr, struct sk_buff *skb)
{
@@ -143,7 +140,7 @@ static bool is_proxy_supervision_frame(struct hsr_priv *hsr,
* frame with MAC addresses from own ProxyNodeTable.
*/
return hsr_is_node_in_db(&hsr->proxy_node_db,
- payload->macaddress_A);
+ payload->macaddress);
}
static struct sk_buff *create_stripped_skb_hsr(struct sk_buff *skb_in,
@@ -403,7 +400,6 @@ static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,
int res, recv_len;
was_multicast_frame = (skb->pkt_type == PACKET_MULTICAST);
- hsr_addr_subst_source(node_src, skb);
skb_pull(skb, ETH_HLEN);
recv_len = skb->len;
res = netif_rx(skb);
@@ -421,8 +417,6 @@ static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,
struct hsr_frame_info *frame)
{
if (frame->port_rcv->type == HSR_PT_MASTER) {
- hsr_addr_subst_dest(frame->node_src, skb, port);
-
/* Address substitution (IEC62439-3 pp 26, 50): replace mac
* address of outgoing frame with that of the outgoing slave's.
*/
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index e44929871274..12c2b6c4bd66 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -38,9 +38,7 @@ bool hsr_addr_is_self(struct hsr_priv *hsr, unsigned char *addr)
if (!sn)
goto out;
- if (ether_addr_equal(addr, sn->macaddress_A) ||
- ether_addr_equal(addr, sn->macaddress_B))
- ret = true;
+ ret = ether_addr_equal(addr, sn->macaddress);
out:
rcu_read_unlock();
return ret;
@@ -48,13 +46,13 @@ bool hsr_addr_is_self(struct hsr_priv *hsr, unsigned char *addr)
/* Search for mac entry. Caller must hold rcu read lock.
*/
-static struct hsr_node *find_node_by_addr_A(struct list_head *node_db,
- const unsigned char addr[ETH_ALEN])
+static struct hsr_node *find_node_by_addr(struct list_head *node_db,
+ const unsigned char addr[ETH_ALEN])
{
struct hsr_node *node;
list_for_each_entry_rcu(node, node_db, mac_list) {
- if (ether_addr_equal(node->macaddress_A, addr))
+ if (ether_addr_equal(node->macaddress, addr))
return node;
}
@@ -66,15 +64,14 @@ static struct hsr_node *find_node_by_addr_A(struct list_head *node_db,
bool hsr_is_node_in_db(struct list_head *node_db,
const unsigned char addr[ETH_ALEN])
{
- return !!find_node_by_addr_A(node_db, addr);
+ return !!find_node_by_addr(node_db, addr);
}
/* Helper for device init; the self_node is used in hsr_handle_frame() to
* recognize frames from self that's been looped over the HSR ring.
*/
int hsr_create_self_node(struct hsr_priv *hsr,
- const unsigned char addr_a[ETH_ALEN],
- const unsigned char addr_b[ETH_ALEN])
+ const unsigned char addr[ETH_ALEN])
{
struct hsr_self_node *sn, *old;
@@ -82,8 +79,7 @@ int hsr_create_self_node(struct hsr_priv *hsr,
if (!sn)
return -ENOMEM;
- ether_addr_copy(sn->macaddress_A, addr_a);
- ether_addr_copy(sn->macaddress_B, addr_b);
+ ether_addr_copy(sn->macaddress, addr);
spin_lock_bh(&hsr->list_lock);
old = rcu_replace_pointer(hsr->self_node, sn,
@@ -121,40 +117,6 @@ static void hsr_free_node_rcu(struct rcu_head *rn)
hsr_free_node(node);
}
-static void hsr_lock_seq_out_pair(struct hsr_node *node_a,
- struct hsr_node *node_b)
-{
- if (node_a == node_b) {
- spin_lock_bh(&node_a->seq_out_lock);
- return;
- }
-
- if (node_a < node_b) {
- spin_lock_bh(&node_a->seq_out_lock);
- spin_lock_nested(&node_b->seq_out_lock, SINGLE_DEPTH_NESTING);
- } else {
- spin_lock_bh(&node_b->seq_out_lock);
- spin_lock_nested(&node_a->seq_out_lock, SINGLE_DEPTH_NESTING);
- }
-}
-
-static void hsr_unlock_seq_out_pair(struct hsr_node *node_a,
- struct hsr_node *node_b)
-{
- if (node_a == node_b) {
- spin_unlock_bh(&node_a->seq_out_lock);
- return;
- }
-
- if (node_a < node_b) {
- spin_unlock(&node_b->seq_out_lock);
- spin_unlock_bh(&node_a->seq_out_lock);
- } else {
- spin_unlock(&node_a->seq_out_lock);
- spin_unlock_bh(&node_b->seq_out_lock);
- }
-}
-
void hsr_del_nodes(struct list_head *node_db)
{
struct hsr_node *node;
@@ -179,7 +141,7 @@ void prp_handle_san_frame(bool san, enum hsr_port_type port,
node->san_b = true;
}
-/* Allocate an hsr_node and add it to node_db. 'addr' is the node's address_A.
+/* Allocate an hsr_node and add it to node_db.
*/
static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
struct list_head *node_db,
@@ -195,7 +157,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
if (!new_node)
return NULL;
- ether_addr_copy(new_node->macaddress_A, addr);
+ ether_addr_copy(new_node->macaddress, addr);
spin_lock_init(&new_node->seq_out_lock);
if (hsr->prot_version == PRP_V1)
@@ -224,9 +186,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
spin_lock_bh(&hsr->list_lock);
list_for_each_entry_rcu(node, node_db, mac_list,
lockdep_is_held(&hsr->list_lock)) {
- if (ether_addr_equal(node->macaddress_A, addr))
- goto out;
- if (ether_addr_equal(node->macaddress_B, addr))
+ if (ether_addr_equal(node->macaddress, addr))
goto out;
}
list_add_tail_rcu(&new_node->mac_list, node_db);
@@ -267,12 +227,7 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
ethhdr = (struct ethhdr *)skb_mac_header(skb);
list_for_each_entry_rcu(node, node_db, mac_list) {
- if (ether_addr_equal(node->macaddress_A, ethhdr->h_source)) {
- if (hsr->proto_ops->update_san_info)
- hsr->proto_ops->update_san_info(node, is_sup);
- return node;
- }
- if (ether_addr_equal(node->macaddress_B, ethhdr->h_source)) {
+ if (ether_addr_equal(node->macaddress, ethhdr->h_source)) {
if (hsr->proto_ops->update_san_info)
hsr->proto_ops->update_san_info(node, is_sup);
return node;
@@ -281,7 +236,7 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
/* Check if required node is not in proxy nodes table */
list_for_each_entry_rcu(node, &hsr->proxy_node_db, mac_list) {
- if (ether_addr_equal(node->macaddress_A, ethhdr->h_source)) {
+ if (ether_addr_equal(node->macaddress, ethhdr->h_source)) {
if (hsr->proto_ops->update_san_info)
hsr->proto_ops->update_san_info(node, is_sup);
return node;
@@ -363,26 +318,19 @@ VISIBLE_IF_KUNIT struct hsr_seq_block *hsr_get_seq_block(struct hsr_node *node,
}
EXPORT_SYMBOL_IF_KUNIT(hsr_get_seq_block);
-/* Use the Supervision frame's info about an eventual macaddress_B for merging
- * nodes that has previously had their macaddress_B registered as a separate
- * node.
+/* Use the Supervision frame's info to ensure the node is in the node table.
*/
void hsr_handle_sup_frame(struct hsr_frame_info *frame)
{
- struct hsr_node *node_curr = frame->node_src;
struct hsr_port *port_rcv = frame->port_rcv;
- struct hsr_seq_block *src_blk, *merge_blk;
struct hsr_priv *hsr = port_rcv->hsr;
- struct hsr_sup_tlv *hsr_sup_tlv;
struct hsr_sup_payload *hsr_sp;
- struct hsr_node *node_real;
struct sk_buff *skb = NULL;
struct list_head *node_db;
+ struct hsr_node *node;
struct ethhdr *ethhdr;
unsigned int total_pull_size = 0;
unsigned int pull_size = 0;
- unsigned long idx;
- int i;
/* Here either frame->skb_hsr or frame->skb_prp should be
* valid as supervision frame always will have protocol
@@ -419,146 +367,19 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
/* get HSR sup payload */
hsr_sp = (struct hsr_sup_payload *)skb->data;
- /* Merge node_curr (registered on macaddress_B) into node_real */
node_db = &port_rcv->hsr->node_db;
- node_real = find_node_by_addr_A(node_db, hsr_sp->macaddress_A);
- if (!node_real)
- /* No frame received from AddrA of this node yet */
- node_real = hsr_add_node(hsr, node_db, hsr_sp->macaddress_A,
- true, port_rcv->type);
- if (!node_real)
- goto done; /* No mem */
- if (node_real == node_curr)
- /* Node has already been merged */
- goto done;
-
- /* Leave the first HSR sup payload. */
- pull_size = sizeof(struct hsr_sup_payload);
- skb_pull(skb, pull_size);
- total_pull_size += pull_size;
-
- /* Get second supervision tlv */
- hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
- /* And check if it is a redbox mac TLV */
- if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
- /* We could stop here after pushing hsr_sup_payload,
- * or proceed and allow macaddress_B and for redboxes.
+ node = find_node_by_addr(node_db, hsr_sp->macaddress);
+ if (!node)
+ /* Node doesn't appear in node_db yet, hsr_add_node() will
+ * check again under lock before really adding.
*/
- /* Sanity check length */
- if (hsr_sup_tlv->HSR_TLV_length != 6)
- goto done;
-
- /* Leave the second HSR sup tlv. */
- pull_size = sizeof(struct hsr_sup_tlv);
- skb_pull(skb, pull_size);
- total_pull_size += pull_size;
-
- /* Get redbox mac address. */
- hsr_sp = (struct hsr_sup_payload *)skb->data;
-
- /* Check if redbox mac and node mac are equal. */
- if (!ether_addr_equal(node_real->macaddress_A, hsr_sp->macaddress_A)) {
- /* This is a redbox supervision frame for a VDAN! */
- goto done;
- }
- }
-
- ether_addr_copy(node_real->macaddress_B, ethhdr->h_source);
- hsr_lock_seq_out_pair(node_real, node_curr);
- for (i = 0; i < HSR_PT_PORTS; i++) {
- if (!node_curr->time_in_stale[i] &&
- time_after(node_curr->time_in[i], node_real->time_in[i])) {
- node_real->time_in[i] = node_curr->time_in[i];
- node_real->time_in_stale[i] =
- node_curr->time_in_stale[i];
- }
- }
-
- xa_for_each(&node_curr->seq_blocks, idx, src_blk) {
- if (hsr_seq_block_is_old(src_blk))
- continue;
-
- merge_blk = hsr_get_seq_block(node_real, src_blk->block_idx);
- if (!merge_blk)
- continue;
- merge_blk->time = min(merge_blk->time, src_blk->time);
- for (i = 0; i < node_real->seq_port_cnt; i++) {
- bitmap_or(merge_blk->seq_nrs[i], merge_blk->seq_nrs[i],
- src_blk->seq_nrs[i], HSR_SEQ_BLOCK_SIZE);
- }
- }
- hsr_unlock_seq_out_pair(node_real, node_curr);
- node_real->addr_B_port = port_rcv->type;
+ hsr_add_node(hsr, node_db, hsr_sp->macaddress, true,
+ port_rcv->type);
- spin_lock_bh(&hsr->list_lock);
- if (!node_curr->removed) {
- list_del_rcu(&node_curr->mac_list);
- node_curr->removed = true;
- call_rcu(&node_curr->rcu_head, hsr_free_node_rcu);
- }
- spin_unlock_bh(&hsr->list_lock);
-
-done:
/* Push back here */
skb_push(skb, total_pull_size);
}
-/* 'skb' is a frame meant for this host, that is to be passed to upper layers.
- *
- * If the frame was sent by a node's B interface, replace the source
- * address with that node's "official" address (macaddress_A) so that upper
- * layers recognize where it came from.
- */
-void hsr_addr_subst_source(struct hsr_node *node, struct sk_buff *skb)
-{
- if (!skb_mac_header_was_set(skb)) {
- WARN_ONCE(1, "%s: Mac header not set\n", __func__);
- return;
- }
-
- memcpy(ð_hdr(skb)->h_source, node->macaddress_A, ETH_ALEN);
-}
-
-/* 'skb' is a frame meant for another host.
- * 'port' is the outgoing interface
- *
- * Substitute the target (dest) MAC address if necessary, so the it matches the
- * recipient interface MAC address, regardless of whether that is the
- * recipient's A or B interface.
- * This is needed to keep the packets flowing through switches that learn on
- * which "side" the different interfaces are.
- */
-void hsr_addr_subst_dest(struct hsr_node *node_src, struct sk_buff *skb,
- struct hsr_port *port)
-{
- struct hsr_node *node_dst;
-
- if (!skb_mac_header_was_set(skb)) {
- WARN_ONCE(1, "%s: Mac header not set\n", __func__);
- return;
- }
-
- if (!is_unicast_ether_addr(eth_hdr(skb)->h_dest))
- return;
-
- node_dst = find_node_by_addr_A(&port->hsr->node_db,
- eth_hdr(skb)->h_dest);
- if (!node_dst && port->hsr->redbox)
- node_dst = find_node_by_addr_A(&port->hsr->proxy_node_db,
- eth_hdr(skb)->h_dest);
-
- if (!node_dst) {
- if (port->hsr->prot_version != PRP_V1 && net_ratelimit())
- netdev_err(skb->dev, "%s: Unknown node\n", __func__);
- return;
- }
- if (port->type != node_dst->addr_B_port)
- return;
-
- if (is_valid_ether_addr(node_dst->macaddress_B))
- ether_addr_copy(eth_hdr(skb)->h_dest, node_dst->macaddress_B);
-}
-
void hsr_register_frame_in(struct hsr_node *node, struct hsr_port *port,
u16 sequence_nr)
{
@@ -698,7 +519,7 @@ void hsr_prune_nodes(struct timer_list *t)
* the master port. Thus the master node will be repeatedly
* pruned leading to packet loss.
*/
- if (hsr_addr_is_self(hsr, node->macaddress_A))
+ if (hsr_addr_is_self(hsr, node->macaddress))
continue;
/* Shorthand */
@@ -727,14 +548,14 @@ void hsr_prune_nodes(struct timer_list *t)
rcu_read_lock();
port = get_late_port(hsr, node);
if (port)
- hsr_nl_ringerror(hsr, node->macaddress_A, port);
+ hsr_nl_ringerror(hsr, node->macaddress, port);
rcu_read_unlock();
}
/* Prune old entries */
if (time_is_before_jiffies(timestamp +
msecs_to_jiffies(HSR_NODE_FORGET_TIME))) {
- hsr_nl_nodedown(hsr, node->macaddress_A);
+ hsr_nl_nodedown(hsr, node->macaddress);
if (!node->removed) {
list_del_rcu(&node->mac_list);
node->removed = true;
@@ -760,7 +581,7 @@ void hsr_prune_proxy_nodes(struct timer_list *t)
spin_lock_bh(&hsr->list_lock);
list_for_each_entry_safe(node, tmp, &hsr->proxy_node_db, mac_list) {
/* Don't prune RedBox node. */
- if (hsr_addr_is_redbox(hsr, node->macaddress_A))
+ if (hsr_addr_is_redbox(hsr, node->macaddress))
continue;
timestamp = node->time_in[HSR_PT_INTERLINK];
@@ -768,7 +589,7 @@ void hsr_prune_proxy_nodes(struct timer_list *t)
/* Prune old entries */
if (time_is_before_jiffies(timestamp +
msecs_to_jiffies(HSR_PROXY_NODE_FORGET_TIME))) {
- hsr_nl_nodedown(hsr, node->macaddress_A);
+ hsr_nl_nodedown(hsr, node->macaddress);
if (!node->removed) {
list_del_rcu(&node->mac_list);
node->removed = true;
@@ -794,13 +615,13 @@ void *hsr_get_next_node(struct hsr_priv *hsr, void *_pos,
node = list_first_or_null_rcu(&hsr->node_db,
struct hsr_node, mac_list);
if (node)
- ether_addr_copy(addr, node->macaddress_A);
+ ether_addr_copy(addr, node->macaddress);
return node;
}
node = _pos;
list_for_each_entry_continue_rcu(node, &hsr->node_db, mac_list) {
- ether_addr_copy(addr, node->macaddress_A);
+ ether_addr_copy(addr, node->macaddress);
return node;
}
@@ -840,23 +661,18 @@ static void fill_last_seq_nrs(struct hsr_node *node, u16 *if1_seq, u16 *if2_seq)
int hsr_get_node_data(struct hsr_priv *hsr,
const unsigned char *addr,
- unsigned char addr_b[ETH_ALEN],
- unsigned int *addr_b_ifindex,
int *if1_age,
u16 *if1_seq,
int *if2_age,
u16 *if2_seq)
{
struct hsr_node *node;
- struct hsr_port *port;
unsigned long tdiff;
- node = find_node_by_addr_A(&hsr->node_db, addr);
+ node = find_node_by_addr(&hsr->node_db, addr);
if (!node)
return -ENOENT;
- ether_addr_copy(addr_b, node->macaddress_B);
-
tdiff = jiffies - node->time_in[HSR_PT_SLAVE_A];
if (node->time_in_stale[HSR_PT_SLAVE_A])
*if1_age = INT_MAX;
@@ -883,15 +699,5 @@ int hsr_get_node_data(struct hsr_priv *hsr,
if (hsr->prot_version != PRP_V1)
fill_last_seq_nrs(node, if1_seq, if2_seq);
- if (node->addr_B_port != HSR_PT_NONE) {
- port = hsr_port_get_hsr(hsr, node->addr_B_port);
- if (port)
- *addr_b_ifindex = port->dev->ifindex;
- else
- *addr_b_ifindex = -1;
- } else {
- *addr_b_ifindex = -1;
- }
-
return 0;
}
diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h
index c65ecb925734..bb18b15c25ef 100644
--- a/net/hsr/hsr_framereg.h
+++ b/net/hsr/hsr_framereg.h
@@ -38,10 +38,6 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame);
bool hsr_addr_is_self(struct hsr_priv *hsr, unsigned char *addr);
bool hsr_addr_is_redbox(struct hsr_priv *hsr, unsigned char *addr);
-void hsr_addr_subst_source(struct hsr_node *node, struct sk_buff *skb);
-void hsr_addr_subst_dest(struct hsr_node *node_src, struct sk_buff *skb,
- struct hsr_port *port);
-
void hsr_register_frame_in(struct hsr_node *node, struct hsr_port *port,
u16 sequence_nr);
int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame);
@@ -50,16 +46,13 @@ void hsr_prune_nodes(struct timer_list *t);
void hsr_prune_proxy_nodes(struct timer_list *t);
int hsr_create_self_node(struct hsr_priv *hsr,
- const unsigned char addr_a[ETH_ALEN],
- const unsigned char addr_b[ETH_ALEN]);
+ const unsigned char addr[ETH_ALEN]);
void *hsr_get_next_node(struct hsr_priv *hsr, void *_pos,
unsigned char addr[ETH_ALEN]);
int hsr_get_node_data(struct hsr_priv *hsr,
const unsigned char *addr,
- unsigned char addr_b[ETH_ALEN],
- unsigned int *addr_b_ifindex,
int *if1_age,
u16 *if1_seq,
int *if2_age,
@@ -99,10 +92,7 @@ struct hsr_node {
struct list_head mac_list;
/* Protect R/W access seq_blocks */
spinlock_t seq_out_lock;
- unsigned char macaddress_A[ETH_ALEN];
- unsigned char macaddress_B[ETH_ALEN];
- /* Local slave through which AddrB frames are received from this node */
- enum hsr_port_type addr_B_port;
+ unsigned char macaddress[ETH_ALEN];
unsigned long time_in[HSR_PT_PORTS];
bool time_in_stale[HSR_PT_PORTS];
/* if the node is a SAN */
diff --git a/net/hsr/hsr_main.c b/net/hsr/hsr_main.c
index 202cf2a3625f..6708145cb423 100644
--- a/net/hsr/hsr_main.c
+++ b/net/hsr/hsr_main.c
@@ -88,12 +88,8 @@ static int hsr_netdev_notify(struct notifier_block *nb, unsigned long event,
}
/* Make sure we recognize frames from ourselves in hsr_handle_frame() */
- port = hsr_port_get_hsr(hsr, HSR_PT_SLAVE_B);
res = hsr_create_self_node(hsr,
- master->dev->dev_addr,
- port ?
- port->dev->dev_addr :
- master->dev->dev_addr);
+ master->dev->dev_addr);
if (res)
netdev_warn(master->dev,
"Could not update HSR node address.\n");
diff --git a/net/hsr/hsr_main.h b/net/hsr/hsr_main.h
index 134e4f3fff60..78b24ee6beca 100644
--- a/net/hsr/hsr_main.h
+++ b/net/hsr/hsr_main.h
@@ -97,7 +97,7 @@ struct hsr_sup_tag {
} __packed;
struct hsr_sup_payload {
- unsigned char macaddress_A[ETH_ALEN];
+ unsigned char macaddress[ETH_ALEN];
} __packed;
static inline void set_hsr_stag_path(struct hsr_sup_tag *hst, u16 path)
@@ -181,8 +181,7 @@ struct hsr_proto_ops {
};
struct hsr_self_node {
- unsigned char macaddress_A[ETH_ALEN];
- unsigned char macaddress_B[ETH_ALEN];
+ unsigned char macaddress[ETH_ALEN];
struct rcu_head rcu_head;
};
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
index 8099f2069a74..958b7b3c5be5 100644
--- a/net/hsr/hsr_netlink.c
+++ b/net/hsr/hsr_netlink.c
@@ -327,12 +327,10 @@ static int hsr_get_node_status(struct sk_buff *skb_in, struct genl_info *info)
void *msg_head;
struct hsr_priv *hsr;
struct hsr_port *port;
- unsigned char hsr_node_addr_b[ETH_ALEN];
int hsr_node_if1_age;
u16 hsr_node_if1_seq;
int hsr_node_if2_age;
u16 hsr_node_if2_seq;
- int addr_b_ifindex;
int res;
if (!info)
@@ -376,8 +374,6 @@ static int hsr_get_node_status(struct sk_buff *skb_in, struct genl_info *info)
res = hsr_get_node_data(hsr,
(unsigned char *)
nla_data(info->attrs[HSR_A_NODE_ADDR]),
- hsr_node_addr_b,
- &addr_b_ifindex,
&hsr_node_if1_age,
&hsr_node_if1_seq,
&hsr_node_if2_age,
@@ -390,18 +386,6 @@ static int hsr_get_node_status(struct sk_buff *skb_in, struct genl_info *info)
if (res < 0)
goto nla_put_failure;
- if (addr_b_ifindex > -1) {
- res = nla_put(skb_out, HSR_A_NODE_ADDR_B, ETH_ALEN,
- hsr_node_addr_b);
- if (res < 0)
- goto nla_put_failure;
-
- res = nla_put_u32(skb_out, HSR_A_ADDR_B_IFINDEX,
- addr_b_ifindex);
- if (res < 0)
- goto nla_put_failure;
- }
-
res = nla_put_u32(skb_out, HSR_A_IF1_AGE, hsr_node_if1_age);
if (res < 0)
goto nla_put_failure;
@@ -450,7 +434,7 @@ static int hsr_get_node_status(struct sk_buff *skb_in, struct genl_info *info)
return res;
}
-/* Get a list of MacAddressA of all nodes known to this node (including self).
+/* Get a list of MAC addresses of all nodes known to this node (including self).
*/
static int hsr_get_node_list(struct sk_buff *skb_in, struct genl_info *info)
{
--
2.55.0
^ permalink raw reply related
* Re: [PATCH net] net: hsr: clean up the slave VLAN filters when deleting a port
From: Felix Maurer @ 2026-07-14 11:07 UTC (permalink / raw)
To: Jiayuan Chen, Eric Dumazet
Cc: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kees Cook, Łukasz Majewski,
Fernando Fernandez Mancera, Hangbin Liu, Jakub Acs,
Xiaoliang Yang, MD Danish Anwar, Murali Karicheri, Jiri Pirko,
linux-kernel
In-Reply-To: <20260714080640.149654-1-jiayuan.chen@linux.dev>
On Tue, Jul 14, 2026 at 04:06:36PM +0800, Jiayuan Chen wrote:
> Our internal syzkaller reported this warning:
>
> netdevsim netdevsim10 eth0: entered promiscuous mode
> netdevsim netdevsim10 eth1: entered promiscuous mode
> netdevsim netdevsim10 eth1 (unregistering): left promiscuous mode
> ------------[ cut here ]------------
> WARNING: drivers/net/netdevsim/netdev.c:1208 at nsim_destroy+0x276/0x6e0, CPU#3: 1/46
> Modules linked in:
> CPU: 3 UID: 0 PID: 46 Comm: kworker/u16:1 Not tainted 7.2.0-rc2+ #286 PREEMPT
> Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Workqueue: netns cleanup_net
> RIP: 0010:nsim_destroy (drivers/net/netdevsim/netdev.c:1031 drivers/net/netdevsim/netdev.c:1201)
> RSP: 0018:ffffc90000337898 EFLAGS: 00010293
> RAX: 0000000000000001 RBX: ffff8881036c8b00 RCX: 0000000000000000
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
> RBP: ffffc900003378f0 R08: 0000000000000000 R09: 0000000000000000
> R10: ffffffffad4186a8 R11: ffffffffad41c3db R12: ffff8881036c9268
> R13: dffffc0000000000 R14: ffffed10206d9164 R15: 0000000000000001
> FS: 0000000000000000(0000) GS:ffff888160866000(0000) knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 000055bcf1a2e0c4 CR3: 0000000105376003 CR4: 0000000000770ef0
> PKRU: 55555554
> Call Trace:
> <TASK>
> __nsim_dev_port_del (drivers/net/netdevsim/dev.c:1547)
> nsim_dev_reload_destroy (drivers/net/netdevsim/dev.c:1561 drivers/net/netdevsim/dev.c:1785)
> nsim_dev_reload_down (drivers/net/netdevsim/dev.c:1038)
> devlink_reload (net/devlink/dev.c:462)
> devlink_pernet_pre_exit (net/devlink/core.c:578)
> ops_undo_list (net/core/net_namespace.c:161 net/core/net_namespace.c:235)
> cleanup_net (net/core/net_namespace.c:706)
> process_one_work (kernel/workqueue.c:3322)
> worker_thread (kernel/workqueue.c:3405 kernel/workqueue.c:3486)
> kthread (kernel/kthread.c:436)
> ret_from_fork (arch/x86/kernel/process.c:158)
> ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> </TASK>
> ---[ end trace 0000000000000000 ]---
> netdevsim netdevsim10 eth0 (unregistering): left promiscuous mode
>
> vlan_vids_add_by_dev() is what passes the VID filter info down to the real
> device. A virtual device does no filtering itself, so it has to push the
> VIDs down to its slaves. HSR already does this correctly on the add side:
> hsr_ndo_vlan_rx_add_vid() calls vlan_vid_add() on each slave.
>
> We just never clean the slaves up on delete. hsr_del_port() drops the port
> and leaves the VIDs sitting on the slave, so the filter entries leak.
> netdevsim keeps a bitmap of the VIDs it was told to filter and checks that
> it is empty when the netdev goes away, which is what trips the warning
> above.
>
> So do the cleanup in hsr_del_port(). We follow hsr_ndo_vlan_rx_add_vid()
> here: it only pushes the VIDs to HSR_PT_SLAVE_A and HSR_PT_SLAVE_B, so the
> del side only touches those two as well.
>
> Reproducer:
> ip netns add ns0
> ip netns exec ns0 sh -c 'echo "10 2" > /sys/bus/netdevsim/new_device'
> ip netns exec ns0 ip link add hsr0 type hsr slave1 eth0 slave2 eth1
> ip netns exec ns0 ip link add link hsr0 name hsr0.1 type vlan id 1
> ip netns del ns0
Thank you for your patch! The explanation and the patch look very
similar to a fix that Eric posted a week ago [1]. Can you check if
Eric's patch also fixes you reproducer? And if it does, please add your
Tested-by and/or Reviewed-by to the thread?
Thanks,
Felix
[1]: https://lore.kernel.org/netdev/20260707082327.3238690-1-edumazet@google.com/
^ permalink raw reply
* [PATCH] net: phy: Add driver for Motorcomm Quad 2.5GbE phy
From: Kyle Switch @ 2026-07-14 11:12 UTC (permalink / raw)
To: Frank.Sae, andrew, hkallweit1, linux, davem, edumazet, kuba,
pabeni
Cc: netdev, linux-kernel, jianmin.wang, ming.xu, xiaolin.xu, jie.han
Add a driver for motorcomm yt8824 quad 2.5G ethernet phy, supports
2.5G/1000M/100M/10M speed.
Signed-off-by: Kyle Switch <kyle.switch@motor-comm.com>
---
drivers/net/phy/motorcomm.c | 1824 +++++++++++++++++++++++++++++++++--
1 file changed, 1737 insertions(+), 87 deletions(-)
diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
index 5071605a1a11..e6f93ed4349c 100644
--- a/drivers/net/phy/motorcomm.c
+++ b/drivers/net/phy/motorcomm.c
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Motorcomm 8511/8521/8522/8531/8531S/8821 PHY driver.
+ * Motorcomm 8511/8521/8522/8531/8531S/8821/8824 PHY driver.
*
* Author: Peter Geis <pgwipeout@gmail.com>
* Author: Frank <Frank.Sae@motor-comm.com>
+ * Author: Kyle <kyle.switch@motor-comm.com>
*/
#include <linux/etherdevice.h>
@@ -12,12 +13,16 @@
#include <linux/phy.h>
#include <linux/property.h>
+/* used for phy8824 baseaddr */
+#define PHY_BASE_ADDR (1)
+
#define PHY_ID_YT8511 0x0000010a
#define PHY_ID_YT8521 0x0000011a
#define PHY_ID_YT8522 0x4f51e928
#define PHY_ID_YT8531 0x4f51e91b
#define PHY_ID_YT8531S 0x4f51e91a
#define PHY_ID_YT8821 0x4f51ea19
+#define PHY_ID_YT8824 0x4f51e8b8
/* YT8521/YT8531S/YT8821 Register Overview
* UTP Register space | FIBER Register space
* ------------------------------------------------------------
@@ -29,6 +34,18 @@
* ------------------------------------------------------------
*/
+/* YT8824 Register Overview
+ * UTP Register space | FIBER Register space
+ * ------------------------------------------------------------
+ * | UTP MII | FIBER MII |
+ * | UTP MMD | |
+ * | UTP Extended | FIBER Extended |
+ * | UTP Top Extended | FIBER Top Extended |
+ * ------------------------------------------------------------
+ * | Common Top Extended |
+ * ------------------------------------------------------------
+ */
+
/* 0x10 ~ 0x15 , 0x1E and 0x1F are common MII registers of yt phy */
/* Specific Function Control Register */
@@ -375,6 +392,12 @@
#define YT8821_CHIP_MODE_AUTO_BX2500_SGMII 0
#define YT8821_CHIP_MODE_FORCE_BX2500 1
+#define YT8824_RSSR_SPACE_MASK BIT(0)
+#define YT8824_RSSR_FIBER_SPACE (0x1)
+#define YT8824_RSSR_UTP_SPACE (0x0)
+#define REG_MII_MMD_CTRL 0x0D
+#define REG_MII_MMD_DATA 0x0E
+
struct yt8521_priv {
/* combo_advertising is used for case of YT8521 in combo mode,
* this means that yt8521 may work in utp or fiber mode which depends
@@ -391,8 +414,29 @@ struct yt8521_priv {
* YT8521_RSSR_TO_BE_ARBITRATED
*/
u8 reg_page;
+ /* YT8824 reg space addr */
+ u8 phy_base_addr;
+ /* top extend reg addr */
+ u8 top_phy_addr;
};
+static inline int ytphy_top_write(struct phy_device *phydev, u32 regnum,
+ u16 val)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ struct mii_bus *bus = phydev->mdio.bus;
+
+ return bus->write(bus, priv->top_phy_addr, regnum, val);
+}
+
+static inline int ytphy_top_read(struct phy_device *phydev, u32 regnum)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ struct mii_bus *bus = phydev->mdio.bus;
+
+ return bus->read(bus, priv->top_phy_addr, regnum);
+}
+
/**
* ytphy_read_ext() - read a PHY's extended register
* @phydev: a pointer to a &struct phy_device
@@ -431,6 +475,70 @@ static int ytphy_read_ext_with_lock(struct phy_device *phydev, u16 regnum)
return ret;
}
+/**
+ * ytphy_read_top_ext() - read a PHY's top extended register for YT8824
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to read
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int ytphy_read_top_ext(struct phy_device *phydev, u16 regnum)
+{
+ int ret;
+
+ ret = ytphy_top_write(phydev, YTPHY_PAGE_SELECT, regnum);
+ if (ret < 0)
+ return ret;
+
+ return ytphy_top_read(phydev, YTPHY_PAGE_DATA);
+}
+
+static int ytphy_read_top_ext_with_lock(struct phy_device *phydev, u16 regnum)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = ytphy_read_top_ext(phydev, regnum);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
+/**
+ * ytphy_write_top_ext() - read a PHY's top extended register for YT8824
+ * @phydev: a pointer to a &struct phy_device
+ * @regnum: register number to read
+ *
+ * NOTE:The caller must have taken the MDIO bus lock.
+ *
+ * returns the value of regnum reg or negative error code
+ */
+static int ytphy_write_top_ext(struct phy_device *phydev, u16 regnum,
+ u16 val)
+{
+ int ret;
+
+ ret = ytphy_top_write(phydev, YTPHY_PAGE_SELECT, regnum);
+ if (ret < 0)
+ return ret;
+
+ return ytphy_top_write(phydev, YTPHY_PAGE_DATA, val);
+}
+
+static int ytphy_write_top_ext_with_lock(struct phy_device *phydev, u16 regnum,
+ u16 val)
+{
+ int ret;
+
+ phy_lock_mdio_bus(phydev);
+ ret = ytphy_write_top_ext(phydev, regnum, val);
+ phy_unlock_mdio_bus(phydev);
+
+ return ret;
+}
+
/**
* ytphy_write_ext() - write a PHY's extended register
* @phydev: a pointer to a &struct phy_device
@@ -521,6 +629,26 @@ static int ytphy_modify_ext_with_lock(struct phy_device *phydev, u16 regnum,
return ret;
}
+static int ytphy_write_mmd(struct phy_device *phydev,
+ u16 device, u16 reg,
+ u16 value)
+{
+ int ret;
+
+ ret = __phy_write(phydev, REG_MII_MMD_CTRL, device);
+ if (ret)
+ return ret;
+ ret = __phy_write(phydev, REG_MII_MMD_DATA, reg);
+ if (ret)
+ return ret;
+ ret = __phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
+ if (ret)
+ return ret;
+ ret = __phy_write(phydev, REG_MII_MMD_DATA, value);
+
+ return ret;
+}
+
/**
* ytphy_get_wol() - report whether wake-on-lan is enabled
* @phydev: a pointer to a &struct phy_device
@@ -3059,99 +3187,1620 @@ static int yt8821_resume(struct phy_device *phydev)
return yt8821_modify_utp_fiber_bmcr(phydev, BMCR_PDOWN, 0);
}
-static struct phy_driver motorcomm_phy_drvs[] = {
- {
- PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
- .name = "YT8511 Gigabit Ethernet",
- .config_init = yt8511_config_init,
- .suspend = genphy_suspend,
- .resume = genphy_resume,
- .read_page = yt8511_read_page,
- .write_page = yt8511_write_page,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_YT8521),
- .name = "YT8521 Gigabit Ethernet",
- .get_features = yt8521_get_features,
- .probe = yt8521_probe,
- .read_page = yt8521_read_page,
- .write_page = yt8521_write_page,
- .get_wol = ytphy_get_wol,
- .set_wol = ytphy_set_wol,
- .config_aneg = yt8521_config_aneg,
- .aneg_done = yt8521_aneg_done,
- .config_init = yt8521_config_init,
- .read_status = yt8521_read_status,
- .soft_reset = yt8521_soft_reset,
- .suspend = yt8521_suspend,
- .resume = yt8521_resume,
- .led_hw_is_supported = yt8521_led_hw_is_supported,
- .led_hw_control_set = yt8521_led_hw_control_set,
- .led_hw_control_get = yt8521_led_hw_control_get,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_YT8522),
- .name = "YT8522 100 Megabit Ethernet",
- .config_aneg = genphy_config_aneg,
- .config_init = yt8522_config_init,
- .suspend = genphy_suspend,
- .resume = genphy_resume,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_YT8531),
- .name = "YT8531 Gigabit Ethernet",
- .probe = yt8531_probe,
- .config_init = yt8531_config_init,
- .suspend = genphy_suspend,
- .resume = genphy_resume,
- .get_wol = ytphy_get_wol,
- .set_wol = yt8531_set_wol,
- .link_change_notify = yt8531_link_change_notify,
- .led_hw_is_supported = yt8521_led_hw_is_supported,
- .led_hw_control_set = yt8521_led_hw_control_set,
- .led_hw_control_get = yt8521_led_hw_control_get,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
- .name = "YT8531S Gigabit Ethernet",
- .get_features = yt8521_get_features,
- .probe = yt8521_probe,
- .read_page = yt8521_read_page,
- .write_page = yt8521_write_page,
- .get_wol = ytphy_get_wol,
- .set_wol = ytphy_set_wol,
- .config_aneg = yt8521_config_aneg,
- .aneg_done = yt8521_aneg_done,
- .config_init = yt8521_config_init,
- .read_status = yt8521_read_status,
- .soft_reset = yt8521_soft_reset,
- .suspend = yt8521_suspend,
- .resume = yt8521_resume,
- },
- {
- PHY_ID_MATCH_EXACT(PHY_ID_YT8821),
- .name = "YT8821 2.5Gbps PHY",
- .get_features = yt8821_get_features,
- .read_page = yt8521_read_page,
- .write_page = yt8521_write_page,
- .get_wol = ytphy_get_wol,
- .set_wol = ytphy_set_wol,
- .config_aneg = genphy_config_aneg,
- .aneg_done = yt8821_aneg_done,
- .config_init = yt8821_config_init,
- .get_rate_matching = yt8821_get_rate_matching,
- .read_status = yt8821_read_status,
- .soft_reset = yt8821_soft_reset,
- .suspend = yt8821_suspend,
- .resume = yt8821_resume,
+/**
+ * yt8824_read_page() - read reg page
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns current reg space of yt8824 (YT8824_RSSR_FIBER_SPACE/
+ * YT8824_RSSR_UTP_SPACE) or negative errno code
+ */
+static int yt8824_read_page(struct phy_device *phydev)
+{
+ int old_page;
+
+ old_page = ytphy_read_top_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG);
+ if (old_page < 0)
+ return old_page;
+
+ if ((old_page & YT8824_RSSR_SPACE_MASK) == YT8824_RSSR_FIBER_SPACE)
+ return YT8824_RSSR_FIBER_SPACE;
+
+ return YT8824_RSSR_UTP_SPACE;
+};
+
+/**
+ * yt8824_write_page() - write reg page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8824_RSSR_FIBER_SPACE/YT8824_RSSR_UTP_SPACE) to write.
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_write_page(struct phy_device *phydev, int page)
+{
+ int old_page;
+ u16 data;
+
+ old_page = ytphy_read_top_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG);
+ data = old_page & (~(0x1));
+ data |= page;
+
+ return ytphy_write_top_ext_with_lock(phydev, YT8521_REG_SPACE_SELECT_REG, data);
+};
+
+/**
+ * configuration YT8824 to one template test mode.
+ */
+static int yt8824_soft_reset_step1_paged(struct phy_device *phydev,
+ int reg_space)
+{
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ if (old_page >= 0) {
+ if (reg_space == YT8824_RSSR_UTP_SPACE) {
+ ret = ytphy_write_mmd(phydev, 0x1, 0x0084, 0x2000);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * enable YT8824 serdes isolate.
+ */
+static int yt8824_soft_reset_step2_paged(struct phy_device *phydev,
+ int reg_space)
+{
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+ if (old_page >= 0) {
+ if (reg_space == YT8824_RSSR_FIBER_SPACE) {
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+ /* isolation */
+ ret |= BIT(10);
+ ret = __phy_write(phydev, MII_BMCR, ret);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * do YT8824 UTP softreset.
+ */
+static int yt8824_soft_reset_step3_paged(struct phy_device *phydev,
+ int reg_space)
+{
+ unsigned int retry = 12;
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+ if (old_page >= 0) {
+ if (reg_space == YT8824_RSSR_UTP_SPACE) {
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+ ret |= BMCR_RESET;
+ ret = __phy_write(phydev, MII_BMCR, ret);
+ if (ret < 0)
+ goto err_restore_page;
+ do {
+ msleep(50);
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+ } while ((ret & BMCR_RESET) && --retry);
+ if (ret & BMCR_RESET)
+ goto err_restore_page;
+ }
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * disable YT8824 template test mode.
+ */
+static int yt8824_soft_reset_step4_paged(struct phy_device *phydev,
+ int reg_space)
+{
+ int old_page;
+ int port;
+ int ret;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ if (old_page >= 0) {
+ if (reg_space == YT8824_RSSR_UTP_SPACE) {
+ /* normal mode */
+ ret = ytphy_write_mmd(phydev, 0x1, 0x0084, 0x0000);
+ if (ret < 0)
+ goto err_restore_page;
+ if (port == 0 || port == 2) {
+ /* read the calibration val of band after power on,
+ * * only for recording.
+ */
+ ret = ytphy_read_ext(phydev, 0x043e);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+ }
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * disable YT8824 serdes isolate.
+ */
+static int yt8824_soft_reset_step5_paged(struct phy_device *phydev,
+ int reg_space)
+{
+ unsigned int retry = 12;
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+ if (old_page >= 0) {
+ if (reg_space == YT8824_RSSR_FIBER_SPACE) {
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+ /* disable isolation */
+ ret &= ~BIT(10);
+ /* soft reset */
+ ret |= BMCR_RESET;
+ ret = __phy_write(phydev, MII_BMCR, ret);
+ if (ret < 0)
+ return ret;
+ do {
+ msleep(50);
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+ } while ((ret & BMCR_RESET) && --retry);
+ if (ret & BMCR_RESET)
+ goto err_restore_page;
+ }
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_soft_reset() - called to issue a PHY software reset
+ * @phydev: a pointer to a &struct phy_device
+ * for internal YT8824
+ * 1) set utp template test mode
+ * 2) utp restart
+ * 3) disable utp template test mode
+ * for external YT8824
+ * 1) set utp template test mode
+ * 2) enable serdes isolate
+ * 3) utp restart
+ * 4) disable utp template test mode
+ * 5) disable serdes isolate
+ * returns 0 or negative errno code
+ */
+static int yt8824_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+ /* invalid test mode */
+ ret = yt8824_soft_reset_step1_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ ret = yt8824_soft_reset_step3_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ /* normal mode */
+ ret = yt8824_soft_reset_step4_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* invalid test mode */
+ ret = yt8824_soft_reset_step1_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* sds isolation */
+ ret = yt8824_soft_reset_step2_paged(phydev,
+ YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* utp soft reset */
+ ret = yt8824_soft_reset_step3_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* normal mode */
+ ret = yt8824_soft_reset_step4_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* sds soft reset and disable isolation */
+ ret = yt8824_soft_reset_step5_paged(phydev,
+ YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * external YT8824 init
+ */
+static int yt8824_config_init_paged(struct phy_device *phydev, int reg_space)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int ret = 0, old_page;
+ u16 val_1, val_2, val_3, tmp;
+ int port;
+
+ port = phydev->mdio.addr - priv->phy_base_addr;
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ if (reg_space == YT8824_RSSR_FIBER_SPACE) {
+ /* read efuse */
+ val_1 = ytphy_read_top_ext_with_lock(phydev, 0xa13e);
+ if (val_1 < 0)
+ goto err_restore_page;
+
+ val_2 = ytphy_read_top_ext_with_lock(phydev, 0xa13f);
+ if (val_2 < 0)
+ goto err_restore_page;
+
+ val_3 = ytphy_read_top_ext_with_lock(phydev, 0xa140);
+ if (val_3 < 0)
+ goto err_restore_page;
+
+ if (port == 0) {
+ /* Serdes optimization */
+ ret = ytphy_write_ext(phydev, 0x04be, 0x000d);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x049f, 0x7ded);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04a9, 0x009f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* analog CDR */
+ ret = ytphy_write_ext(phydev, 0x0406, 0x0800);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized VCO */
+ ret = ytphy_write_ext(phydev, 0x0438, 0x9024);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0439, 0x00c0);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized PLL lock */
+ ret = ytphy_read_ext(phydev, 0x0429);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~(BIT(13) | BIT(12));
+ tmp = (val_1 & (BIT(7) | BIT(6)) >> 6);
+ ret |= (tmp << 12);
+ ret = ytphy_write_ext(phydev, 0x0429, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_read_ext(phydev, 0x0441);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~(BIT(1) | BIT(0));
+ tmp = (val_1 & (BIT(5) | BIT(4)) >> 4);
+ ret |= tmp;
+ ret = ytphy_write_ext(phydev, 0x0441, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_read_ext(phydev, 0x042b);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~(BIT(13) | BIT(12));
+ tmp = (val_3 & (BIT(1) | BIT(0)));
+ ret |= (tmp << 12);
+ ret = ytphy_write_ext(phydev, 0x042b, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x043a, 0x1006);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x042a, 0xf070);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* cable length threshold */
+ ret = ytphy_write_ext(phydev, 0x0491, 0x007f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0492, 0x7f7f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* Serdes training threshold */
+ ret = ytphy_write_ext(phydev, 0x0454, 0x0f14);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0497, 0x0a44);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* digital eye diagram of SerDes */
+ ret = ytphy_write_ext(phydev, 0x04cd, 0x0000);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* Serdes LDO */
+ ret = ytphy_read_ext(phydev, 0x04b5);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~(BIT(6) | BIT(5) | BIT(4));
+ tmp = (val_2 & (BIT(4) | BIT(3) | BIT(2)) >> 2);
+ ret |= (tmp << 4);
+ ret = ytphy_write_ext(phydev, 0x04b5, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_read_ext(phydev, 0x04b4);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~(BIT(10) | BIT(9) | BIT(8));
+ tmp = (val_2 & (BIT(7) | BIT(6) | BIT(5)) >> 5);
+ ret |= (tmp << 8);
+ ret = ytphy_write_ext(phydev, 0x04b4, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized Serdes RX */
+ ret = ytphy_write_ext(phydev, 0x04af, 0x45e3);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x048a, 0x0fff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0408, 0x7c00);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04d6, 0x007f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x044f, 0xff08);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized Serdes TX */
+ ret = ytphy_write_ext(phydev, 0x048e, 0x7d00);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x000d, 0x0606);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* Serdes manual config */
+ ret = ytphy_write_ext(phydev, 0x04b0, 0x0804);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04b1, 0x7074);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04af, 0x45e7);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* restart calibration */
+ ret = ytphy_write_ext(phydev, 0x0003, 0x5603);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0492, 0x7fff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0492, 0x7f7f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x2000, 0x0040);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x2000, 0x0000);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+ /* TX preamble padded to 8; RX IPG always > 8 */
+ ret = __phy_write(phydev, 0x0017, 0x2007);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = __phy_write(phydev, 0x0000, 0x9000);
+ if (ret < 0)
+ goto err_restore_page;
+ } else if (reg_space == YT8824_RSSR_UTP_SPACE) {
+ /* power down */
+ ret = __phy_write(phydev, 0x0000, 0x1900);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* pll calibration */
+ ret = ytphy_write_ext(phydev, 0x0001, 0x0003);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa20e, 0x0cba);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa20a, 0xc3f1);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa20c, 0x1620);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa2b6, 0x0a00);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa2b6, 0x0e00);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimization utp */
+ ret = ytphy_write_ext(phydev, 0x0001, 0x0003);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* enable nibble */
+ ret = ytphy_write_ext(phydev, 0xa003, 0x0003);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* idle err detect enable */
+ ret = ytphy_write_ext(phydev, 0x03d0, 0x5210);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized 2.5G long cable performance */
+ ret = ytphy_write_ext(phydev, 0x0372, 0x5038);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x037c, 0x6068);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0388, 0x00a0);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized fast retrain */
+ ret = ytphy_write_ext(phydev, 0x0359, 0x2140);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x000c, 0xc1a0);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* 2.5G template tone */
+ ret = ytphy_write_ext(phydev, 0xa2fa, 0x0083);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04e2, 0x0149);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized 2.5G template */
+ ret = ytphy_write_ext(phydev, 0x047e, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x047f, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0480, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0481, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized 1000M cable length threshold */
+ ret = ytphy_write_ext(phydev, 0x0336, 0xab0a);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0340, 0x301d);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* 100M template amplitude */
+ ret = ytphy_write_ext(phydev, 0x046e, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x046f, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0470, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0471, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized 100M cable length threshold */
+ ret = ytphy_write_ext(phydev, 0x030b, 0xaa1d);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x071f, 0x0036);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* 10M template amplitude */
+ ret = ytphy_write_ext(phydev, 0x046b, 0x1818);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x046c, 0x1818);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized 10M cable length threshold */
+ ret = ytphy_write_ext(phydev, 0x0466, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0467, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0468, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0469, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimize utp 1000M performance */
+ ret = ytphy_write_ext(phydev, 0x034a, 0xff03);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x00f8, 0xb3ff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0059, 0x4040);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x032c, 0x5094);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x032d, 0xd094);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x032e, 0x5308);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x0322, 0x6440);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04d3, 0x5220);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x04d2, 0x5220);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized EMC CS */
+ ret = ytphy_write_ext(phydev, 0x00c8, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x00be, 0x6406);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0x037a, 0x40ff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* optimized EMC RE */
+ ret = ytphy_write_ext(phydev, 0x0482, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa2d5, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa2d6, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa2d7, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa2d8, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa218, 0x006e);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa01d, 0xfff0);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa01e, 0xfff0);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa01d, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_write_ext(phydev, 0xa01e, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * internal YT8824 init
+ */
+static int yt8824_internal_config_init_paged(struct phy_device *phydev,
+ int reg_space)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int old_page;
+ int port = 0;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ port = phydev->mdio.addr - priv->phy_base_addr;
+ ret = ytphy_write_ext(phydev, 0x1, 0x3);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = __phy_write(phydev, MII_BMCR, 0x1900);
+ if (ret < 0)
+ goto err_restore_page;
+ if (port == 0 || port == 2) {
+ ret = ytphy_write_ext(phydev, 0xa20e, 0xcba);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa20a, 0xc3f1);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa20c, 0x1620);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa2b6, 0xa00);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa2b6, 0xe00);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa003, 0x3);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+ ret = ytphy_write_ext(phydev, 0x3d0, 0x5210);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x372, 0x5038);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x37c, 0x6068);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x388, 0xa0);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x359, 0x2140);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_top_ext(phydev, 0xa2fa, 0x83);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x4e2, 0x149);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 2.5G tempate */
+ ret = ytphy_write_ext(phydev, 0x47e, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x47f, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x480, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x481, 0x3939);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 1000 cable length threshold */
+ ret = ytphy_write_ext(phydev, 0x336, 0xab0a);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x340, 0x301d);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 1000 performance */
+ ret = ytphy_write_ext(phydev, 0x34a, 0xff03);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xf8, 0xb3ff);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x32c, 0x5094);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x32d, 0xd094);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x32e, 0x5308);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x322, 0x6440);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x4d3, 0x5220);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x4d2, 0x5220);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 100 tempate */
+ ret = ytphy_write_ext(phydev, 0x46e, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x46f, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x470, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x471, 0x4545);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 100 cable length threshold */
+ ret = ytphy_write_ext(phydev, 0x30b, 0xaa1d);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x71f, 0x36);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 10 tempate */
+ ret = ytphy_write_ext(phydev, 0x46b, 0x1818);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x46c, 0x1818);
+ if (ret < 0)
+ goto err_restore_page;
+ /* 10 tempate MAU*/
+ ret = ytphy_write_ext(phydev, 0x466, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x467, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x468, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x469, 0x6c6c);
+ if (ret < 0)
+ goto err_restore_page;
+ /* EMC CS */
+ ret = ytphy_write_ext(phydev, 0xc8, 0xfff);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xbe, 0x6406);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0x37a, 0x40ff);
+ if (ret < 0)
+ goto err_restore_page;
+ /* EMC RE*/
+ ret = ytphy_write_ext(phydev, 0x482, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+ if (port == 0 || port == 2) {
+ ret = ytphy_write_ext(phydev, 0x482, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa2d5, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa2d6, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa2d7, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa2d8, 0x1f1f);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa218, 0x6e);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa01d, 0xfff0);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa01e, 0xfff0);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa01d, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_ext(phydev, 0xa01e, 0xffff);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+ ret = ytphy_write_ext(phydev, 0xc, 0x41a1);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_mmd(phydev, 0x1, 0x84, 0x2000);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = __phy_write(phydev, MII_BMCR, 0x9140);
+ if (ret < 0)
+ goto err_restore_page;
+ ret = ytphy_write_mmd(phydev, 0x1, 0x84, 0x0);
+ if (ret < 0)
+ goto err_restore_page;
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_config_init() - phy initializatioin
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * Returns: 0 or negative errno code
+ */
+static int yt8824_config_init(struct phy_device *phydev)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+ /* base addr and top addr update for internal YT8824*/
+ priv->phy_base_addr = 0x4;
+ priv->top_phy_addr = 0x9;
+ ret = yt8824_internal_config_init_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = yt8824_config_init_paged(phydev, YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+ ret = yt8824_config_init_paged(phydev, YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ }
+ ret = yt8824_soft_reset(phydev);
+ if (ret < 0)
+ return ret;
+
+ netdev_info(phydev->attached_dev,
+ "%s done, phy addr: %d, phy base addr = %d\n",
+ __func__, phydev->mdio.addr, priv->phy_base_addr);
+
+ return 0;
+}
+
+static int yt8824_config_intr(struct phy_device *phydev)
+{
+ struct yt8521_priv *priv = phydev->priv;
+ int ret = 0, old_page;
+ int port;
+
+ port = phydev->mdio.addr - priv->phy_base_addr;
+ old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ /* top ext reg 0xa000
+ * bit6 int_polarity 1'b0 low active, 1'b1 high active
+ */
+ ret = ytphy_top_read(phydev, YT8521_REG_SPACE_SELECT_REG);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_top_write(phydev, YT8521_REG_SPACE_SELECT_REG, ret & (~BIT(6)));
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* top ext reg 0xa019
+ * bit5 intr_phy_pulse_en 1'b0 level, 1'b1 pulse
+ */
+ ret = ytphy_top_read(phydev, 0xa019);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_top_write(phydev, 0xa019, ret | BIT(5));
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* top ext reg 0xa018 phy_pulse_lth bit14:8
+ * top ext reg 0xa019 timer_tick_sel bit8:7
+ * bit14:8 phy_pulse_lth bit8:7 timer_tick_sel pulse width
+ * 0x007a 0x0002 10ms pulse width
+ * 0x0064 0x0001 1ms pulse width
+ * 0x004e 0x0000 100ms pulse width
+ * 0x0009 0x0000 10ms pulse width(default)
+ */
+ ret = ytphy_top_read(phydev, 0xa018);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~0x7f00;
+ ret |= (0x0009 << 8);
+ ret = ytphy_top_write(phydev, 0xa018, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = ytphy_top_read(phydev, 0xa019);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~0x180;
+ ret |= (0x0000 << 7);
+ ret = ytphy_top_write(phydev, 0xa019, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* top ext reg 0xa01c interrupt state(Read Clear)
+ * bit11 PHY3 interrupt 1: phy3 link up/down interrupt happened, 0: interrupt not happened
+ * bit10 PHY2 interrupt 1: phy2 link up/down interrupt happened, 0: interrupt not happened
+ * bit9 PHY1 interrupt 1: phy1 link up/down interrupt happened, 0: interrupt not happened
+ * bit8 PHY0 interrupt 1: phy0 link up/down interrupt happened, 0: interrupt not happened
+ *
+ * bit7 PHY3 link down 1: phy3 link down interrupt happened, 0: interrupt not happened
+ * bit6 PHY2 link down 1: phy2 link down interrupt happened, 0: interrupt not happened
+ * bit5 PHY1 link down 1: phy1 link down interrupt happened, 0: interrupt not happened
+ * bit4 PHY0 link down 1: phy0 link down interrupt happened, 0: interrupt not happened
+ *
+ * bit3 PHY3 link up 1: phy3 link up interrupt happened, 0: interrupt not happened
+ * bit2 PHY2 link up 1: phy2 link up interrupt happened, 0: interrupt not happened
+ * bit1 PHY1 link up 1: phy1 link up interrupt happened, 0: interrupt not happened
+ * bit0 PHY0 link up 1: phy0 link up interrupt happened, 0: interrupt not happened
+ */
+ ret = ytphy_top_read(phydev, 0xa01c);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* top ext reg 0xa01a
+ * bit5 intn_wol_mode 1'b1 enable intn_wol pin output
+ * bit4 intn_mode 1'b1 enable intn pin output
+ */
+ ret = ytphy_top_read(phydev, 0xa01a);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~BIT(5);
+ ret |= BIT(4);
+ ret = ytphy_top_write(phydev, 0xa01a, ret);
+ if (ret < 0)
+ goto err_restore_page;
+
+ /* top ext reg 0xa01b interrupt mask
+ * bit11 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+ * bit7 1'b1 enable interrupt signal(link down) output to interrupt pin
+ * bit3 1'b1 enable interrupt signal(link up) output to interrupt pin
+ *
+ * bit10 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+ * bit6 1'b1 enable interrupt signal(link down) output to interrupt pin
+ * bit2 1'b1 enable interrupt signal(link up) output to interrupt pin
+ *
+ * bit9 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+ * bit5 1'b1 enable interrupt signal(link down) output to interrupt pin
+ * bit1 1'b1 enable interrupt signal(link up) output to interrupt pin
+ *
+ * bit8 1'b1 enable interrupt signal(link up/down) output to interrupt pin
+ * bit4 1'b1 enable interrupt signal(link down) output to interrupt pin
+ * bit0 1'b1 enable interrupt signal(link up) output to interrupt pin
+ */
+ ret = ytphy_top_read(phydev, 0xa01b);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret |= (BIT(port + 8) | BIT(port + 4) | BIT(port));
+ ret = ytphy_top_write(phydev, 0xa01b, ret);
+ if (ret < 0)
+ goto err_restore_page;
+ }
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+static irqreturn_t yt8824_handle_interrupt(struct phy_device *phydev)
+{
+ int ret = 0, old_page;
+
+ old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ /* top ext reg 0xa01c interrupt state(Read Clear)
+ * bit11 PHY3 interrupt 1: phy3 link up/down interrupt happened, 0: interrupt not happened
+ * bit10 PHY2 interrupt 1: phy2 link up/down interrupt happened, 0: interrupt not happened
+ * bit9 PHY1 interrupt 1: phy1 link up/down interrupt happened, 0: interrupt not happened
+ * bit8 PHY0 interrupt 1: phy0 link up/down interrupt happened, 0: interrupt not happened
+ * bit7 PHY3 link down 1: phy3 link down interrupt happened, 0: interrupt not happened
+ * bit6 PHY2 link down 1: phy2 link down interrupt happened, 0: interrupt not happened
+ * bit5 PHY1 link down 1: phy1 link down interrupt happened, 0: interrupt not happened
+ * bit4 PHY0 link down 1: phy0 link down interrupt happened, 0: interrupt not happened
+ * bit3 PHY3 link up 1: phy3 link up interrupt happened, 0: interrupt not happened
+ * bit2 PHY2 link up 1: phy2 link up interrupt happened, 0: interrupt not happened
+ * bit1 PHY1 link up 1: phy1 link up interrupt happened, 0: interrupt not happened
+ * bit0 PHY0 link up 1: phy0 link up interrupt happened, 0: interrupt not happened
+ */
+ ret = ytphy_read_top_ext(phydev, 0xa01c);
+ if (ret < 0)
+ goto err_restore_page;
+
+ phy_trigger_machine(phydev);
+
+err_restore_page:
+ phy_restore_page(phydev, old_page, ret);
+ if (ret > 0)
+ return IRQ_HANDLED;
+ else
+ return IRQ_NONE;
+}
+
+/**
+ * yt8824_config_aneg()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_aneg_done(struct phy_device *phydev)
+{
+ int link = 0;
+ int old_page;
+ int ret = 0;
+
+ old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ link = !!(__phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG) &
+ YTPHY_SSR_LINK);
+
+ netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
+ __func__, phydev->mdio.addr, link);
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_read_status_paged() - determines the speed and duplex of one page
+ * @phydev: a pointer to a &struct phy_device
+ * @page: The reg page(YT8824_RSSR_FIBER_SPACE/YT8824_RSSR_UTP_SPACE) to
+ * operate.
+ *
+ * returns 1 (utp or fiber link),0 (no link) or negative errno code
+ */
+static int yt8824_read_status_paged(struct phy_device *phydev, int page,
+ int *status, int *lpa)
+{
+ int old_page;
+ int ret = 0;
+
+ page &= YT8824_RSSR_SPACE_MASK;
+ old_page = phy_select_page(phydev, page);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ ret = __phy_read(phydev, MII_LPA);
+ *lpa = ret;
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = __phy_read(phydev, YTPHY_SPECIFIC_STATUS_REG);
+ *status = ret;
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = !!(*status & YTPHY_SSR_LINK);
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_read_status() - determines the negotiated speed and duplex
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_read_status(struct phy_device *phydev)
+{
+ int link;
+ int lpa;
+ int val;
+
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ phydev->link = 0;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+
+ link = yt8824_read_status_paged(phydev,
+ YT8824_RSSR_UTP_SPACE, &val, &lpa);
+ if (link < 0)
+ return link;
+
+ if (link) {
+ phydev->link = 1;
+ phydev->pause = !!(lpa & BIT(10));
+ phydev->asym_pause = !!(lpa & BIT(11));
+
+ /* update speed & duplex */
+ yt8821_adjust_status(phydev, val);
+ } else {
+ phydev->link = 0;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ }
+
+ return 0;
+}
+
+/**
+ * yt8824_utp_power_on()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_utp_power_on(struct phy_device *phydev,
+ int reg_space)
+{
+ int old_page;
+ int ret;
+
+ old_page = phy_select_page(phydev, reg_space);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret &= ~BMCR_PDOWN;
+ ret &= ~BMCR_ISOLATE;
+
+ ret = __phy_write(phydev, MII_BMCR, ret);
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_utp_power_down()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_utp_power_down(struct phy_device *phydev,
+ int reg_space)
+{
+ int ret = 0, old_page;
+
+ old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ ret = __phy_read(phydev, MII_BMCR);
+ if (ret < 0)
+ goto err_restore_page;
+
+ ret = __phy_write(phydev, MII_BMCR, ret | BMCR_PDOWN);
+ if (ret < 0)
+ goto err_restore_page;
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_power_on()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ * need WA like softreset
+ */
+static int yt8824_power_on(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+ /* invalid test mode */
+ ret = yt8824_soft_reset_step1_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ /* utp power on */
+ ret = yt8824_utp_power_on(phydev, YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ /* normal mode */
+ ret = yt8824_soft_reset_step4_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* invalid test mode */
+ ret = yt8824_soft_reset_step1_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* sds isolation */
+ ret = yt8824_soft_reset_step2_paged(phydev,
+ YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* utp power on */
+ ret = yt8824_utp_power_on(phydev, YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* normal mode */
+ ret = yt8824_soft_reset_step4_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* sds soft reset and disable isolation */
+ ret = yt8824_soft_reset_step5_paged(phydev,
+ YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int yt8824_resume(struct phy_device *phydev)
+{
+ return yt8824_power_on(phydev);
+}
+
+static int yt8824_power_down(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_INTERNAL) {
+ /* invalid test mode */
+ ret = yt8824_soft_reset_step1_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ /* utp power down */
+ ret = yt8824_utp_power_down(phydev, YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ /* normal mode */
+ ret = yt8824_soft_reset_step4_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* invalid test mode */
+ ret = yt8824_soft_reset_step1_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* sds isolation */
+ ret = yt8824_soft_reset_step2_paged(phydev,
+ YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* utp power down */
+ ret = yt8824_utp_power_down(phydev, YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* normal mode */
+ ret = yt8824_soft_reset_step4_paged(phydev,
+ YT8824_RSSR_UTP_SPACE);
+ if (ret < 0)
+ return ret;
+
+ /* sds soft reset and disable isolation */
+ ret = yt8824_soft_reset_step5_paged(phydev,
+ YT8824_RSSR_FIBER_SPACE);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int yt8824_suspend(struct phy_device *phydev)
+{
+ return yt8824_power_down(phydev);
+}
+
+/**
+ * yt8824_config_aneg()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_config_aneg(struct phy_device *phydev)
+{
+ int phy_ctrl = 0;
+ int old_page;
+ int ret;
+
+ old_page = phy_select_page(phydev, YT8824_RSSR_UTP_SPACE);
+ if (old_page < 0)
+ goto err_restore_page;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->advertising))
+ phy_ctrl = MDIO_AN_10GBT_CTRL_ADV2_5G;
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+ MDIO_AN_10GBT_CTRL,
+ MDIO_AN_10GBT_CTRL_ADV2_5G,
+ phy_ctrl);
+ if (ret)
+ goto err_restore_page;
+
+ ret = genphy_config_aneg(phydev);
+ if (ret)
+ goto err_restore_page;
+
+err_restore_page:
+ return phy_restore_page(phydev, old_page, ret);
+}
+
+/**
+ * yt8824_probe()
+ * @phydev: a pointer to a &struct phy_device
+ *
+ * returns 0 or negative errno code
+ */
+static int yt8824_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct yt8521_priv *priv;
+ u32 phy_base_addr;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ /* get base addr and top addr for external YT8824 */
+ if (!device_property_read_u32(dev, "motorcomm,base-address",
+ &phy_base_addr))
+ priv->phy_base_addr = phy_base_addr;
+ else
+ priv->phy_base_addr = PHY_BASE_ADDR;
+
+ priv->top_phy_addr = priv->phy_base_addr + 4;
+
+ return 0;
+}
+
+static struct phy_driver motorcomm_phy_drvs[] = {
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8511),
+ .name = "YT8511 Gigabit Ethernet",
+ .config_init = yt8511_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_page = yt8511_read_page,
+ .write_page = yt8511_write_page,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8521),
+ .name = "YT8521 Gigabit Ethernet",
+ .get_features = yt8521_get_features,
+ .probe = yt8521_probe,
+ .read_page = yt8521_read_page,
+ .write_page = yt8521_write_page,
+ .get_wol = ytphy_get_wol,
+ .set_wol = ytphy_set_wol,
+ .config_aneg = yt8521_config_aneg,
+ .aneg_done = yt8521_aneg_done,
+ .config_init = yt8521_config_init,
+ .read_status = yt8521_read_status,
+ .soft_reset = yt8521_soft_reset,
+ .suspend = yt8521_suspend,
+ .resume = yt8521_resume,
+ .led_hw_is_supported = yt8521_led_hw_is_supported,
+ .led_hw_control_set = yt8521_led_hw_control_set,
+ .led_hw_control_get = yt8521_led_hw_control_get,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8522),
+ .name = "YT8522 100 Megabit Ethernet",
+ .config_aneg = genphy_config_aneg,
+ .config_init = yt8522_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8531),
+ .name = "YT8531 Gigabit Ethernet",
+ .probe = yt8531_probe,
+ .config_init = yt8531_config_init,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .get_wol = ytphy_get_wol,
+ .set_wol = yt8531_set_wol,
+ .link_change_notify = yt8531_link_change_notify,
+ .led_hw_is_supported = yt8521_led_hw_is_supported,
+ .led_hw_control_set = yt8521_led_hw_control_set,
+ .led_hw_control_get = yt8521_led_hw_control_get,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S),
+ .name = "YT8531S Gigabit Ethernet",
+ .get_features = yt8521_get_features,
+ .probe = yt8521_probe,
+ .read_page = yt8521_read_page,
+ .write_page = yt8521_write_page,
+ .get_wol = ytphy_get_wol,
+ .set_wol = ytphy_set_wol,
+ .config_aneg = yt8521_config_aneg,
+ .aneg_done = yt8521_aneg_done,
+ .config_init = yt8521_config_init,
+ .read_status = yt8521_read_status,
+ .soft_reset = yt8521_soft_reset,
+ .suspend = yt8521_suspend,
+ .resume = yt8521_resume,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8821),
+ .name = "YT8821 2.5Gbps PHY",
+ .get_features = yt8821_get_features,
+ .read_page = yt8521_read_page,
+ .write_page = yt8521_write_page,
+ .get_wol = ytphy_get_wol,
+ .set_wol = ytphy_set_wol,
+ .config_aneg = genphy_config_aneg,
+ .aneg_done = yt8821_aneg_done,
+ .config_init = yt8821_config_init,
+ .get_rate_matching = yt8821_get_rate_matching,
+ .read_status = yt8821_read_status,
+ .soft_reset = yt8821_soft_reset,
+ .suspend = yt8821_suspend,
+ .resume = yt8821_resume,
+ },
+ {
+ PHY_ID_MATCH_EXACT(PHY_ID_YT8824),
+ .name = "YT8824 Quad Ports 2.5Gbps Ethernet",
+ .get_features = yt8821_get_features,
+ .read_page = yt8824_read_page,
+ .write_page = yt8824_write_page,
+ .config_intr = yt8824_config_intr,
+ .handle_interrupt = yt8824_handle_interrupt,
+ .probe = yt8824_probe,
+ .config_aneg = yt8824_config_aneg,
+ .aneg_done = yt8824_aneg_done,
+ .config_init = yt8824_config_init,
+ .read_status = yt8824_read_status,
+ .soft_reset = yt8824_soft_reset,
+ .suspend = yt8824_suspend,
+ .resume = yt8824_resume,
},
};
module_phy_driver(motorcomm_phy_drvs);
-MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821 PHY driver");
+MODULE_DESCRIPTION("Motorcomm 8511/8521/8531/8531S/8821/8824 PHY driver");
MODULE_AUTHOR("Peter Geis");
MODULE_AUTHOR("Frank");
+MODULE_AUTHOR("Kyle");
MODULE_LICENSE("GPL");
static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
@@ -3161,6 +4810,7 @@ static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531) },
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8531S) },
{ PHY_ID_MATCH_EXACT(PHY_ID_YT8821) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_YT8824) },
{ /* sentinel */ }
};
--
2.25.1
^ permalink raw reply related
* Re: [PATCH net] net: hsr: clean up the slave VLAN filters when deleting a port
From: Jiayuan Chen @ 2026-07-14 11:13 UTC (permalink / raw)
To: Felix Maurer, Eric Dumazet
Cc: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kees Cook, Łukasz Majewski,
Fernando Fernandez Mancera, Hangbin Liu, Jakub Acs,
Xiaoliang Yang, MD Danish Anwar, Murali Karicheri, Jiri Pirko,
linux-kernel
In-Reply-To: <alYYhIPZBbMo5qMe@thinkpad>
On 7/14/26 7:07 PM, Felix Maurer wrote:
> On Tue, Jul 14, 2026 at 04:06:36PM +0800, Jiayuan Chen wrote:
>> Our internal syzkaller reported this warning:
>>
>> netdevsim netdevsim10 eth0: entered promiscuous mode
>> netdevsim netdevsim10 eth1: entered promiscuous mode
>> netdevsim netdevsim10 eth1 (unregistering): left promiscuous mode
>> ------------[ cut here ]------------
>> WARNING: drivers/net/netdevsim/netdev.c:1208 at nsim_destroy+0x276/0x6e0, CPU#3: 1/46
>> Modules linked in:
>> CPU: 3 UID: 0 PID: 46 Comm: kworker/u16:1 Not tainted 7.2.0-rc2+ #286 PREEMPT
>> Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
>> Workqueue: netns cleanup_net
>> RIP: 0010:nsim_destroy (drivers/net/netdevsim/netdev.c:1031 drivers/net/netdevsim/netdev.c:1201)
>> RSP: 0018:ffffc90000337898 EFLAGS: 00010293
>> RAX: 0000000000000001 RBX: ffff8881036c8b00 RCX: 0000000000000000
>> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
>> RBP: ffffc900003378f0 R08: 0000000000000000 R09: 0000000000000000
>> R10: ffffffffad4186a8 R11: ffffffffad41c3db R12: ffff8881036c9268
>> R13: dffffc0000000000 R14: ffffed10206d9164 R15: 0000000000000001
>> FS: 0000000000000000(0000) GS:ffff888160866000(0000) knlGS:0000000000000000
>> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
>> CR2: 000055bcf1a2e0c4 CR3: 0000000105376003 CR4: 0000000000770ef0
>> PKRU: 55555554
>> Call Trace:
>> <TASK>
>> __nsim_dev_port_del (drivers/net/netdevsim/dev.c:1547)
>> nsim_dev_reload_destroy (drivers/net/netdevsim/dev.c:1561 drivers/net/netdevsim/dev.c:1785)
>> nsim_dev_reload_down (drivers/net/netdevsim/dev.c:1038)
>> devlink_reload (net/devlink/dev.c:462)
>> devlink_pernet_pre_exit (net/devlink/core.c:578)
>> ops_undo_list (net/core/net_namespace.c:161 net/core/net_namespace.c:235)
>> cleanup_net (net/core/net_namespace.c:706)
>> process_one_work (kernel/workqueue.c:3322)
>> worker_thread (kernel/workqueue.c:3405 kernel/workqueue.c:3486)
>> kthread (kernel/kthread.c:436)
>> ret_from_fork (arch/x86/kernel/process.c:158)
>> ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
>> </TASK>
>> ---[ end trace 0000000000000000 ]---
>> netdevsim netdevsim10 eth0 (unregistering): left promiscuous mode
>>
>> vlan_vids_add_by_dev() is what passes the VID filter info down to the real
>> device. A virtual device does no filtering itself, so it has to push the
>> VIDs down to its slaves. HSR already does this correctly on the add side:
>> hsr_ndo_vlan_rx_add_vid() calls vlan_vid_add() on each slave.
>>
>> We just never clean the slaves up on delete. hsr_del_port() drops the port
>> and leaves the VIDs sitting on the slave, so the filter entries leak.
>> netdevsim keeps a bitmap of the VIDs it was told to filter and checks that
>> it is empty when the netdev goes away, which is what trips the warning
>> above.
>>
>> So do the cleanup in hsr_del_port(). We follow hsr_ndo_vlan_rx_add_vid()
>> here: it only pushes the VIDs to HSR_PT_SLAVE_A and HSR_PT_SLAVE_B, so the
>> del side only touches those two as well.
>>
>> Reproducer:
>> ip netns add ns0
>> ip netns exec ns0 sh -c 'echo "10 2" > /sys/bus/netdevsim/new_device'
>> ip netns exec ns0 ip link add hsr0 type hsr slave1 eth0 slave2 eth1
>> ip netns exec ns0 ip link add link hsr0 name hsr0.1 type vlan id 1
>> ip netns del ns0
> Thank you for your patch! The explanation and the patch look very
> similar to a fix that Eric posted a week ago [1]. Can you check if
> Eric's patch also fixes you reproducer? And if it does, please add your
> Tested-by and/or Reviewed-by to the thread?
>
> Thanks,
> Felix
>
>
> [1]: https://lore.kernel.org/netdev/20260707082327.3238690-1-edumazet@google.com/
Thanks, It seems my patch is duplicated.
^ permalink raw reply
* Re: [PATCH nf v3 2/2] ipvs: use bitops for destination overload state
From: Julian Anastasov @ 2026-07-14 11:16 UTC (permalink / raw)
To: Yizhou Zhao
Cc: David Ahern, Ido Schimmel, Simon Horman, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Pablo Neira Ayuso,
Florian Westphal, Phil Sutter, netdev, lvs-devel, linux-kernel,
netfilter-devel, coreteam, stable, Yuxiang Yang, Ao Wang,
Xuewei Feng, Qi Li, Ke Xu
In-Reply-To: <edc095e05c89cc6481613126de5f2a91ed601fa9.1783931964.git.zhaoyz24@mails.tsinghua.edu.cn>
Hi Yizhou,
On Mon, 13 Jul 2026, Yizhou Zhao wrote:
> IPVS destination schedulers read the overload state from packet processing
> paths, while connection accounting and destination updates can change it
> concurrently. IP_VS_DEST_F_OVERLOAD currently shares dest->flags with
> IP_VS_DEST_F_AVAILABLE, so plain read-modify-write operations on the two
> independent states can race and lose either update.
>
> KCSAN reports the race with the SH scheduler and an upper connection
> threshold configured:
>
> BUG: KCSAN: data-race in __ip_vs_update_dest / ip_vs_sh_schedule
>
> IP_VS_DEST_F_AVAILABLE is changed under service_mutex. Keep it in the
> existing flags word, but move the overload state to a separate unsigned
> long and access it with bitops. Use test_bit() in scheduler paths and
> set_bit()/clear_bit() in ip_vs_dest_update_overload(). This serializes the
> overload bit accesses and prevents updates to the available and overload
> states from clobbering each other.
>
> The destination flags are not exposed by the IPVS sockopt or netlink
> interfaces, so move their definitions out of the UAPI header. Place the
> new overload word next to weight, which keeps the existing flags,
> conn_flags and weight offsets unchanged. On x86-64 this grows struct
> ip_vs_dest from 472 to 480 bytes.
>
> test_bit() does not add reader-side ordering. Schedulers can still observe
> stale destination state, as they could before this change; this does not
> provide a fresh cross-field snapshot.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@vger.kernel.org
> Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
> Reported-by: Ao Wang <wangao@seu.edu.cn>
> Reported-by: Xuewei Feng <fengxw06@126.com>
> Reported-by: Qi Li <qli01@tsinghua.edu.cn>
> Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
> Assisted-by: Claude-Code:GLM-5.2
> Suggested-by: Julian Anastasov <ja@ssi.bg>
> Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> ---
> include/net/ip_vs.h | 8 ++++++++
> include/uapi/linux/ip_vs.h | 6 ------
> net/netfilter/ipvs/ip_vs_conn.c | 7 ++++---
> net/netfilter/ipvs/ip_vs_dh.c | 4 ++--
> net/netfilter/ipvs/ip_vs_fo.c | 2 +-
> net/netfilter/ipvs/ip_vs_lblc.c | 4 ++--
> net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++++----
> net/netfilter/ipvs/ip_vs_lc.c | 2 +-
> net/netfilter/ipvs/ip_vs_mh.c | 2 +-
> net/netfilter/ipvs/ip_vs_nq.c | 2 +-
> net/netfilter/ipvs/ip_vs_ovf.c | 2 +-
> net/netfilter/ipvs/ip_vs_rr.c | 2 +-
> net/netfilter/ipvs/ip_vs_sed.c | 4 ++--
> net/netfilter/ipvs/ip_vs_sh.c | 2 +-
> net/netfilter/ipvs/ip_vs_twos.c | 4 ++--
> net/netfilter/ipvs/ip_vs_wlc.c | 4 ++--
> net/netfilter/ipvs/ip_vs_wrr.c | 2 +-
> 17 files changed, 34 insertions(+), 31 deletions(-)
>
> diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
> index 3fc864a320fb..5e8e55f82b04 100644
> --- a/include/net/ip_vs.h
> +++ b/include/net/ip_vs.h
> @@ -36,6 +36,13 @@
> #define IP_VS_HDR_INVERSE 1
> #define IP_VS_HDR_ICMP 2
>
> +/* Destination Server Flags */
> +#define IP_VS_DEST_F_AVAILABLE 0x0001 /* server is available */
> +
> +enum {
> + IP_VS_DEST_FL_OVERLOAD,
> +};
> +
> /* conn_tab limits (as per Kconfig) */
> #define IP_VS_CONN_TAB_MIN_BITS 8
> #if BITS_PER_LONG > 32
> @@ -976,6 +983,7 @@ struct ip_vs_dest {
> volatile unsigned int flags; /* dest status flags */
Sashiko has some comments that we should fix somehow:
https://sashiko.dev/#/patchset/cover.1783931964.git.zhaoyz24%40mails.tsinghua.edu.cn
One option is IP_VS_DEST_F_AVAILABLE to become
IP_VS_DEST_CF_AVAILABLE (CF=Config Flag)
> atomic_t conn_flags; /* flags to copy to conn */
> atomic_t weight; /* server weight */
> + unsigned long flags2; /* dest status flags */
unsigned long cfg_flags;
We then put IP_VS_DEST_CF_AVAILABLE in this new cache line
that most of the schedulers will not read until dest is selected.
DH even should not check the IP_VS_DEST_F_AVAILABLE flag,
only lblc/lblcr should use this flag.
We can preserve IP_VS_DEST_F_OVERLOAD in 'flags',
even we may not need to use bitops if we start to use
spin_lock_bh(&dest->dst_lock), as this lock is already
present in the dest structure. See below...
> atomic_t last_weight; /* server latest weight */
> __u16 tun_type; /* tunnel type */
> __be16 tun_port; /* tunnel port */
> diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
> index fa3fbd597f3f..2591f4e143f8 100644
> --- a/net/netfilter/ipvs/ip_vs_conn.c
> +++ b/net/netfilter/ipvs/ip_vs_conn.c
> @@ -1006,7 +1006,7 @@ __always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
We can add new arg 'bool locked'. Also, we will
return false if caller should retry under lock.
It will happen when we change IP_VS_DEST_F_OVERLOAD and
require its changes to be synchronized with the
thresholds and the number of connections.
> goto unset;
> conns = ip_vs_dest_totalconns(dest);
> if (conns >= u) {
> - dest->flags |= IP_VS_DEST_F_OVERLOAD;
> + set_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
if (conns >= u) {
if (!locked)
return false;
dest->flags |= IP_VS_DEST_F_OVERLOAD;
return true;
}
> return;
> }
> /* Low threshold defaults to 75% of upper threshold */
> @@ -1015,7 +1015,8 @@ __always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
> return;
>
> unset:
> - dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
> + if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
> + clear_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2);
if (dest->flags & IP_VS_DEST_F_OVERLOAD) {
if (!locked)
return false;
dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
}
return true;
> }
>
> /*
> @@ -1174,7 +1175,7 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
> atomic_dec(&dest->persistconns);
> }
>
> - if (dest->flags & IP_VS_DEST_F_OVERLOAD)
> + if (test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2))
> ip_vs_dest_update_overload(dest);
if (dest->flags & IP_VS_DEST_F_OVERLOAD) {
if (!ip_vs_dest_update_overload(dest, false)) {
spin_lock_bh(&dest->dst_lock);
ip_vs_dest_update_overload(dest, true);
spin_unlock_bh(&dest->dst_lock);
}
}
In __ip_vs_update_dest() we will always use lock:
spin_lock_bh(&dest->dst_lock);
WRITE_ONCE(dest->u_threshold, udest->u_threshold);
WRITE_ONCE(dest->l_threshold, udest->l_threshold);
ip_vs_dest_update_overload(dest, true);
spin_unlock_bh(&dest->dst_lock);
The goal is to avoid the lock for the common case
when flag does not change. What do you think?
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply
* Re: [PATCH net v2] net: gre: fix lltx regression for GRE tunnels with SEQ/CSUM
From: Ido Schimmel @ 2026-07-14 11:33 UTC (permalink / raw)
To: Yun Zhou
Cc: dsahern, davem, edumazet, kuba, pabeni, horms, netdev,
linux-kernel
In-Reply-To: <20260713150945.1779628-1-yun.zhou@windriver.com>
On Mon, Jul 13, 2026 at 11:09:45PM +0800, Yun Zhou wrote:
> Before commit 00d066a4d4ed ("netdev_features: convert NETIF_F_LLTX to
> dev->lltx"), NETIF_F_LLTX was set unconditionally in both
> __gre_tunnel_init() and ip6gre_tnl_init_features() alongside
> GRE_FEATURES:
>
> dev->features |= GRE_FEATURES | NETIF_F_LLTX;
>
> When that commit converted NETIF_F_LLTX to the dev->lltx flag, it
> placed 'dev->lltx = true' after the SEQ/CSUM early returns instead
> of before them. This causes GRE/GRETAP/ip6gre tunnels with SEQ or
> CSUM+encap to lose lockless TX, reintroducing _xmit_lock acquisition
> around their ndo_start_xmit. Since GRE xmit re-enters the stack via
> ip_tunnel_xmit(), holding _xmit_lock risks ABBA deadlock with the
> underlay device.
>
> CPU0 CPU1
> ---- ----
> lock(&qdisc_xmit_lock_key#6);
> lock(&qdisc_xmit_lock_key#3);
> lock(&qdisc_xmit_lock_key#6);
> lock(&qdisc_xmit_lock_key#3);
>
> Fix by moving dev->lltx = true before the early returns in both
> functions, restoring the original unconditional behavior.
>
> Fixes: 00d066a4d4ed ("netdev_features: convert NETIF_F_LLTX to dev->lltx")
> Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply
* Re: [PATCH net-next v2 1/6] net: hold instance lock around NETDEV_DOWN/GOING_DOWN
From: Kuniyuki Iwashima @ 2026-07-14 11:36 UTC (permalink / raw)
To: sdf.kernel; +Cc: davem, edumazet, kuba, netdev, pabeni
In-Reply-To: <20260702224150.3730033-2-sdf@fomichev.me>
From: Stanislav Fomichev <sdf.kernel@gmail.com>
Date: Thu, 2 Jul 2026 15:41:45 -0700
> Mirror what call_netdevice_register_net_notifiers does but for the
> teardown. Cover only DOWN and GOING_DOWN. UNREGISTER is still unlocked
> because of the SW devices using dev_xxx methods.
>
> Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
> ---
> net/core/dev.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/net/core/dev.c b/net/core/dev.c
> index 4b3d5cfdf6e0..9d49493f4fb5 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -1912,9 +1912,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb,
> struct net_device *dev)
> {
> if (dev->flags & IFF_UP) {
> + netdev_lock_ops(dev);
> call_netdevice_notifier(nb, NETDEV_GOING_DOWN,
> dev);
> call_netdevice_notifier(nb, NETDEV_DOWN, dev);
FYI, this is the report I mentioned yesterday.
Most likely false-positive because open/close, which takes RTNL,
are never called under epoll lock.
https://lore.kernel.org/netdev/6a55fc56.c90005c7.37d349.001c.GAE@google.com/
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox