blob: 12a22d4c4d6a47f845f973546fc4d117dcc403fa [file] [edit]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2020, VMware, Tzvetomir Stoyanov tz.stoyanov@gmail.com>
*
*/
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h>
#include "trace-cmd.h"
#include "trace-cmd-private.h"
#include "tracefs.h"
#include "trace-tsync-local.h"
#define KVM_DEBUG_FS "/sys/kernel/debug/kvm"
#define KVM_DEBUG_OFFSET_FILE "tsc-offset"
#define KVM_DEBUG_SCALING_FILE "tsc-scaling-ratio"
#define KVM_DEBUG_FRACTION_FILE "tsc-scaling-ratio-frac-bits"
#define KVM_DEBUG_VCPU_DIR "vcpu"
/* default KVM scaling values, taken from the Linux kernel */
#define KVM_SCALING_AMD_DEFAULT (1ULL<<32)
#define KVM_SCALING_INTEL_DEFAULT (1ULL<<48)
#define KVM_SYNC_PKT_REQUEST 1
#define KVM_SYNC_PKT_RESPONSE 2
typedef __s64 s64;
#define KVM_ACCURACY 0
#define KVM_NAME "kvm"
struct kvm_clock_sync {
int vcpu_count;
char **vcpu_offsets;
char **vcpu_scalings;
char **vcpu_frac;
int marker_fd;
struct tep_handle *tep;
int raw_id;
unsigned long long ts;
};
struct kvm_clock_offset_msg {
s64 ts;
s64 offset;
s64 scaling;
s64 frac;
};
static int read_ll_from_file(char *file, long long *res)
{
char buf[32];
int ret;
int fd;
if (!file)
return -1;
fd = open(file, O_RDONLY | O_NONBLOCK);
if (fd < 0)
return -1;
ret = read(fd, buf, 32);
close(fd);
if (ret <= 0)
return -1;
*res = strtoll(buf, NULL, 0);
return 0;
}
static bool kvm_scaling_check_vm_cpu(char *vname, char *cpu)
{
long long scaling, frac;
bool has_scaling = false;
bool has_frac = false;
char *path;
int ret;
if (asprintf(&path, "%s/%s/%s", vname, cpu, KVM_DEBUG_SCALING_FILE) < 0)
return false;
ret = read_ll_from_file(path, &scaling);
free(path);
if (!ret)
has_scaling = true;
if (asprintf(&path, "%s/%s/%s", vname, cpu, KVM_DEBUG_FRACTION_FILE) < 0)
return false;
ret = read_ll_from_file(path, &frac);
free(path);
if (!ret)
has_frac = true;
if (has_scaling != has_frac)
return false;
return true;
}
static bool kvm_scaling_check_vm(char *name)
{
struct dirent *entry;
char *vdir;
DIR *dir;
if (asprintf(&vdir, "%s/%s", KVM_DEBUG_FS, name) < 0)
return true;
dir = opendir(vdir);
if (!dir) {
free(vdir);
return true;
}
while ((entry = readdir(dir))) {
if (entry->d_type == DT_DIR && !strncmp(entry->d_name, "vcpu", 4) &&
!kvm_scaling_check_vm_cpu(vdir, entry->d_name))
break;
}
closedir(dir);
free(vdir);
return entry == NULL;
}
static bool kvm_scaling_check(void)
{
struct dirent *entry;
DIR *dir;
dir = opendir(KVM_DEBUG_FS);
if (!dir)
return true;
while ((entry = readdir(dir))) {
if (entry->d_type == DT_DIR && isdigit(entry->d_name[0]) &&
!kvm_scaling_check_vm(entry->d_name))
break;
}
closedir(dir);
return entry == NULL;
}
static bool kvm_support_check(bool guest)
{
struct stat st;
int ret;
if (guest)
return true;
ret = stat(KVM_DEBUG_FS, &st);
if (ret < 0)
return false;
if (!S_ISDIR(st.st_mode))
return false;
return kvm_scaling_check();
}
static int kvm_open_vcpu_dir(struct kvm_clock_sync *kvm, int cpu, char *dir_str)
{
struct dirent *entry;
char path[PATH_MAX];
DIR *dir;
dir = opendir(dir_str);
if (!dir)
goto error;
while ((entry = readdir(dir))) {
if (entry->d_type != DT_DIR) {
if (!strcmp(entry->d_name, KVM_DEBUG_OFFSET_FILE)) {
snprintf(path, sizeof(path), "%s/%s",
dir_str, entry->d_name);
kvm->vcpu_offsets[cpu] = strdup(path);
}
if (!strcmp(entry->d_name, KVM_DEBUG_SCALING_FILE)) {
snprintf(path, sizeof(path), "%s/%s",
dir_str, entry->d_name);
kvm->vcpu_scalings[cpu] = strdup(path);
}
if (!strcmp(entry->d_name, KVM_DEBUG_FRACTION_FILE)) {
snprintf(path, sizeof(path), "%s/%s",
dir_str, entry->d_name);
kvm->vcpu_frac[cpu] = strdup(path);
}
}
}
if (!kvm->vcpu_offsets[cpu])
goto error;
closedir(dir);
return 0;
error:
if (dir)
closedir(dir);
free(kvm->vcpu_offsets[cpu]);
kvm->vcpu_offsets[cpu] = NULL;
free(kvm->vcpu_scalings[cpu]);
kvm->vcpu_scalings[cpu] = NULL;
free(kvm->vcpu_frac[cpu]);
kvm->vcpu_frac[cpu] = NULL;
return -1;
}
static int kvm_open_debug_files(struct kvm_clock_sync *kvm, int pid)
{
char *vm_dir_str = NULL;
struct dirent *entry;
char *pid_str = NULL;
char path[PATH_MAX];
long vcpu;
DIR *dir;
int i;
dir = opendir(KVM_DEBUG_FS);
if (!dir)
goto error;
if (asprintf(&pid_str, "%d-", pid) <= 0)
goto error;
while ((entry = readdir(dir))) {
if (!(entry->d_type == DT_DIR &&
!strncmp(entry->d_name, pid_str, strlen(pid_str))))
continue;
asprintf(&vm_dir_str, "%s/%s", KVM_DEBUG_FS, entry->d_name);
break;
}
closedir(dir);
dir = NULL;
if (!vm_dir_str)
goto error;
dir = opendir(vm_dir_str);
if (!dir)
goto error;
while ((entry = readdir(dir))) {
if (!(entry->d_type == DT_DIR &&
!strncmp(entry->d_name, KVM_DEBUG_VCPU_DIR, strlen(KVM_DEBUG_VCPU_DIR))))
continue;
vcpu = strtol(entry->d_name + strlen(KVM_DEBUG_VCPU_DIR), NULL, 10);
if (vcpu < 0 || vcpu >= kvm->vcpu_count)
continue;
snprintf(path, sizeof(path), "%s/%s", vm_dir_str, entry->d_name);
if (kvm_open_vcpu_dir(kvm, vcpu, path) < 0)
goto error;
}
for (i = 0; i < kvm->vcpu_count; i++) {
if (!kvm->vcpu_offsets[i])
goto error;
}
closedir(dir);
free(pid_str);
free(vm_dir_str);
return 0;
error:
free(pid_str);
free(vm_dir_str);
if (dir)
closedir(dir);
return -1;
}
static int kvm_clock_sync_init_host(struct tracecmd_time_sync *tsync,
struct kvm_clock_sync *kvm)
{
kvm->vcpu_count = tsync->vcpu_count;
kvm->vcpu_offsets = calloc(kvm->vcpu_count, sizeof(char *));
kvm->vcpu_scalings = calloc(kvm->vcpu_count, sizeof(char *));
kvm->vcpu_frac = calloc(kvm->vcpu_count, sizeof(char *));
if (!kvm->vcpu_offsets || !kvm->vcpu_scalings || !kvm->vcpu_frac)
goto error;
if (kvm_open_debug_files(kvm, tsync->guest_pid) < 0)
goto error;
return 0;
error:
free(kvm->vcpu_offsets);
free(kvm->vcpu_scalings);
free(kvm->vcpu_frac);
return -1;
}
static int kvm_clock_sync_init_guest(struct tracecmd_time_sync *tsync,
struct kvm_clock_sync *kvm)
{
const char *systems[] = {"ftrace", NULL};
struct clock_sync_context *clock_context;
struct tep_event *raw;
char *path;
clock_context = (struct clock_sync_context *)tsync->context;
path = tracefs_instance_get_dir(clock_context->instance);
if (!path)
goto error;
kvm->tep = tracefs_local_events_system(path, systems);
tracefs_put_tracing_file(path);
if (!kvm->tep)
goto error;
raw = tep_find_event_by_name(kvm->tep, "ftrace", "raw_data");
if (!raw)
goto error;
kvm->raw_id = raw->id;
tep_set_file_bigendian(kvm->tep, tracecmd_host_bigendian());
tep_set_local_bigendian(kvm->tep, tracecmd_host_bigendian());
path = tracefs_instance_get_file(clock_context->instance, "trace_marker_raw");
if (!path)
goto error;
kvm->marker_fd = open(path, O_WRONLY);
tracefs_put_tracing_file(path);
return 0;
error:
if (kvm->tep)
tep_free(kvm->tep);
if (kvm->marker_fd >= 0)
close(kvm->marker_fd);
return -1;
}
static int kvm_clock_sync_init(struct tracecmd_time_sync *tsync)
{
struct clock_sync_context *clock_context;
struct kvm_clock_sync *kvm;
int ret;
if (!tsync || !tsync->context)
return -1;
clock_context = (struct clock_sync_context *)tsync->context;
if (!kvm_support_check(clock_context->is_guest))
return -1;
kvm = calloc(1, sizeof(struct kvm_clock_sync));
if (!kvm)
return -1;
kvm->marker_fd = -1;
if (clock_context->is_guest)
ret = kvm_clock_sync_init_guest(tsync, kvm);
else
ret = kvm_clock_sync_init_host(tsync, kvm);
if (ret < 0)
goto error;
clock_context->proto_data = kvm;
return 0;
error:
free(kvm);
return -1;
}
static int kvm_clock_sync_free(struct tracecmd_time_sync *tsync)
{
struct clock_sync_context *clock_context;
struct kvm_clock_sync *kvm = NULL;
int i;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context)
kvm = (struct kvm_clock_sync *)clock_context->proto_data;
if (kvm) {
for (i = 0; i < kvm->vcpu_count; i++) {
free(kvm->vcpu_offsets[i]);
kvm->vcpu_offsets[i] = NULL;
free(kvm->vcpu_scalings[i]);
kvm->vcpu_scalings[i] = NULL;
free(kvm->vcpu_frac[i]);
kvm->vcpu_frac[i] = NULL;
}
if (kvm->tep)
tep_free(kvm->tep);
if (kvm->marker_fd >= 0)
close(kvm->marker_fd);
free(kvm);
}
return -1;
}
static int kvm_clock_host(struct tracecmd_time_sync *tsync,
long long *offset, long long *scaling, long long *frac,
long long *timestamp, unsigned int cpu)
{
char sync_proto[TRACECMD_TSYNC_PNAME_LENGTH];
struct clock_sync_context *clock_context;
struct kvm_clock_offset_msg packet;
struct kvm_clock_sync *kvm = NULL;
long long kvm_scaling = 1;
unsigned int sync_msg;
long long kvm_offset;
long long kvm_frac = 0;
unsigned int size;
char *msg;
int ret;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context)
kvm = (struct kvm_clock_sync *)clock_context->proto_data;
if (!kvm || !kvm->vcpu_offsets || !kvm->vcpu_offsets[0])
return -1;
if (cpu >= kvm->vcpu_count)
return -1;
ret = read_ll_from_file(kvm->vcpu_offsets[cpu], &kvm_offset);
if (ret < 0)
return -1;
if (kvm->vcpu_scalings && kvm->vcpu_scalings[cpu]) {
read_ll_from_file(kvm->vcpu_scalings[cpu], &kvm_scaling);
if (kvm_scaling == KVM_SCALING_AMD_DEFAULT ||
kvm_scaling == KVM_SCALING_INTEL_DEFAULT)
kvm_scaling = 1;
}
if (kvm->vcpu_frac && kvm->vcpu_frac[cpu] && kvm_scaling != 1)
ret = read_ll_from_file(kvm->vcpu_frac[cpu], &kvm_frac);
msg = (char *)&packet;
size = sizeof(packet);
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, &msg);
if (ret || strncmp(sync_proto, KVM_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != KVM_SYNC_PKT_REQUEST)
return -1;
packet.offset = -kvm_offset;
packet.scaling = kvm_scaling;
packet.frac = kvm_frac;
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, KVM_NAME,
KVM_SYNC_PKT_RESPONSE, sizeof(packet),
(char *)&packet);
if (ret)
return -1;
*scaling = packet.scaling;
*offset = packet.offset;
*frac = kvm_frac;
*timestamp = packet.ts;
return 0;
}
#define KVM_EVENT_MARKER "kvm sync event"
static int kvm_marker_find(struct tep_event *event, struct tep_record *record,
int cpu, void *context)
{
struct kvm_clock_sync *kvm = (struct kvm_clock_sync *)context;
struct tep_format_field *field;
struct tep_format_field *id;
char *marker;
/* Make sure this is our event */
if (event->id != kvm->raw_id)
return 0;
id = tep_find_field(event, "id");
field = tep_find_field(event, "buf");
if (field && id &&
record->size >= (id->offset + strlen(KVM_EVENT_MARKER) + 1)) {
marker = (char *)(record->data + id->offset);
if (!strcmp(marker, KVM_EVENT_MARKER)) {
kvm->ts = record->ts;
return 1;
}
}
return 0;
}
static int kvm_clock_guest(struct tracecmd_time_sync *tsync,
long long *offset,
long long *scaling,
long long *frac,
long long *timestamp)
{
char sync_proto[TRACECMD_TSYNC_PNAME_LENGTH];
struct clock_sync_context *clock_context;
struct kvm_clock_offset_msg packet;
struct kvm_clock_sync *kvm = NULL;
unsigned int sync_msg;
unsigned int size;
char *msg;
int ret;
clock_context = (struct clock_sync_context *)tsync->context;
if (clock_context)
kvm = (struct kvm_clock_sync *)clock_context->proto_data;
if (!kvm)
return -1;
kvm->ts = 0;
memset(&packet, 0, sizeof(packet));
tracefs_instance_file_write(clock_context->instance, "trace", "\0");
write(kvm->marker_fd, KVM_EVENT_MARKER, strlen(KVM_EVENT_MARKER) + 1);
kvm->ts = 0;
tracefs_iterate_raw_events(kvm->tep, clock_context->instance,
NULL, 0, kvm_marker_find, kvm);
packet.ts = kvm->ts;
ret = tracecmd_msg_send_time_sync(tsync->msg_handle, KVM_NAME,
KVM_SYNC_PKT_REQUEST, sizeof(packet),
(char *)&packet);
if (ret)
return -1;
msg = (char *)&packet;
size = sizeof(packet);
ret = tracecmd_msg_recv_time_sync(tsync->msg_handle,
sync_proto, &sync_msg,
&size, &msg);
if (ret || strncmp(sync_proto, KVM_NAME, TRACECMD_TSYNC_PNAME_LENGTH) ||
sync_msg != KVM_SYNC_PKT_RESPONSE)
return -1;
*scaling = packet.scaling;
*offset = packet.offset;
*frac = packet.frac;
*timestamp = packet.ts;
return 0;
}
static int kvm_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;
if (clock_context->is_guest)
ret = kvm_clock_guest(tsync, offset, scaling, frac, timestamp);
else
ret = kvm_clock_host(tsync, offset, scaling, frac, timestamp, cpu);
return ret;
}
int kvm_clock_sync_register(void)
{
int role = TRACECMD_TIME_SYNC_ROLE_GUEST;
int clock = 0;
if (kvm_support_check(false)) {
role |= TRACECMD_TIME_SYNC_ROLE_HOST;
clock = TRACECMD_CLOCK_X86_TSC;
}
return tracecmd_tsync_proto_register(KVM_NAME, KVM_ACCURACY,
role, clock, 0,
kvm_clock_sync_init,
kvm_clock_sync_free,
kvm_clock_sync_calc);
}
int kvm_clock_sync_unregister(void)
{
return tracecmd_tsync_proto_unregister(KVM_NAME);
}