Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mattias Walstrom <mattias.walstrom@wires.se>
Date: Mon, 24 Aug 2026 14:35:00 +0200
Subject: [PATCH 52/52] net: dsa: mv88e6xxx: Shorten PTP timecounter refresh
period

Hardware timestamps are reconstructed with timecounter_cyc2time(),
which is only correct for timestamps within half a counter wrap of
cycle_last. On 250MHz devices (4ns/tick, e.g. 88E6190) the 32-bit
counter wraps every 2^34 ns (~17.2s), so half a wrap is ~8.6s. The
overflow work refreshes cycle_last every 8s, leaving only ~0.6s of
margin for delayed_work scheduling latency.

On a loaded system the work can slip past that margin, and timestamps
then convert exactly one wrap (2^34 ns) off. Fed to ptp4l, such a
sample either steps the clock ~17.2s away (recovery at max_adj takes
hours) or repeatedly slams the PI servo into its frequency clamp, so
the clock never converges.

Refresh every 2s instead, giving ~6.6s of margin on the fastest
(4ns/tick) devices. The 8ns and 10ns devices wrap even slower, so the
margin there is larger still. The cost is one TAI read per 2s.

Observed on a CN9130 board with three 88E6190 switches: offsets
reported as -2^34 ns + slew, with sporadic outliers at exactly
true_offset + 2^34 ns.
---
drivers/net/dsa/mv88e6xxx/ptp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c
index 1111111111111..2222222222222 100644
--- a/drivers/net/dsa/mv88e6xxx/ptp.c
+++ b/drivers/net/dsa/mv88e6xxx/ptp.c
@@ -480,7 +480,7 @@ static u64 mv88e6xxx_ptp_clock_read(struct cyclecounter *cc)
/* With a 250MHz input clock, the 32-bit timestamp counter overflows in ~17.2
* seconds; this task forces periodic reads so that we don't miss any.
*/
-#define MV88E6XXX_TAI_OVERFLOW_PERIOD (HZ * 8)
+#define MV88E6XXX_TAI_OVERFLOW_PERIOD (HZ * 2)
static void mv88e6xxx_ptp_overflow_check(struct work_struct *work)
{
struct delayed_work *dw = to_delayed_work(work);
--
2.43.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
From 0000000000000000000000000000000000000001 Mon Sep 17 00:00:00 2001
From: Mattias Walstrom <mattias.walstrom@wires.se>
Date: Tue, 25 Aug 2026 09:00:00 +0200
Subject: [PATCH 53/53] net: dsa: mv88e6xxx: DEBUG: log hardware timestamps at
risk of wrap error

NOT FOR UPSTREAM - debug instrumentation.

Hardware timestamps are reconstructed against the timecounter's
cycle_last; a timestamp more than half a counter wrap away converts
exactly one wrap (2^34 ns on 4ns/tick devices) off. With the refresh
period at 2s, a legitimate conversion is never more than ~2.5s
(~0x25000000 ticks) from cycle_last.

Warn on every conversion in the 4.3s-12.9s band (raw delta between
0x40000000 and 0xc0000000 ticks): these are the candidates for a
2^34 ns wrap error, and the log tells us which port, direction, and
how far off - i.e. whether the refresh work stalled or the timestamp
sat latched too long.
---
drivers/net/dsa/mv88e6xxx/hwtstamp.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
index 3333333333333..4444444444444 100644
--- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c
+++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c
@@ -20,6 +20,26 @@

#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))

+/* DEBUG: must be called with the reg lock held. Warn on conversions in
+ * the danger band around half a counter wrap from cycle_last; on 4ns/tick
+ * devices these risk coming out exactly 2^34 ns off.
+ */
+static u64 mv88e6xxx_hwts_cyc2time(struct mv88e6xxx_chip *chip, int port,
+ const char *dir, u32 raw)
+{
+ u32 last = (u32)chip->tstamp_tc.cycle_last;
+ u32 delta = raw - last;
+ u64 ns;
+
+ ns = timecounter_cyc2time(&chip->tstamp_tc, raw);
+ if (delta > 0x40000000 && delta < 0xc0000000)
+ dev_warn(chip->dev,
+ "p%d: %s hwtstamp %08x is %u ticks from cycle_last %08x, converted %llu - possible 2^34 ns wrap\n",
+ port, dir, raw, delta, last, ns);
+
+ return ns;
+}
+
static int mv88e6xxx_port_ptp_read(struct mv88e6xxx_chip *chip, int port,
int addr, u16 *data, int len)
{
@@ -291,7 +311,7 @@ static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
ns = timehi << 16 | timelo;

mv88e6xxx_reg_lock(chip);
- ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
+ ns = mv88e6xxx_hwts_cyc2time(chip, ps->port_id, "rx", ns);
mv88e6xxx_reg_unlock(chip);
shwt = skb_hwtstamps(skb);
memset(shwt, 0, sizeof(*shwt));
@@ -411,7 +431,7 @@ static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip,
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
time_raw = ((u32)departure_block[2] << 16) | departure_block[1];
mv88e6xxx_reg_lock(chip);
- ns = timecounter_cyc2time(&chip->tstamp_tc, time_raw);
+ ns = mv88e6xxx_hwts_cyc2time(chip, ps->port_id, "tx", time_raw);
mv88e6xxx_reg_unlock(chip);
shhwtstamps.hwtstamp = ns_to_ktime(ns);

--
2.43.0
15 changes: 12 additions & 3 deletions test/case/interfaces/lag_failure/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
each failure mode step using the `mon` interface.

"""
import json
from time import time
import infamy
import infamy.lag
Expand All @@ -37,7 +38,7 @@ def forward(self, lb1, lb2):
getattr(self.lb1, lb1)()
getattr(self.lb2, lb2)()

def fail_check(self, peer):
def fail_check(self, peer, duts=()):
"""Verify connectivity with a given peer during failure."""
sequence = [
("forward", "forward"),
Expand All @@ -55,14 +56,22 @@ def fail_check(self, peer):
try:
print(f"{lb1:<8} | {lb2:<8} | {'...':<8}", end="\r# ")
self.forward(lb1, lb2)
self.net.must_reach(peer, timeout=30)
self.net.must_reach(peer, timeout=60)
print(f"{lb1:<8} | {lb2:<8} | {'OK':<8} in "
f"{time() - state_start:.2f}s")
except Exception as e:
print(f"{lb1:<8} | {lb2:<8} | {'FAIL':<8} after "
f"{time() - state_start:.2f}s")
print(f"\nError encountered: {e}")
print(f"Link breakers were in state: LB1='{lb1}', LB2='{lb2}'")
for dut in duts:
try:
state = dut.get_data("/ietf-interfaces:interfaces"
"/interface[name='lag0']")
print(f"{dut} lag0 state: "
f"{json.dumps(state, indent=2)}")
except Exception as err:
print(f"{dut}: failed to get lag0 state: {err}")
raise

print(f"Total time: {time() - total_start:.2f}s")
Expand Down Expand Up @@ -139,6 +148,6 @@ def dut_init(dut, addr, peer):
ns.must_reach(IP2, timeout=30)

with test.step("Verify failure modes"):
lb.fail_check(IP2)
lb.fail_check(IP2, (dut1, dut2))

test.succeed()
Loading