[RTEMS Project] #2230: PowerPC with mpc6xx clock: Nanoseconds extension doesn't compensate for pending clock tick, time may go backwards

RTEMS trac trac at rtems.org
Mon Jan 5 04:52:24 UTC 2015


#2230: PowerPC with mpc6xx clock: Nanoseconds extension doesn't compensate for
pending clock tick, time may go backwards
-------------------------------------------------+-------------------------
 Reporter:  nick.withers                         |       Owner:
     Type:  defect                               |      Status:  new
 Priority:  normal                               |   Milestone:  4.11
Component:  bsps                                 |     Version:  4.11
 Severity:  normal                               |  Resolution:
 Keywords:  clock, time, backwards, race,        |
  nanoseconds, monotonic                         |
-------------------------------------------------+-------------------------

Comment (by nick.withers):

 In attempting to address this in a clean way, I've had trouble trying to
 make sure I haven't introduced integer overflows / wraps on other BSPs,
 where values used in computations (''Clock_Decrementer_value'' and
 ''BSP_bus_frequency/BSP_time_base_divisor'') are potentially set at run-
 time, e.g., from ''c/src/lib/libbsp/powerpc/shared/startup/bspstart.c'':
 {{{
   BSP_bus_frequency       = residualCopy.VitalProductData.ProcessorBusHz;
   BSP_processor_frequency = residualCopy.VitalProductData.ProcessorHz;
   BSP_time_base_divisor   =
 (residualCopy.VitalProductData.TimeBaseDivisor?
                     residualCopy.VitalProductData.TimeBaseDivisor : 4000);
 }}}


 These changes potentially add precision (without ever reducing it? I'm
 struggling with my modulo maths...) and, I think, will not cause any not-
 already-present overflow / wrapping problems so long as
 {{{BSP_bus_frequency/BSP_time_base_divisor >= 2}}} {{{[1]}}}. If that
 assumption's not safe, I could scale the newly-added
 ''Clock_Decrementer_reference'' value by half and the inverse operation by
 2, though that feels a bit messy.
 {{{
 diff --git a/c/src/lib/libcpu/powerpc/mpc6xx/clock/c_clock.c
 b/c/src/lib/libcpu/powerpc/mpc6xx/clock/c_clock.c
 index f14acab..4116894 100644
 --- a/c/src/lib/libcpu/powerpc/mpc6xx/clock/c_clock.c
 +++ b/c/src/lib/libcpu/powerpc/mpc6xx/clock/c_clock.c
 @@ -48,6 +48,12 @@ volatile uint32_t   Clock_driver_ticks;
   */
  uint32_t   Clock_Decrementer_value;

 +/*
 + *  This is the value by which elapsed count down timer ticks are
 multiplied to
 + *  give an elapsed duration in microseconds, left-shifted by 32 bits
 + */
 +uint64_t   Clock_Decrementer_reference;
 +
  void clockOff(void* unused)
  {
    rtems_interrupt_level l;
 @@ -195,18 +201,36 @@ void Clock_exit( void )

  static uint32_t Clock_driver_nanoseconds_since_last_tick(void)
  {
 -  uint32_t clicks, tmp;
 +  uint32_t clicks;
 +  uint64_t tmp;

    PPC_Get_decrementer( clicks );

    /*
 -   * Multiply by 1000 here separately from below so we do not overflow
 -   * and get a negative value.
 +   * Check whether a clock tick interrupt is pending and the
 +   * decrementer wrapped.
 +   *
 +   * If so, we'll compensate by returning a time one tick period longer.
 +   *
 +   * We have to check (Book E) interrupt status after reading the
 +   * decrementer. If we don't, we may miss an interrupt and read a
 +   * wrapped decrementer value without compensating for it
     */
 -  tmp = (Clock_Decrementer_value - clicks) * 1000;
 -  tmp /= (BSP_bus_frequency/BSP_time_base_divisor);
 +  if ( ppc_cpu_is_bookE() && (_read_BOOKE_TSR() & BOOKE_TSR_DIS) )
 +  {
 +    /*
 +     * Re-read the decrementer: The tick interrupt may have been
 +     * generated and the decrementer wrapped during the time since we
 +     * last read it and the time we checked the interrupt status
 +     */
 +    PPC_Get_decrementer( clicks );
 +
 +    tmp = Clock_Decrementer_value + (Clock_Decrementer_value - clicks);
 +  }
 +  else
 +    tmp = Clock_Decrementer_value - clicks;

 -  return tmp * 1000;
 +  return (uint32_t)(((tmp * Clock_Decrementer_reference) >> 32) * 1000U);
  }

  /*
 @@ -225,6 +249,9 @@ rtems_device_driver Clock_initialize(
    Clock_Decrementer_value = (BSP_bus_frequency/BSP_time_base_divisor)*
              (rtems_configuration_get_microseconds_per_tick()/1000);

 +  Clock_Decrementer_reference = ((uint64_t)1000U<<32)/
 +            (BSP_bus_frequency/BSP_time_base_divisor);
 +
    /* set the decrementer now, prior to installing the handler
     * so no interrupts will happen in a while.
     */
 }}}

 If there're stronger assumptions that can be made, I could potentially
 increase the precision of the result further.


 An alternate fix would add
 ''rtems_configuration_get_nanoseconds_per_tick()'' to the final result if
 a tick was pending, which seems a lot safer, but possibly less clean?


 P.S.:
   - With either fix suggested here, time might still "go backwards" if a
 tick interrupt goes unserviced across another hardware decrementer wrap
   - One's complement, 1-is-zero values that can be returned by the non-
 Book-E decrementer don't appear to be properly handled in
 ''Clock_driver_nanoseconds_since_last_tick()''
   - There's potential for improving the accuracy of the calculated
 ''Clock_Decrementer_value'', but I haven't touched that (yet?); e.g.,
 ''Clock_Decrementer_value'' on my MVME3100s is calculated to be 416660;
 416666 would be more accurate and there'd therefore be less cumulative
 error in uptime


 {{{[1]}}} Assumes that {{{Clock_Decrementer_value * 1000 < 2*32 - 1}}} and
 {{{((Clock_Decrementer_value * 1000) /
 (BSP_bus_frequency/BSP_time_base_divisor)) * 1000 < 2^32 - 1}}}, which
 must already hold unless ''tmp'' / the return in
 ''Clock_driver_nanoseconds_since_last_tick()'' can already overflow / wrap
 with the existing code

--
Ticket URL: <http://devel.rtems.org/ticket/2230#comment:1>
RTEMS Project <http://www.rtems.org/>
RTEMS Project


More information about the bugs mailing list