From: Hikaru Nishida <hikal...@chromium.org> This adds a sysfs interface /sys/kernel/boottime_adj to enable advancing CLOCK_BOOTTIME from the userspace without actual susupend/resume cycles.
This gives a way to mitigate CLOCK_BOOTTIME divergence between guest and host on virtualized environments after suspend/resume cycles on the host. We observed an issue of a guest application that expects there is a gap between CLOCK_BOOTTIME and CLOCK_MONOTONIC after the device is suspended to detect whether the device went into suspend or not. Since the guest is paused instead of being actually suspended during the host's suspension, guest kernel doesn't advance CLOCK_BOOTTIME correctly and there is no way to correct that. To solve the problem, this change introduces a way to modify a gap between those clocks and align the timer behavior to host's one. Signed-off-by: Hikaru Nishida <hikal...@chromium.org> --- drivers/virt/Kconfig | 9 ++++++ drivers/virt/Makefile | 1 + drivers/virt/boottime_adj.c | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 drivers/virt/boottime_adj.c diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig index 80c5f9c16ec1..149b4e763e4d 100644 --- a/drivers/virt/Kconfig +++ b/drivers/virt/Kconfig @@ -13,6 +13,15 @@ menuconfig VIRT_DRIVERS if VIRT_DRIVERS +config BOOTTIME_ADJUSTMENT + tristate "CLOCK_BOOTTIME adjustment sysfs interface" + help + The CLOCK_BOOTTIME adjustment sysfs interface driver + provides a sysfs interface ( /sys/kernel/boottime_adj ) + to enable adjusting CLOCK_BOOTTIME from the userspace. + + If unsure, say N. + config FSL_HV_MANAGER tristate "Freescale hypervisor management driver" depends on FSL_SOC diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index f28425ce4b39..1bbb476ddba9 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -3,6 +3,7 @@ # Makefile for drivers that support virtualization # +obj-$(CONFIG_BOOTTIME_ADJUSTMENT) += boottime_adj.o obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o obj-y += vboxguest/ diff --git a/drivers/virt/boottime_adj.c b/drivers/virt/boottime_adj.c new file mode 100644 index 000000000000..9cc717d8accc --- /dev/null +++ b/drivers/virt/boottime_adj.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CLOCK_BOOTTIME Adjustment Interface Driver + */ + +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/timekeeping.h> + +static struct kobject *kobj_boottime_adj; + +/* + * Write to /sys/kernel/boottime_adj advances CLOCK_BOOTTIME by given delta. + */ +static ssize_t boottime_adj_write(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, + size_t count) +{ + int error; + struct timespec64 delta; + + if (sscanf(buf, "%lld %ld", &delta.tv_sec, &delta.tv_nsec) != 2) + return -EINVAL; + + error = timekeeping_adjust_boottime(&delta); + if (error) + return error; + + pr_info("%s: CLOCK_BOOTTIME has been advanced by %+lld seconds and %+ld nanoseconds\n", + __func__, delta.tv_sec, delta.tv_nsec); + return count; +} + +static struct kobj_attribute boottime_adj_attr = +__ATTR(boottime_adj, 0200, NULL, boottime_adj_write); + +static int __init boottime_adj_init(void) +{ + int error; + + error = sysfs_create_file(kernel_kobj, &boottime_adj_attr.attr); + if (error) { + pr_warn("%s: failed to init\n", __func__); + return error; + } + return 0; +} + +static void __exit boottime_adj_cleanup(void) +{ + kobject_put(kobj_boottime_adj); +} + +module_init(boottime_adj_init); +module_exit(boottime_adj_cleanup); +MODULE_DESCRIPTION("CLOCK_BOOTTIME adjustment interface driver"); +MODULE_LICENSE("GPL"); -- 2.30.0.478.g8a0d178c01-goog