Skip to content

Commit a75244c

Browse files
pohlydavem330
authored andcommitted
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 <[email protected]> Signed-off-by: Patrick Ohly <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent a038a35 commit a75244c

File tree

3 files changed

+317
-1
lines changed

3 files changed

+317
-1
lines changed

include/linux/timecompare.h

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 <[email protected]>
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 */

kernel/time/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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
22

33
obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o
44
obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o

kernel/time/timecompare.c

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright (C) 2009 Intel Corporation.
3+
* Author: Patrick Ohly <[email protected]>
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);

0 commit comments

Comments
 (0)