|  | /* | 
|  | * 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); |