blob: 20e6e6f1ab59f1b2b52561e204107e1c540a0646 [file] [edit]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2019, VMware, Tzvetomir Stoyanov tz.stoyanov@gmail.com>
*
*/
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <linux/types.h>
#include <time.h>
#include <sched.h>
#include <limits.h>
#include "trace-cmd.h"
#include "trace-cmd-private.h"
#include "tracefs.h"
#include "trace-tsync-local.h"
#include "trace-msg.h"
#include "trace-cmd-local.h"
typedef __be32 be32;
typedef __u64 u64;
typedef __s64 s64;
#define PTP_SYNC_LOOP 339
#define PTP_SYNC_PKT_START 1
#define PTP_SYNC_PKT_PROBE 2
#define PTP_SYNC_PKT_PROBES 3
#define PTP_SYNC_PKT_OFFSET 4
#define PTP_SYNC_PKT_END 5
/* print time sync debug messages */
/* #define TSYNC_DEBUG */
struct ptp_clock_sync {
struct tep_handle *tep;
struct tep_format_field *id;
int raw_id;
int marker_fd;
int series_id;
int flags;
int debug_fd;
};
enum {
/*
* Consider only the probe with fastest response time,
* otherwise make a histogram from all probes.
*/
PTP_FLAG_FASTEST_RESPONSE = (1 << 0),
/*
* Use trace marker to get the clock,
* otherwise use the system clock directly.
*/
PTP_FLAG_USE_MARKER = (1 << 1),
};
static int ptp_flags = PTP_FLAG_FASTEST_RESPONSE | PTP_FLAG_USE_MARKER;
/*
* Calculated using formula [CPU rate]*[calculated offset deviation]
* tested on 3GHz CPU, with x86-tsc trace clock and compare the calculated
* offset with /sys/kernel/debug/kvm/<VM ID>/vcpu0/tsc-offset
* measured 2000ns deviation
* using PTP flags PTP_FLAG_FASTEST_RESPONSE | PTP_FLAG_USE_MARKER
*/
#define PTP_ACCURACY 6000
#define PTP_NAME "ptp"
struct ptp_clock_start_msg {
be32 series_id;
be32 flags;
} __packed;
struct ptp_clock_sample {
s64 ts;
be32 id;
} __packed;
struct ptp_clock_result_msg {
be32 series_id;
be32 count;
struct ptp_clock_sample samples[2*PTP_SYNC_LOOP];
} __packed;
struct ptp_clock_offset_msg {
s64 ts;
s64 offset;
};
struct ptp_markers_context {
struct clock_sync_context *clock;
struct ptp_clock_sync *ptp;
struct ptp_clock_result_msg msg;
int size;
};
struct ptp_marker_buf {
int local_id;
int remote_id;
int count;
int packet_id;
} __packed;
struct ptp_marker {
int series_id;
struct ptp_marker_buf data;
} __packed;
static int ptp_clock_sync_init(struct tracecmd_time_sync *tsync)
{
const char *systems[] = {"ftrace", NULL};
struct clock_sync_context *clock_context;
struct ptp_clock_sync *ptp;
struct tep_event *raw;
char *path;
if (!tsync || !tsync->context)
return -1;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context->proto_data)
return 0;
ptp = calloc(1, sizeof(struct ptp_clock_sync));
if (!ptp)
return -1;
ptp->marker_fd = -1;
ptp->debug_fd = -1;
path = tracefs_instance_get_dir(clock_context->instance);
if (!path)
goto error;
ptp->tep = tracefs_local_events_system(path, systems);
tracefs_put_tracing_file(path);
if (!ptp->tep)
goto error;
raw = tep_find_event_by_name(ptp->tep, "ftrace", "raw_data");
if (!raw)
goto error;
ptp->id = tep_find_field(raw, "id");
if (!ptp->id)
goto error;
ptp->raw_id = raw->id;
tep_set_file_bigendian(ptp->tep, tracecmd_host_bigendian());
tep_set_local_bigendian(ptp->tep, tracecmd_host_bigendian());
path = tracefs_instance_get_file(clock_context->instance, "trace_marker_raw");
if (!path)
goto error;
ptp->marker_fd = open(path, O_WRONLY);
tracefs_put_tracing_file(path);
clock_context->proto_data = ptp;
#ifdef TSYNC_DEBUG
if (clock_context->is_server) {
char buff[256];
int res_fd;
sprintf(buff, "res-id%d.txt", clock_context->remote_id);
res_fd = open(buff, O_CREAT|O_WRONLY|O_TRUNC, 0644);
if (res_fd > 0)
close(res_fd);
}
#endif
return 0;
error:
if (ptp) {
tep_free(ptp->tep);
if (ptp->marker_fd >= 0)
close(ptp->marker_fd);
}
free(ptp);
return -1;
}
static int ptp_clock_sync_free(struct tracecmd_time_sync *tsync)
{
struct clock_sync_context *clock_context;
struct ptp_clock_sync *ptp;
if (!tsync || !tsync->context)
return -1;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context && clock_context->proto_data) {
ptp = (struct ptp_clock_sync *)clock_context->proto_data;
tep_free(ptp->tep);
if (ptp->marker_fd >= 0)
close(ptp->marker_fd);
if (ptp->debug_fd >= 0)
close(ptp->debug_fd);
free(clock_context->proto_data);
clock_context->proto_data = NULL;
}
return 0;
}
/* Save the timestamps of sent ('s') and returned ('r') probes in the
* ctx->msg.samples[] array. Depending of the context (server or client), there
* may be only returned probes, or both sent and returned probes. The returned
* probes are saved first in the array, after them are the sent probes.
* Depending of the context, the array can be with size:
* [0 .. max data.count] - holds only returned probes
* [0 .. 2 * max data.count] - holds both returned and sent probes
*/
static void ptp_probe_store(struct ptp_markers_context *ctx,
struct ptp_marker *marker,
unsigned long long ts)
{
int index = -1;
if (marker->data.packet_id == 'r' &&
marker->data.count <= ctx->size) {
index = marker->data.count - 1;
} else if (marker->data.packet_id == 's' &&
marker->data.count * 2 <= ctx->size){
index = ctx->size / 2 + marker->data.count - 1;
}
if (index >= 0) {
ctx->msg.samples[index].id = marker->data.count;
ctx->msg.samples[index].ts = ts;
ctx->msg.count++;
}
}
static int ptp_marker_find(struct tep_event *event, struct tep_record *record,
int cpu, void *context)
{
struct ptp_markers_context *ctx;
struct ptp_marker *marker;
ctx = (struct ptp_markers_context *)context;
/* Make sure this is our event */
if (event->id != ctx->ptp->raw_id || !ctx->ptp->id)
return 0;
if (record->size >= (ctx->ptp->id->offset + sizeof(struct ptp_marker))) {
marker = (struct ptp_marker *)(record->data + ctx->ptp->id->offset);
if (marker->data.local_id == ctx->clock->local_id &&
marker->data.remote_id == ctx->clock->remote_id &&
marker->series_id == ctx->ptp->series_id &&
marker->data.count)
ptp_probe_store(ctx, marker, record->ts);
}
return 0;
}
static inline bool good_probe(struct ptp_clock_sample *server_sample,
struct ptp_clock_sample *send_sample,
struct ptp_clock_sample *client_sample,
int *bad_probes)
{
if (server_sample->ts && send_sample->ts && client_sample->ts &&
server_sample->id == send_sample->id &&
server_sample->id == client_sample->id)
return true;
(*bad_probes)++;
return false;
}
static int ptp_calc_offset_fastest(struct clock_sync_context *clock,
struct ptp_clock_result_msg *server,
struct ptp_clock_result_msg *client,
long long *offset_ret, long long *ts_ret,
int *bad_probes)
{
struct ptp_clock_sample *sample_send;
long long delta_min = LLONG_MAX;
long long offset = 0;
long long delta = 0;
long long ts = 0;
int max_i;
int i;
*bad_probes = 0;
sample_send = server->samples + (server->count / 2);
max_i = server->count / 2 < client->count ?
server->count / 2 : client->count;
for (i = 0; i < max_i; i++) {
if (!good_probe(&server->samples[i], &sample_send[i],
&client->samples[i], bad_probes))
continue;
ts = (sample_send[i].ts + server->samples[i].ts) / 2;
offset = client->samples[i].ts - ts;
delta = server->samples[i].ts - sample_send[i].ts;
if (delta_min > delta) {
delta_min = delta;
*offset_ret = offset;
*ts_ret = ts;
}
#ifdef TSYNC_DEBUG
{
struct ptp_clock_sync *ptp;
ptp = (struct ptp_clock_sync *)clock->proto_data;
if (ptp && ptp->debug_fd > 0) {
char buff[256];
sprintf(buff, "%lld %lld %lld\n",
ts, client->samples[i].ts, offset);
write(ptp->debug_fd, buff, strlen(buff));
}
}
#endif
}
return 0;
}
static int ptp_calc_offset_hist(struct clock_sync_context *clock,
struct ptp_clock_result_msg *server,
struct ptp_clock_result_msg *client,
long long *offset_ret, long long *ts_ret,
int *bad_probes)
{
struct ptp_clock_sample *sample_send;
long long timestamps[PTP_SYNC_LOOP];
long long offsets[PTP_SYNC_LOOP];
long long offset_min = LLONG_MAX;
long long offset_max = 0;
int hist[PTP_SYNC_LOOP];
int ind, max = 0;
long long bin;
int i, k = 0;
*bad_probes = 0;
memset(hist, 0, sizeof(int) * PTP_SYNC_LOOP);
sample_send = server->samples + (server->count / 2);
for (i = 0; i * 2 < server->count && i < client->count; i++) {
if (!good_probe(&server->samples[i], &sample_send[i],
&client->samples[i], bad_probes))
continue;
timestamps[k] = (sample_send[i].ts + server->samples[i].ts) / 2;
offsets[k] = client->samples[i].ts - timestamps[k];
if (offset_max < llabs(offsets[k]))
offset_max = llabs(offsets[k]);
if (offset_min > llabs(offsets[k]))
offset_min = llabs(offsets[k]);
#ifdef TSYNC_DEBUG
{
struct ptp_clock_sync *ptp;
ptp = (struct ptp_clock_sync *)clock->proto_data;
if (ptp && ptp->debug_fd > 0) {
char buff[256];
sprintf(buff, "%lld %lld %lld\n",
timestamps[k],
client->samples[i].ts, offsets[k]);
write(ptp->debug_fd, buff, strlen(buff));
}
}
#endif
k++;
}
bin = (offset_max - offset_min) / PTP_SYNC_LOOP;
for (i = 0; i < k; i++) {
ind = (llabs(offsets[i]) - offset_min) / bin;
if (ind < PTP_SYNC_LOOP) {
hist[ind]++;
if (max < hist[ind]) {
max = hist[ind];
*offset_ret = offsets[i];
*ts_ret = timestamps[i];
}
}
}
return 0;
}
static void ntoh_ptp_results(struct ptp_clock_result_msg *msg)
{
int i;
msg->count = ntohl(msg->count);
for (i = 0; i < msg->count; i++) {
msg->samples[i].id = ntohl(msg->samples[i].id);
msg->samples[i].ts = ntohll(msg->samples[i].ts);
}
msg->series_id = ntohl(msg->series_id);
}
static void hton_ptp_results(struct ptp_clock_result_msg *msg)
{
int i;
for (i = 0; i < msg->count; i++) {
msg->samples[i].id = htonl(msg->samples[i].id);
msg->samples[i].ts = htonll(msg->samples[i].ts);
}
msg->series_id = htonl(msg->series_id);
msg->count = htonl(msg->count);
}
static inline void ptp_track_clock(struct ptp_markers_context *ctx,
struct ptp_marker *marker)
{
if (ctx->ptp->flags & PTP_FLAG_USE_MARKER) {
write(ctx->ptp->marker_fd, marker, sizeof(struct ptp_marker));
} else {
struct timespec clock;
unsigned long long ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &clock);
ts = clock.tv_sec * 1000000000LL;
ts += clock.tv_nsec;
ptp_probe_store(ctx, marker, ts);
}
}
static int ptp_clock_client(struct tracecmd_time_sync *tsync,
long long *offset, long long *timestamp)
{
char sync_proto[TRACECMD_TSYNC_PNAME_LENGTH];
struct clock_sync_context *clock_context;
struct ptp_clock_offset_msg res_offset;
struct ptp_clock_start_msg start;
struct ptp_markers_context ctx;
struct ptp_clock_sync *ptp;
struct ptp_marker marker;
unsigned int sync_msg;
unsigned int size;
char *msg;
int count;
int ret;
if (!tsync || !tsync->context || !tsync->msg_handle)
return -1;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context->proto_data == NULL)
return -1;
ptp = (struct ptp_clock_sync *)clock_context->proto_data;
size = sizeof(start);
msg = (char *)&start;
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, &msg);
if (ret || strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_START)
return -1;
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_START, sizeof(start),
(char *)&start);
marker.data.local_id = clock_context->local_id;
marker.data.remote_id = clock_context->remote_id;
marker.series_id = ntohl(start.series_id);
marker.data.packet_id = 'r';
ptp->series_id = marker.series_id;
ptp->flags = ntohl(start.flags);
msg = (char *)&count;
size = sizeof(count);
ctx.msg.count = 0;
ctx.size = PTP_SYNC_LOOP;
ctx.ptp = ptp;
ctx.clock = clock_context;
ctx.msg.series_id = ptp->series_id;
while (true) {
count = 0;
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, &msg);
if (ret || strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_PROBE || !ntohl(count))
break;
marker.data.count = ntohl(count);
ptp_track_clock(&ctx, &marker);
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_PROBE,
sizeof(count), (char *)&count);
if (ret)
break;
}
if (strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_END)
return -1;
if (ptp->flags & PTP_FLAG_USE_MARKER)
tracefs_iterate_raw_events(ptp->tep, clock_context->instance,
NULL, 0, ptp_marker_find, &ctx);
hton_ptp_results(&ctx.msg);
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_PROBES,
sizeof(ctx.msg), (char *)&ctx.msg);
msg = (char *)&res_offset;
size = sizeof(res_offset);
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, (char **)&msg);
if (ret || strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_OFFSET)
return -1;
*offset = ntohll(res_offset.offset);
*timestamp = ntohll(res_offset.ts);
return 0;
}
static int ptp_clock_server(struct tracecmd_time_sync *tsync,
long long *offset, long long *timestamp)
{
char sync_proto[TRACECMD_TSYNC_PNAME_LENGTH];
struct ptp_clock_result_msg *results = NULL;
struct clock_sync_context *clock_context;
struct ptp_clock_offset_msg res_offset;
struct ptp_clock_start_msg start;
struct ptp_markers_context ctx;
int sync_loop = PTP_SYNC_LOOP;
struct ptp_clock_sync *ptp;
struct ptp_marker marker;
unsigned int sync_msg;
unsigned int size;
int bad_probes;
int count = 1;
int msg_count;
int msg_ret;
char *msg;
int ret;
if (!tsync || !tsync->context || !tsync->msg_handle)
return -1;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context->proto_data == NULL)
return -1;
ptp = (struct ptp_clock_sync *)clock_context->proto_data;
ptp->flags = ptp_flags;
memset(&start, 0, sizeof(start));
start.series_id = htonl(ptp->series_id + 1);
start.flags = htonl(ptp->flags);
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_START, sizeof(start),
(char *)&start);
if (!ret)
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
NULL, NULL);
if (ret || strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_START)
return -1;
tracefs_instance_file_write(clock_context->instance, "trace", "\0");
ptp->series_id++;
marker.data.local_id = clock_context->local_id;
marker.data.remote_id = clock_context->remote_id;
marker.series_id = ptp->series_id;
msg = (char *)&msg_ret;
size = sizeof(msg_ret);
ctx.size = 2*PTP_SYNC_LOOP;
ctx.ptp = ptp;
ctx.clock = clock_context;
ctx.msg.count = 0;
ctx.msg.series_id = ptp->series_id;
do {
marker.data.count = count++;
marker.data.packet_id = 's';
msg_count = htonl(marker.data.count);
ptp_track_clock(&ctx, &marker);
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_PROBE,
sizeof(msg_count),
(char *)&msg_count);
if (!ret)
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, &msg);
marker.data.packet_id = 'r';
ptp_track_clock(&ctx, &marker);
if (ret || strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_PROBE ||
ntohl(msg_ret) != marker.data.count)
break;
} while (--sync_loop);
if (sync_loop)
return -1;
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_END, 0, NULL);
size = 0;
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, (char **)&results);
if (ret || strncmp(sync_proto, PTP_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != PTP_SYNC_PKT_PROBES || size == 0 || results == NULL)
return -1;
ntoh_ptp_results(results);
if (ptp->flags & PTP_FLAG_USE_MARKER)
tracefs_iterate_raw_events(ptp->tep, clock_context->instance,
NULL, 0, ptp_marker_find, &ctx);
if (ptp->flags & PTP_FLAG_FASTEST_RESPONSE)
ptp_calc_offset_fastest(clock_context, &ctx.msg, results, offset,
timestamp, &bad_probes);
else
ptp_calc_offset_hist(clock_context, &ctx.msg, results, offset,
timestamp, &bad_probes);
#ifdef TSYNC_DEBUG
{
char buff[256];
int res_fd;
sprintf(buff, "res-id%d.txt", clock_context->remote_id);
res_fd = open(buff, O_WRONLY|O_APPEND, 0644);
if (res_fd > 0) {
if (*offset && *timestamp) {
sprintf(buff, "%d %lld %lld\n",
ptp->series_id, *offset, *timestamp);
write(res_fd, buff, strlen(buff));
}
close(res_fd);
}
printf("\n calculated offset %d: %lld, %d probes, filtered out %d, PTP flags 0x%X\n\r",
ptp->series_id, *offset, results->count, bad_probes, ptp->flags);
if (ptp && ptp->debug_fd > 0) {
sprintf(buff, "%lld %lld 0\n", *offset, *timestamp);
write(ptp->debug_fd, buff, strlen(buff));
close(ptp->debug_fd);
ptp->debug_fd = -1;
}
}
#endif
res_offset.offset = htonll(*offset);
res_offset.ts = htonll(*timestamp);
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, PTP_NAME,
PTP_SYNC_PKT_OFFSET,
sizeof(res_offset),
(char *)&res_offset);
free(results);
return 0;
}
static int ptp_clock_sync_calc(struct tracecmd_time_sync *tsync,
long long *offset, long long *scaling, long long *frac,
long long *timestamp, unsigned int cpu)
{
struct clock_sync_context *clock_context;
int ret;
if (!tsync || !tsync->context)
return -1;
clock_context = (struct clock_sync_context *)tsync->context;
#ifdef TSYNC_DEBUG
if (clock_context->is_server) {
struct ptp_clock_sync *ptp;
char buff[256];
ptp = (struct ptp_clock_sync *)clock_context->proto_data;
if (ptp->debug_fd > 0)
close(ptp->debug_fd);
sprintf(buff, "s-id%d_%d.txt",
clock_context->remote_id, ptp->series_id+1);
ptp->debug_fd = open(buff, O_CREAT|O_WRONLY|O_TRUNC, 0644);
}
#endif
if (scaling)
*scaling = 1;
if (frac)
*frac = 0;
if (clock_context->is_server)
ret = ptp_clock_server(tsync, offset, timestamp);
else
ret = ptp_clock_client(tsync, offset, timestamp);
return ret;
}
int ptp_clock_sync_register(void)
{
return tracecmd_tsync_proto_register(PTP_NAME, PTP_ACCURACY,
TRACECMD_TIME_SYNC_ROLE_GUEST |
TRACECMD_TIME_SYNC_ROLE_HOST |
TRACECMD_TIME_SYNC_ROLE_CLIENT |
TRACECMD_TIME_SYNC_ROLE_SERVER,
0, TRACECMD_TSYNC_FLAG_INTERPOLATE,
ptp_clock_sync_init,
ptp_clock_sync_free,
ptp_clock_sync_calc);
}
int ptp_clock_sync_unregister(void)
{
return tracecmd_tsync_proto_unregister(PTP_NAME);
}