Commit 7b95663a authored by Thomas Gleixner's avatar Thomas Gleixner
Browse files

timekeeping: Provide interface to control auxiliary clocks



Auxiliary clocks are disabled by default and attempts to access them
fail.

Provide an interface to enable/disable them at run-time.

Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Acked-by: default avatarJohn Stultz <jstultz@google.com>
Link: https://lore.kernel.org/all/20250625183758.444626478@linutronix.de
parent e6d4c007
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
What:		/sys/kernel/time/aux_clocks/<ID>/enable
Date:		May 2025
Contact:	Thomas Gleixner <tglx@linutronix.de>
Description:
		Controls the enablement of auxiliary clock timekeepers.
+116 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include <linux/timekeeper_internal.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/kobject.h>
#include <linux/percpu.h>
#include <linux/init.h>
#include <linux/mm.h>
@@ -2916,6 +2917,121 @@ const struct k_clock clock_aux = {
	.clock_adj		= aux_clock_adj,
};

static void aux_clock_enable(clockid_t id)
{
	struct tk_read_base *tkr_raw = &tk_core.timekeeper.tkr_raw;
	struct tk_data *aux_tkd = aux_get_tk_data(id);
	struct timekeeper *aux_tks = &aux_tkd->shadow_timekeeper;

	/* Prevent the core timekeeper from changing. */
	guard(raw_spinlock_irq)(&tk_core.lock);

	/*
	 * Setup the auxiliary clock assuming that the raw core timekeeper
	 * clock frequency conversion is close enough. Userspace has to
	 * adjust for the deviation via clock_adjtime(2).
	 */
	guard(raw_spinlock_nested)(&aux_tkd->lock);

	/* Remove leftovers of a previous registration */
	memset(aux_tks, 0, sizeof(*aux_tks));
	/* Restore the timekeeper id */
	aux_tks->id = aux_tkd->timekeeper.id;
	/* Setup the timekeeper based on the current system clocksource */
	tk_setup_internals(aux_tks, tkr_raw->clock);

	/* Mark it valid and set it live */
	aux_tks->clock_valid = true;
	timekeeping_update_from_shadow(aux_tkd, TK_UPDATE_ALL);
}

static void aux_clock_disable(clockid_t id)
{
	struct tk_data *aux_tkd = aux_get_tk_data(id);

	guard(raw_spinlock_irq)(&aux_tkd->lock);
	aux_tkd->shadow_timekeeper.clock_valid = false;
	timekeeping_update_from_shadow(aux_tkd, TK_UPDATE_ALL);
}

static DEFINE_MUTEX(aux_clock_mutex);

static ssize_t aux_clock_enable_store(struct kobject *kobj, struct kobj_attribute *attr,
				      const char *buf, size_t count)
{
	/* Lazy atoi() as name is "0..7" */
	int id = kobj->name[0] & 0x7;
	bool enable;

	if (!capable(CAP_SYS_TIME))
		return -EPERM;

	if (kstrtobool(buf, &enable) < 0)
		return -EINVAL;

	guard(mutex)(&aux_clock_mutex);
	if (enable == test_bit(id, &aux_timekeepers))
		return count;

	if (enable) {
		aux_clock_enable(CLOCK_AUX + id);
		set_bit(id, &aux_timekeepers);
	} else {
		aux_clock_disable(CLOCK_AUX + id);
		clear_bit(id, &aux_timekeepers);
	}
	return count;
}

static ssize_t aux_clock_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
	unsigned long active = READ_ONCE(aux_timekeepers);
	/* Lazy atoi() as name is "0..7" */
	int id = kobj->name[0] & 0x7;

	return sysfs_emit(buf, "%d\n", test_bit(id, &active));
}

static struct kobj_attribute aux_clock_enable_attr = __ATTR_RW(aux_clock_enable);

static struct attribute *aux_clock_enable_attrs[] = {
	&aux_clock_enable_attr.attr,
	NULL
};

static const struct attribute_group aux_clock_enable_attr_group = {
	.attrs = aux_clock_enable_attrs,
};

static int __init tk_aux_sysfs_init(void)
{
	struct kobject *auxo, *tko = kobject_create_and_add("time", kernel_kobj);

	if (!tko)
		return -ENOMEM;

	auxo = kobject_create_and_add("aux_clocks", tko);
	if (!auxo) {
		kobject_put(tko);
		return -ENOMEM;
	}

	for (int i = 0; i <= MAX_AUX_CLOCKS; i++) {
		char id[2] = { [0] = '0' + i, };
		struct kobject *clk = kobject_create_and_add(id, auxo);

		if (!clk)
			return -ENOMEM;

		int ret = sysfs_create_group(clk, &aux_clock_enable_attr_group);

		if (ret)
			return ret;
	}
	return 0;
}
late_initcall(tk_aux_sysfs_init);

static __init void tk_aux_setup(void)
{
	for (int i = TIMEKEEPER_AUX_FIRST; i <= TIMEKEEPER_AUX_LAST; i++)