Skip to content
Merged
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ echo "3:${IF0_IDX} 3:${IF1_IDX}" > /sys/bus/netdevsim/link_device
exec 3<&-
```

The mock PHC exposes a per-device `extts_delay_ms` sysfs attribute under
`/sys/class/nsim_ptp/nsim_ptpN/`. It delays EXTTS/PPS event delivery after each
`CLOCK_TAI` second boundary while keeping the event timestamp aligned to the
configured delayed edge. The default is `0`; values from `0` to `900` ms are
accepted. For example, a 400 ms delay exceeds linuxptp ts2phc's 333 ms poll
window:

```bash
echo 400 | sudo tee /sys/class/nsim_ptp/nsim_ptp0/extts_delay_ms
# Restore normal PPS timing
echo 0 | sudo tee /sys/class/nsim_ptp/nsim_ptp0/extts_delay_ms
```

## Container / Kubernetes Notes

When using netdevsim inside containers or Kubernetes pods:
Expand Down
3 changes: 3 additions & 0 deletions include/linux/ptp_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <linux/ptp_clock_kernel.h>
#include <linux/kref.h>
#include <linux/hrtimer.h>
#include <linux/sysfs.h>

struct device;
struct mock_phc {
Expand All @@ -34,6 +35,8 @@ struct mock_phc {
bool extts_enabled;
int extts_channel;
u64 last_extts_sec; /* dedup: last CLOCK_TAI second we reported */
unsigned int extts_delay_ms; /* delivery delay after each TAI second boundary */
struct kobj_attribute extts_delay_attr;
};

#if IS_ENABLED(CONFIG_PTP_1588_CLOCK_MOCK)
Expand Down
85 changes: 72 additions & 13 deletions ptp/ptp_mock.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ static int mock_phc_gettime64(struct ptp_clock_info *info,
}

/*
* Simulate a GNSS 1PPS input: one EXTTS edge per CLOCK_TAI second, with
* the event timestamp equal to the PHC reading at that edge.
* Simulate a GNSS 1PPS input: one EXTTS event per CLOCK_TAI second, optionally
* delayed by extts_delay_ms, with a timestamp captured at the delayed edge.
*
* Why TAI (not PHC) second boundaries?
* ts2phc pairs EXTTS with NMEA, which is UTC converted to TAI via the
Expand All @@ -129,22 +129,25 @@ static int mock_phc_gettime64(struct ptp_clock_info *info,
* (offset 0 ↔ -1e9), which flapped T-GM clock class 6↔248 and broke
* downstream OC lock.
*
* Why report the PHC reading corrected back to the TAI edge?
* A polled hrtimer can run up to ~1 ms after the boundary. Stamping
* the live PHC then injects that latency into every sample (typically
* Why correct the timestamp to the configured edge?
* A polled hrtimer can run up to ~1 ms after the edge. Stamping the
* live PHC then injects that latency into every sample (typically
* several µs–ms of jitter), which exceeds ts2phc's lock threshold
* (1500 ns). Subtracting the observed TAI lateness approximates the
* PHC capture at the true edge (valid while |freq_ppb| is modest).
* (1500 ns). Subtract timer lateness beyond the configured delay so
* the timestamp approximates the PHC capture at the delayed edge
* (valid while |freq_ppb| is modest).
*/
#define EXTTS_IDLE_POLL_NS (NSEC_PER_SEC / 10) /* 100 ms while disabled */
#define EXTTS_EDGE_SLACK_NS (NSEC_PER_MSEC) /* wake 1 ms after edge */
#define EXTTS_MAX_DELAY_MS 900 /* keep events within the TAI second */

static enum hrtimer_restart mock_phc_extts_timer(struct hrtimer *timer)
{
struct mock_phc *phc = container_of(timer, struct mock_phc, extts_timer);
struct ptp_clock_event event;
unsigned long flags;
u64 tai_ns, tai_sec, phc_ns, late_ns, next_delta_ns;
u64 delay_ns = (u64)READ_ONCE(phc->extts_delay_ms) * NSEC_PER_MSEC;

if (!READ_ONCE(phc->extts_enabled)) {
hrtimer_forward_now(timer, ns_to_ktime(EXTTS_IDLE_POLL_NS));
Expand All @@ -156,20 +159,29 @@ static enum hrtimer_restart mock_phc_extts_timer(struct hrtimer *timer)
late_ns = tai_ns - tai_sec * NSEC_PER_SEC;

if (tai_sec != phc->last_extts_sec) {
/* Wait until the configured phase delay has elapsed after the edge. */
if (late_ns < delay_ns) {
hrtimer_forward_now(timer,
ns_to_ktime(delay_ns - late_ns));
return HRTIMER_RESTART;
}

phc->last_extts_sec = tai_sec;

spin_lock_irqsave(&phc->lock, flags);
phc_ns = mock_phc_read_locked(phc);
spin_unlock_irqrestore(&phc->lock, flags);

event.timestamp = phc_ns - late_ns;
/* Timestamp the delayed edge, removing only timer scheduling jitter. */
event.timestamp = phc_ns - (late_ns - delay_ns);
event.type = PTP_CLOCK_EXTTS;
event.index = phc->extts_channel;
ptp_clock_event(phc->clock, &event);
}

/* Aim for ~1 ms after the next TAI second (MONOTONIC ≈ TAI rate). */
next_delta_ns = (NSEC_PER_SEC - late_ns) + EXTTS_EDGE_SLACK_NS;
/* Aim for the configured delay after the next TAI second. */
next_delta_ns = (NSEC_PER_SEC - late_ns) + delay_ns +
EXTTS_EDGE_SLACK_NS;
if (next_delta_ns < EXTTS_EDGE_SLACK_NS)
next_delta_ns = EXTTS_EDGE_SLACK_NS;
hrtimer_forward_now(timer, ns_to_ktime(next_delta_ns));
Expand Down Expand Up @@ -224,6 +236,40 @@ static int mock_phc_enable(struct ptp_clock_info *info,
}
}

static ssize_t extts_delay_ms_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct mock_phc *phc = container_of(attr, struct mock_phc,
extts_delay_attr);

return sysfs_emit(buf, "%u\n", READ_ONCE(phc->extts_delay_ms));
}

static ssize_t extts_delay_ms_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct mock_phc *phc = container_of(attr, struct mock_phc,
extts_delay_attr);
unsigned int delay_ms;
int err;

err = kstrtouint(buf, 0, &delay_ms);
if (err)
return err;
if (delay_ms > EXTTS_MAX_DELAY_MS)
return -ERANGE;

WRITE_ONCE(phc->extts_delay_ms, delay_ms);
/* Re-evaluate promptly, especially when lowering an active delay. */
if (READ_ONCE(phc->extts_enabled))
hrtimer_start(&phc->extts_timer,
ns_to_ktime(EXTTS_EDGE_SLACK_NS),
HRTIMER_MODE_REL);

return count;
}

int mock_phc_index(struct mock_phc *phc)
{
return ptp_clock_index(phc->clock);
Expand Down Expand Up @@ -306,14 +352,25 @@ struct mock_phc *mock_phc_create(struct device *dev, int logical_clk_id)
phc->freq_ppb = 0;

phc->clock = ptp_clock_register(&phc->info, dev);
if (!IS_ERR(phc->clock))
hrtimer_start(&phc->extts_timer, ns_to_ktime(EXTTS_IDLE_POLL_NS),
HRTIMER_MODE_REL);
if (IS_ERR(phc->clock)) {
err = PTR_ERR(phc->clock);
goto out_free_phc;
}

sysfs_attr_init(&phc->extts_delay_attr.attr);
phc->extts_delay_attr.attr.name = "extts_delay_ms";
phc->extts_delay_attr.attr.mode = 0644;
phc->extts_delay_attr.show = extts_delay_ms_show;
phc->extts_delay_attr.store = extts_delay_ms_store;
err = sysfs_create_file(mock_phc_dev_kobj(phc),
&phc->extts_delay_attr.attr);
if (err) {
ptp_clock_unregister(phc->clock);
goto out_free_phc;
}

hrtimer_start(&phc->extts_timer, ns_to_ktime(EXTTS_IDLE_POLL_NS),
HRTIMER_MODE_REL);
return phc;

out_free_phc:
Expand All @@ -327,6 +384,8 @@ static void mock_phc_destroy(struct kref *ref)
{
struct mock_phc *phc = container_of(ref, struct mock_phc, ref);

sysfs_remove_file(mock_phc_dev_kobj(phc),
Comment thread
lack marked this conversation as resolved.
Resolved
&phc->extts_delay_attr.attr);
hrtimer_cancel(&phc->extts_timer);
ptp_clock_unregister(phc->clock);
kfree(phc);
Expand Down
71 changes: 53 additions & 18 deletions scripts/test-phc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# - adjtime (time step)
# - adjfine (frequency adjustment via scaled_ppm)
# - EXTTS enable, event delivery, second-boundary alignment
# - Configurable per-PHC EXTTS delivery delay
# - EXTTS self-correction after PHC time step
# - PHC sharing across ports (same logical_clk_id)
# - Pin configuration (2 pins: NONE, GNSS1PPS)
Expand Down Expand Up @@ -721,9 +722,43 @@ fi
echo

# -------------------------------------------------------------------
# 10. EXTTS self-correction after PHC time step
# 10. Configurable EXTTS delivery delay
# -------------------------------------------------------------------
log "10. EXTTS self-correction after time step"
log "10. Configurable EXTTS delivery delay"

EXTTS_DELAY_ATTR="/sys/class/nsim_ptp/${PTP_CLASS_DEV}/extts_delay_ms"
if [[ -w "$EXTTS_DELAY_ATTR" ]]; then
# A 400 ms delay exceeds linuxptp ts2phc's 333 ms polling window.
echo 400 > "$EXTTS_DELAY_ATTR"
assert_eq "EXTTS delay sysfs readback" "400" "$(cat "$EXTTS_DELAY_ATTR")"

phc_cmd settime "$PTP_DEV" 1700000000 0 >/dev/null
phc_cmd adjfine "$PTP_DEV" 0 >/dev/null
phc_cmd enable_extts "$PTP_DEV" 0 >/dev/null
EVENTS=$(phc_cmd read_extts "$PTP_DEV" 5.0)
phc_cmd disable_extts "$PTP_DEV" 0 >/dev/null
echo 0 > "$EXTTS_DELAY_ATTR"

DELAY_OK=$(echo "$EVENTS" | python3 -c "
import sys, json
values = [e['nsec'] for e in json.load(sys.stdin)]
print('ok' if len(values) >= 2 and all(390000000 <= n <= 410000000 for n in values) else 'bad:' + repr(values))
")
if [[ "$DELAY_OK" == "ok" ]]; then
pass "EXTTS event timestamps reflect configured 400 ms delay"
else
fail "EXTTS delay was not applied as expected ($DELAY_OK)"
fi
else
skip "EXTTS delay sysfs attribute unavailable: $EXTTS_DELAY_ATTR"
fi

echo

# -------------------------------------------------------------------
# 11. EXTTS self-correction after PHC time step
# -------------------------------------------------------------------
log "11. EXTTS self-correction after time step"

phc_cmd settime "$PTP_DEV" 1700000000 0 >/dev/null
phc_cmd adjfine "$PTP_DEV" 0 >/dev/null
Expand Down Expand Up @@ -773,9 +808,9 @@ phc_cmd disable_extts "$PTP_DEV" 0 >/dev/null
echo

# -------------------------------------------------------------------
# 11. EXTTS with frequency adjustment
# 12. EXTTS with frequency adjustment
# -------------------------------------------------------------------
log "11. EXTTS with frequency adjustment"
log "12. EXTTS with frequency adjustment"

phc_cmd settime "$PTP_DEV" 1700000000 0 >/dev/null
phc_cmd adjfine "$PTP_DEV" 6553600 >/dev/null # +100 ppm
Expand Down Expand Up @@ -813,9 +848,9 @@ fi
echo

# -------------------------------------------------------------------
# 12. PHC sharing (same logical_clk_id)
# 13. PHC sharing (same logical_clk_id)
# -------------------------------------------------------------------
log "12. PHC sharing across ports (same logical_clk_id)"
log "13. PHC sharing across ports (same logical_clk_id)"

cleanup_all_devices
create_device 1 1 2 1 1 # 2 ports, clock_id=1
Expand Down Expand Up @@ -846,9 +881,9 @@ fi
echo

# -------------------------------------------------------------------
# 13. Multiple settime64 operations
# 14. Multiple settime64 operations
# -------------------------------------------------------------------
log "13. Multiple settime64 operations (no corruption)"
log "14. Multiple settime64 operations (no corruption)"

cleanup_all_devices
create_device 1 1 2 1 1
Expand Down Expand Up @@ -879,9 +914,9 @@ fi
echo

# -------------------------------------------------------------------
# 14. REGRESSION: EXTTS with realistic TAI time (large offset_ns)
# 15. REGRESSION: EXTTS with realistic TAI time (large offset_ns)
# -------------------------------------------------------------------
log "14. REGRESSION: EXTTS with realistic TAI time (offset_ns ≈ 56 years)"
log "15. REGRESSION: EXTTS with realistic TAI time (offset_ns ≈ 56 years)"

# Bug: with CLOCK_MONOTONIC_RAW base, setting PHC to TAI time created
# offset_ns ≈ 1.78e18. ts2phc's servo drove freq_ppb to -500M, causing
Expand Down Expand Up @@ -962,9 +997,9 @@ fi
echo

# -------------------------------------------------------------------
# 15. REGRESSION: EXTTS must not rapid-fire with extreme freq_ppb
# 16. REGRESSION: EXTTS must not rapid-fire with extreme freq_ppb
# -------------------------------------------------------------------
log "15. REGRESSION: EXTTS with extreme freq_ppb = -500M"
log "16. REGRESSION: EXTTS with extreme freq_ppb = -500M"

# Bug: when freq_ppb = -500000000 (max negative), PHC advances at 50%
# speed. With CLOCK_MONOTONIC_RAW the EXTTS delay was doubled, but the
Expand Down Expand Up @@ -1023,9 +1058,9 @@ fi
echo

# -------------------------------------------------------------------
# 16. REGRESSION: freq_ppb stays small after realistic settime
# 17. REGRESSION: freq_ppb stays small after realistic settime
# -------------------------------------------------------------------
log "16. REGRESSION: freq_ppb stays near zero with MONOTONIC base"
log "17. REGRESSION: freq_ppb stays near zero with MONOTONIC base"

# Bug: with CLOCK_MONOTONIC_RAW, after settime to TAI, the PHC drifted
# and ts2phc pushed freq_ppb to -500M. With CLOCK_MONOTONIC base, the
Expand Down Expand Up @@ -1076,9 +1111,9 @@ fi
echo

# -------------------------------------------------------------------
# 17. dmesg sanity
# 18. dmesg sanity
# -------------------------------------------------------------------
log "17. dmesg sanity check"
log "18. dmesg sanity check"

DMESG_PHC=$(dmesg | grep -i "ptp_mock\|mock_phc\|nsim_ptp" | tail -20 || true)
if echo "$DMESG_PHC" | grep -qiE "bug|oops|panic|call.trace|rcu.*stall"; then
Expand All @@ -1091,9 +1126,9 @@ fi
echo

# -------------------------------------------------------------------
# 18. Cleanup
# 19. Cleanup
# -------------------------------------------------------------------
log "18. Cleanup"
log "19. Cleanup"

cleanup_all_devices
pass "all devices cleaned up"
Expand Down
Loading