|
6 | 6 | #include <linux/timekeeper_internal.h> |
7 | 7 | #include <linux/module.h> |
8 | 8 | #include <linux/interrupt.h> |
| 9 | +#include <linux/kobject.h> |
9 | 10 | #include <linux/percpu.h> |
10 | 11 | #include <linux/init.h> |
11 | 12 | #include <linux/mm.h> |
@@ -2916,6 +2917,121 @@ const struct k_clock clock_aux = { |
2916 | 2917 | .clock_adj = aux_clock_adj, |
2917 | 2918 | }; |
2918 | 2919 |
|
| 2920 | +static void aux_clock_enable(clockid_t id) |
| 2921 | +{ |
| 2922 | + struct tk_read_base *tkr_raw = &tk_core.timekeeper.tkr_raw; |
| 2923 | + struct tk_data *aux_tkd = aux_get_tk_data(id); |
| 2924 | + struct timekeeper *aux_tks = &aux_tkd->shadow_timekeeper; |
| 2925 | + |
| 2926 | + /* Prevent the core timekeeper from changing. */ |
| 2927 | + guard(raw_spinlock_irq)(&tk_core.lock); |
| 2928 | + |
| 2929 | + /* |
| 2930 | + * Setup the auxiliary clock assuming that the raw core timekeeper |
| 2931 | + * clock frequency conversion is close enough. Userspace has to |
| 2932 | + * adjust for the deviation via clock_adjtime(2). |
| 2933 | + */ |
| 2934 | + guard(raw_spinlock_nested)(&aux_tkd->lock); |
| 2935 | + |
| 2936 | + /* Remove leftovers of a previous registration */ |
| 2937 | + memset(aux_tks, 0, sizeof(*aux_tks)); |
| 2938 | + /* Restore the timekeeper id */ |
| 2939 | + aux_tks->id = aux_tkd->timekeeper.id; |
| 2940 | + /* Setup the timekeeper based on the current system clocksource */ |
| 2941 | + tk_setup_internals(aux_tks, tkr_raw->clock); |
| 2942 | + |
| 2943 | + /* Mark it valid and set it live */ |
| 2944 | + aux_tks->clock_valid = true; |
| 2945 | + timekeeping_update_from_shadow(aux_tkd, TK_UPDATE_ALL); |
| 2946 | +} |
| 2947 | + |
| 2948 | +static void aux_clock_disable(clockid_t id) |
| 2949 | +{ |
| 2950 | + struct tk_data *aux_tkd = aux_get_tk_data(id); |
| 2951 | + |
| 2952 | + guard(raw_spinlock_irq)(&aux_tkd->lock); |
| 2953 | + aux_tkd->shadow_timekeeper.clock_valid = false; |
| 2954 | + timekeeping_update_from_shadow(aux_tkd, TK_UPDATE_ALL); |
| 2955 | +} |
| 2956 | + |
| 2957 | +static DEFINE_MUTEX(aux_clock_mutex); |
| 2958 | + |
| 2959 | +static ssize_t aux_clock_enable_store(struct kobject *kobj, struct kobj_attribute *attr, |
| 2960 | + const char *buf, size_t count) |
| 2961 | +{ |
| 2962 | + /* Lazy atoi() as name is "0..7" */ |
| 2963 | + int id = kobj->name[0] & 0x7; |
| 2964 | + bool enable; |
| 2965 | + |
| 2966 | + if (!capable(CAP_SYS_TIME)) |
| 2967 | + return -EPERM; |
| 2968 | + |
| 2969 | + if (kstrtobool(buf, &enable) < 0) |
| 2970 | + return -EINVAL; |
| 2971 | + |
| 2972 | + guard(mutex)(&aux_clock_mutex); |
| 2973 | + if (enable == test_bit(id, &aux_timekeepers)) |
| 2974 | + return count; |
| 2975 | + |
| 2976 | + if (enable) { |
| 2977 | + aux_clock_enable(CLOCK_AUX + id); |
| 2978 | + set_bit(id, &aux_timekeepers); |
| 2979 | + } else { |
| 2980 | + aux_clock_disable(CLOCK_AUX + id); |
| 2981 | + clear_bit(id, &aux_timekeepers); |
| 2982 | + } |
| 2983 | + return count; |
| 2984 | +} |
| 2985 | + |
| 2986 | +static ssize_t aux_clock_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| 2987 | +{ |
| 2988 | + unsigned long active = READ_ONCE(aux_timekeepers); |
| 2989 | + /* Lazy atoi() as name is "0..7" */ |
| 2990 | + int id = kobj->name[0] & 0x7; |
| 2991 | + |
| 2992 | + return sysfs_emit(buf, "%d\n", test_bit(id, &active)); |
| 2993 | +} |
| 2994 | + |
| 2995 | +static struct kobj_attribute aux_clock_enable_attr = __ATTR_RW(aux_clock_enable); |
| 2996 | + |
| 2997 | +static struct attribute *aux_clock_enable_attrs[] = { |
| 2998 | + &aux_clock_enable_attr.attr, |
| 2999 | + NULL |
| 3000 | +}; |
| 3001 | + |
| 3002 | +static const struct attribute_group aux_clock_enable_attr_group = { |
| 3003 | + .attrs = aux_clock_enable_attrs, |
| 3004 | +}; |
| 3005 | + |
| 3006 | +static int __init tk_aux_sysfs_init(void) |
| 3007 | +{ |
| 3008 | + struct kobject *auxo, *tko = kobject_create_and_add("time", kernel_kobj); |
| 3009 | + |
| 3010 | + if (!tko) |
| 3011 | + return -ENOMEM; |
| 3012 | + |
| 3013 | + auxo = kobject_create_and_add("aux_clocks", tko); |
| 3014 | + if (!auxo) { |
| 3015 | + kobject_put(tko); |
| 3016 | + return -ENOMEM; |
| 3017 | + } |
| 3018 | + |
| 3019 | + for (int i = 0; i <= MAX_AUX_CLOCKS; i++) { |
| 3020 | + char id[2] = { [0] = '0' + i, }; |
| 3021 | + struct kobject *clk = kobject_create_and_add(id, auxo); |
| 3022 | + |
| 3023 | + if (!clk) |
| 3024 | + return -ENOMEM; |
| 3025 | + |
| 3026 | + int ret = sysfs_create_group(clk, &aux_clock_enable_attr_group); |
| 3027 | + |
| 3028 | + if (ret) |
| 3029 | + return ret; |
| 3030 | + } |
| 3031 | + return 0; |
| 3032 | +} |
| 3033 | +late_initcall(tk_aux_sysfs_init); |
| 3034 | + |
2919 | 3035 | static __init void tk_aux_setup(void) |
2920 | 3036 | { |
2921 | 3037 | for (int i = TIMEKEEPER_AUX_FIRST; i <= TIMEKEEPER_AUX_LAST; i++) |
|
0 commit comments