late_time_init

在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中我们只需要注册将其注册为时钟事件源即可。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值