Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

timecompare: generic infrastructure to map between two time bases

Mapping from a struct timecounter to a time returned by functions like
ktime_get_real() is implemented. This is sufficient to use this code
in a network device driver which wants to support hardware time
stamping and transformation of hardware time stamps to system time.

The interface could have been made more versatile by not depending on
a time counter, but this wasn't done to avoid writing glue code
elsewhere.

The method implemented here is the one used and analyzed under the name
"assisted PTP" in the LCI PTP paper:
http://www.linuxclustersinstitute.org/conferences/archive/2008/PDF/Ohly_92221.pdf

Acked-by: John Stultz <johnstul@us.ibm.com>
Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Patrick Ohly and committed by
David S. Miller
a75244c3 a038a353

+317 -1
+125
include/linux/timecompare.h
··· 1 + /* 2 + * Utility code which helps transforming between two different time 3 + * bases, called "source" and "target" time in this code. 4 + * 5 + * Source time has to be provided via the timecounter API while target 6 + * time is accessed via a function callback whose prototype 7 + * intentionally matches ktime_get() and ktime_get_real(). These 8 + * interfaces where chosen like this so that the code serves its 9 + * initial purpose without additional glue code. 10 + * 11 + * This purpose is synchronizing a hardware clock in a NIC with system 12 + * time, in order to implement the Precision Time Protocol (PTP, 13 + * IEEE1588) with more accurate hardware assisted time stamping. In 14 + * that context only synchronization against system time (= 15 + * ktime_get_real()) is currently needed. But this utility code might 16 + * become useful in other situations, which is why it was written as 17 + * general purpose utility code. 18 + * 19 + * The source timecounter is assumed to return monotonically 20 + * increasing time (but this code does its best to compensate if that 21 + * is not the case) whereas target time may jump. 22 + * 23 + * The target time corresponding to a source time is determined by 24 + * reading target time, reading source time, reading target time 25 + * again, then assuming that average target time corresponds to source 26 + * time. In other words, the assumption is that reading the source 27 + * time is slow and involves equal time for sending the request and 28 + * receiving the reply, whereas reading target time is assumed to be 29 + * fast. 30 + * 31 + * Copyright (C) 2009 Intel Corporation. 32 + * Author: Patrick Ohly <patrick.ohly@intel.com> 33 + * 34 + * This program is free software; you can redistribute it and/or modify it 35 + * under the terms and conditions of the GNU General Public License, 36 + * version 2, as published by the Free Software Foundation. 37 + * 38 + * This program is distributed in the hope it will be useful, but WITHOUT 39 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 40 + * FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for 41 + * more details. 42 + * 43 + * You should have received a copy of the GNU General Public License along with 44 + * this program; if not, write to the Free Software Foundation, Inc., 45 + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. 46 + */ 47 + #ifndef _LINUX_TIMECOMPARE_H 48 + #define _LINUX_TIMECOMPARE_H 49 + 50 + #include <linux/clocksource.h> 51 + #include <linux/ktime.h> 52 + 53 + /** 54 + * struct timecompare - stores state and configuration for the two clocks 55 + * 56 + * Initialize to zero, then set source/target/num_samples. 57 + * 58 + * Transformation between source time and target time is done with: 59 + * target_time = source_time + offset + 60 + * (source_time - last_update) * skew / 61 + * TIMECOMPARE_SKEW_RESOLUTION 62 + * 63 + * @source: used to get source time stamps via timecounter_read() 64 + * @target: function returning target time (for example, ktime_get 65 + * for monotonic time, or ktime_get_real for wall clock) 66 + * @num_samples: number of times that source time and target time are to 67 + * be compared when determining their offset 68 + * @offset: (target time - source time) at the time of the last update 69 + * @skew: average (target time - source time) / delta source time * 70 + * TIMECOMPARE_SKEW_RESOLUTION 71 + * @last_update: last source time stamp when time offset was measured 72 + */ 73 + struct timecompare { 74 + struct timecounter *source; 75 + ktime_t (*target)(void); 76 + int num_samples; 77 + 78 + s64 offset; 79 + s64 skew; 80 + u64 last_update; 81 + }; 82 + 83 + /** 84 + * timecompare_transform - transform source time stamp into target time base 85 + * @sync: context for time sync 86 + * @source_tstamp: the result of timecounter_read() or 87 + * timecounter_cyc2time() 88 + */ 89 + extern ktime_t timecompare_transform(struct timecompare *sync, 90 + u64 source_tstamp); 91 + 92 + /** 93 + * timecompare_offset - measure current (target time - source time) offset 94 + * @sync: context for time sync 95 + * @offset: average offset during sample period returned here 96 + * @source_tstamp: average source time during sample period returned here 97 + * 98 + * Returns number of samples used. Might be zero (= no result) in the 99 + * unlikely case that target time was monotonically decreasing for all 100 + * samples (= broken). 101 + */ 102 + extern int timecompare_offset(struct timecompare *sync, 103 + s64 *offset, 104 + u64 *source_tstamp); 105 + 106 + extern void __timecompare_update(struct timecompare *sync, 107 + u64 source_tstamp); 108 + 109 + /** 110 + * timecompare_update - update offset and skew by measuring current offset 111 + * @sync: context for time sync 112 + * @source_tstamp: the result of timecounter_read() or 113 + * timecounter_cyc2time(), pass zero to force update 114 + * 115 + * Updates are only done at most once per second. 116 + */ 117 + static inline void timecompare_update(struct timecompare *sync, 118 + u64 source_tstamp) 119 + { 120 + if (!source_tstamp || 121 + (s64)(source_tstamp - sync->last_update) >= NSEC_PER_SEC) 122 + __timecompare_update(sync, source_tstamp); 123 + } 124 + 125 + #endif /* _LINUX_TIMECOMPARE_H */
+1 -1
kernel/time/Makefile
··· 1 - obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o 1 + obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o 2 2 3 3 obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o 4 4 obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o
+191
kernel/time/timecompare.c
··· 1 + /* 2 + * Copyright (C) 2009 Intel Corporation. 3 + * Author: Patrick Ohly <patrick.ohly@intel.com> 4 + * 5 + * This program is free software; you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License as published by 7 + * the Free Software Foundation; either version 2 of the License, or 8 + * (at your option) any later version. 9 + * 10 + * This program is distributed in the hope that it will be useful, 11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 + * GNU General Public License for more details. 14 + * 15 + * You should have received a copy of the GNU General Public License 16 + * along with this program; if not, write to the Free Software 17 + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 + */ 19 + 20 + #include <linux/timecompare.h> 21 + #include <linux/module.h> 22 + #include <linux/math64.h> 23 + 24 + /* 25 + * fixed point arithmetic scale factor for skew 26 + * 27 + * Usually one would measure skew in ppb (parts per billion, 1e9), but 28 + * using a factor of 2 simplifies the math. 29 + */ 30 + #define TIMECOMPARE_SKEW_RESOLUTION (((s64)1)<<30) 31 + 32 + ktime_t timecompare_transform(struct timecompare *sync, 33 + u64 source_tstamp) 34 + { 35 + u64 nsec; 36 + 37 + nsec = source_tstamp + sync->offset; 38 + nsec += (s64)(source_tstamp - sync->last_update) * sync->skew / 39 + TIMECOMPARE_SKEW_RESOLUTION; 40 + 41 + return ns_to_ktime(nsec); 42 + } 43 + EXPORT_SYMBOL(timecompare_transform); 44 + 45 + int timecompare_offset(struct timecompare *sync, 46 + s64 *offset, 47 + u64 *source_tstamp) 48 + { 49 + u64 start_source = 0, end_source = 0; 50 + struct { 51 + s64 offset; 52 + s64 duration_target; 53 + } buffer[10], sample, *samples; 54 + int counter = 0, i; 55 + int used; 56 + int index; 57 + int num_samples = sync->num_samples; 58 + 59 + if (num_samples > sizeof(buffer)/sizeof(buffer[0])) { 60 + samples = kmalloc(sizeof(*samples) * num_samples, GFP_ATOMIC); 61 + if (!samples) { 62 + samples = buffer; 63 + num_samples = sizeof(buffer)/sizeof(buffer[0]); 64 + } 65 + } else { 66 + samples = buffer; 67 + } 68 + 69 + /* run until we have enough valid samples, but do not try forever */ 70 + i = 0; 71 + counter = 0; 72 + while (1) { 73 + u64 ts; 74 + ktime_t start, end; 75 + 76 + start = sync->target(); 77 + ts = timecounter_read(sync->source); 78 + end = sync->target(); 79 + 80 + if (!i) 81 + start_source = ts; 82 + 83 + /* ignore negative durations */ 84 + sample.duration_target = ktime_to_ns(ktime_sub(end, start)); 85 + if (sample.duration_target >= 0) { 86 + /* 87 + * assume symetric delay to and from source: 88 + * average target time corresponds to measured 89 + * source time 90 + */ 91 + sample.offset = 92 + ktime_to_ns(ktime_add(end, start)) / 2 - 93 + ts; 94 + 95 + /* simple insertion sort based on duration */ 96 + index = counter - 1; 97 + while (index >= 0) { 98 + if (samples[index].duration_target < 99 + sample.duration_target) 100 + break; 101 + samples[index + 1] = samples[index]; 102 + index--; 103 + } 104 + samples[index + 1] = sample; 105 + counter++; 106 + } 107 + 108 + i++; 109 + if (counter >= num_samples || i >= 100000) { 110 + end_source = ts; 111 + break; 112 + } 113 + } 114 + 115 + *source_tstamp = (end_source + start_source) / 2; 116 + 117 + /* remove outliers by only using 75% of the samples */ 118 + used = counter * 3 / 4; 119 + if (!used) 120 + used = counter; 121 + if (used) { 122 + /* calculate average */ 123 + s64 off = 0; 124 + for (index = 0; index < used; index++) 125 + off += samples[index].offset; 126 + *offset = div_s64(off, used); 127 + } 128 + 129 + if (samples && samples != buffer) 130 + kfree(samples); 131 + 132 + return used; 133 + } 134 + EXPORT_SYMBOL(timecompare_offset); 135 + 136 + void __timecompare_update(struct timecompare *sync, 137 + u64 source_tstamp) 138 + { 139 + s64 offset; 140 + u64 average_time; 141 + 142 + if (!timecompare_offset(sync, &offset, &average_time)) 143 + return; 144 + 145 + if (!sync->last_update) { 146 + sync->last_update = average_time; 147 + sync->offset = offset; 148 + sync->skew = 0; 149 + } else { 150 + s64 delta_nsec = average_time - sync->last_update; 151 + 152 + /* avoid division by negative or small deltas */ 153 + if (delta_nsec >= 10000) { 154 + s64 delta_offset_nsec = offset - sync->offset; 155 + s64 skew; /* delta_offset_nsec * 156 + TIMECOMPARE_SKEW_RESOLUTION / 157 + delta_nsec */ 158 + u64 divisor; 159 + 160 + /* div_s64() is limited to 32 bit divisor */ 161 + skew = delta_offset_nsec * TIMECOMPARE_SKEW_RESOLUTION; 162 + divisor = delta_nsec; 163 + while (unlikely(divisor >= ((s64)1) << 32)) { 164 + /* divide both by 2; beware, right shift 165 + of negative value has undefined 166 + behavior and can only be used for 167 + the positive divisor */ 168 + skew = div_s64(skew, 2); 169 + divisor >>= 1; 170 + } 171 + skew = div_s64(skew, divisor); 172 + 173 + /* 174 + * Calculate new overall skew as 4/16 the 175 + * old value and 12/16 the new one. This is 176 + * a rather arbitrary tradeoff between 177 + * only using the latest measurement (0/16 and 178 + * 16/16) and even more weight on past measurements. 179 + */ 180 + #define TIMECOMPARE_NEW_SKEW_PER_16 12 181 + sync->skew = 182 + div_s64((16 - TIMECOMPARE_NEW_SKEW_PER_16) * 183 + sync->skew + 184 + TIMECOMPARE_NEW_SKEW_PER_16 * skew, 185 + 16); 186 + sync->last_update = average_time; 187 + sync->offset = offset; 188 + } 189 + } 190 + } 191 + EXPORT_SYMBOL(__timecompare_update);