From e7f568bfa92ef9d55cec91ade4cee6e998a4e141 Mon Sep 17 00:00:00 2001 From: bmandal Date: Fri, 24 Jul 2026 14:50:39 +0200 Subject: [PATCH 1/4] fix: register DPLL pins on both EEC and PPS devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GNSS pin (and ext/port pins) were only registered on the PPS DPLL, resulting in pin.ParentDevice having a single entry. linuxptp-daemon's PhaseOffsetPin() requires len(ParentDevice) > PPS_PIN_INDEX (1) to read the phase offset — with only one parent the check fails, phaseOffset stays at FaultyPhaseOffset (99999999999), isOffsetInRange() returns false, and ptp4l remains at clock class 248 even when DPLL reports LOCKED_HO_ACQ. Register all pins on both EEC (index 0) and PPS (index 1) DPLLs, matching real E810 driver behavior. This gives each pin two parent device entries so the phase offset is read correctly and ptp4l can transition to clock class 6. --- netdevsim/dpll.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/netdevsim/dpll.c b/netdevsim/dpll.c index f95b7a3..ff60745 100644 --- a/netdevsim/dpll.c +++ b/netdevsim/dpll.c @@ -683,7 +683,10 @@ int nsim_dpll_init(struct nsim_dev *nsim_dev) if (err) goto err_eec_put; - /* GNSS input pin on PPS DPLL */ + /* GNSS input pin on both EEC and PPS DPLLs (matches real E810 behavior). + * linuxptp-daemon expects pin.ParentDevice to have entries at both + * index 0 (EEC) and index 1 (PPS) — the phase offset is read from + * ParentDevice[PPS_PIN_INDEX=1].PhaseOffset. */ ndpll->gnss_pin_priv.direction = DPLL_PIN_DIRECTION_INPUT; ndpll->gnss_pin_priv.frequency = DPLL_PIN_FREQUENCY_1_HZ; @@ -695,12 +698,18 @@ int nsim_dpll_init(struct nsim_dev *nsim_dev) goto err_eec_unreg; } - err = dpll_pin_register(ndpll->pps_dpll, ndpll->gnss_pin, + err = dpll_pin_register(ndpll->eec_dpll, ndpll->gnss_pin, &nsim_dpll_gnss_pin_ops, &ndpll->gnss_pin_priv); if (err) goto err_gnss_put; + err = dpll_pin_register(ndpll->pps_dpll, ndpll->gnss_pin, + &nsim_dpll_gnss_pin_ops, + &ndpll->gnss_pin_priv); + if (err) + goto err_gnss_unreg_eec; + /* E810-compatible external pins (SMA1, SMA2, U.FL1, U.FL2) */ { int i; @@ -729,10 +738,21 @@ int nsim_dpll_init(struct nsim_dev *nsim_dev) ndpll->ext_pin_privs[i].frequency = DPLL_PIN_FREQUENCY_1_HZ; + err = dpll_pin_register(ndpll->eec_dpll, epin, + &nsim_dpll_gnss_pin_ops, + &ndpll->ext_pin_privs[i]); + if (err) { + dpll_pin_put(epin); + goto err_ext_cleanup; + } + err = dpll_pin_register(ndpll->pps_dpll, epin, &nsim_dpll_gnss_pin_ops, &ndpll->ext_pin_privs[i]); if (err) { + dpll_pin_unregister(ndpll->eec_dpll, epin, + &nsim_dpll_gnss_pin_ops, + &ndpll->ext_pin_privs[i]); dpll_pin_put(epin); goto err_ext_cleanup; } @@ -796,9 +816,19 @@ int nsim_dpll_init(struct nsim_dev *nsim_dev) goto err_ports_cleanup; } + err = dpll_pin_register(ndpll->eec_dpll, pin, + &nsim_dpll_rclk_pin_ops, npin); + if (err) { + dpll_pin_put(pin); + kfree(npin); + goto err_ports_cleanup; + } + err = dpll_pin_register(ndpll->pps_dpll, pin, &nsim_dpll_rclk_pin_ops, npin); if (err) { + dpll_pin_unregister(ndpll->eec_dpll, pin, + &nsim_dpll_rclk_pin_ops, npin); dpll_pin_put(pin); kfree(npin); goto err_ports_cleanup; @@ -933,12 +963,19 @@ int nsim_dpll_init(struct nsim_dev *nsim_dev) ndpll->ext_pins[i], &nsim_dpll_gnss_pin_ops, &ndpll->ext_pin_privs[i]); + dpll_pin_unregister(ndpll->eec_dpll, + ndpll->ext_pins[i], + &nsim_dpll_gnss_pin_ops, + &ndpll->ext_pin_privs[i]); dpll_pin_put(ndpll->ext_pins[i]); } ndpll->num_ext_pins = 0; } dpll_pin_unregister(ndpll->pps_dpll, ndpll->gnss_pin, &nsim_dpll_gnss_pin_ops, &ndpll->gnss_pin_priv); +err_gnss_unreg_eec: + dpll_pin_unregister(ndpll->eec_dpll, ndpll->gnss_pin, + &nsim_dpll_gnss_pin_ops, &ndpll->gnss_pin_priv); err_gnss_put: dpll_pin_put(ndpll->gnss_pin); err_eec_unreg: @@ -962,6 +999,8 @@ static void nsim_dpll_cleanup_port_pins(struct nsim_dpll *ndpll) dpll_netdev_pin_clear(npin->ns->netdev); dpll_pin_unregister(ndpll->pps_dpll, npin->pin, &nsim_dpll_rclk_pin_ops, npin); + dpll_pin_unregister(ndpll->eec_dpll, npin->pin, + &nsim_dpll_rclk_pin_ops, npin); dpll_pin_put(npin->pin); list_del(&npin->list); kfree(npin); @@ -1002,6 +1041,10 @@ void nsim_dpll_exit(struct nsim_dev *nsim_dev) ndpll->ext_pins[i], &nsim_dpll_gnss_pin_ops, &ndpll->ext_pin_privs[i]); + dpll_pin_unregister(ndpll->eec_dpll, + ndpll->ext_pins[i], + &nsim_dpll_gnss_pin_ops, + &ndpll->ext_pin_privs[i]); dpll_pin_put(ndpll->ext_pins[i]); } ndpll->num_ext_pins = 0; @@ -1009,6 +1052,8 @@ void nsim_dpll_exit(struct nsim_dev *nsim_dev) dpll_pin_unregister(ndpll->pps_dpll, ndpll->gnss_pin, &nsim_dpll_gnss_pin_ops, &ndpll->gnss_pin_priv); + dpll_pin_unregister(ndpll->eec_dpll, ndpll->gnss_pin, + &nsim_dpll_gnss_pin_ops, &ndpll->gnss_pin_priv); dpll_pin_put(ndpll->gnss_pin); dpll_device_unregister(ndpll->eec_dpll, &nsim_dpll_device_ops, ndpll); From 0e9a0b092a3efe88aabea17ac236834e738b5f21 Mon Sep 17 00:00:00 2001 From: bnshr Date: Fri, 24 Jul 2026 14:45:30 +0000 Subject: [PATCH 2/4] ci: point ptp-operator clone to integrate-add-gnss Use bnshr/ptp-operator@integrate-add-gnss so CI exercises the GNSS simulation work that this DPLL pin fix targets. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c470a7a..8e0b470 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,8 @@ concurrency: env: DKMS_PKG: netdevsim DKMS_VER: "6.9.5" - PTP_REPO: https://github.com/k8snetworkplumbingwg/ptp-operator.git - PTP_BRANCH: main + PTP_REPO: https://github.com/bnshr/ptp-operator.git + PTP_BRANCH: integrate-add-gnss jobs: # ------------------------------------------------------------------ From 53f1a25546abd2d3668ee16b4e11537a314ff5c5 Mon Sep 17 00:00:00 2001 From: bnshr Date: Fri, 24 Jul 2026 16:40:35 +0000 Subject: [PATCH 3/4] fix: enable UBX NAV timer on CFG-MSGOUT (CFG-VALSET) linuxptp-daemon enables periodic NAV-STATUS/NAV-CLOCK via CFG-MSGOUT-UBX_NAV_* CFG-VALSET keys, not classic CFG-MSG. netdevsim only started the 1 Hz emitter on CFG-MSG, so ubxtool never saw NAV frames, GNSS stayed s0, and ptp4l remained at clock class 248 despite DPLL/ts2phc being locked. Recognize CFG-MSGOUT STATUS/CLOCK keys (group 0x91) with a non-zero rate and start the NAV timer, matching real receiver behavior used by the daemon. --- netdevsim/dpll.c | 105 +++++++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/netdevsim/dpll.c b/netdevsim/dpll.c index ff60745..4f82e1d 100644 --- a/netdevsim/dpll.c +++ b/netdevsim/dpll.c @@ -207,6 +207,16 @@ static ssize_t nsim_dpll_lock_status_store(struct kobject *kobj, #define UBX_CFG_VALSET_HDR_LEN 4 #define UBX_CFGKEY_INFIL_NCNOTHRS 0x201100aa +/* CFG-MSGOUT group (bits 23:16 of the 32-bit key). linuxptp-daemon enables + * periodic NAV-STATUS/NAV-CLOCK via CFG-VALSET CFG-MSGOUT-UBX_NAV_* keys + * rather than classic CFG-MSG. */ +#define UBX_CFG_GROUP_MSGOUT 0x91 +/* Item IDs for UBX-NAV-STATUS on I2C/UART1/UART2/USB/SPI (0x2091001a..1e) */ +#define UBX_MSGOUT_NAV_STATUS_FIRST 0x001a +#define UBX_MSGOUT_NAV_STATUS_LAST 0x001e +/* Item IDs for UBX-NAV-CLOCK on I2C/UART1/UART2/USB/SPI (0x20910065..69) */ +#define UBX_MSGOUT_NAV_CLOCK_FIRST 0x0065 +#define UBX_MSGOUT_NAV_CLOCK_LAST 0x0069 #define UBX_NAV_STATUS_LEN 16 #define UBX_NAV_CLOCK_LEN 20 @@ -476,6 +486,38 @@ static void nsim_gnss_close(struct gnss_device *gdev) } } +/* Start the 1 Hz NAV-STATUS/NAV-CLOCK emitter if not already running. */ +static void nsim_ubx_enable_nav_timer(struct nsim_dpll *ndpll) +{ + if (ndpll->ubx_nav_enabled) + return; + + ndpll->ubx_nav_enabled = true; + hrtimer_start(&ndpll->ubx_timer, ns_to_ktime(NSEC_PER_SEC), + HRTIMER_MODE_REL); + pr_info("netdevsim: UBX NAV-STATUS/NAV-CLOCK timer enabled\n"); +} + +/* True if key is CFG-MSGOUT-UBX_NAV_{STATUS,CLOCK}_* with a non-zero rate. */ +static bool nsim_ubx_cfgkey_enables_nav(u32 key, u8 val) +{ + u8 group = (key >> 16) & 0xff; + u16 item = key & 0xffff; + + if (group != UBX_CFG_GROUP_MSGOUT || val == 0) + return false; + + if (item >= UBX_MSGOUT_NAV_STATUS_FIRST && + item <= UBX_MSGOUT_NAV_STATUS_LAST) + return true; + + if (item >= UBX_MSGOUT_NAV_CLOCK_FIRST && + item <= UBX_MSGOUT_NAV_CLOCK_LAST) + return true; + + return false; +} + static int nsim_gnss_write_raw(struct gnss_device *gdev, const unsigned char *buf, size_t count) { @@ -499,11 +541,6 @@ static int nsim_gnss_write_raw(struct gnss_device *gdev, return count; } - // #region agent log - pr_info("netdevsim: d753ad gnss_write_raw: count=%zu first=0x%02x signal_blocked=%d\n", - count, buf[0], ndpll->signal_blocked); - // #endregion - /* UBX frame: sync(2) + class(1) + id(1) + len(2) minimum */ if (count < UBX_HDR_LEN || buf[0] != UBX_SYNC1 || buf[1] != UBX_SYNC2) return count; @@ -511,11 +548,6 @@ static int nsim_gnss_write_raw(struct gnss_device *gdev, cls = buf[2]; id = buf[3]; - // #region agent log - pr_info("netdevsim: d753ad UBX frame: cls=0x%02x id=0x%02x count=%zu\n", - cls, id, count); - // #endregion - switch (cls) { case UBX_CLASS_MON: if (id == UBX_MON_VER) { @@ -531,14 +563,8 @@ static int nsim_gnss_write_raw(struct gnss_device *gdev, if (msg_cls == UBX_CLASS_NAV && (msg_id == UBX_NAV_STATUS || - msg_id == UBX_NAV_CLOCK)) { - if (!ndpll->ubx_nav_enabled) { - ndpll->ubx_nav_enabled = true; - hrtimer_start(&ndpll->ubx_timer, - ns_to_ktime(NSEC_PER_SEC), - HRTIMER_MODE_REL); - } - } + msg_id == UBX_NAV_CLOCK)) + nsim_ubx_enable_nav_timer(ndpll); len = ubx_build_ack(resp, sizeof(resp), cls, id); if (len > 0) @@ -549,41 +575,38 @@ static int nsim_gnss_write_raw(struct gnss_device *gdev, size_t kv_end = count - UBX_CK_LEN; size_t pos = UBX_HDR_LEN + UBX_CFG_VALSET_HDR_LEN; - // #region agent log - pr_info("netdevsim: d753ad CFG-VALSET: payload_start=%zu kv_end=%zu\n", - pos, kv_end); - // #endregion - while (pos + 4 < kv_end) { + /* Key/value pairs are U1-sized for the keys we handle + * (CFG-MSGOUT rates and INFIL_NCNOTHRS). */ + while (pos + 5 <= kv_end) { u32 key = kv[0] | (kv[1] << 8) | (kv[2] << 16) | (kv[3] << 24); - kv += 4; - pos += 4; + u8 val = kv[4]; - // #region agent log - pr_info("netdevsim: d753ad CFG-VALSET key=0x%08x pos=%zu expect=0x%08x\n", - key, pos, UBX_CFGKEY_INFIL_NCNOTHRS); - // #endregion - if (key == UBX_CFGKEY_INFIL_NCNOTHRS && pos < kv_end) { - u8 val = *kv; + kv += 5; + pos += 5; + + if (nsim_ubx_cfgkey_enables_nav(key, val)) { + nsim_ubx_enable_nav_timer(ndpll); + continue; + } + if (key == UBX_CFGKEY_INFIL_NCNOTHRS) { if (val > 0) { ndpll->signal_blocked = true; ndpll->gnss_gps_fix = 0x00; - ndpll->lock_status = DPLL_LOCK_STATUS_HOLDOVER; - pr_info("netdevsim: UBX CFG-VALSET INFIL_NCNOTHRS=%u → signal blocked, holdover\n", val); + ndpll->lock_status = + DPLL_LOCK_STATUS_HOLDOVER; + pr_info("netdevsim: UBX CFG-VALSET INFIL_NCNOTHRS=%u → signal blocked, holdover\n", + val); } else { - ndpll->signal_blocked = false; - ndpll->gnss_gps_fix = 0x03; - ndpll->lock_status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; + ndpll->signal_blocked = false; + ndpll->gnss_gps_fix = 0x03; + ndpll->lock_status = + DPLL_LOCK_STATUS_LOCKED_HO_ACQ; pr_info("netdevsim: UBX CFG-VALSET INFIL_NCNOTHRS=0 → signal restored, locked\n"); } schedule_work(&ndpll->ntf_work); - kv++; - pos++; - break; } - kv++; - pos++; } len = ubx_build_ack(resp, sizeof(resp), cls, id); From d3661f406e576767e9d10b91dd349cc04fe5d74a Mon Sep 17 00:00:00 2001 From: bnshr Date: Fri, 24 Jul 2026 19:26:26 +0000 Subject: [PATCH 4/4] fix: drive DPLL holdover from GGA fix quality When gnss-sim reports NMEA fix quality 0, update gpsFix and transition kernel DPLL from locked to holdover (with netlink notify), matching the UBX INFIL path. Do not clobber sysfs freerun while GGA stays invalid. --- netdevsim/dpll.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/netdevsim/dpll.c b/netdevsim/dpll.c index 4f82e1d..75d4332 100644 --- a/netdevsim/dpll.c +++ b/netdevsim/dpll.c @@ -432,6 +432,43 @@ static u8 nsim_gga_quality_to_gps_fix(u8 gga_quality) * concatenated NMEA sentences from a single write() call, so we * search for '$' start markers rather than only checking buf[0]. */ +/* + * Mirror GGA-derived gpsFix into DPLL lock_status so gnss-sim signal + * loss (NMEA fix quality 0) drives the same holdover path as UBX + * INFIL_NCNOTHRS: + * locked + gpsFix < 2 → HOLDOVER + * gpsFix >= 2 → LOCKED_HO_ACQ + * Do not promote UNLOCKED (sysfs freerun after holdover timeout) back + * to HOLDOVER while GGA remains invalid. + */ +static void nsim_dpll_apply_gps_fix(struct nsim_dpll *ndpll, u8 gps_fix) +{ + enum dpll_lock_status new_status; + + if (gps_fix != ndpll->gnss_gps_fix) + ndpll->gnss_gps_fix = gps_fix; + + if (ndpll->signal_blocked) + return; + + if (gps_fix < 2) { + if (ndpll->lock_status != DPLL_LOCK_STATUS_LOCKED_HO_ACQ) + return; + new_status = DPLL_LOCK_STATUS_HOLDOVER; + } else { + new_status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ; + } + + if (new_status == ndpll->lock_status) + return; + + ndpll->lock_status = new_status; + pr_info("netdevsim: GGA gpsFix=%u → DPLL %s\n", gps_fix, + (new_status == DPLL_LOCK_STATUS_HOLDOVER) ? "holdover" + : "locked"); + schedule_work(&ndpll->ntf_work); +} + static void nsim_parse_gga_fix(struct nsim_dpll *ndpll, const unsigned char *buf, size_t count) { @@ -457,9 +494,10 @@ static void nsim_parse_gga_fix(struct nsim_dpll *ndpll, if (i + 1 < count && buf[i + 1] >= '0' && buf[i + 1] <= '9') { - ndpll->gnss_gps_fix = + nsim_dpll_apply_gps_fix( + ndpll, nsim_gga_quality_to_gps_fix( - buf[i + 1] - '0'); + buf[i + 1] - '0')); } return; }