在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,
- };
- /**
- * 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时钟源和时钟事件源的注册工作完成了!
如果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;
- }
- 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中我们只需要注册将其注册为时钟事件源即可。