在驱动中,clock设备在注册的时候会被注册进clockevent,每个clockevent会转化为tick device,然后会进行一次选择,最后成为某种类型的tick device(包括oneshot/periodic/broadcast)
1. clockevents
1.1 clockevent设备
1.1.1 clockevent结构体
clockevent设备用一下结构体表示
( include/linux/clockchips.h )
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;
1.1.2 clockevent状态
一个clockevent可以被配置成某种状态
enum clock_event_state {
CLOCK_EVT_STATE_DETACHED,----------------分离状态
CLOCK_EVT_STATE_SHUTDOWN,----------------关闭状态
CLOCK_EVT_STATE_PERIODIC,----------------周期性模式
CLOCK_EVT_STATE_ONESHOT,-----------------单步走模式
CLOCK_EVT_STATE_ONESHOT_STOPPED,---------单步走模式关闭
};
1.2 clockevent list
每个被注册为clockevent的设备都会加入到链表clockevent_devices中
( kernel/time/clockevents.c )
static LIST_HEAD(clockevent_devices);
1.3 clockevent注册函数
extern void clockevents_config_and_register(struct clock_event_device *dev,
u32 freq, unsigned long min_delta,
unsigned long max_delta);
1.4 clockevent注册过程
在clocksource的注册过程中,会进行clockevent的注册,下面以arm global timer为例来介绍注册过程,调用函数如下图:
对clockevent初始化完成后,进入tick device设备的创建过程,调用函数是tick_check_new_device,下节进行介绍
2. tick设备(tick device)
2.1 tick device结构体及类别
tick设备用以下结构体来表示:
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
tick device的模式:
enum tick_device_mode {
TICKDEV_MODE_PERIODIC,
TICKDEV_MODE_ONESHOT,
};
2.2 tick device创建
2.2.1 tick_check_new_device
tick device创建调用函数tick_check_new_device
(kernel/time/tick-internal.h)
void tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);------------得到对应cpu的tick_cpu_device(全局tick device)
curdev = td->evtdev;
/* cpu local device ? */
if (!tick_check_percpu(curdev, newdev, cpu))
goto out_bc;------------------tick device不是per cpu的,goto
/* Preference decision */
if (!tick_check_preferred(curdev, newdev))
goto out_bc;------------------优先级不高的,goto
if (!try_module_get(newdev->owner))
return;
/*
* Replace the eventually existing device by the new
* device. If the current device is the broadcast device, do
* not give it back to the clockevents layer !
*/
if (tick_is_broadcast_device(curdev)) {
clockevents_shutdown(curdev);
curdev = NULL;
}
clockevents_exchange_device(curdev, newdev);
tick_setup_device(td, newdev, cpu, cpumask_of(cpu));----正式创建特定的tick device
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_oneshot_notify();
return;
out_bc:
/*
* Can the new device be used as a broadcast device ?
*/
tick_install_broadcast_device(newdev);------------------创建broadcast tick device
}
2.2.2 tick_setup_device
static void tick_setup_device(struct tick_device *td,
struct clock_event_device *newdev, int cpu,
const struct cpumask *cpumask)
{
ktime_t next_event;
void (*handler)(struct clock_event_device *) = NULL;
/*
* First device setup ?
*/
if (!td->evtdev) {-------第一次进行tick device设置时进入
/*
* If no cpu took the do_timer update, assign it to
* this cpu:
*/
if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
if (!tick_nohz_full_cpu(cpu))
tick_do_timer_cpu = cpu;
else
tick_do_timer_cpu = TICK_DO_TIMER_NONE;
tick_next_period = ktime_get();
tick_period = ktime_set(0, NSEC_PER_SEC / HZ);
}
/*
* Startup in periodic mode first.
*/
td->mode = TICKDEV_MODE_PERIODIC;
} else {---------------非第一次进行tick device设置时进入
handler = td->evtdev->event_handler;
next_event = td->evtdev->next_event;
td->evtdev->event_handler = clockevents_handle_noop;
}
td->evtdev = newdev;
/*
* When the device is not per cpu, pin the interrupt to the
* current cpu:
*/
if (!cpumask_equal(newdev->cpumask, cpumask))
irq_set_affinity(newdev->irq, cpumask);
/*
* When global broadcasting is active, check if the current
* device is registered as a placeholder for broadcast mode.
* This allows us to handle this x86 misfeature in a generic
* way. This function also returns !=0 when we keep the
* current active broadcast state for this CPU.
*/
if (tick_device_uses_broadcast(newdev, cpu))
return;
if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0);--------------------创建periodic tick-device
else
tick_setup_oneshot(newdev, handler, next_event);---创建oneshot tick-device
}
2.3 periodic & oneshot tick-device
periodic和oneshot tick-device的区别在于,periodic的设备是周期性的,不用每次设置下一次中断的时间,但是one shot的设备是需要每次中断到来时,要设置下一次中断的时间的;由于这个原因,one shot的设备要更灵活或者说“精度”更高,所以一般说periodic设备是低精度设备,oneshot设备用于高精度设备。
在clock设备第一次注册的时候,一般都默认设置tick-device为periodic的,不过在后边的运行过程中,会对tick-device设备做出一些改变并设置为oneshot,这要看clock设备的支持了。
tick_setup_periodic和tick_setup_oneshot是分别设置clockevent为periodic和oneshot的,这两个函数的目的大致相同:
- 设置clock_event_device->event_handler
(对于periodic是tick_handle_periodic,对于送呢oneshot是hrtimer_interrupt) - 设置clock_event_device->state_use_accessors(CLOCK_EVT_STATE_PERIODIC或者CLOCK_EVT_STATE_ONESHOT)
- 如果是oneshot的,那么clockevents_program_event设置下次中断时间
3.change log
date | content | linux |
---|---|---|
2016.12.18 | clockevent和tick device基本介绍 | linux4.6.3 |