Thread (29 messages) 29 messages, 6 authors, 6d ago

Re: [PATCH net-next v6 8/9] net: dsa: realtek: rtl8365mb: re-latch the family D SerDes

flat view

From: Linus Walleij <linusw@kernel.org>
Date: 2026-10-16 20:17:31
Also in: lkml

On Fri, Oct 16, 2026 at 3:08 AM Mieczyslaw Nalewaj [off-list ref] wrote:
The family D receiver latches on the DISABLE -> HSGMII edge in SDS_MISC
rather than on the value, and it will not latch until the MAC at the
other end of the trunk has brought its own half of the link up. If it
does not latch, the trunk reports 2.5Gbps/Full on both sides but moves
no frames: the CPU MAC transmits, the switch CPU port counts no octets,
and there are no CRC, alignment or FIFO errors.

The two ends can be seconds apart, e.g. on an IPQ5018 board:

  5.27  rtl8365mb: configuring for fixed/2500base-x   <- pcs_config()
  7.72  ipq5018-gmac-dwmac eth0: configuring for fixed/2500base-x
  8.02  ipq5018-gmac-dwmac eth0: Link is Up - 2.5Gbps/Full
  8.56  SerDes latches

pcs_config() runs seconds before the far-end MAC even starts
configuring, so a single edge driven from there is not enough: the
receiver has nothing to latch onto yet. Neither is pcs_link_up(),
which is seconds early for the same reason. phylink does not help
either - it never calls rtl8365mb_pcs_get_state() for this port in
the first place, since mb->pcs.poll only arms phylink's own poll for
in-band links, and this CPU port is a fixed link.

There is no local signal that predicts when the far-end MAC will
bring up its half of the link (bit 8 of the SDS link-status word
does not change ahead of it), so the work cannot wait for it; it can
only tell afterwards, from the link-status bit, whether an edge took.

The far-end MAC starts when its conduit is brought up, which DSA
reports through ->conduit_state_change(), so an episode of re-latch
attempts starts there. pcs_config() only arms the work, and starts an
episode itself if the conduit is already up. The work re-arms itself
after each edge; the next run checks whether the edge took, rather
than waiting on a poll that will not come. When the conduit goes down
the work is stopped, so a link without a far end is not reported as
lost and not parked every 10 s.

Each attempt (park for 20 ms, then restore the target mode) is
performed by a work item rather than from the callbacks, because the
attempts have to continue for seconds after they have returned.
pcs_config(), pcs_disable(), teardown() and conduit_state_change() are
serialized by a new sds_cfg_lock and cancel the work before touching
SDS_MISC or restarting it, so an attempt never interleaves with a
reconfiguration. Before parking, the work reads the link status itself
and does not park if the receiver has already latched, since the
condition that queued it may be stale by the time it runs; it then ends
the episode instead.

Attempts are spaced about 1 s apart and capped at
RTL8365MB_D_SDS_RELATCH_MAX_TRIES (15) per fast episode. After the last
attempt one more run checks whether it took and only then warns that
the SerDes did not latch. The work then keeps driving one edge per
RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL, so a far end that comes up late
or a link that drops again is recovered without a reconfiguration,
at a bounded cost of one 20 ms park per interval.

Once the receiver has latched, the work keeps checking the link every
10 s (RTL8365MB_D_SDS_HEALTHCHECK_INTERVAL). If it finds the link down
outside a reconfiguration, e.g. after the far end reset its PLL, it
logs that and starts a new episode.

For family D, pcs_config() keeps the masked update of SDS_MISC and saves
the value it wrote as the target the work restores. The work parks and
restores with masked updates over the same fields, so other bits are
never touched.

The work reads the SerDes link status through the SDS_INDACS indirect
access window, a multi-register ADR/CMD/DATA sequence that the PCS
callbacks also use, so those sequences are serialized by a new sds_lock.
The SDS_MISC updates need no extra locking: each one is a single regmap
read-modify-write.

The work is only armed for 2500base-x, the mode the re-latch edge was
observed with. It is cancelled from pcs_disable(), from pcs_config()
before SDS_MISC is touched, from conduit_state_change() and from
teardown(), and disabled by a reboot notifier and by a devres action.
disable_delayed_work_sync() is used in the latter two so that a late
pcs_config() cannot queue the work again, and so that it never outlives
the PCS or the driver data and does not touch the bus while it is shut
down. dsa_switch_shutdown() does not stop the PCS, which is why the
reboot notifier is needed.

If restoring the target mode fails, the lane stays parked. The mode
field in SDS_MISC tells so, and the next run of the work puts it back
before doing anything else. If the work is stopped before that run, the
lane stays parked until the next pcs_config().

No chip uses family D before the last patch of the series, so this code
is unreachable until then.

Co-developed-by: Oleg Gavrilov <redacted>
Signed-off-by: Oleg Gavrilov <redacted>
Signed-off-by: Mieczyslaw Nalewaj <redacted>
It's a lot of comments and commit message in this patch,
and it's pretty hard to understand. But FWIW it looks good
and maintainable to me:
Reviewed-by: Linus Walleij <linusw@kernel.org>

Yours,
Linus Walleij
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help