Skip to content

Commit

Permalink
timemaster: Add support for virtual clocks.
Browse files Browse the repository at this point in the history
Add "use_vclocks" option to enable synchronization with virtual clocks.
This enables multiple ptp4l instances sharing an interface to use
hardware timestamping. By default, vclocks are enabled if running on
Linux 5.18 or later, which should have all features to make them work as
well as physical clocks.

When preparing the script, count how many vclocks are needed for each
physical clock. Add a placeholder option in the form of "--phc_index
%PHC0-0%" to the generated ptp4l commands that need hardware clock. The
index of the virtual clock is unknown at this point.

When running the script (not just printing), create the required number
of virtual clocks by writing to /sys/.../n_vclocks and fix the
--phc_index options to refer to the indices of the created vclocks. On
exit, remove the virtual clocks to restore the original state.

Signed-off-by: Miroslav Lichvar <[email protected]>
  • Loading branch information
mlichvar authored and richardcochran committed Mar 8, 2022
1 parent e29a334 commit 4d9f449
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 11 deletions.
14 changes: 12 additions & 2 deletions timemaster.8
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ configuration error). If a process was terminated and is not started again,
\fBtimemaster\fR will kill the other processes and exit with a non-zero status.
The default value is 1 (enabled).

.TP
.B use_vclocks
Enable or disable synchronization with virtual clocks. If enabled,
\fBtimemaster\fR will create virtual clocks running on top of physical clocks
needed by configured PTP domains. This enables hardware time stamping for
multiple \fBptp4l\fR instances using the same network interface. The default
value is -1, which enables the virtual clocks if running on Linux 5.18 or
later.

.SS [ntp_server address]

The \fBntp_server\fR section specifies an NTP server that should be used as a
Expand Down Expand Up @@ -140,8 +149,8 @@ Specify which network interfaces should be used for this PTP domain. A separate
\fBptp4l\fR instance will be started for each group of interfaces sharing the
same PHC and for each interface that supports only SW time stamping. HW time
stamping is enabled automatically. If an interface with HW time stamping is
specified also in other PTP domains, only the \fBptp4l\fR instance from the
first PTP domain will be using HW time stamping.
specified also in other PTP domains and virtual clocks are disabled, only the
\fBptp4l\fR instance from the first PTP domain will be using HW time stamping.

.TP
.B ntp_poll
Expand Down Expand Up @@ -333,6 +342,7 @@ ntp_program chronyd
rundir /var/run/timemaster
first_shm_segment 1
restart_processes 0
use_vclocks 0
[chronyd]
path /usr/sbin/chronyd
Expand Down
196 changes: 187 additions & 9 deletions timemaster.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <ctype.h>
#include <errno.h>
#include <glob.h>
#include <libgen.h>
#include <limits.h>
#include <time.h>
Expand All @@ -33,6 +34,7 @@
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <unistd.h>

Expand All @@ -46,6 +48,7 @@

#define DEFAULT_FIRST_SHM_SEGMENT 0
#define DEFAULT_RESTART_PROCESSES 1
#define DEFAULT_USE_VCLOCKS -1

#define DEFAULT_NTP_PROGRAM CHRONYD
#define DEFAULT_NTP_MINPOLL 6
Expand Down Expand Up @@ -111,6 +114,7 @@ struct timemaster_config {
char *rundir;
int first_shm_segment;
int restart_processes;
int use_vclocks;
struct program_config chronyd;
struct program_config ntpd;
struct program_config phc2sys;
Expand All @@ -122,7 +126,13 @@ struct config_file {
char *content;
};

struct phc_vclocks {
int pclock_index;
int vclocks;
};

struct script {
struct phc_vclocks **vclocks;
struct config_file **configs;
char ***commands;
int **command_groups;
Expand Down Expand Up @@ -393,6 +403,8 @@ static int parse_timemaster_settings(char **settings,
r = parse_int(value, &config->first_shm_segment);
} else if (!strcasecmp(name, "restart_processes")) {
r = parse_int(value, &config->restart_processes);
} else if (!strcasecmp(name, "use_vclocks")) {
r = parse_int(value, &config->use_vclocks);
} else {
pr_err("unknown timemaster setting %s", name);
return 1;
Expand Down Expand Up @@ -520,6 +532,20 @@ static void config_destroy(struct timemaster_config *config)
free(config);
}

static int check_kernel_version(int version, int patch)
{
struct utsname uts;
int v, p;

if (uname(&uts) < 0)
return 1;
if (sscanf(uts.release, "%d.%d", &v, &p) < 2)
return 1;
if (version > v || (version == v && patch > p))
return 1;
return 0;
}

static struct timemaster_config *config_parse(char *path)
{
struct timemaster_config *config = xcalloc(1, sizeof(*config));
Expand All @@ -533,6 +559,7 @@ static struct timemaster_config *config_parse(char *path)
config->rundir = xstrdup(DEFAULT_RUNDIR);
config->first_shm_segment = DEFAULT_FIRST_SHM_SEGMENT;
config->restart_processes = DEFAULT_RESTART_PROCESSES;
config->use_vclocks = DEFAULT_USE_VCLOCKS;

init_program_config(&config->chronyd, "chronyd",
NULL, DEFAULT_CHRONYD_SETTINGS, NULL);
Expand Down Expand Up @@ -593,6 +620,9 @@ static struct timemaster_config *config_parse(char *path)

fclose(f);

if (config->use_vclocks < 0)
config->use_vclocks = !check_kernel_version(5, 18);

if (section_name)
free(section_name);
if (section_lines)
Expand All @@ -608,7 +638,7 @@ static struct timemaster_config *config_parse(char *path)

static char **get_ptp4l_command(struct program_config *config,
struct config_file *file, char **interfaces,
int hw_ts)
char *phc_index, int hw_ts)
{
char **command = (char **)parray_new();

Expand All @@ -617,6 +647,9 @@ static char **get_ptp4l_command(struct program_config *config,
parray_extend((void ***)&command,
xstrdup("-f"), xstrdup(file->path),
xstrdup(hw_ts ? "-H" : "-S"), NULL);
if (phc_index && phc_index[0])
parray_extend((void ***)&command,
xstrdup("--phc_index"), xstrdup(phc_index), NULL);

for (; *interfaces; interfaces++)
parray_extend((void ***)&command,
Expand Down Expand Up @@ -706,14 +739,32 @@ static int add_ntp_source(struct ntp_server *source, char **ntp_config)
return 0;
}

static int add_vclock(struct script *script, int pclock_index)
{
struct phc_vclocks **vclocks, *v;

for (vclocks = script->vclocks; *vclocks; vclocks++) {
if ((*vclocks)->pclock_index != pclock_index)
continue;
return (*vclocks)->vclocks++;
}

v = xmalloc(sizeof(*v));
v->pclock_index = pclock_index;
v->vclocks = 1;
parray_append((void ***)&script->vclocks, v);

return 0;
}

static int add_ptp_source(struct ptp_domain *source,
struct timemaster_config *config, int *shm_segment,
int *command_group, int ***allocated_phcs,
char **ntp_config, struct script *script)
{
struct config_file *config_file;
char **command, *uds_path, *uds_path2, **interfaces, *message_tag;
char ts_interface[IF_NAMESIZE];
char ts_interface[IF_NAMESIZE], vclock_index[20];
int i, j, num_interfaces, *phc, *phcs, hw_ts, sw_ts;
struct sk_ts_info ts_info;

Expand Down Expand Up @@ -801,10 +852,18 @@ static int add_ptp_source(struct ptp_domain *source,
}
}

/* don't use this PHC in other sources */
phc = xmalloc(sizeof(int));
*phc = phcs[i];
parray_append((void ***)allocated_phcs, phc);
if (config->use_vclocks) {
/* request new vclock for the PHC */
int vclock = add_vclock(script, phcs[i]);
snprintf(vclock_index, sizeof(vclock_index),
"%%PHC%d-%d%%", phcs[i], vclock);
} else {
/* don't use this PHC in other sources */
phc = xmalloc(sizeof(int));
*phc = phcs[i];
parray_append((void ***)allocated_phcs, phc);
vclock_index[0] = '\0';
}
}

uds_path = string_newf("%s/ptp4l.%d.socket",
Expand Down Expand Up @@ -842,7 +901,8 @@ static int add_ptp_source(struct ptp_domain *source,
if (phcs[i] >= 0) {
/* HW time stamping */
command = get_ptp4l_command(&config->ptp4l, config_file,
interfaces, 1);
interfaces,
vclock_index, 1);
add_command(command, *command_group, script);

command = get_phc2sys_command(&config->phc2sys,
Expand All @@ -854,7 +914,7 @@ static int add_ptp_source(struct ptp_domain *source,
} else {
/* SW time stamping */
command = get_ptp4l_command(&config->ptp4l, config_file,
interfaces, 0);
interfaces, NULL, 0);
add_command(command, (*command_group)++, script);

string_appendf(&config_file->content,
Expand Down Expand Up @@ -943,6 +1003,11 @@ static void script_destroy(struct script *script)
char ***commands, **command;
int **groups;
struct config_file *config, **configs;
struct phc_vclocks **vclocks;

for (vclocks = script->vclocks; *vclocks; vclocks++)
free(*vclocks);
free(script->vclocks);

for (configs = script->configs; *configs; configs++) {
config = *configs;
Expand Down Expand Up @@ -974,6 +1039,7 @@ static struct script *script_create(struct timemaster_config *config)
int **allocated_phcs = (int **)parray_new();
int ret = 0, shm_segment, command_group = 0;

script->vclocks = (struct phc_vclocks **)parray_new();
script->configs = (struct config_file **)parray_new();
script->commands = (char ***)parray_new();
script->command_groups = (int **)parray_new();
Expand Down Expand Up @@ -1116,6 +1182,102 @@ static int remove_config_files(struct config_file **configs)
return 0;
}

static int set_phc_n_vclocks(int phc_index, int n_vclocks)
{
char path[PATH_MAX];
FILE *f;

snprintf(path, sizeof(path), "/sys/class/ptp/ptp%d/n_vclocks",
phc_index);
f = fopen(path, "w");
if (!f) {
pr_err("failed to open %s: %m", path);
return 1;
}
fprintf(f, "%d\n", n_vclocks);
fclose(f);

return 0;
}

static int create_vclocks(struct phc_vclocks **phc_vclocks)
{
struct phc_vclocks **vclocks;

for (vclocks = phc_vclocks; *vclocks; vclocks++) {
if (set_phc_n_vclocks((*vclocks)->pclock_index,
(*vclocks)->vclocks))
return 1;
}

return 0;
}

static int remove_vclocks(struct phc_vclocks **phc_vclocks)
{
struct phc_vclocks **vclocks;

for (vclocks = phc_vclocks; *vclocks; vclocks++) {
if (set_phc_n_vclocks((*vclocks)->pclock_index, 0))
return 1;
}

return 0;
}

static int get_vclock_index(int pindex, int vclock)
{
char pattern[PATH_MAX], *s;
int n, vindex;
glob_t gl;

snprintf(pattern, sizeof(pattern), "/sys/class/ptp/ptp%d/ptp[0-9]*",
pindex);

if (glob(pattern, 0, NULL, &gl)) {
pr_err("glob(%s) failed", pattern);
return -1;
}

if (vclock >= gl.gl_pathc ||
!(s = strrchr(gl.gl_pathv[vclock], '/')) ||
sscanf(s + 1, "ptp%d%n", &vindex, &n) != 1 ||
n != strlen(s + 1)) {
pr_err("missing vclock %d:%d", pindex, vclock);
globfree(&gl);
return -1;
}

globfree(&gl);

return vindex;
}

static int translate_vclock_options(char ***commands)
{
int n, pindex, vclock, vindex, blen;
char **command;

for (; *commands; commands++) {
for (command = *commands; *command; command++) {
if (sscanf(*command, "%%PHC%d-%d%%%n",
&pindex, &vclock, &n) != 2 ||
n != strlen(*command))
continue;
vindex = get_vclock_index(pindex, vclock);
if (vindex < 0)
return 1;

/* overwrite the string with the vclock PHC index */
blen = strlen(*command) + 1;
if (snprintf(*command, blen, "%d", vindex) >= blen)
return 1;
}
}

return 0;
}

static int script_run(struct script *script)
{
struct timespec ts_start, ts_now;
Expand All @@ -1135,6 +1297,12 @@ static int script_run(struct script *script)
if (create_config_files(script->configs))
return 1;

if (create_vclocks(script->vclocks))
return 1;

if (translate_vclock_options(script->commands))
return 1;

sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGTERM);
Expand Down Expand Up @@ -1278,6 +1446,9 @@ static int script_run(struct script *script)

free(pids);

if (remove_vclocks(script->vclocks))
return 1;

if (remove_config_files(script->configs))
return 1;

Expand All @@ -1289,13 +1460,20 @@ static void script_print(struct script *script)
char ***commands, **command;
int **groups;
struct config_file *config, **configs;
struct phc_vclocks **vclocks;

for (configs = script->configs; *configs; configs++) {
config = *configs;
fprintf(stderr, "%s:\n\n%s\n", config->path, config->content);
}

fprintf(stderr, "commands:\n\n");
fprintf(stderr, "virtual clocks:\n\n");
for (vclocks = script->vclocks; *vclocks; vclocks++) {
fprintf(stderr, "PHC%d: %d\n",
(*vclocks)->pclock_index, (*vclocks)->vclocks);
}

fprintf(stderr, "\ncommands:\n\n");
for (commands = script->commands, groups = script->command_groups;
*commands; commands++, groups++) {
fprintf(stderr, "[%d] ", **groups);
Expand Down

0 comments on commit 4d9f449

Please sign in to comment.