在timer_init()中,我们将late_time_init初始化为x86_late_time_init():
static __init void x86_late_time_init(void)
{
x86_init.timers.timer_init(); //最终调用hpet_time_init
tsc_init();
}
/* Default timer init function */
void __init hpet_time_init(void)
{
if (!hpet_enable()) // 尝试设置高精度事件定时器(HPET)
setup_pit_timer(); //如果HPET不能使用,则设置可编程间隔定时器
setup_default_timer_irq();
}
我们来看hpet_enable(),它检测HPET是否可用,如果可用则将时钟源设置为HPET:
/**
* hpet_enable - Try to setup the HPET timer. Returns 1 on success.
*/
int __init hpet_enable(void)
{
unsigned long hpet_period;
unsigned int id;
u64 freq;
int i;
if (!is_hpet_capable()) //HPET是否可用
return 0;
hpet_set_mapping(); //HPET有自己的内存映射空间
/*
* Read the period and check for a sane value:
*/
hpet_period = hpet_readl(HPET_PERIOD); //从内存中读取HPET_PERIOD
/*
* AMD SB700 based systems with spread spectrum enabled use a
* SMM based HPET emulation to provide proper frequency
* setting. The SMM code is initialized with the first HPET
* register access and takes some time to complete. During
* this time the config register reads 0xffffffff. We check
* for max. 1000 loops whether the config register reads a non
* 0xffffffff value to make sure that HPET is up and running
* before we go further. A counting loop is safe, as the HPET
* access takes thousands of CPU cycles. On non SB700 based
* machines this check is only done once and has no side
* effects.
*/
for (i = 0; hpet_readl(HPET_CFG) == 0xFFFFFFFF; i++) {
if (i == 1000) {
printk(KERN_WARNING
"HPET config register value = 0xFFFFFFFF. "
"Disabling HPET\n");
goto out_nohpet;
}
}
if (hpet_period < HPET_MIN_PERIOD || hpet_period > HPET_MAX_PERIOD)
goto out_nohpet;
/*
* The period is a femto seconds value. Convert it to a
* frequency.
*/
freq = FSEC_PER_SEC;
do_div(freq, hpet_period);
hpet_freq = freq;
/*
* Read the HPET ID register to retrieve the IRQ routing
* information and the number of channels
*/
id = hpet_readl(HPET_ID);
hpet_print_config();
#ifdef CONFIG_HPET_EMULATE_RTC
/*
* The legacy routing mode needs at least two channels, tick timer
* and the rtc emulation channel.
*/
if (!(id & HPET_ID_NUMBER))
goto out_nohpet;
#endif
if (hpet_clocksource_register()) //注册HPET时钟源
goto out_nohpet;
if (id & HPET_ID_LEGSUP) {
hpet_legacy_clockevent_register(); //注册HPET时钟事件源设备(源)。
return 1;
}
return 0;
out_nohpet:
hpet_clear_mapping();
hpet_address = 0;
return 0;
}
整个函数中最重要的两个操作:
(1)hpet_clocksource_register(),注册HPET时钟源
static int hpet_clocksource_register(void)
{
u64 start, now;
cycle_t t1;
/* Start the counter */
hpet_restart_counter();
/* Verify whether hpet counter works */
t1 = hpet_readl(HPET_COUNTER);
rdtscll(start);
/*
* We don't know the TSC frequency yet, but waiting for
* 200000 TSC cycles is safe:
* 4 GHz == 50us
* 1 GHz == 200us
*/
do {
rep_nop();
rdtscll(now);
} while ((now - start) < 200000UL);
if (t1 == hpet_readl(HPET_COUNTER)) {
printk(KERN_WARNING
"HPET counter not counting. HPET disabled\n");
return -ENODEV;
}
clocksource_register_hz(&clocksource_hpet, (u32)hpet_freq);
return 0;
}
static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
return __clocksource_register_scale(cs, 1, hz);
}
/**
* __clocksource_register_scale - Used to install new clocksources
* @t: clocksource to be registered
* @scale: Scale factor multiplied against freq to get clocksource hz
* @freq: clocksource frequency (cycles per second) divided by scale
*
* Returns -EBUSY if registration fails, zero otherwise.
*
* This *SHOULD NOT* be called directly! Please use the
* clocksource_register_hz() or clocksource_register_khz helper functions.
*/
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{
/* Initialize mult/shift and max_idle_ns */
__clocksource_updatefreq_scale(cs, scale, freq);
/* Add clocksource to the clcoksource list */
mutex_lock(&clocksource_mutex);
clocksource_enqueue(cs); //将hpet时钟源插入到clocksource_list中
clocksource_enqueue_watchdog(cs); //看门狗???
clocksource_select(); //重新选择系统时钟源
mutex_unlock(&clocksource_mutex);
return 0;
}
在注册了HPET后,系统会选择HPET作为系统的时钟源:
/**
* clocksource_select - Select the best clocksource available
*
* Private function. Must hold clocksource_mutex when called.
*
* Select the clocksource with the best rating, or the clocksource,
* which is selected by userspace override.
*/
static void clocksource_select(void)
{
struct clocksource *best, *cs;
if (!finished_booting || list_empty(&clocksource_list))
return;
/* First clocksource on the list has the best rating. */
best = list_first_entry(&clocksource_list, struct clocksource, list);
/* Check for the override clocksource. */
list_for_each_entry(cs, &clocksource_list, list) {
if (strcmp(cs->name, override_name) != 0)
continue;
/*
* Check to make sure we don't switch to a non-highres
* capable clocksource if the tick code is in oneshot
* mode (highres or nohz)
*/
if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) &&
tick_oneshot_mode_active()) {
/* Override clocksource cannot be used. */
printk(KERN_WARNING "Override clocksource %s is not "
"HRT compatible. Cannot switch while in "
"HRT/NOHZ mode\n", cs->name);
override_name[0] = 0;
} else
/* Override clocksource can be used. */
best = cs;
break;
}
if (curr_clocksource != best) {
printk(KERN_INFO "Switching to clocksource %s\n", best->name);
curr_clocksource = best;
timekeeping_notify(curr_clocksource);
}
}
(2)在注册并且选定了HPET后,要hpet_legacy_clockevent_register()
static void hpet_legacy_clockevent_register(void)
{
/* Start HPET legacy interrupts */
hpet_enable_legacy_int();
/*
* Start hpet with the boot cpu mask and make it
* global after the IO_APIC has been initialized.
*/
hpet_clockevent.cpumask = cpumask_of(smp_processor_id());
clockevents_config_and_register(&hpet_clockevent, hpet_freq,
HPET_MIN_PROG_DELTA, 0x7FFFFFFF);
global_clock_event = &hpet_clockevent;
printk(KERN_DEBUG "hpet clockevent registered\n");
}
static void hpet_enable_legacy_int(void)
{
unsigned int cfg = hpet_readl(HPET_CFG); //从相关内存区读取HPET的CFG
cfg |= HPET_CFG_LEGACY; //将HPET的CFG设置为系统默认的clockevent_device。
hpet_writel(cfg, HPET_CFG);
hpet_legacy_int_enabled = 1;
}
我们来看一下hpet_clockevent:
/*
* The hpet clock event device
*/
static struct clock_event_device hpet_clockevent = {
.name = "hpet",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.set_mode = hpet_legacy_set_mode,
.set_next_event = hpet_legacy_next_event,
.irq = 0,
.rating = 50,
};
继续hpet_legacy_clockevent_register:
/**
* clockevents_config_and_register - Configure and register a clock event device
* @dev: device to register
* @freq: The clock frequency
* @min_delta: The minimum clock ticks to program in oneshot mode
* @max_delta: The maximum clock ticks to program in oneshot mode
*
* min/max_delta can be 0 for devices which do not support oneshot mode.
*/
void clockevents_config_and_register(struct clock_event_device *dev,
u32 freq, unsigned long min_delta,
unsigned long max_delta)
{
dev->min_delta_ticks = min_delta;
dev->max_delta_ticks = max_delta;
clockevents_config(dev, freq);
clockevents_register_device(dev);
}
static void clockevents_config(struct clock_event_device *dev,
u32 freq)
{
u64 sec;
if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return;
/*
* Calculate the maximum number of seconds we can sleep. Limit
* to 10 minutes for hardware which can program more than
* 32bit ticks so we still get reasonable conversion values.
*/
sec = dev->max_delta_ticks;
do_div(sec, freq);
if (!sec)
sec = 1;
else if (sec > 600 && dev->max_delta_ticks > UINT_MAX)
sec = 600;
clockevents_calc_mult_shift(dev, freq, sec);
dev->min_delta_ns = clockevent_delta2ns(dev->min_delta_ticks, dev);
dev->max_delta_ns = clockevent_delta2ns(dev->max_delta_ticks, dev);
}
/**
* clockevents_register_device - register a clock event device
* @dev: device to register
*/
void clockevents_register_device(struct clock_event_device *dev)
{
unsigned long flags;
BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
if (!dev->cpumask) {
WARN_ON(num_possible_cpus() > 1);
dev->cpumask = cpumask_of(smp_processor_id());
}
raw_spin_lock_irqsave(&clockevents_lock, flags);
list_add(&dev->list, &clockevent_devices); //将hpet_clockevent挂到clockevent_devices上。
clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev);
clockevents_notify_released();
raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}
/*We have converted clockevent_devices to store all active devices, and
*clockevents_released to store all fail-to-add/replace-out devices.
*/
/*
* Called after a notify add to make devices available which were
* released from the notifier call.
*/
static void clockevents_notify_released(void)
{
struct clock_event_device *dev;
while (!list_empty(&clockevents_released)) {
dev = list_entry(clockevents_released.next,
struct clock_event_device, list);
list_del(&dev->list);
list_add(&dev->list, &clockevent_devices);
clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev);
}
}
这里,我们需要hpet_clockevent挂到clockevent_devices上的方式。我们看到,clockevent_devices只不过是普通的list_head结构,而非想象中的由clockevent_device组成的链表,在想clockevent_devices上添加元素时,只是将该元素的list字段链入到clockevent_devices上即可,这是内核链表结构的精妙之处!
到现在,我们已经将hpet时钟源和时钟事件源的注册工作完成了!
如果HPET是不可用的,那么
/*
* Initialize the conversion factor and the min/max deltas of the clock event
* structure and register the clock event source with the framework.
*/
void __init setup_pit_timer(void)
{
/*
* Start pit with the boot cpu mask and make it global after the
* IO_APIC has been initialized.
*/
pit_ce.cpumask = cpumask_of(smp_processor_id());
clockevents_config_and_register(&pit_ce, CLOCK_TICK_RATE, 0xF, 0x7FFF);
global_clock_event = &pit_ce;
}
首先来看一下pit_ce:
static struct clock_event_device pit_ce = {
.name = "pit",
.features = CLOCK_EVT_FEAT_PERIODIC,
.set_mode = pit_set_mode,
.set_next_event = pit_set_next_event,
.shift = 32,
};
由于PIT是做为默认时钟源的,因此在setup_pit_timer中我们只需要注册将其注册为时钟事件源即可。