您的位置:首页 > 其它

[时钟管理] arm 时间系统 2

2014-05-24 16:46 316 查看
*******************************************************************************************

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 唤醒流程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: