2009-02-12 05:03:35 +00:00
/*
* 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 0213 9 , USA .
*/
# include <linux/timecompare.h>
# include <linux/module.h>
# include <linux/math64.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 ) ;
}
2009-11-11 19:06:30 -08:00
EXPORT_SYMBOL_GPL ( timecompare_transform ) ;
2009-02-12 05:03:35 +00:00
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 > sizeof ( buffer ) / sizeof ( buffer [ 0 ] ) ) {
samples = kmalloc ( sizeof ( * samples ) * num_samples , GFP_ATOMIC ) ;
if ( ! samples ) {
samples = buffer ;
num_samples = sizeof ( buffer ) / sizeof ( buffer [ 0 ] ) ;
}
} 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 =
2009-12-15 16:45:34 -08:00
( ktime_to_ns ( end ) + ktime_to_ns ( start ) ) / 2 -
2009-02-12 05:03:35 +00:00
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 ;
}
2009-11-11 19:06:30 -08:00
EXPORT_SYMBOL_GPL ( timecompare_offset ) ;
2009-02-12 05:03:35 +00:00
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 ;
}
}
}
2009-11-11 19:06:30 -08:00
EXPORT_SYMBOL_GPL ( __timecompare_update ) ;