20.2 内核节拍驱动
Linux 2.6的早期(Linux2.6.21之前)内核是基于节拍设计的,一般SoC将Linux移植到芯片上时,会从芯片内部找一个定时器,并将该定时器配置为赫兹的频率,在每个时钟节拍到来时,调用ARM Linux内核核心层的timer_tick()函数,引发系统里的一系列行为。如Linux 2.6.17中arch/arm/mach-s3c2410/time.c的做法类似于代码清单20.1所示。
代码清单20.1 早期内核的节拍驱动
/*
* IRQ handler for the timer 定时器中断服务程序
*/
static irqreturn_t
s3c2410_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
write_seqlock(&xtime_lock);
timer_tick(regs);
write_sequnlock(&xtime_lock);
return IRQ_HANDLED;
}
static struct irqaction s3c2410_timer_irq = {
.name = "S3C2410 Timer Tick",
.flags = SA_INTERRUPT | SA_TIMER,
.handler = s3c2410_timer_interrupt, // 中断处理函数
};
static void __init s3c2410_timer_init (void)
{
s3c2410_timer_setup();
setup_irq(IRQ_TIMER4, &s3c2410_timer_irq);
}
分析:将硬件的TIMER4定时器配置为周期触发中断,每个中断到来会自动调用内核函数timer_tick()。
当前的Linux多采用无节拍方案,并支持高精度定时器,内核的配置一般会使能NO_HZ(即无节拍,或
者说动态节拍)和HIGH_RES_TIMERS。强调的是无节拍并不是说系统中没有时钟节拍,而是说这个节拍不再像以前那样周期性地产生。如果画一个时间轴,周期节拍的系统节拍中断发生的时序如图20.1所示:
无节拍意味着,根据系统的运行情况,以事件驱动的方式动态决定下一个节拍在何时发生。而NO_HZ(无节拍)的Linux的运行节拍如图20.2所示,看起来是:两次定时器中断发生的时间间隔可长可短:
在当前的Linux系统中,SoC底层的定时器被实现为一个clock_event_device和clocksource形式的驱动。在clock_event_device结构体中,实现其set_mode()和set_next_event()成员函数;在clocksource结构体
中,主要实现read()成员函数。而在定时器中断服务程序中,不再调用timer_tick(),而是调用clock_event_device的event_handler()成员函数。一个典型SoC的底层节拍定时器驱动形如代码清单20.2所
示。
代码清单20.2 新内核基于clocksource和clock_event的节拍驱动
static irqreturn_t xxx_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *ce = dev_id;
…
ce->event_handler(ce);
return IRQ_HANDLED;}
/* read 64-bit timer counter */
static cycle_t xxx_timer_read(struct clocksource *cs)
{
u64 cycles;
/* read the 64-bit timer counter */
cycles = readl_relaxed(xxx_timer_base + LATCHED_HI);
cycles=(cycles<<32)|readl_relaxed(xxx_timer_base + LATCHED_LO);
return cycles;
}
static int xxx_timer_set_next_event(unsigned long delta,
struct clock_event_device *ce)
{
unsigned long now, next;
now = readl_relaxed(xxx_timer_base + LATCHED_LO);
next = now + delta;// 产生下一次节拍中断
writel_relaxed(next, xxx_timer_base + SIRFSOC_TIMER_MATCH_0);
...
}
static void xxx_timer_set_mode(enum clock_event_mode mode,
struct clock_event_device *ce)
{
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
…
case CLOCK_EVT_MODE_ONESHOT:
…
case CLOCK_EVT_MODE_SHUTDOWN:
…
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_RESUME:
break;
}
}
static struct clock_event_device xxx_clockevent = {
.name = "xxx_clockevent",
.rating = 200,
.features = CLOCK_EVT_FEAT_ONESHOT,
.set_mode = xxx_timer_set_mode,
.set_next_event = xxx_timer_set_next_event,
};
static struct clocksource xxx_clocksource = {
.name = "xxx_clocksource",
.rating = 200,
.mask = CLOCKSOURCE_MASK(64),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
.read = xxx_timer_read,
.suspend = xxx_clocksource_suspend,
.resume = xxx_clocksource_resume,
};
static struct irqaction xxx_timer_irq = {
.name = "xxx_tick",
.flags = IRQF_TIMER,
.irq = 0,
.handler = xxx_timer_interrupt,
.dev_id = &xxx_clockevent,
};
static void __init xxx_clockevent_init(void)
{
clockevents_calc_mult_shift(&xxx_clockevent, CLOCK_TICK_RATE, 60);
xxx_clockevent.max_delta_ns =
clockevent_delta2ns(-2, &xxx_clockevent);
xxx_clockevent.min_delta_ns =
clockevent_delta2ns(2, &xxx_clockevent);
xxx_clockevent.cpumask = cpumask_of(0);
clockevents_register_device(&xxx_clockevent);
}
/* initialize the kernel jiffy timer source */
static void __init xxx_timer_init(void)
{
…
BUG_ON(clocksource_register_hz(&xxx_clocksource, CLOCK_TICK_RATE));
BUG_ON(setup_irq(xxx_timer_irq.irq, &xxx_timer_irq));
xxx_clockevent_init();
}
struct sys_timer xxx_timer = {
.init = xxx_timer_init,
};
特别关注如下的函数:
linux/clockchips.h
struct clock_event_device {
void (*event_handler)(struct clock_event_device *);
int (*set_next_event)(unsigned long delta, 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;
linux/clocksource.h
struct clocksource {
u64 (*read)(struct clocksource *cs);
u64 mask;
u32 mult;
u32 shift;
u64 max_idle_ns;
u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
struct arch_clocksource_data archdata;
#endif
u64 max_cycles;
const char *name;
struct list_head list;
int rating;
int (*enable)(struct clocksource *cs);
void (*disable)(struct clocksource *cs);
unsigned long flags;
void (*suspend)(struct clocksource *cs);
void (*resume)(struct clocksource *cs);
void (*mark_unstable)(struct clocksource *cs);
void (*tick_stable)(struct clocksource *cs);
/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
/* Watchdog related data, used by the framework */
struct list_head wd_list;
u64 cs_last;
u64 wd_last;
#endif
struct module *owner;
};
1.clock_event_device的set_next_event成员函数xxx_timer_set_next_event()
该函数的delta参数是Linux内核传递给底层定时器的一个差值,其含义是下一次节拍中断产生的硬件定时器中计数器的值相对于当前计数器的差值。在该函数中将硬件定时器设置为在“当前计数器计数值+delta”的时刻产生下一次节拍中断。
xxx_clockevent_init()函数中设置可接受的最小和最大delta值对应的纳秒数,即xxx_clockevent.min_delta_ns和xxx_clockevent.max_delta_ns。
2.clocksource的read成员函数xxx_timer_read()
该函数可读取出从开机到当前时刻定时器计数器已经走过的值,无论有没有设置当计数器达到某值时产生中断,硬件的计数总是在进行的(我们要理解,计数总是在进行,而计数到某值后要产生中断则需要软件设置)。因此,该函数给Linux系统提供了一个底层的准确的参考时间。
3.定时器的中断服务程序xxx_timer_interrupt()
在该中断服务程序中,直接调用clock_event_device的event_handler()成员函数,event_handler()成员函数的具体工作是Linux内核根据Linux内核配置和运行情况自行设置的。
4.clock_event_device的set_mode成员函数xxx_timer_set_mode()
用于设置定时器的模式以及恢复、关闭等功能,目前一般采用ONESHOT模式,即一次一次产生中断。新版的Linux也可以使用老的周期性模式,如果内核在编译时没有选择NO_HZ(无节拍),该底层的定时器驱动依然可以为内核的运行提供支持。
这些函数的结合使得ARM Linux内核底层所需要的时钟得以运行。
对于多核处理器,一般的做法是给每个核分配一个独立的定时器,各个核根据自身的运行情况动态地设置自己时钟中断发生的时刻。