*******************************************************************************************
cpu0 local timer clock_event_device注册过程:
start_kernel()->reset_init()->kernel_init()->smp_prepare_cpus()
->percpu_timer_setup()->twd_timer_setup()->clockevents_config_and_register()
->twd_local_timer_common_register()->local_timer_register()
这里也是板级相关代码,不过内核提供了一些core方法。当然只有smp才会执行到这里的路径。
这个时候cpu1仍然处于wfi状态。不过随后cpu1被唤醒之后同样也会执行类似方法设置相应的local timer。
twd_timer是arm每个核内部的timer。
local timer的中断处理函数基本上就是调用了关联的clock_event_device->event_handler()方法。
clock_event_device对象是每cpu分配的对象。同上面一样,event_handler方法也是后面才初始化的。
static irqreturn_t twd_handler(int irq, void *dev_id)
{
struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
if (twd_timer_ack()) {
evt->event_handler(evt);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
可以看出来local timer同sp804 clock_event_device流程非常相似,中断处理函数都是调用了自己的
clock_event_device的event_handler方法。不过这两个clock_event_device代表了不同的硬件,他们的
set_next_event触发中断方法不同。rating优先级不一样,一般localtimer=350,sp804=300,就是说localtimer的
优先级要高。irq中断号当然也不一样。。
通过tick_check_new_device()新的clock_event_device注册到系统中的时候,rating有很大的作用。
当增加一个新的newdevice,如果系统当前没有device,则设置当前device等于newdevice。
当增加一个新的newdevice,如果newdevice的rating小于当前device的rating,则会忽略这个newdevice。
反之newdevice通过clockevents_exchange_device关闭当前的device,然后切换当前device等于newdevice。
接着通过tick_setup_device继续设置event_handler=tick_handle_periodic。
所以,这里local timer会替代之前注册的sp804 timer(只有cpu0才有)。从这里开始,sp804 timer0实际上等同
disable的,它的next_event中断时间是一个超大的值。而local timer中断不断的产生并且处理了。
*******************************************************************************************
接着看看tick_handle_periodic如何切换成hrtimer_interrupt的。
首先在init_jiffies_clocksource中注册了jiffies clocksource到kernel。
static int __init init_jiffies_clocksource(void)
{
return clocksource_register(&clocksource_jiffies);
}
core_initcall(init_jiffies_clocksource);
clocksource_enqueue()实际上就是按照优先级rating将所有clocksource链接到clocksource_list上。
并且链表头部的clocksource是rating最高的。
clocksource_select()会选择best clocksource,等于当前最高优先级的clocksource,
或者是用户指定的clocksource(如果指定了的话)。这时,如果best clocksource不等于当前clocksource,
调用timekeeping_notify通知内核新的clocksource生效了。
int clocksource_register(struct clocksource *cs)
{
/* calculate max adjustment for given mult/shift */
cs->maxadj = clocksource_max_adjustment(cs);
WARN_ONCE(cs->mult + cs->maxadj < cs->mult,
"Clocksource %s might overflow on 11%% adjustment\n",
cs->name);
/* calculate max idle time permitted for this clocksource */
cs->max_idle_ns = clocksource_max_deferment(cs);
mutex_lock(&clocksource_mutex);
clocksource_enqueue(cs);
clocksource_enqueue_watchdog(cs);
clocksource_select();
mutex_unlock(&clocksource_mutex);
return 0;
}
timekeeping_notify实际上会调用到change_clocksource方法来更新timekeeper的clocksource为best clocksource。
tick_clock_notify()会触发kernel去检测是否要切换到hrtimer模式。
void timekeeping_notify(struct clocksource *clock)
{
struct timekeeper *tk = &timekeeper;
if (tk->clock == clock)
return;
stop_machine(change_clocksource, clock, NULL);
tick_clock_notify();
}
static int change_clocksource(void *data)
{
struct timekeeper *tk = &timekeeper;
struct clocksource *new, *old;
unsigned long flags;
new = (struct clocksource *) data;
write_seqlock_irqsave(&tk->lock, flags);
timekeeping_forward_now(tk);
if (!new->enable || new->enable(new) == 0) {
old = tk->clock;
tk_setup_internals(tk, new);
if (old->disable)
old->disable(old);
}
timekeeping_update(tk, true);
write_sequnlock_irqrestore(&tk->lock, flags);
return 0;
}
其实这个时候,sp804 timer1作为free run timer的clocksource早已经注册到系统中了。
所以现在注册jiffies clocksource的时候,就会触发timekeeping_notify来更新timekeeper的clocksource,
同时通知kernel去检查是否要切换hrtimer模式。
当前的时钟中断处理函数回调是tick_handle_periodic()
tick_periodic()->update_process_times->run_local_timers()激活TIMER_SOFTIRQ处理timer软中断。
hrtimer_run_queues如果kernel没有启动hrtimer模式的话,就在这里处理hrtimer事件。所以hrtimer api
仍然可以使用,但是是通过tick来触发的。
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ);
}
timer软中断其实还在hrtimer_run_pending检测是否要切换到hrtimer模式。
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __this_cpu_read(tvec_bases);
hrtimer_run_pending();
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}
void hrtimer_run_pending(void)
{
if (hrtimer_hres_active())
return;
/*
* This _is_ ugly: We have to check in the softirq context,
* whether we can switch to highres and / or nohz mode. The
* clocksource switch happens in the timer interrupt with
* xtime_lock held. Notification from there only sets the
* check bit in the tick_oneshot code, otherwise we might
* deadlock vs. xtime_lock.
*/
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
hrtimer_switch_to_hres();
}
int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
//tick_clock_notify会设置check_clocks位。所以这里就会要继续检测了。
//然后又清0,所以系统绝大部分事件这个方法下面都不会走的。
if (!test_and_clear_bit(0, &ts->check_clocks))
return 0;
if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
return 0;
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
return 0;
if (!allow_nohz)
return 1;
tick_nohz_switch_to_nohz();
return 0;
}
检查timekeeper的clocksource时候有hrtimer能力。当然我们的sp804 timer1是符合要求的。
int timekeeping_valid_for_hres(void)
{
struct timekeeper *tk = &timekeeper;
unsigned long seq;
int ret;
do {
seq = read_seqbegin(&tk->lock);
ret = tk->clock->flags & CLOCK_SOURCE_VALID_FOR_HRES;
} while (read_seqretry(&tk->lock, seq));
return ret;
}
然后通过hrtimer_switch_to_hres切换perodioc到oneshot hrtimer模式。
tick_setup_sched_timer()是通过hrtimer模拟的tick中断,当然系统在idle的时候,
这个hrtimer中断会关闭的。
static int hrtimer_switch_to_hres(void)
{
int i, cpu = smp_processor_id();
struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
unsigned long flags;
if (base->hres_active)
return 1;
local_irq_save(flags);
if (tick_init_highres()) {
local_irq_restore(flags);
printk(KERN_WARNING "Could not switch to high resolution "
"mode on CPU %d\n", cpu);
return 0;
}
base->hres_active = 1;
for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
base->clock_base .resolution = KTIME_HIGH_RES;
tick_setup_sched_timer();
/* "Retrigger" the interrupt to get things going */
retrigger_next_event(NULL);
local_irq_restore(flags);
return 1;
}
切换每cpu的tick_cpu_device->clock_event_device的event_handler方法为hrtimer_interrupt。
当然在tick_check_new_device的时候每cpu的tick_cpu_device->clock_event_device已经指向了localtimer device。
int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
todo:
cpu1 唤醒流程。
cpu0 local timer clock_event_device注册过程:
start_kernel()->reset_init()->kernel_init()->smp_prepare_cpus()
->percpu_timer_setup()->twd_timer_setup()->clockevents_config_and_register()
->twd_local_timer_common_register()->local_timer_register()
这里也是板级相关代码,不过内核提供了一些core方法。当然只有smp才会执行到这里的路径。
这个时候cpu1仍然处于wfi状态。不过随后cpu1被唤醒之后同样也会执行类似方法设置相应的local timer。
twd_timer是arm每个核内部的timer。
local timer的中断处理函数基本上就是调用了关联的clock_event_device->event_handler()方法。
clock_event_device对象是每cpu分配的对象。同上面一样,event_handler方法也是后面才初始化的。
static irqreturn_t twd_handler(int irq, void *dev_id)
{
struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
if (twd_timer_ack()) {
evt->event_handler(evt);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
可以看出来local timer同sp804 clock_event_device流程非常相似,中断处理函数都是调用了自己的
clock_event_device的event_handler方法。不过这两个clock_event_device代表了不同的硬件,他们的
set_next_event触发中断方法不同。rating优先级不一样,一般localtimer=350,sp804=300,就是说localtimer的
优先级要高。irq中断号当然也不一样。。
通过tick_check_new_device()新的clock_event_device注册到系统中的时候,rating有很大的作用。
当增加一个新的newdevice,如果系统当前没有device,则设置当前device等于newdevice。
当增加一个新的newdevice,如果newdevice的rating小于当前device的rating,则会忽略这个newdevice。
反之newdevice通过clockevents_exchange_device关闭当前的device,然后切换当前device等于newdevice。
接着通过tick_setup_device继续设置event_handler=tick_handle_periodic。
所以,这里local timer会替代之前注册的sp804 timer(只有cpu0才有)。从这里开始,sp804 timer0实际上等同
disable的,它的next_event中断时间是一个超大的值。而local timer中断不断的产生并且处理了。
*******************************************************************************************
接着看看tick_handle_periodic如何切换成hrtimer_interrupt的。
首先在init_jiffies_clocksource中注册了jiffies clocksource到kernel。
static int __init init_jiffies_clocksource(void)
{
return clocksource_register(&clocksource_jiffies);
}
core_initcall(init_jiffies_clocksource);
clocksource_enqueue()实际上就是按照优先级rating将所有clocksource链接到clocksource_list上。
并且链表头部的clocksource是rating最高的。
clocksource_select()会选择best clocksource,等于当前最高优先级的clocksource,
或者是用户指定的clocksource(如果指定了的话)。这时,如果best clocksource不等于当前clocksource,
调用timekeeping_notify通知内核新的clocksource生效了。
int clocksource_register(struct clocksource *cs)
{
/* calculate max adjustment for given mult/shift */
cs->maxadj = clocksource_max_adjustment(cs);
WARN_ONCE(cs->mult + cs->maxadj < cs->mult,
"Clocksource %s might overflow on 11%% adjustment\n",
cs->name);
/* calculate max idle time permitted for this clocksource */
cs->max_idle_ns = clocksource_max_deferment(cs);
mutex_lock(&clocksource_mutex);
clocksource_enqueue(cs);
clocksource_enqueue_watchdog(cs);
clocksource_select();
mutex_unlock(&clocksource_mutex);
return 0;
}
timekeeping_notify实际上会调用到change_clocksource方法来更新timekeeper的clocksource为best clocksource。
tick_clock_notify()会触发kernel去检测是否要切换到hrtimer模式。
void timekeeping_notify(struct clocksource *clock)
{
struct timekeeper *tk = &timekeeper;
if (tk->clock == clock)
return;
stop_machine(change_clocksource, clock, NULL);
tick_clock_notify();
}
static int change_clocksource(void *data)
{
struct timekeeper *tk = &timekeeper;
struct clocksource *new, *old;
unsigned long flags;
new = (struct clocksource *) data;
write_seqlock_irqsave(&tk->lock, flags);
timekeeping_forward_now(tk);
if (!new->enable || new->enable(new) == 0) {
old = tk->clock;
tk_setup_internals(tk, new);
if (old->disable)
old->disable(old);
}
timekeeping_update(tk, true);
write_sequnlock_irqrestore(&tk->lock, flags);
return 0;
}
其实这个时候,sp804 timer1作为free run timer的clocksource早已经注册到系统中了。
所以现在注册jiffies clocksource的时候,就会触发timekeeping_notify来更新timekeeper的clocksource,
同时通知kernel去检查是否要切换hrtimer模式。
当前的时钟中断处理函数回调是tick_handle_periodic()
tick_periodic()->update_process_times->run_local_timers()激活TIMER_SOFTIRQ处理timer软中断。
hrtimer_run_queues如果kernel没有启动hrtimer模式的话,就在这里处理hrtimer事件。所以hrtimer api
仍然可以使用,但是是通过tick来触发的。
void run_local_timers(void)
{
hrtimer_run_queues();
raise_softirq(TIMER_SOFTIRQ);
}
timer软中断其实还在hrtimer_run_pending检测是否要切换到hrtimer模式。
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __this_cpu_read(tvec_bases);
hrtimer_run_pending();
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}
void hrtimer_run_pending(void)
{
if (hrtimer_hres_active())
return;
/*
* This _is_ ugly: We have to check in the softirq context,
* whether we can switch to highres and / or nohz mode. The
* clocksource switch happens in the timer interrupt with
* xtime_lock held. Notification from there only sets the
* check bit in the tick_oneshot code, otherwise we might
* deadlock vs. xtime_lock.
*/
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
hrtimer_switch_to_hres();
}
int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
//tick_clock_notify会设置check_clocks位。所以这里就会要继续检测了。
//然后又清0,所以系统绝大部分事件这个方法下面都不会走的。
if (!test_and_clear_bit(0, &ts->check_clocks))
return 0;
if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
return 0;
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
return 0;
if (!allow_nohz)
return 1;
tick_nohz_switch_to_nohz();
return 0;
}
检查timekeeper的clocksource时候有hrtimer能力。当然我们的sp804 timer1是符合要求的。
int timekeeping_valid_for_hres(void)
{
struct timekeeper *tk = &timekeeper;
unsigned long seq;
int ret;
do {
seq = read_seqbegin(&tk->lock);
ret = tk->clock->flags & CLOCK_SOURCE_VALID_FOR_HRES;
} while (read_seqretry(&tk->lock, seq));
return ret;
}
然后通过hrtimer_switch_to_hres切换perodioc到oneshot hrtimer模式。
tick_setup_sched_timer()是通过hrtimer模拟的tick中断,当然系统在idle的时候,
这个hrtimer中断会关闭的。
static int hrtimer_switch_to_hres(void)
{
int i, cpu = smp_processor_id();
struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
unsigned long flags;
if (base->hres_active)
return 1;
local_irq_save(flags);
if (tick_init_highres()) {
local_irq_restore(flags);
printk(KERN_WARNING "Could not switch to high resolution "
"mode on CPU %d\n", cpu);
return 0;
}
base->hres_active = 1;
for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
base->clock_base .resolution = KTIME_HIGH_RES;
tick_setup_sched_timer();
/* "Retrigger" the interrupt to get things going */
retrigger_next_event(NULL);
local_irq_restore(flags);
return 1;
}
切换每cpu的tick_cpu_device->clock_event_device的event_handler方法为hrtimer_interrupt。
当然在tick_check_new_device的时候每cpu的tick_cpu_device->clock_event_device已经指向了localtimer device。
int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
todo:
cpu1 唤醒流程。