<kernel v5.4>
Timer -5- (Clock Events Subsytem)
clock events subsystem은 타이머를 프로그래밍하고, 만료되어 처리될 핸들러를 지정할 수 있도록 hw 독립형 코드로 구성된 framework을 제공한다. 이를 사용하여 쉽게 타이머 hw를 제어하는 clock event 디바이스 드라이버를 구성할 수 있다.
다음 그림은 clock events subsystem이 동작하는 과정을 보여준다.
- 타이머 hw는 사이클을 기록하여 동작하고, 커널의 clock events subsystem은 나노초(ns) 단위의 monotonic 절대 시각을 사용한다.
클럭 이벤트 디바이스 기능 타입
- CLOCK_EVT_FEAT_PERIODIC(0x000001)
- shutdown 으로 제어하지 않는 한 규칙적으로 이벤트가 발생한다.
- CLOCK_EVT_FEAT_ONESHOT(0x000002)
- 타이머 장치가 인터럽트 컨트롤러에 연결되어 단발 이벤트 프로그램이 가능하다.
- CLOCK_EVT_FEAT_KTIME(0x000004)
- 아래 CLOCK_EVT_FEAT_HRTIMER 기능과 같이 사용된다.
- CLOCK_EVT_FEAT_C3STOP(0x00008)
- cpu가 c3(deep-sleep) 상태에 진입할 때 타이머도 전원이 꺼지는 절전 기능을 가진다.
- nohz 구현 시 틱 브로드캐스트 디바이스에 의해 wakeup 된다.
- CLOCK_EVT_FEAT_DUMMY(0x000010)
- x86 시스템의 Local APIC 에 사용되는 아무것도 수행하지 않는 더미 디바이스 드라이버에서 사용된다.
- CLOCK_EVT_FEAT_DYNIRQ(0x000020)
- 브로드캐스트 목적의 인터럽트가 특정 cpu에 고정되지 않고 cpu를 선택(set_irq_affinity)하여 사용할 수 있으며 다음 드라이버에서 사용되고 있다.
- armv7 또는 armv8 아키텍처에 내장된 generic 타이머의 메모리 mapped 드라이버 – “arm,armv7-timer-mem”
- “st,nomadik-mtu”
- 브로드캐스트 목적의 인터럽트가 특정 cpu에 고정되지 않고 cpu를 선택(set_irq_affinity)하여 사용할 수 있으며 다음 드라이버에서 사용되고 있다.
- CLOCK_EVT_FEAT_PERCPU(0x000040)
- arm cortex-a9 아키텍처에 긴밀하게 부착된 타이머 장치 타입으로 틱 브로드캐스트 디바이스로 사용되지 않게 제한한다.
- “arm,cortex-a9-global_timer” 드라이버에서 사용된다.
- CLOCK_EVT_FEAT_HRTIMER
- 브로드캐스트에 IPI가 아닌 hrtimer를 사용하여 클럭 이벤트 디바이스로 사용된다.
- kernel/time/tick-broadcast-hrtimer.c – ONESHOT | KTIME | HRTIMER 기능을 사용하는 것을 볼 수 있다.
- 참고: tick: Introduce hrtimer based broadcast
Clock Events 설정 및 등록 -1-
clockevents_config_and_register()
kernel/time/clockevents.c
/** * clockevents_config_and_register - Configure and register a clock event device * @dev: device to register * @freq: The clock frequency * @min_delta: The minimum clock ticks to program in oneshot mode * @max_delta: The maximum clock ticks to program in oneshot mode * * min/max_delta can be 0 for devices which do not support oneshot mode. */
void clockevents_config_and_register(struct clock_event_device *dev, u32 freq, unsigned long min_delta, unsigned long max_delta) { dev->min_delta_ticks = min_delta; dev->max_delta_ticks = max_delta; clockevents_config(dev, freq); clockevents_register_device(dev); } EXPORT_SYMBOL_GPL(clockevents_config_and_register);
요청 주파수의 clock_event_device를 최소 클럭 틱에서 최대 클럭 틱 값 사이에서 동작하도록 등록한다.
- 예) rpi3 & 4: max_delta_ticks=0x7fff_ffff, 54Mhz, 1000HZ 사용 시 최대 ns=약 39.7초
- 예) rpi2: max_delta_ticks=0x7fff_ffff, 19.2Mhz, 1000HZ 사용 시 최대 ns=약 111.8초
- tick 값들은 32bit long 형을 사용하고 위의 rpi2와 같이 한계치인 0x7fff_ffff 값을 사용할 때 틱의 프로그램은 최대 111.8초를 초과할 수 없다.
clockevents_config()
kernel/time/clockevents.c
void clockevents_config(struct clock_event_device *dev, u32 freq) { u64 sec; if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return; /* * Calculate the maximum number of seconds we can sleep. Limit * to 10 minutes for hardware which can program more than * 32bit ticks so we still get reasonable conversion values. */ sec = dev->max_delta_ticks; do_div(sec, freq); if (!sec) sec = 1; else if (sec > 600 && dev->max_delta_ticks > UINT_MAX) sec = 600; clockevents_calc_mult_shift(dev, freq, sec); dev->min_delta_ns = cev_delta2ns(dev->min_delta_ticks, dev, false); dev->max_delta_ns = cev_delta2ns(dev->max_delta_ticks, dev, true); }
요청 주파수의 clock_event_device에 대해 mult/shift 값 및 min_delta_ns/max_delta_ns 값을 조정한다.
- 코드 라인 5~6에서 디바이스가 oneshot 모드를 지원하지 않는 경우 함수를 빠져나간다.
- 코드 라인 13~18에서 최대 max_delta_ticks를 freq로 나누면 소요 초가 산출되는데 32bit tick을 초과한 경우 최대 600초로 제한한다.
- 10분 이상의 오차 보정을 할 필요 없어서 제한한다.
- 코드 라인 20에서 max_delta_ticks, 주파수와 소요시간(sec)으로 mult 및 shift 값을 산출한다.
- 코드 라인 21~22에서 min_delta_ticks, max_delta_ticks 값과 산출된 mult/shift 값으로 min_delta_ns와 max_delta_ns 값을 산출한다.
- min_delta_ns 값은 최하 1000 ns(1 ms) 이상으로 한다.
다음 그림은 192.Mhz로 클럭 이벤트 디바이스의 mult/shift, min(max)_delta_ns 값을 설정하는 것을 보여준다.
clockevents_calc_mult_shift()
include/linux/clockchips.h
static inline void clockevents_calc_mult_shift(struct clock_event_device *ce, u32 freq, u32 maxsec) { return clocks_calc_mult_shift(&ce->mult, &ce->shift, NSEC_PER_SEC, freq, maxsec); }
1초 -> freq로 변환 시 요청 기간(sec) 동안 필요한 mult 및 shift 값을 산출한다.
- 예) 1G -> 54Mhz에 39초인 경우
- mult=0xdd2_f1aa, shift=32
- 예) 1G -> 19.2Mhz에 111초인 경우
- mult=0x682_aaab, shift=32
cev_delta2ns()
kernel/time/clockevents.c
static u64 cev_delta2ns(unsigned long latch, struct clock_event_device *evt, bool ismax) { u64 clc = (u64) latch << evt->shift; u64 rnd; if (WARN_ON(!evt->mult)) evt->mult = 1; rnd = (u64) evt->mult - 1; /* * Upper bound sanity check. If the backwards conversion is * not equal latch, we know that the above shift overflowed. */ if ((clc >> evt->shift) != (u64)latch) clc = ~0ULL; /* * Scaled math oddities: * * For mult <= (1 << shift) we can safely add mult - 1 to * prevent integer rounding loss. So the backwards conversion * from nsec to device ticks will be correct. * * For mult > (1 << shift), i.e. device frequency is > 1GHz we * need to be careful. Adding mult - 1 will result in a value * which when converted back to device ticks can be larger * than latch by up to (mult - 1) >> shift. For the min_delta * calculation we still want to apply this in order to stay * above the minimum device ticks limit. For the upper limit * we would end up with a latch value larger than the upper * limit of the device, so we omit the add to stay below the * device upper boundary. * * Also omit the add if it would overflow the u64 boundary. */ if ((~0ULL - clc > rnd) && (!ismax || evt->mult <= (1ULL << evt->shift))) clc += rnd; do_div(clc, evt->mult); /* Deltas less than 1usec are pointless noise */ return clc > 1000 ? clc : 1000; }
latch 값을 nano 초 값으로 변환한다. 정확도를 위해 주파수에 따라 다음의 수식을 사용한다.
- 주파수가 1Ghz 이하인 경우 mult로 나눌 때 반올림 처리한다. 이렇게 해야 nsec에서 장치 틱으로 역방향 시 변환이 더 정확해진다.
- (latch << shift + mult – 1) / mult = ns
- 주파수가 1Ghz를 초과하는 경우 그대로 mult로 나눈다.
- (latch << shift) / mult = ns
Clock Events 설정 및 등록 -2-
clockevents_register_device()
kernel/time/clockevents.c
/** * clockevents_register_device - register a clock event device * @dev: device to register */
void clockevents_register_device(struct clock_event_device *dev) { unsigned long flags; /* Initialize state to DETACHED */ clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED); if (!dev->cpumask) { WARN_ON(num_possible_cpus() > 1); dev->cpumask = cpumask_of(smp_processor_id()); } if (dev->cpumask == cpu_all_mask) { WARN(1, "%s cpumask == cpu_all_mask, using cpu_possible_mask instead\n", dev->name); dev->cpumask = cpu_possible_mask; } raw_spin_lock_irqsave(&clockevents_lock, flags); list_add(&dev->list, &clockevent_devices); tick_check_new_device(dev); clockevents_notify_released(); raw_spin_unlock_irqrestore(&clockevents_lock, flags); } EXPORT_SYMBOL_GPL(clockevents_register_device);
클럭 이벤트 디바이스를 등록한다.
- 코드 라인 6에서 클럭 이벤트 디바이스 상태를 CLOCK_EVT_STATE_DETACHED로 설정한다.
- 코드 라인 8~11에서 클럭 이벤트 디바이스가 동작할 cpu를 현재 태스크가 수행 중인 cpu 번호로 설정한다. (cpumask 형태로 지정)
- 코드 라인 13~17에서 모든 cpu를 대상으로 하는 경우 cpumask에 cpu_possible_mask를 대입한다.
- 코드 라인 21에서 clockevent_devices 리스트에 클럭 이벤트 디바이스를 등록한다.
- 코드 라인 22에서 기존 tick 디바이스보다 새 tick 디바이스가 더 좋은 rating 등급인 경우 변경하여 사용할지 체크한다.
- 처음 호출 시에는 요청 디바이스가 tick 디바이스로 사용된다.
- nohz 구현을 위해 경우에 따라 등록되는 클럭 이벤트 디바이스가 틱 브로드캐스트 디바이스로 동작할 수도 있다.
- 참고: Timer -8- (Tick Device) | 문c
- 코드 라인 23에서 clockevents_released 리스트에 등록된 클럭 이벤트 디바이스들을 제거하고 clockevent_devices 리스트에 추가한 후 새로운 tick 디바이스로 사용할 수 있는지 체크한다.
clockevents_notify_released()
kernel/time/clockevents.c
/* * Called after a notify add to make devices available which were * released from the notifier call. */
static void clockevents_notify_released(void) { struct clock_event_device *dev; while (!list_empty(&clockevents_released)) { dev = list_entry(clockevents_released.next, struct clock_event_device, list); list_del(&dev->list); list_add(&dev->list, &clockevent_devices); tick_check_new_device(dev); } }
clockevents_released 리스트에 등록된 클럭 이벤트 디바이스들을 제거하고 clockevent_devices 리스트에 추가한 후 새로운 tick 디바이스로 사용할 수 있는지 체크한다.
Clock Events 변경(Exchange)
clockevents_exchange_device()
kernel/time/clockevents.c
/** * clockevents_exchange_device - release and request clock devices * @old: device to release (can be NULL) * @new: device to request (can be NULL) * * Called from various tick functions with clockevents_lock held and * interrupts disabled. */
void clockevents_exchange_device(struct clock_event_device *old, struct clock_event_device *new) { /* * Caller releases a clock event device. We queue it into the * released list and do a notify add later. */ if (old) { module_put(old->owner); clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); list_del(&old->list); list_add(&old->list, &clockevents_released); } if (new) { BUG_ON(!clockevent_state_detached(new)); clockevents_shutdown(new); } }
old 클럭 이벤트 디바이스를 release 하고 new 클럭 이벤트 디바이스로 교체한다. 만일 교체한 new 클럭 이벤트 디바이스가 동작 중인 경우 shutdown 상태로 변경한다.
- 코드 라인 8~13에서 old 클럭 이벤트 디바이스를 unused 모드로 설정하고 clockevent_devices 리스트에서 제거한 후 clockevents_release 리스트에 추가한다.
- 코드 라인 15~18에서 new 클럭 이벤트 디바이스를 shutdown 설정한다.
clockevents_switch_state()
kernel/time/clockevents.c
/** * clockevents_switch_state - set the operating state of a clock event device * @dev: device to modify * @state: new state * * Must be called with interrupts disabled ! */
void clockevents_switch_state(struct clock_event_device *dev, enum clock_event_state state) { if (clockevent_get_state(dev) != state) { if (__clockevents_switch_state(dev, state)) return; clockevent_set_state(dev, state); /* * A nsec2cyc multiplicator of 0 is invalid and we'd crash * on it, so fix it up and emit a warning: */ if (clockevent_state_oneshot(dev)) { if (WARN_ON(!dev->mult)) dev->mult = 1; } } }
요청한 클럭 이벤트 디바이스의 상태를 설정한다.
- 코드 라인 4~8에서 현재 클럭 이벤트 디바이스의 상태와 다른 상태 설정이 요청되면 디바이스에 등록된 상태 전환 함수를 호출하고, 그 후 상태를 변경한다.
- 코드 라인 14~17에서 oneshot 상태 설정을 요청한 하였고 mult 값이 0인 경우 1로 변경한다.
clockevent_set_state()
kernel/time/tick-internal.h
static inline void clockevent_set_state(struct clock_event_device *dev, enum clock_event_state state) { dev->state_use_accessors = state; }
요청한 클럭 이벤트 디바이스의 상태를 설정한다
__clockevents_switch_state()
kernel/time/clockevents.c
static int __clockevents_switch_state(struct clock_event_device *dev, enum clock_event_state state) { if (dev->features & CLOCK_EVT_FEAT_DUMMY) return 0; /* Transition with new state-specific callbacks */ switch (state) { case CLOCK_EVT_STATE_DETACHED: /* The clockevent device is getting replaced. Shut it down. */ case CLOCK_EVT_STATE_SHUTDOWN: if (dev->set_state_shutdown) return dev->set_state_shutdown(dev); return 0; case CLOCK_EVT_STATE_PERIODIC: /* Core internal bug */ if (!(dev->features & CLOCK_EVT_FEAT_PERIODIC)) return -ENOSYS; if (dev->set_state_periodic) return dev->set_state_periodic(dev); return 0; case CLOCK_EVT_STATE_ONESHOT: /* Core internal bug */ if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return -ENOSYS; if (dev->set_state_oneshot) return dev->set_state_oneshot(dev); return 0; case CLOCK_EVT_STATE_ONESHOT_STOPPED: /* Core internal bug */ if (WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n", clockevent_get_state(dev))) return -EINVAL; if (dev->set_state_oneshot_stopped) return dev->set_state_oneshot_stopped(dev); else return -ENOSYS; default: return -ENOSYS; } }
클럭 이벤트 디바이스의 새 상태 전환 시 해당 상태의 콜백 함수를 호출한다.
- 코드 라인 4~5에서 CLOCK_EVT_FEAT_DUMMY 상태로 전환된 경우 정상 값 0을 반환한다.
- 코드 라인 8~15에서 CLOCK_EVT_STATE_DETACHED 및 CLOCK_EVT_STATE_SHUTDOWN 상태로 전환된 경우 (*set_state_shutdown) 후크 함수를 호출한다.
- 코드 라인 17~23에서 CLOCK_EVT_STATE_PERIODIC 상태로 전환된 경우 (*set_state_periodic) 후크 함수를 호출한다. 단 periodic 기능이 없는 경우 -ENOSYS 에러를 반환한다.
- 코드 라인 25~31에서 CLOCK_EVT_STATE_ONESHOT 상태로 전환된 경우 (*set_state_oneshot) 후크 함수를 호출한다. 단 oneshot 기능이 없는 경우 -ENOSYS 에러를 반환한다.
- 코드 라인 33~43에서 CLOCK_EVT_STATE_ONESHOT_STOPPED 상태로 전환된 경우 (*set_state_oneshot_stopped) 후크 함수를 호출한다. 단 oneshot 상태가 아닌 경우 -EINVAL 에러를 반환한다.
- 코드 라인 45~47에서 그 외의 상태로 전환된 경우 -ENOSYS 에러를 반환한다.
clockevents_shutdown()
kernel/time/clockevents.c
/** * clockevents_shutdown - shutdown the device and clear next_event * @dev: device to shutdown */
void clockevents_shutdown(struct clock_event_device *dev) { clockevents_switch_state(dev, CLOCK_EVT_STATE_SHUTDOWN); dev->next_event.tv64 = KTIME_MAX; }
요청한 클럭 이벤트 디바이스를 shutdown 설정한다.
Clock Events 프로그램 이벤트
clockevents_program_event()
kernel/time/clockevents.c
/** * clockevents_program_event - Reprogram the clock event device. * @dev: device to program * @expires: absolute expiry time (monotonic clock) * @force: program minimum delay if expires can not be set * * Returns 0 on success, -ETIME when the event is in the past. */
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, bool force) { unsigned long long clc; int64_t delta; int rc; if (WARN_ON_ONCE(expires < 0)) return -ETIME; dev->next_event = expires; if (clockevent_state_shutdown(dev)) return 0; /* We must be in ONESHOT state here */ WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n", clockevent_get_state(dev)); /* Shortcut for clockevent devices that can deal with ktime. */ if (dev->features & CLOCK_EVT_FEAT_KTIME) return dev->set_next_ktime(expires, dev); delta = ktime_to_ns(ktime_sub(expires, ktime_get())); if (delta <= 0) return force ? clockevents_program_min_delta(dev) : -ETIME; delta = min(delta, (int64_t) dev->max_delta_ns); delta = max(delta, (int64_t) dev->min_delta_ns); clc = ((unsigned long long) delta * dev->mult) >> dev->shift; rc = dev->set_next_event((unsigned long) clc, dev); return (rc && force) ? clockevents_program_min_delta(dev) : rc; }
클럭 이벤트 디바이스의 만료시간을 재프로그램 한다. force=1인 경우 요청한 만료 시간이 이미 지난 경우라 하더라도 최소 시간(min_delay_ns)이내에 이벤트가 발생하도록 프로그램한다.
- 코드 라인 8~9에서 만료시간이 0보다 작은 경우 경고 메시지를 출력하고 에러 값으로 -ETIME을 반환한다.
- 코드 라인 11에서 디바이스의 next_event에 만료 시간을 대입한다.
- 코드 라인 13~14에서 디바이스가 shutdown 상태인 경우 더 이상 진행할 필요 없으므로 성공(0)을 반환한다.
- 코드 라인 21~22에서 ktime 기능이 있는 경우 다음 이벤트의 만료시간을 설정할 때 (*set_next_ktime) 후크 함수를 호출하고 그 결과를 반환한다.
- 코드 라인 24~26에서 요청한 만료 시간이 이미 현재 시간을 지난 경우 force 값에 따라 min_delta_ns 값 또는 -ETIME 값을 반환한다.
- 코드 라인 28~29에서 delta 값이 min_delta_ns ~ max_delta_ns 범위를 벗어나는 경우 조정(clamp)한다.
- 코드 라인 31~32에서 (delta * mult) >> shift를 산출한 값으로 (*set_next_event) 후크 함수를 호출하여 이벤트의 만료시간을 설정한다.
- 코드 라인 34에서 에러이면서 force=1인 경우 min_delta 값으로 다음 이벤트의 만료시간을 설정한다.
다음 그림은 최소 지연 시간 1000us으로 이벤트를 프로그램하는 것을 보여준다.
clockevents_program_min_delta()
kernel/time/clockevents.c
/** * clockevents_program_min_delta - Set clock event device to the minimum delay. * @dev: device to program * * Returns 0 on success, -ETIME when the retry loop failed. */
static int clockevents_program_min_delta(struct clock_event_device *dev) { unsigned long long clc; int64_t delta; int i; for (i = 0;;) { delta = dev->min_delta_ns; dev->next_event = ktime_add_ns(ktime_get(), delta); if (clockevent_state_shutdown()) return 0; dev->retries++; clc = ((unsigned long long) delta * dev->mult) >> dev->shift; if (dev->set_next_event((unsigned long) clc, dev) == 0) return 0; if (++i > 2) { /* * We tried 3 times to program the device with the * given min_delta_ns. Try to increase the minimum * delta, if that fails as well get out of here. */ if (clockevents_increase_min_delta(dev)) return -ETIME; i = 0; } } }
클럭 이벤트 디바이스를 최소 딜레이 시간(min_delay_ns)으로 프로그램한다.
- 코드 라인 7~9에서 현재 시간 보다 min_delta_ns 값을 더해 다음 이벤트의 만료 시간을 설정한다.
- 코드 라인 11~12에서 디바이스가 shutdown 상태인 경우 아직 동작시킬 수 없으므로 성공(0)으로 함수를 빠져나간다.
- 코드 라인 14~17에서 retries 값을 증가시키고 (delta * mult) >> shift 값으로 다음 이벤트의 만료 시간을 설정하고 함수를 성공(0)으로 빠져나간다.
- 코드 라인 19~28에서 주어진 min_delta_ns 값으로 3번을 다시 시도해보고 안되는 경우 min_delta_ns 값을 증가시키고 다시 시도한다.
clockevents_increase_min_delta()
kernel/time/clockevents.c
/** * clockevents_increase_min_delta - raise minimum delta of a clock event device * @dev: device to increase the minimum delta * * Returns 0 on success, -ETIME when the minimum delta reached the limit. */
static int clockevents_increase_min_delta(struct clock_event_device *dev) { /* Nothing to do if we already reached the limit */ if (dev->min_delta_ns >= MIN_DELTA_LIMIT) { printk_deferred(KERN_WARNING "CE: Reprogramming failure. Giving up\n"); dev->next_event.tv64 = KTIME_MAX; return -ETIME; } if (dev->min_delta_ns < 5000) dev->min_delta_ns = 5000; else dev->min_delta_ns += dev->min_delta_ns >> 1; if (dev->min_delta_ns > MIN_DELTA_LIMIT) dev->min_delta_ns = MIN_DELTA_LIMIT; printk_deferred(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n", dev->name ? dev->name : "?", (unsigned long long) dev->min_delta_ns); return 0; }
클럭 이벤트 디바이스의 최소 딜레이 시간(min_delta_ns)을 증가시킨다. 호출 될 때마다 5us부터 시작하여 1.5배씩 증가하며 마지막에 최대 1 jiffies 만큼 상승한다. 그 이후에는 -ETIME 에러를 반환한다.
- 코드 라인 4~9에서 min_delta_ns 값이 한계값(1 jiffies) 이상이되면 경고 메시지 출력과 함께 에러 -ETIME을 반환한다.
- MIN_DELTA_LIMIT
- (NSEC_PER_SEC / HZ) 값으로 1 jiffies 소요 시간과 동일하다.
- MIN_DELTA_LIMIT
- 코드 라인 11~14에서 min_delta_ns 값이 5us 미만인 경우 5us로 하고 그렇지 않은 경우 현재 min_delta_ns 값에서 50%를 증가시킨다.
- 코드 라인 16~17에서 min_delta_ns 값이 한계값을 초과하면 한계값으로 설정한다.
- 코드 라인 19~23에서 min_delta_ns 값이 증가되었음을 경고 메시지로 출력하고 성공(0)으로 반환한다.
구조체
clock_event_device 구조체
include/linux/clockchips.h
/** * struct clock_event_device - clock event device descriptor * @event_handler: Assigned by the framework to be called by the low * level handler of the event source * @set_next_event: set next event function using a clocksource delta * @set_next_ktime: set next event function using a direct ktime value * @next_event: local storage for the next event in oneshot mode * @max_delta_ns: maximum delta value in ns * @min_delta_ns: minimum delta value in ns * @mult: nanosecond to cycles multiplier * @shift: nanoseconds to cycles divisor (power of two) * @state_use_accessors:current state of the device, assigned by the core code * @features: features * @retries: number of forced programming retries * @set_state_periodic: switch state to periodic * @set_state_oneshot: switch state to oneshot * @set_state_oneshot_stopped: switch state to oneshot_stopped * @set_state_shutdown: switch state to shutdown * @tick_resume: resume clkevt device * @broadcast: function to broadcast events * @min_delta_ticks: minimum delta value in ticks stored for reconfiguration * @max_delta_ticks: maximum delta value in ticks stored for reconfiguration * @name: ptr to clock event name * @rating: variable to rate clock event devices * @irq: IRQ number (only for non CPU local devices) * @bound_on: Bound on CPU * @cpumask: cpumask to indicate for which CPUs this device works * @list: list head for the management code * @owner: module reference */
struct clock_event_device { void (*event_handler)(struct clock_event_device *); int (*set_next_event)(unsigned long evt, struct clock_event_device *); int (*set_next_ktime)(ktime_t expires, struct clock_event_device *); ktime_t next_event; u64 max_delta_ns; u64 min_delta_ns; u32 mult; u32 shift; enum clock_event_state state_use_accessors; unsigned int features; unsigned long retries; int (*set_state_periodic)(struct clock_event_device *); int (*set_state_oneshot)(struct clock_event_device *); int (*set_state_oneshot_stopped)(struct clock_event_device *); int (*set_state_shutdown)(struct clock_event_device *); int (*tick_resume)(struct clock_event_device *); void (*broadcast)(const struct cpumask *mask); void (*suspend)(struct clock_event_device *); void (*resume)(struct clock_event_device *); unsigned long min_delta_ticks; unsigned long max_delta_ticks; const char *name; int rating; int irq; int bound_on; const struct cpumask *cpumask; struct list_head list; struct module *owner; } ____cacheline_aligned;
- (*event_handler)
- 만료 시간 시 호출될 이벤트 핸들러 함수가 등록된다.
- (*set_next_event)
- 다음 이벤트의 만료 시간을 설정할 클럭 이벤트 드라이버 함수가 등록된다.
- (*set_next_ktime)
- 다음 이벤트의 만료 시간을 ktime 으로 지정하여 설정하는 클럭 이벤트 드라이버 함수가 등록된다.
- next_event
- oneshot 모드에서 다음 이벤트가 만료될 ktime 값
- max_delta_ns
- 최대 프로그래밍 허용한 다음 이벤트의 최대 지연 nano 초
- min_delta_ns
- 최소 프로그래밍 허용한 다음 이벤트의 최소 지연 nano 초
- mult
- 1 ns를 만들기 위해 cycle 카운터에 곱할 값이며 shift와 같이 사용된다.
- 1 ns = (1 cycle 카운터 x mult) >> shift
- 예) 2Ghz, 2개의 cycle = 1 ns일 경우
- mult=2, shift=0
- shift
- 1 ns를 만들기 위해 cycle 카운터에 1 << shift 값으로 분배할 값이며 mult와 같이 사용된다.
- 예) 19.2Mhz, 1개의 cycle = 52 ns일 경우
- mult=0x4ea_4a8c, shift=32
- state_use_accessors
- 클럭 이벤트 운영 상태
- CLOCK_EVT_STATE_DETACHED(0)
- CLOCK_EVT_STATE_SHUTDOWN(1)
- CLOCK_EVT_STATE_PERIODIC(2)
- CLOCK_EVT_STATE_ONESHOT(3)
- CLOCK_EVT_STATE_ONESHOT_STOPPED(4)
- 클럭 이벤트 운영 상태
- features
- 기능 플래그들로 이 글의 처음에 자세히 설명하였다
- retries
- 프로그래밍 재시도 수
- (*set_state_periodic)
- periodic 상태 전환 시 호출될 후크 함수
- (*set_state_oneshot)
- oneshot 상태 전환 시 호출될 후크 함수
- (*set_state_oneshot_stopped)
- oneshot stop 상태 전환 시 호출될 후크 함수
- (*set_state_shutdown)
- shutdown 상태 전환 시 호출될 후크 함수
- (*tick_resume)
- suspend 상태에서 복귀시 호출될 후크 함수
- (*broadcast)
- 이벤트를 브로드캐스트하는 후크 함수
- (*suspend)
- 타이머 디바이스의 suspend 기능을 지원하는 후크 함수
- (*resume)
- suspend된 타이머 디바이스에 대해 resume을 지원하는 후크 함수
- min_delta_ticks
- 최소 프로그래밍 허용한 다음 tick 수
- max_delta_ticks
- 최대 프로그래밍 허용한 다음 tick 수
- *name
- 클럭 이벤트 명
- rating
- 정밀도 등급
- irq
- irq 번호 (per-cpu 디바이스가 아닌 경우)
- bound_on
- cpu 고정
- cpumask
- 이 클럭 이벤트 디바이스가 동작할 cpumask
- list
- 클럭 이벤트를 관리하고 있는 리스트에 연결되어 사용된다.
- owner
- 모듈 참조자
참고
- 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