<kernel v5.4>
Timecounter/Cyclecounter
h/w 독립적인 타임카운터 API를 제공하며 이 카운터를 사용하는 드라이버가 많지 않아 원래 코드가 있었던 clocksource에서 코드를 제거하여 별도의 파일로 분리하였다.
- arm 아키텍트 타이머에서 56비트 cyclecounter를 사용하여 Timecounter를 초기화하여 사용한다.
주로 고속 이더넷 드라이버의 PTP(Precision Time Protocol) 기능을 위해 h/w 타이머를 연동하였고 인텔 HD 오디오 드라이버에서도 사용되었음을 확인할 수 있다.
- 사용 드라이버
- drivers/net/ethernet/amd/xgbe/xgbe-drv.c
- 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
- 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
02 | * timecounter_init - initialize a time counter |
03 | * @tc: Pointer to time counter which is to be initialized/reset |
04 | * @cc: A cycle counter, ready to be used. |
05 | * @start_tstamp: Arbitrary initial time stamp. |
07 | * After this call the current cycle register (roughly) corresponds to |
08 | * the initial time stamp. Every call to timecounter_read() increments |
09 | * the time stamp counter by the number of elapsed nanoseconds. |
01 | void timecounter_init( struct timecounter *tc, |
02 | const struct cyclecounter *cc, |
06 | tc->cycle_last = cc->read(cc); |
07 | tc->nsec = start_tstamp; |
08 | tc->mask = (1ULL << cc->shift) - 1; |
11 | 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
2 | * timecounter_read - return nanoseconds elapsed since timecounter_init() |
3 | * plus the initial time stamp |
4 | * @tc: Pointer to time counter. |
6 | * In other words, keeps track of time since the same epoch as |
7 | * the function which generated the initial time stamp. |
01 | u64 timecounter_read( struct timecounter *tc) |
06 | nsec = timecounter_read_delta(tc); |
12 | EXPORT_SYMBOL_GPL(timecounter_read); |
마지막 호출로부터 경과한 delta(ns) 값을 추가한 값(ns)을 tc->nsec에 갱신하고 반환한다.
다음 그림은 timecouter_init()으로 초기화한 후 100 사이클(5208ns 소요)이 지난 후 처음 timecounter_read() 함수를 호출한 경우 처리되는 모습을 보여준다.

timecounter_read_delta()
kernel/time/timecounter.c
02 | * timecounter_read_delta - get nanoseconds since last call of this function |
03 | * @tc: Pointer to time counter |
05 | * When the underlying cycle counter runs over, this will be handled |
06 | * correctly as long as it does not run over more than once between |
09 | * The first call to this function for a new time counter initializes |
10 | * the time tracking and returns an undefined result. |
01 | static u64 timecounter_read_delta( struct timecounter *tc) |
03 | cycle_t cycle_now, cycle_delta; |
07 | cycle_now = tc->cc->read(tc->cc); |
10 | cycle_delta = (cycle_now - tc->cycle_last) & tc->cc->mask; |
13 | ns_offset = cyclecounter_cyc2ns(tc->cc, cycle_delta, |
17 | tc->cycle_last = cycle_now; |
cycle 카운트 값을 읽어 tc->cycle_last에 저장하고 마지막 호출로부터 경과한 delta(ns) 값을 반환한다.
- 코드 라인 7에서 cyclecounter에 연결된 h/w 타이머 cycle 카운트 값을 읽어온다.
- 코드 라인 10에서 읽은 cycle 값 – 지난 cycle 값에 mask로 필터한 값을 cycle_delta에 대입한다.
- 코드 라인 13~14에서 cycle_delta 값으로 소요 시간(ns)을 알아온다.
- (cycle_delta * cc->mult) >> cc->shift
- 코드 라인 17에서 읽었었던 cycle 카운트는 tc->cycle_last에 저장한다.
cyclecounter_cyc2ns()
include/linux/timecounter.h
2 | * cyclecounter_cyc2ns - converts cycle counter cycles to nanoseconds |
3 | * @cc: Pointer to cycle counter. |
5 | * @mask: bit mask for maintaining the 'frac' field |
6 | * @frac: pointer to storage for the fractional nanoseconds. |
1 | static inline u64 cyclecounter_cyc2ns( const struct cyclecounter *cc, |
2 | cycle_t cycles, u64 mask, u64 *frac) |
6 | ns = (ns * cc->mult) + *frac; |
8 | return ns >> cc->shift; |
cycle 카운터 값을 nano 초로 변환한다.
timecounter_adjtime()
include/linux/timecounter.h
2 | * timecounter_adjtime - Shifts the time of the clock. |
3 | * @delta: Desired change in nanoseconds. |
1 | static inline void timecounter_adjtime( struct timecounter *tc, s64 delta) |
타임카운터의 시간 ns만 delta 만큼 더해 조정한다. (cycle 값은 바꾸지 않는다.)
구조체
timecounter 구조체
include/linux/timecounter.h
02 | * struct timecounter - layer above a %struct cyclecounter which counts nanoseconds |
03 | * Contains the state needed by timecounter_read() to detect |
04 | * cycle counter wrap around. Initialize with |
05 | * timecounter_init(). Also used to convert cycle counts into the |
06 | * corresponding nanosecond counts with timecounter_cyc2time(). Users |
07 | * of this code are responsible for initializing the underlying |
08 | * cycle counter hardware, locking issues and reading the time |
09 | * more often than the cycle counter wraps around. The nanosecond |
10 | * counter will only wrap around after ~585 years. |
12 | * @cc: the cycle counter used by this instance |
13 | * @cycle_last: most recent cycle counter value seen by |
15 | * @nsec: continuously increasing count |
16 | * @mask: bit mask for maintaining the 'frac' field |
17 | * @frac: accumulated fractional nanoseconds |
2 | const struct cyclecounter *cc; |
- *cc
- h/w 타이머 카운트(cycle) 값에 대응하는 cyclecounter 구조체와 연결해야 한다.
- cycle_last
- cyclecounter를 통해 읽은 최종 cycle 값을 저장해둔다.
- 이 cycle 값을 사용하여 delta cycle을 구하기 위해 사용한다.
- nsec
- cycle_last에 대응하는 실제 시간(ns)가 담긴다.
- mask
- cycle 마스크로 이 마스크 값을 초과하는 cycle 값은 overflow된 cycle 값이다.
- frac
cyclecounter 구조체
include/linux/timecounter.h
02 | * struct cyclecounter - hardware abstraction for a free running counter |
03 | * Provides completely state-free accessors to the underlying hardware. |
04 | * Depending on which hardware it reads, the cycle counter may wrap |
05 | * around quickly. Locking rules (if necessary) have to be defined |
06 | * by the implementor and user of specific instances of this API. |
08 | * @read: returns the current cycle value |
09 | * @mask: bitmask for two's complement |
10 | * subtraction of non 64 bit counters, |
11 | * see CYCLECOUNTER_MASK() helper macro |
12 | * @mult: cycle to nanosecond multiplier |
13 | * @shift: cycle to nanosecond divisor (power of two) |
2 | u64 (*read)( const struct cyclecounter *cc); |
- (*read)
- h/w 타이머의 카운터 값을 읽어오는 함수와 연결되는 후크이다.
- mask
- cycle 카운터 마스크로 h/w 카운터에서 읽은 값에서 유효한 비트만을 마스크한다.
- 예) 56비트 카운터 = 0x00ff_ffff_ffff_ffff
- mult & shift
- 1 cycle 당 ns 값을 산출하기 위해 mult 값으로 곱한 후 우측으로 shift한다.
- 예) mult=0x3415_5555, shift=24
참고