<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
- 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.
- drivers/net/ethernet/amd/xgbe/xgbe-drv.c
- 참고: 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
/** * timecounter_init - initialize a time counter * @tc: Pointer to time counter which is to be initialized/reset * @cc: A cycle counter, ready to be used. * @start_tstamp: Arbitrary initial time stamp. * * After this call the current cycle register (roughly) corresponds to * the initial time stamp. Every call to timecounter_read() increments * the time stamp counter by the number of elapsed nanoseconds. */
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
/** * timecounter_read - return nanoseconds elapsed since timecounter_init() * plus the initial time stamp * @tc: Pointer to time counter. * * In other words, keeps track of time since the same epoch as * the function which generated the initial time stamp. */
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) 값을 반환한다.
- 코드 라인 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
/** * 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 { u64 (*read)(const struct cyclecounter *cc); u64 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
참고
- Timer -1- (Lowres Timer) | 문c
- Timer -2- (HRTimer) | 문c
- Timer -3- (Clock Sources Subsystem) | 문c
- Timer -4- (Clock Sources Watchdog) | 문c
- Timer -5- (Clock Events Subsystem) | 문c
- Timer -6- (Clock Source & Timer Driver) | 문c
- Timer -7- (Sched Clock & Delay Timers) | 문c
- Timer -8- (Timecounter) | 문c – 현재 글
- Timer -9- (Tick Device) | 문c
- Timer -10- (Timekeeping) | 문c
- Timer -11- (Posix Clock & Timers) | 문c
- time_init() | 문c
- sched_clock_postinit() | 문c
- tick_init() | 문c
- timekeeping_init() | 문c
- calibrate_delay() | 문c