| /* | |
| * Copyright (C) 2009 Intel Corporation. | |
| * Author: Patrick Ohly <patrick.ohly@intel.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
| */ | |
| #include <linux/timecompare.h> | |
| #include <linux/module.h> | |
| #include <linux/slab.h> | |
| #include <linux/math64.h> | |
| #include <linux/kernel.h> | |
| /* | |
| * fixed point arithmetic scale factor for skew | |
| * | |
| * Usually one would measure skew in ppb (parts per billion, 1e9), but | |
| * using a factor of 2 simplifies the math. | |
| */ | |
| #define TIMECOMPARE_SKEW_RESOLUTION (((s64)1)<<30) | |
| ktime_t timecompare_transform(struct timecompare *sync, | |
| u64 source_tstamp) | |
| { | |
| u64 nsec; | |
| nsec = source_tstamp + sync->offset; | |
| nsec += (s64)(source_tstamp - sync->last_update) * sync->skew / | |
| TIMECOMPARE_SKEW_RESOLUTION; | |
| return ns_to_ktime(nsec); | |
| } | |
| EXPORT_SYMBOL_GPL(timecompare_transform); | |
| int timecompare_offset(struct timecompare *sync, | |
| s64 *offset, | |
| u64 *source_tstamp) | |
| { | |
| u64 start_source = 0, end_source = 0; | |
| struct { | |
| s64 offset; | |
| s64 duration_target; | |
| } buffer[10], sample, *samples; | |
| int counter = 0, i; | |
| int used; | |
| int index; | |
| int num_samples = sync->num_samples; | |
| if (num_samples > ARRAY_SIZE(buffer)) { | |
| samples = kmalloc(sizeof(*samples) * num_samples, GFP_ATOMIC); | |
| if (!samples) { | |
| samples = buffer; | |
| num_samples = ARRAY_SIZE(buffer); | |
| } | |
| } else { | |
| samples = buffer; | |
| } | |
| /* run until we have enough valid samples, but do not try forever */ | |
| i = 0; | |
| counter = 0; | |
| while (1) { | |
| u64 ts; | |
| ktime_t start, end; | |
| start = sync->target(); | |
| ts = timecounter_read(sync->source); | |
| end = sync->target(); | |
| if (!i) | |
| start_source = ts; | |
| /* ignore negative durations */ | |
| sample.duration_target = ktime_to_ns(ktime_sub(end, start)); | |
| if (sample.duration_target >= 0) { | |
| /* | |
| * assume symetric delay to and from source: | |
| * average target time corresponds to measured | |
| * source time | |
| */ | |
| sample.offset = | |
| (ktime_to_ns(end) + ktime_to_ns(start)) / 2 - | |
| ts; | |
| /* simple insertion sort based on duration */ | |
| index = counter - 1; | |
| while (index >= 0) { | |
| if (samples[index].duration_target < | |
| sample.duration_target) | |
| break; | |
| samples[index + 1] = samples[index]; | |
| index--; | |
| } | |
| samples[index + 1] = sample; | |
| counter++; | |
| } | |
| i++; | |
| if (counter >= num_samples || i >= 100000) { | |
| end_source = ts; | |
| break; | |
| } | |
| } | |
| *source_tstamp = (end_source + start_source) / 2; | |
| /* remove outliers by only using 75% of the samples */ | |
| used = counter * 3 / 4; | |
| if (!used) | |
| used = counter; | |
| if (used) { | |
| /* calculate average */ | |
| s64 off = 0; | |
| for (index = 0; index < used; index++) | |
| off += samples[index].offset; | |
| *offset = div_s64(off, used); | |
| } | |
| if (samples && samples != buffer) | |
| kfree(samples); | |
| return used; | |
| } | |
| EXPORT_SYMBOL_GPL(timecompare_offset); | |
| void __timecompare_update(struct timecompare *sync, | |
| u64 source_tstamp) | |
| { | |
| s64 offset; | |
| u64 average_time; | |
| if (!timecompare_offset(sync, &offset, &average_time)) | |
| return; | |
| if (!sync->last_update) { | |
| sync->last_update = average_time; | |
| sync->offset = offset; | |
| sync->skew = 0; | |
| } else { | |
| s64 delta_nsec = average_time - sync->last_update; | |
| /* avoid division by negative or small deltas */ | |
| if (delta_nsec >= 10000) { | |
| s64 delta_offset_nsec = offset - sync->offset; | |
| s64 skew; /* delta_offset_nsec * | |
| TIMECOMPARE_SKEW_RESOLUTION / | |
| delta_nsec */ | |
| u64 divisor; | |
| /* div_s64() is limited to 32 bit divisor */ | |
| skew = delta_offset_nsec * TIMECOMPARE_SKEW_RESOLUTION; | |
| divisor = delta_nsec; | |
| while (unlikely(divisor >= ((s64)1) << 32)) { | |
| /* divide both by 2; beware, right shift | |
| of negative value has undefined | |
| behavior and can only be used for | |
| the positive divisor */ | |
| skew = div_s64(skew, 2); | |
| divisor >>= 1; | |
| } | |
| skew = div_s64(skew, divisor); | |
| /* | |
| * Calculate new overall skew as 4/16 the | |
| * old value and 12/16 the new one. This is | |
| * a rather arbitrary tradeoff between | |
| * only using the latest measurement (0/16 and | |
| * 16/16) and even more weight on past measurements. | |
| */ | |
| #define TIMECOMPARE_NEW_SKEW_PER_16 12 | |
| sync->skew = | |
| div_s64((16 - TIMECOMPARE_NEW_SKEW_PER_16) * | |
| sync->skew + | |
| TIMECOMPARE_NEW_SKEW_PER_16 * skew, | |
| 16); | |
| sync->last_update = average_time; | |
| sync->offset = offset; | |
| } | |
| } | |
| } | |
| EXPORT_SYMBOL_GPL(__timecompare_update); |