9G10内核时钟tick实现

9G10中PIT(Periodic Interval Timer)提供OS调度中断,它提供了最高精度和最有效的管理(即使系统长时间响应)。
一. 硬件
PIT目标是提供OS的周期中断。PIT提供一个可编程溢出计数器和一个reset-on-read特性。它包含两个计数器:20bit CPIV counter和12bit PICNT(Periodic Interval) counter。两个计数器都工作在Master Clock/16。
CPIV从0增加到PIT_MR中PIV设定值,到达设定值后复位(0)且PICNT加1。假如中断是能,PIT_SR的PITS位置位,触发一个中断。
写一个新的PIV值到PITMR不会reset/restart计数器。
当读取PIT_PIVR(Periodic Interval Value Register)时,PICNT复位(0),且PITS清零(获取到一个中断)。PICNT表示自从上次读取PIT_PIVR后多少个Periodic Interval过去了。
当读取PIT_PIIR(Periodic Interval Image Register)时,对CPIV和PICNT无影响。
PIT可通过PIT_MR的PITEN位使能或关闭,仅当CPIV变为0时PITEN才生效。
PIT共有4个寄存器,分别是PIT_MR(Mode Register),PIT_SR(Status Register),PIT_PIVR(Periodic Interval Value Register),PIT_PIIR(Periodic Interval Image Register)。
PIT_MR: bit0-19 PIV, bit24 PITEN, bit25 PITIEN
PIT_SR:bit0 PITS
PIT_PIVR:bit0-19 CPIV,bit20-31 PICNT
PIT_PIIR:bit0-19 CPIV,bit20-31 PICNT
二. 软件
在板级文件中machine_desc中设置timer=&at91sam926x_timer,下面看下at91sam926x_timer的初始化函数。
at91sam926x_time.c
/*
* Set up both clocksource and clockevent support.
*/
static void __init at91sam926x_pit_init(void)
{
unsigned long pit_rate;
unsigned bits;

/*
* Use our actual MCK to figure out how many MCK/16 ticks per
* 1/HZ period (instead of a compile-time constant LATCH).
*/
pit_rate = clk_get_rate(clk_get(NULL, "mck")) / 16;
pit_cycle = (pit_rate + HZ/2) / HZ; //向上取整,四舍五入
WARN_ON(((pit_cycle - 1) & ~AT91_PIT_PIV) != 0);

/* Initialize and enable the timer */
at91sam926x_pit_reset();

/*
* Register clocksource. The high order bits of PIV are unused,
* so this isn't a 32-bit counter unless we get clockevent irqs.
*/
/* mult = (10^9 << shift) / pit_rate */
pit_clk.mult = clocksource_hz2mult(pit_rate, pit_clk.shift);
bits = 12 /* PICNT */ + ilog2(pit_cycle) /* PIV */;
pit_clk.mask = CLOCKSOURCE_MASK(bits);
clocksource_register(&pit_clk); // hz或cycle到ns的转换

/* Set up irq handler */
setup_irq(AT91_ID_SYS, &at91sam926x_pit_irq);

/* Set up and register clockevents */
/* factor = (pit_rate << shift) / NSEC_PER_SEC */
pit_clkevt.mult = div_sc(pit_rate, NSEC_PER_SEC, pit_clkevt.shift);
pit_clkevt.cpumask = cpumask_of(0);
clockevents_register_device(&pit_clkevt); // ns到hz或cycle的转换
}


static struct clocksource pit_clk = {
.name = "pit",
.rating = 175,
.read = read_pit_clk,
.shift = 20,
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static struct clock_event_device pit_clkevt = {
.name = "pit",
.features = CLOCK_EVT_FEAT_PERIODIC,
.shift = 32,
.rating = 100,
.set_mode = pit_clkevt_mode,
};
Clocksource注册过程
Clocksource_register(&pit_clk); -->clocksource_enqueue(&pit_clk);
将pit_clk添加到系统时钟源list中。
Clockevents_register_device(&pit_clkevt);
-->clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, &pit_clkevt);
--> raw_notifier_call_chain(&clockevents_chain, reason, &pit_clkevt);
-->__raw_notifier_call_chain(nh, val, v, -1, NULL);
-->notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
-->nb->notifier_call(nb, val, v);
即nb->notifier_call((&clockevents_chain)->head, CLOCK_EVT_NOTIFY_ADD, &pit_clkevt);
Kernel/notifier.c
notifier_call()函数的初始化在start_kernel()的tick_init()中,后面分析。
-->tick_check_new_device(&pit_clkevt); //kernel/time/tick-common.c
确认clock_event_device的rating是否高于当前的,高于则替换当前时钟事件设备
若pit_clkevt->features&CLOCK_EVT_FEAT_ONESHOT 还要调用 tick_oneshot_notify()
-->tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
-->tick_setup_periodic(newdev, 0);
---->tick_set_periodic_handler(newdev, 0);
------>(&pit_clkevt)->event_handler = tick_handle_periodic;
// kernel/time/tick-common.c
即设置pit_clkevt的事件处理函数为tick_handle_periodic()。
---->clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
---->(&pit_clkevt)->set_mode(CLOCK_EVT_MODE_PERIODIC, &pit_clkevt);
pit_clkevt.mode = CLOCK_EVT_MODE_PERIODIC;
至此调用pit_clkevt的set_mode()函数,完成全部初始化工作

在init/main.c的start_kernel()中tick_init()调用过程:
tick_init(); //kernel/time/tick-common.c
-->clockevents_register_notifier(&tick_notifier);
-->raw_notifier_chain_register(&clockevents_chain, nb);
-->notifier_chain_register(&nh->head, n); // kernel/notifier.c
-->向clockevents_chian注册tick_notifier()

static int tick_notify(struct notifier_block *nb, unsigned long reason,
void *dev)
{
switch (reason) {

case CLOCK_EVT_NOTIFY_ADD:
return tick_check_new_device(dev);

case CLOCK_EVT_NOTIFY_BROADCAST_ON:
case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
tick_broadcast_on_off(reason, dev);
break;

case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
tick_broadcast_oneshot_control(reason);
break;

case CLOCK_EVT_NOTIFY_CPU_DYING:
tick_handover_do_timer(dev);
break;

case CLOCK_EVT_NOTIFY_CPU_DEAD:
tick_shutdown_broadcast_oneshot(dev);
tick_shutdown_broadcast(dev);
tick_shutdown(dev);
break;

case CLOCK_EVT_NOTIFY_SUSPEND:
tick_suspend();
tick_suspend_broadcast();
break;

case CLOCK_EVT_NOTIFY_RESUME:
tick_resume();
break;

default:
break;
}
return NOTIFY_OK;
}

static struct notifier_block tick_notifier = {
.notifier_call = tick_notify,
};
即向系统注册了notifier_block结构体tick_notifier的notifier_call()回调函数。


中断处理过程
根据初始化过程,每个tick或Hz产生一次中断。
static struct irqaction at91sam926x_pit_irq = {
.name = "at91_tick",
.flags = IRQF_SHARED | IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.handler = at91sam926x_pit_interrupt
};
static irqreturn_t at91sam926x_pit_interrupt(int irq, void *dev_id)
{

/* The PIT interrupt may be disabled, and is shared */
if ((pit_clkevt.mode == CLOCK_EVT_MODE_PERIODIC)
&& (at91_sys_read(AT91_PIT_SR) & AT91_PIT_PITS)) {
unsigned nr_ticks;

/* Get number of ticks performed before irq, and ack it */
nr_ticks = PIT_PICNT(at91_sys_read(AT91_PIT_PIVR));
do {
pit_cnt += pit_cycle;
pit_clkevt.event_handler(&pit_clkevt);
nr_ticks--;
} while (nr_ticks);
return IRQ_HANDLED;
}

return IRQ_NONE;
}

at91sam926x_pit_interrupt()
-->pit_clkevt.event_handler(&pit_clkevt);
-->tick_handle_periodic(&pit_clkevt); //kernel/time/tick-common.c
-->tick_periodic(cpu);
-->do_timer(1); //kernel/timer.c 更新jiffies_64和时间
-->update_times(ticks);
-->update_wall_time(); //更新xtime

void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
update_times(ticks);
}
static inline void update_times(unsigned long ticks)
{
update_wall_time();
calc_load(ticks);
}
三. 知识扩展
1)时钟系统中最重要的结构体变量:clockevent clocksource xtime。
clockevent为kernel提供了时钟中断的一些处理函数,特别是对于tickless系统,提供了设置下一次时钟中断时间点的接口set_next_event和timer模式设置接口set_mode。
clocksource则是kernel真正的计数者 时钟源,常规的时钟中断中的计数以及提高精度的补充计数都来自于clocksource。
clocksource成员rating代表了时钟精度,参考值如下:
1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
100--199:基本可用,可用作真实的时钟源,但不推荐;
200--299:精度较好,可用作真实的时钟源;
300--399:很好,精确的时钟源;
400--499:理想的时钟源,如有可能就必须选择它作为时钟源;
xtime是kernel的墙上时间,记录从1970-1-1至今的时间差,不管是用户空间还是内核空间要获取的系统时间都需要去读取xtime。
2)timer中断模式
kernel下timer的中断模式支持2种:周期性和一次性,也就是periodic和oneshot。
对于固定tick(1/HZ)系统的timer使用periodic。但是对于tickless系统,则必须要使用oneshot 模式了,因为每次timer中断间隔不一样长。
3系统启动时读取硬件时间,一般在rtc驱动加载后,调用rtc_hctosys()(driver/rtc/hctosys.c)。其根据内核配置CONFIG_RTC_HCTOSYS来决定是否同步时间,并读取CONFIG_RTC_HCTOSYS_DEVICE(一般为rtc0)来获取RTC时间。
late_initcall(rtc_hctosys);

参考:
http://blog.csdn.net/skyflying2012/article/details/44727409

原文地址:https://www.cnblogs.com/embedded-linux/p/6013731.html