Timer -5- (Clock Events Subsytem)

<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”
  • 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 소요 시간과 동일하다.
  • 코드 라인 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
    • 모듈 참조자

 

 참고

댓글 남기기