Timer -7- (Timecounter)

 

Timecounter

h/w 독립적인 타임카운터 API를 제공하며 이 카운터를 사용하는 드라이버가 많지 않아 원래 코드가 있었던 clocksource에서 코드를 제거하여 별도의 파일로 분리하였다.

주로 고속 이더넷 드라이버의 PTP 기능을 위해 h/w 타이머를 연동하였고 인텔 HD 오디오 드라이버에서도 사용되었음을 확인할 수 있다.

  • 사용 드라이버
    • drivers/net/ethernet/amd/xgbe/xgbe-drv.c
      • AMD 10Gb Ethernet driver
    • drivers/net/ethernet/broadcom/bnx2x/bnx2x_main.c
      • Broadcom Everest network driver
    • drivers/net/ethernet/freescale/fec_ptp.c
      • Fast Ethernet Controller (ENET) PTP driver for MX6x
    • drivers/net/ethernet/ti/cpts.c
      • TI Common Platform Time Sync
    • drivers/net/ethernet/mellanox/mlx4/en_clock.c
      • mlx4 ptp clock
    • drivers/net/ethernet/intel/igb/igb_ptp.c
      • PTP Hardware Clock (PHC) driver for the Intel 82576 and 82580
    • drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c
      • Intel 10 Gigabit PCI Express Linux driver
    • drivers/net/ethernet/intel/e1000e/netdev.c
      • Intel PRO/1000 Linux driver
    • sound/pci/hda/hda_controller.c
      • Implementation of primary alsa driver code base for Intel HD Audio.
  • 참고: time: move the timecounter/cyclecounter code into its own file.

 

사용 API

  • timecounter_init()
    • 하드웨어 카운터 레지스터에 연동한다.
  • timecounter_read()
    • timcounter_init() 한 후 지난 시간을 ns 값으로 변환하여 반환한다.
  • cyclecounter_cyc2ns()
    • cycle 카운터 값을 ns 값으로 변환하여 반환한다.
  • timecounter_adjtime()
    • tv->nsec 값에 delta 값을 더해 교정한다.
  • timecounter_cyc2time()
    • 요청 cycle – 지난 cycle 카운터 값으로 delta cycle을 구한 후 ns 값으로 변환하여 반환한다.

 

timecounter_init()

kernel/time/timecounter.c

void timecounter_init(struct timecounter *tc,
                      const struct cyclecounter *cc,
                      u64 start_tstamp)
{       
        tc->cc = cc;
        tc->cycle_last = cc->read(cc);
        tc->nsec = start_tstamp;
        tc->mask = (1ULL << cc->shift) - 1;
        tc->frac = 0;
}
EXPORT_SYMBOL_GPL(timecounter_init);

요청한 timecount 및 cyclecounter 구조체에 시작 값(ns)으로 초기화하고 cycle_last에는 h/w 타이머로부터 cycle 값을 읽어 저장한다.

  • cycle_last 값에 현재 지정된 64bit 타이머 카운터 값을 읽어 cycle_last에 대입한다.
  • nsec에는 처음 시작 값을 대입한다.
  • rpi2: 아키텍처 generic 타이머 사용
    • mask 값에는 24bit 값으로 0xff_ffff를 사용한다.
    • 이 값으로 frac(fractional nanoseconds) 필드의 비트마스크 값으로 사용된다.

 

아래 그림은 rpi2의 armv7 아키텍처 generic 타이머를 사용하여 56비트 타임카운터를 초기화하는 모습을 보여준다.

 

timecounter_read()

kernel/time/timecounter.c

u64 timecounter_read(struct timecounter *tc)
{
        u64 nsec;

        /* increment time by nanoseconds since last call */
        nsec = timecounter_read_delta(tc);
        nsec += tc->nsec;
        tc->nsec = nsec;

        return nsec;
}
EXPORT_SYMBOL_GPL(timecounter_read);

마지막 호출로부터 경과한 delta(ns) 값을 추가한 값(ns)을 tc->nsec에 갱신하고 반환한다.

 

다음 그림은 timecouter_init()으로 초기화한 후 100 사이클(5208ns 소요)이 지난 후 처음 timecounter_read() 함수를 호출한 경우 처리되는 모습을 보여준다.

 

timecounter_read_delta()

kernel/time/timecounter.c

/**
 * timecounter_read_delta - get nanoseconds since last call of this function
 * @tc:         Pointer to time counter
 *
 * When the underlying cycle counter runs over, this will be handled
 * correctly as long as it does not run over more than once between
 * calls.
 *
 * The first call to this function for a new time counter initializes
 * the time tracking and returns an undefined result.
 */
static u64 timecounter_read_delta(struct timecounter *tc)
{
        cycle_t cycle_now, cycle_delta;
        u64 ns_offset;

        /* read cycle counter: */
        cycle_now = tc->cc->read(tc->cc);

        /* calculate the delta since the last timecounter_read_delta(): */
        cycle_delta = (cycle_now - tc->cycle_last) & tc->cc->mask;

        /* convert to nanoseconds: */
        ns_offset = cyclecounter_cyc2ns(tc->cc, cycle_delta,
                                        tc->mask, &tc->frac);

        /* update time stamp of timecounter_read_delta() call: */
        tc->cycle_last = cycle_now;

        return ns_offset;
}

cycle 카운트 값을 읽어 tc->cycle_last에 저장하고 마지막 호출로부터 경과한 delta(ns) 값을 반환한다.

  • 코드 라인 18에서 cyclecounter에 연결된 h/w 타이머 cycle 카운트 값을 읽어온다.
  • 코드 라인 21에서 읽은 cycle 값 – 지난 cycle 값에 mask로 필터한 값을 cycle_delta에 대입한다.
  • 코드 라인 24~25에서 cycle_delta 값으로 소요 시간(ns)을 알아온다.
    • (cycle_delta * cc->mult) >> cc->shift
  • 코드 라인 28에서 읽었었던 cycle 카운트는 tc->cycle_last에 저장한다.

 

cyclecounter_cyc2ns()

include/linux/timecounter.h

/**
 * cyclecounter_cyc2ns - converts cycle counter cycles to nanoseconds
 * @cc:         Pointer to cycle counter.
 * @cycles:     Cycles
 * @mask:       bit mask for maintaining the 'frac' field
 * @frac:       pointer to storage for the fractional nanoseconds.
 */
static inline u64 cyclecounter_cyc2ns(const struct cyclecounter *cc,
                                      cycle_t cycles, u64 mask, u64 *frac)
{
        u64 ns = (u64) cycles;

        ns = (ns * cc->mult) + *frac;
        *frac = ns & mask;
        return ns >> cc->shift;
}

cycle 카운터 값을 nano 초로 변환한다.

 

timecounter_adjtime()

include/linux/timecounter.h

/**
 * timecounter_adjtime - Shifts the time of the clock.
 * @delta:      Desired change in nanoseconds.
 */
static inline void timecounter_adjtime(struct timecounter *tc, s64 delta)
{
        tc->nsec += delta;
}

타임카운터의 시간 ns만 delta 만큼 더해 조정한다. (cycle 값은 바꾸지 않는다.)

 

구조체

timecounter 구조체

include/linux/timecounter.h

/**
 * struct timecounter - layer above a %struct cyclecounter which counts nanoseconds
 *      Contains the state needed by timecounter_read() to detect
 *      cycle counter wrap around. Initialize with
 *      timecounter_init(). Also used to convert cycle counts into the
 *      corresponding nanosecond counts with timecounter_cyc2time(). Users
 *      of this code are responsible for initializing the underlying
 *      cycle counter hardware, locking issues and reading the time
 *      more often than the cycle counter wraps around. The nanosecond
 *      counter will only wrap around after ~585 years.
 *
 * @cc:                 the cycle counter used by this instance
 * @cycle_last:         most recent cycle counter value seen by
 *                      timecounter_read()
 * @nsec:               continuously increasing count
 * @mask:               bit mask for maintaining the 'frac' field
 * @frac:               accumulated fractional nanoseconds
 */
struct timecounter {
        const struct cyclecounter *cc;
        cycle_t cycle_last;
        u64 nsec;
        u64 mask;
        u64 frac;
};
  • *cc
    • h/w 타이머 카운트(cycle) 값에 대응하는 cyclecounter 구조체와 연결해야 한다.
  • cycle_last
    • cyclecounter를 통해 읽은 최종 cycle 값을 저장해둔다.
    • 이 cycle 값을 사용하여 delta cycle을 구하기 위해 사용한다.
  • nsec
    • cycle_last에 대응하는 실제 시간(ns)가 담긴다.
  • mask
    • cycle 마스크로 이 마스크 값을 초과하는 cycle 값은 overflow된 cycle 값이다.
  • frac
    • fractional nano 초

 

cyclecounter 구조체

include/linux/timecounter.h

/**
 * struct cyclecounter - hardware abstraction for a free running counter
 *      Provides completely state-free accessors to the underlying hardware.
 *      Depending on which hardware it reads, the cycle counter may wrap
 *      around quickly. Locking rules (if necessary) have to be defined
 *      by the implementor and user of specific instances of this API.
 *
 * @read:               returns the current cycle value
 * @mask:               bitmask for two's complement
 *                      subtraction of non 64 bit counters,
 *                      see CYCLECOUNTER_MASK() helper macro
 * @mult:               cycle to nanosecond multiplier
 * @shift:              cycle to nanosecond divisor (power of two)
 */
struct cyclecounter {
        cycle_t (*read)(const struct cyclecounter *cc);
        cycle_t mask;
        u32 mult;
        u32 shift;
};
  • (*read)
    • h/w 타이머의 카운터 값을 읽어오는 함수와 연결되는 후크이다.
  • mask
    • cycle 카운터 마스크로 h/w 카운터에서 읽은 값에서 유효한 비트만을 마스크한다.
    • 예) 56비트 카운터 = 0x00ff_ffff_ffff_ffff
  • mult & shift
    • 1 cycle 당 ns 값을 산출하기 위해 mult 값을 우측으로 shift한다.
    • 예) mult=0x3415_5555, shift=24
      • 1 cycle = 52ns

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.