| Patrick Ohly | a75244c | 2009-02-12 05:03:35 +0000 | [diff] [blame] | 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 | } | 
| David S. Miller | 3586e0a | 2009-11-11 19:06:30 -0800 | [diff] [blame] | 43 | EXPORT_SYMBOL_GPL(timecompare_transform); | 
| Patrick Ohly | a75244c | 2009-02-12 05:03:35 +0000 | [diff] [blame] | 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 = | 
| Barry Song | f065f41 | 2009-12-15 16:45:34 -0800 | [diff] [blame] | 92 | 				(ktime_to_ns(end) + ktime_to_ns(start)) / 2 - | 
| Patrick Ohly | a75244c | 2009-02-12 05:03:35 +0000 | [diff] [blame] | 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 | } | 
| David S. Miller | 3586e0a | 2009-11-11 19:06:30 -0800 | [diff] [blame] | 134 | EXPORT_SYMBOL_GPL(timecompare_offset); | 
| Patrick Ohly | a75244c | 2009-02-12 05:03:35 +0000 | [diff] [blame] | 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 | } | 
| David S. Miller | 3586e0a | 2009-11-11 19:06:30 -0800 | [diff] [blame] | 191 | EXPORT_SYMBOL_GPL(__timecompare_update); |