Skip to content

Commit

Permalink
Add support for write phase mode.
Browse files Browse the repository at this point in the history
Recently the Linux kernel's PTP Hardware Clock interface was expanded
to include a "write phase" mode where the clock servo in implemented
in hardware.  This mode hearkens back to the tradition ntp_adjtime
interface, passing a measured offset into the kernel's servo.

This patch adds a new configuration option and logic to support the
write phase mode.

Because the hardware's adjustment bandwidth may be limited, this mode
is only activated when the servo reaches SERVO_LOCKED_STABLE state, in
order to achieve reasonably fast locking times.  Users may control the
SERVO_LOCKED_STABLE state by configuring 'servo_offset_threshold' and
'servo_num_offset_values' accordingly.

Example configuration file highlights:

  unicast_listen          1
  logSyncInterval         0
  logMinDelayReqInterval  0
  first_step_threshold    0.001000000
  step_threshold          0
  clock_servo             pi

  write_phase_mode        1
  servo_offset_threshold  50
  servo_num_offset_values 10
  tsproc_mode             raw

Signed-off-by: Vincent Cheng <[email protected]>
Signed-off-by: Richard Cochran <[email protected]>
  • Loading branch information
richardcochran committed May 24, 2020
1 parent d4b97f4 commit 7df88af
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 21 deletions.
60 changes: 42 additions & 18 deletions clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/queue.h>

#include "address.h"
Expand Down Expand Up @@ -102,6 +103,7 @@ struct clock {
int sde;
int free_running;
int freq_est_interval;
int write_phase_mode;
int grand_master_capable; /* for 802.1AS only */
int utc_timescale;
int utc_offset_set;
Expand Down Expand Up @@ -1028,6 +1030,7 @@ struct clock *clock_create(enum clock_type type, struct config *config,
c->config = config;
c->free_running = config_get_int(config, NULL, "free_running");
c->freq_est_interval = config_get_int(config, NULL, "freq_est_interval");
c->write_phase_mode = config_get_int(config, NULL, "write_phase_mode");
c->grand_master_capable = config_get_int(config, NULL, "gmCapable");
c->kernel_leap = config_get_int(config, NULL, "kernel_leap");
c->utc_offset = config_get_int(config, NULL, "utc_offset");
Expand Down Expand Up @@ -1076,6 +1079,12 @@ struct clock *clock_create(enum clock_type type, struct config *config,
and return 0. Set the frequency back to make sure fadj is
the actual frequency of the clock. */
clockadj_set_freq(c->clkid, fadj);

/* Disable write phase mode if not implemented by driver */
if (c->write_phase_mode && !phc_has_writephase(c->clkid)) {
pr_err("clock does not support write phase mode");
return NULL;
}
}
c->servo = servo_create(c->config, servo, -fadj, max_adj, sw_ts);
if (!c->servo) {
Expand Down Expand Up @@ -1632,10 +1641,22 @@ int clock_switch_phc(struct clock *c, int phc_index)
return 0;
}

static void clock_synchronize_locked(struct clock *c, double adj)
{
clockadj_set_freq(c->clkid, -adj);
if (c->clkid == CLOCK_REALTIME) {
sysclk_set_sync();
}
if (c->sanity_check) {
clockcheck_set_freq(c->sanity_check, -adj);
}
}

enum servo_state clock_synchronize(struct clock *c, tmv_t ingress, tmv_t origin)
{
double adj, weight;
enum servo_state state = SERVO_UNLOCKED;
double adj, weight;
int64_t offset;

c->ingress_ts = ingress;

Expand All @@ -1659,19 +1680,11 @@ enum servo_state clock_synchronize(struct clock *c, tmv_t ingress, tmv_t origin)
return clock_no_adjust(c, ingress, origin);
}

adj = servo_sample(c->servo, tmv_to_nanoseconds(c->master_offset),
tmv_to_nanoseconds(ingress), weight, &state);
offset = tmv_to_nanoseconds(c->master_offset);
adj = servo_sample(c->servo, offset, tmv_to_nanoseconds(ingress),
weight, &state);
c->servo_state = state;

if (c->stats.max_count > 1) {
clock_stats_update(&c->stats, tmv_dbl(c->master_offset), adj);
} else {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"path delay %9" PRId64,
tmv_to_nanoseconds(c->master_offset), state, adj,
tmv_to_nanoseconds(c->path_delay));
}

tsproc_set_clock_rate_ratio(c->tsproc, clock_rate_ratio(c));

switch (state) {
Expand All @@ -1689,16 +1702,27 @@ enum servo_state clock_synchronize(struct clock *c, tmv_t ingress, tmv_t origin)
tsproc_reset(c->tsproc, 0);
break;
case SERVO_LOCKED:
clock_synchronize_locked(c, adj);
break;
case SERVO_LOCKED_STABLE:
clockadj_set_freq(c->clkid, -adj);
if (c->clkid == CLOCK_REALTIME) {
sysclk_set_sync();
}
if (c->sanity_check) {
clockcheck_set_freq(c->sanity_check, -adj);
if (c->write_phase_mode) {
clockadj_set_phase(c->clkid, -offset);
adj = 0;
} else {
clock_synchronize_locked(c, adj);
}
break;
}

if (c->stats.max_count > 1) {
clock_stats_update(&c->stats, tmv_dbl(c->master_offset), adj);
} else {
pr_info("master offset %10" PRId64 " s%d freq %+7.0f "
"path delay %9" PRId64,
tmv_to_nanoseconds(c->master_offset), state, adj,
tmv_to_nanoseconds(c->path_delay));
}

return state;
}

Expand Down
12 changes: 12 additions & 0 deletions clockadj.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ double clockadj_get_freq(clockid_t clkid)
return f;
}

void clockadj_set_phase(clockid_t clkid, long offset)
{
struct timex tx;
memset(&tx, 0, sizeof(tx));

tx.modes = ADJ_OFFSET | ADJ_NANO;
tx.offset = offset;
if (clock_adjtime(clkid, &tx) < 0) {
pr_err("failed to set the clock offset: %m");
}
}

void clockadj_step(clockid_t clkid, int64_t step)
{
struct timex tx;
Expand Down
7 changes: 7 additions & 0 deletions clockadj.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ void clockadj_set_freq(clockid_t clkid, double freq);
*/
double clockadj_get_freq(clockid_t clkid);

/**
* Set clock's phase offset.
* @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME.
* @param offset The phase offset in nanoseconds.
*/
void clockadj_set_phase(clockid_t clkid, long offset);

/**
* Step clock's time.
* @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME.
Expand Down
1 change: 1 addition & 0 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ struct config_item config_tab[] = {
GLOB_ITEM_STR("userDescription", ""),
GLOB_ITEM_INT("utc_offset", CURRENT_UTC_OFFSET, 0, INT_MAX),
GLOB_ITEM_INT("verbose", 0, 0, 1),
GLOB_ITEM_INT("write_phase_mode", 0, 0, 1),
};

static struct unicast_master_table *current_uc_mtab;
Expand Down
1 change: 1 addition & 0 deletions configs/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ ntpshm_segment 0
msg_interval_request 0
servo_num_offset_values 10
servo_offset_threshold 0
write_phase_mode 0
#
# Transport options
#
Expand Down
13 changes: 10 additions & 3 deletions missing.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#define HAVE_MISSING_H

#include <linux/ptp_clock.h>
#include <linux/version.h>
#include <sys/syscall.h>
#include <sys/timex.h>
#include <time.h>
Expand Down Expand Up @@ -75,9 +76,9 @@ enum {
#define PTP_PEROUT_REQUEST2 PTP_PEROUT_REQUEST
#endif

#ifndef PTP_PIN_SETFUNC
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,8,0)

/* from Linux kernel version 5.4 */
/* from upcoming Linux kernel version 5.8 */
struct compat_ptp_clock_caps {
int max_adj; /* Maximum frequency adjustment in parts per billon. */
int n_alarm; /* Number of programmable alarms. */
Expand All @@ -87,11 +88,17 @@ struct compat_ptp_clock_caps {
int n_pins; /* Number of input/output pins. */
/* Whether the clock supports precise system-device cross timestamps */
int cross_timestamping;
int rsv[13]; /* Reserved for future use. */
/* Whether the clock supports adjust phase */
int adjust_phase;
int rsv[12]; /* Reserved for future use. */
};

#define ptp_clock_caps compat_ptp_clock_caps

#endif /*LINUX_VERSION_CODE < 5.8*/

#ifndef PTP_PIN_SETFUNC

enum ptp_pin_function {
PTP_PF_NONE,
PTP_PF_EXTTS,
Expand Down
10 changes: 10 additions & 0 deletions phc.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,13 @@ int phc_has_pps(clockid_t clkid)
return 0;
return caps.pps;
}

int phc_has_writephase(clockid_t clkid)
{
struct ptp_clock_caps caps;

if (phc_get_caps(clkid, &caps)) {
return 0;
}
return caps.adjust_phase;
}
10 changes: 10 additions & 0 deletions phc.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,14 @@ int phc_pin_setfunc(clockid_t clkid, struct ptp_pin_desc *desc);
*/
int phc_has_pps(clockid_t clkid);

/**
* Checks whether the given PTP hardware clock device supports write phase mode.
*
* @param clkid A clock ID obtained using phc_open().
*
* @return Zero if write phase mode is not supported by the clock, non-zero
* otherwise.
*/
int phc_has_writephase(clockid_t clkid);

#endif
6 changes: 6 additions & 0 deletions ptp4l.8
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,12 @@ The offset threshold used in order to transition from the SERVO_LOCKED
to the SERVO_LOCKED_STABLE state. The transition occurs once the last
'servo_num_offset_values' offsets are all below the threshold value.
The default value of offset_threshold is 0 (disabled).
.TP
.B write_phase_mode
This option enables using the "write phase" feature of a PTP Hardware
Clock. If supported by the device, this mode uses the hardware's
built in phase offset control instead of frequency offset control.
The default value is 0 (disabled).

.SH UNICAST DISCOVERY OPTIONS

Expand Down

0 comments on commit 7df88af

Please sign in to comment.