The ptpclient example corrected only the instantaneous phase offset by
calling rte_eth_timesync_adjust_time() once per DELAY_RESP. It never
compensated for oscillator frequency drift, so the master/slave offset
settled at a non-zero steady-state value (proportional to the drift rate
times the sync interval) instead of converging towards zero.

Replace the one-shot phase step with a PI clock servo, using the same
model as linuxptp. The servo output is a frequency correction that is
applied through the best mechanism the PMD provides:

- When the driver supports hardware frequency adjustment, the correction
  is applied with rte_eth_timesync_adjust_freq() (for example the E810
  INCVAL register).
- Otherwise the same correction is realized in software as an
  incremental phase step over the elapsed interval using
  rte_eth_timesync_adjust_time(), which every timesync-capable PMD
  provides.

Hardware support is probed once at runtime, so any PMD that implements
the adjust_freq operation in the future uses the hardware path without
further changes. The selected path is logged once per port.

Bugzilla ID: 1474

Signed-off-by: Soumyadeep Hore <[email protected]>
---
 examples/ptpclient/ptpclient.c | 114 ++++++++++++++++++++++++++++++++-
 1 file changed, 111 insertions(+), 3 deletions(-)

diff --git a/examples/ptpclient/ptpclient.c b/examples/ptpclient/ptpclient.c
index 59ce892742..d3e947941e 100644
--- a/examples/ptpclient/ptpclient.c
+++ b/examples/ptpclient/ptpclient.c
@@ -34,6 +34,12 @@ static volatile bool force_quit;
 #define NSEC_PER_SEC        1000000000L
 #define KERNEL_TIME_ADJUST_LIMIT  20000
 
+/* PI clock servo parameters (same model as linuxptp). */
+#define PTP_SERVO_KP              0.7
+#define PTP_SERVO_KI              0.3
+/* Step the clock directly when the offset exceeds this threshold. */
+#define PTP_STEP_THRESHOLD_NS     100000
+
 struct rte_mempool *mbuf_pool;
 uint32_t ptp_enabled_port_mask;
 uint8_t ptp_enabled_port_nb;
@@ -97,6 +103,18 @@ struct ptpv2_time_receiver_ordinary {
 
 static struct ptpv2_time_receiver_ordinary ptp_data;
 
+/* Per-port clock servo state. */
+struct ptp_clock_servo {
+       int freq_adj_supported;     /* -1 unknown, 0 software, 1 hardware */
+       int count;                  /* number of samples processed */
+       int64_t last_offset;        /* previous offset (ns) */
+       uint64_t last_local;        /* NIC clock at previous sample (ns) */
+       double drift_ppb;           /* integral term / standing frequency (ppb) 
*/
+};
+
+static struct ptp_clock_servo ptp_servo[RTE_MAX_ETHPORTS];
+
+
 static inline uint64_t timespec64_to_ns(const struct timespec *ts)
 {
        return ((uint64_t) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
@@ -229,7 +247,7 @@ print_clock_info(struct ptpv2_time_receiver_ordinary *ptp)
        int64_t nsec;
        struct timespec net_time, sys_time;
 
-       printf("time transmitter clock id: 
%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
+       printf("\ntime transmitter clock id: 
%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
                ptp->transmitter_clock_id[0],
                ptp->transmitter_clock_id[1],
                ptp->transmitter_clock_id[2],
@@ -308,6 +326,93 @@ delta_eval(struct ptpv2_time_receiver_ordinary *ptp)
        return delta;
 }
 
+/*
+ * Convert a frequency correction in ppb to the scaled ppm (16-bit fractional)
+ * unit expected by rte_eth_timesync_adjust_freq().
+ */
+static int64_t
+ppb_to_scaled_ppm(double ppb)
+{
+       return (int64_t)(ppb * 65536.0 / 1000.0);
+}
+
+/*
+ * PI clock servo. Drives the master/slave offset to zero by compensating for
+ * clock frequency drift, not just the instantaneous phase offset.
+ *
+ * The servo output is a frequency correction (ppb). When the PMD supports
+ * hardware frequency adjustment it is applied via 
rte_eth_timesync_adjust_freq()
+ * (e.g. the E810 INCVAL register). Otherwise the same correction is realized 
in
+ * software as an incremental phase step over the elapsed interval using
+ * rte_eth_timesync_adjust_time(), which every timesync-capable PMD provides.
+ */
+static void
+clock_servo(struct ptpv2_time_receiver_ordinary *ptp, int64_t offset)
+{
+       struct ptp_clock_servo *servo = &ptp_servo[ptp->portid];
+       struct timespec now;
+       uint64_t local;
+       double ppb, ki_term;
+
+       /* Probe hardware frequency adjustment support once. */
+       if (servo->freq_adj_supported < 0) {
+               servo->freq_adj_supported =
+                       (rte_eth_timesync_adjust_freq(ptp->portid, 0) == 0) ? 1 
: 0;
+               printf("Port %u clock frequency adjustment: %s\n", ptp->portid,
+                      servo->freq_adj_supported ? "hardware" : "software");
+       }
+
+       if (rte_eth_timesync_read_time(ptp->portid, &now) != 0)
+               return;
+       local = timespec64_to_ns(&now);
+
+       /* Large offset: step the clock directly and (re)start the servo. */
+       if (offset >= PTP_STEP_THRESHOLD_NS || offset <= 
-PTP_STEP_THRESHOLD_NS) {
+               rte_eth_timesync_adjust_time(ptp->portid, offset);
+               servo->last_offset = 0;
+               servo->last_local = local;
+               if (servo->count == 0)
+                       servo->count = 1;
+               return;
+       }
+
+       switch (servo->count) {
+       case 0:
+               /* First sample: establish the baseline only. */
+               servo->last_offset = offset;
+               servo->last_local = local;
+               servo->count = 1;
+               return;
+       case 1:
+               /* Second sample: seed the drift estimate from the slope. */
+               if (local > servo->last_local)
+                       servo->drift_ppb = 1e9 *
+                               (double)(offset - servo->last_offset) /
+                               (double)(local - servo->last_local);
+               servo->count = 2;
+               break;
+       default:
+               ki_term = PTP_SERVO_KI * (double)offset;
+               servo->drift_ppb += ki_term;
+               break;
+       }
+
+       ppb = PTP_SERVO_KP * (double)offset + servo->drift_ppb;
+
+       if (servo->freq_adj_supported == 1) {
+               rte_eth_timesync_adjust_freq(ptp->portid, 
ppb_to_scaled_ppm(ppb));
+       } else if (local > servo->last_local) {
+               /* Realize the frequency correction as a phase step. */
+               double dt = (double)(local - servo->last_local);
+
+               rte_eth_timesync_adjust_time(ptp->portid,
+                                            (int64_t)(ppb * dt / 1e9));
+       }
+
+       servo->last_offset = offset;
+       servo->last_local = local;
+}
+
 /*
  * Parse the PTP SYNC message.
  */
@@ -507,8 +612,7 @@ parse_drsp(struct ptpv2_time_receiver_ordinary *ptp)
                        /* Evaluate the delta for adjustment. */
                        ptp->delta = delta_eval(ptp);
 
-                       rte_eth_timesync_adjust_time(ptp->portid,
-                                                       ptp->delta);
+                       clock_servo(ptp, ptp->delta);
 
                        ptp->current_ptp_port = ptp->portid;
 
@@ -576,6 +680,10 @@ lcore_main(void)
        printf("\nCore %u Waiting for SYNC packets. [Ctrl+C to quit]\n",
                        rte_lcore_id());
 
+       /* Hardware frequency support is probed on the first adjustment. */
+       for (portid = 0; portid < RTE_MAX_ETHPORTS; portid++)
+               ptp_servo[portid].freq_adj_supported = -1;
+
        /* Run until the application is quit or killed. */
 
        while (!force_quit) {
-- 
2.43.0

Reply via email to