1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ARM global timerの実装を読み解いていく

Posted at

さて、ここまで読み解いてきたので、ARMのglobal timer実装をちょっとずつ読み解いていく。


path: root/drivers/clocksource/arm_global_timer.c

TIMER_OF_DECLARE()

arm_global_timer.c
/* Only tested on r2p2 and r3p0  */
TIMER_OF_DECLARE(arm_gt, "arm,cortex-a9-global-timer",
			global_timer_of_register);

cortex-a9-global-timer が下記行で登録される。

global_timer_of_register()

CPU rivision check

Cortex A9 r2p0以後でなければ利用できない。

global_timer_of_register()@arm_global_timer.c
static int __init global_timer_of_register(struct device_node *np)
{
	struct clk *gt_clk;
	int err = 0;
	/*
	 * In A9 r2p0 the comparators for each processor with the global timer
	 * fire when the timer value is greater than or equal to. In previous
	 * revisions the comparators fired when the timer value was equal to.
	 */
	if (read_cpuid_part() == ARM_CPU_PART_CORTEX_A9
	    && (read_cpuid_id() & 0xf0000f) < 0x200000) {
		pr_warn("global-timer: non support for this cpu version.\n");
		return -ENOSYS;
	}

device treeからパラメータを参照する。

参照するパラメータは以下。

No. 名前 目的
1 gt_ppi タイマー割り込み検知のため
2 gt_base レジスタ制御のため
3 gt_clk タイマーの周波数取得のため
global_timer_of_register()@arm_global_timer.c
	gt_ppi = irq_of_parse_and_map(np, 0);
	if (!gt_ppi) {
		pr_warn("global-timer: unable to parse irq\n");
		return -EINVAL;
	}

	gt_base = of_iomap(np, 0);
	if (!gt_base) {
		pr_warn("global-timer: invalid base address\n");
		return -ENXIO;
	}

	gt_clk = of_clk_get(np, 0);
	if (!IS_ERR(gt_clk)) {
		err = clk_prepare_enable(gt_clk);
		if (err)
			goto out_unmap;
	} else {
		pr_warn("global-timer: clk not found\n");
		err = -EINVAL;
		goto out_unmap;
	}

各CPU割り込み登録

  • 先ほど取得してクロックから、clock rateを計算し。
  • CPU毎に異なるメモリ領域 gt_evtを確保
  • 各CPUの割り込みに登録
  • delay timerを登録
global_timer_of_register()@arm_global_timer.c
	gt_clk_rate = clk_get_rate(gt_clk);
	gt_evt = alloc_percpu(struct clock_event_device);
	if (!gt_evt) {
		pr_warn("global-timer: can't allocate memory\n");
		err = -ENOMEM;
		goto out_clk;
	}

	err = request_percpu_irq(gt_ppi, gt_clockevent_interrupt,
				 "gt", gt_evt);
	if (err) {
		pr_warn("global-timer: can't register interrupt %d (%d)\n",
			gt_ppi, err);
		goto out_free;
	}

起動時のCPUのglobal timerに対して、設定を行う。

global_timer_of_register()@arm_global_timer.c
	/* Register and immediately configure the timer on the boot CPU */
	err = gt_clocksource_init();
	if (err)
		goto out_irq;
	
	err = cpuhp_setup_state(CPUHP_AP_ARM_GLOBAL_TIMER_STARTING,
				"clockevents/arm/global_timer:starting",
				gt_starting_cpu, gt_dying_cpu);
	if (err)
		goto out_irq;

	gt_delay_timer_init();

	return 0;

out_irq:
	free_percpu_irq(gt_ppi, gt_evt);
out_free:
	free_percpu(gt_evt);
out_clk:
	clk_disable_unprepare(gt_clk);
out_unmap:
	iounmap(gt_base);
	WARN(err, "ARM Global timer register failed (%d)\n", err);

	return err;
}

gt_clockevent_interrupt()

gt_clockevent_interrupt()@arm_global_timer.c
static irqreturn_t gt_clockevent_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *evt = dev_id;

	if (!(readl_relaxed(gt_base + GT_INT_STATUS) &
				GT_INT_STATUS_EVENT_FLAG))
		return IRQ_NONE;

	/**
	 * ERRATA 740657( Global Timer can send 2 interrupts for
	 * the same event in single-shot mode)
	 * Workaround:
	 *	Either disable single-shot mode.
	 *	Or
	 *	Modify the Interrupt Handler to avoid the
	 *	offending sequence. This is achieved by clearing
	 *	the Global Timer flag _after_ having incremented
	 *	the Comparator register	value to a higher value.
	 */
	if (clockevent_state_oneshot(evt))
		gt_compare_set(ULONG_MAX, 0);

	writel_relaxed(GT_INT_STATUS_EVENT_FLAG, gt_base + GT_INT_STATUS);
	evt->event_handler(evt);

	return IRQ_HANDLED;
}

gt_clocksource_init()

  • Global TimerのCONTROLを停止
  • カウンターのUPPER/LOWERともに0をセット。
  • Global TimerをCONTROLをENABLEにして稼働
  • (CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCKが定義されていたら) sched_clock_register()を呼び出してSCHED登録
  • clocksource_register_hz()を呼び出して、clocksource登録
gt_clocksource_init()@arm_global_timer.c
static int __init gt_clocksource_init(void)
{
	writel(0, gt_base + GT_CONTROL);
	writel(0, gt_base + GT_COUNTER0);
	writel(0, gt_base + GT_COUNTER1);
	/* enables timer on all the cores */
	writel(GT_CONTROL_TIMER_ENABLE, gt_base + GT_CONTROL);

#ifdef CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
	sched_clock_register(gt_sched_clock_read, 64, gt_clk_rate);
#endif
	return clocksource_register_hz(&gt_clocksource, gt_clk_rate);
}

schedとclocksourceに登録されたのは、下記構造体。

arm_global_timer.c
static struct clocksource gt_clocksource = {
	.name	= "arm_global_timer",
	.rating	= 300,
	.read	= gt_clocksource_read,
	.mask	= CLOCKSOURCE_MASK(64),
	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
	.resume = gt_resume,
};

#ifdef CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
static u64 notrace gt_sched_clock_read(void)
{
	return _gt_counter_read();
}
#endif

gt_clocksource_read() / gt_resume()

  • gt_clocksource_read() は、gt_counter_read()のalias.
  • gt_resume()はレジュームする時に、global timerを再動させる。
gt_clocksource_read()/gt_resume()@arm_global_timer.c

static u64 gt_clocksource_read(struct clocksource *cs)
{
	return gt_counter_read();
}

static void gt_resume(struct clocksource *cs)
{
	unsigned long ctrl;

	ctrl = readl(gt_base + GT_CONTROL);
	if (!(ctrl & GT_CONTROL_TIMER_ENABLE))
		/* re-enable timer on resume */
		writel(GT_CONTROL_TIMER_ENABLE, gt_base + GT_CONTROL);
}

gt_counter_read()

カウンター値を読み出す。

  • 上位32bitを読み出す。
  • 下位32bitを読み出す。
  • 上位32bitを再度読み出し直す。これが先の上位32bitと異なっていた場合には、再度下位32bitを読み出す。
gt_counter_read()@arm_global_timer.c
/*
 * To get the value from the Global Timer Counter register proceed as follows:
 * 1. Read the upper 32-bit timer counter register
 * 2. Read the lower 32-bit timer counter register
 * 3. Read the upper 32-bit timer counter register again. If the value is
 *  different to the 32-bit upper value read previously, go back to step 2.
 *  Otherwise the 64-bit timer counter value is correct.
 */
static u64 notrace _gt_counter_read(void)
{
	u64 counter;
	u32 lower;
	u32 upper, old_upper;

	upper = readl_relaxed(gt_base + GT_COUNTER1);
	do {
		old_upper = upper;
		lower = readl_relaxed(gt_base + GT_COUNTER0);
		upper = readl_relaxed(gt_base + GT_COUNTER1);
	} while (upper != old_upper);

	counter = upper;
	counter <<= 32;
	counter |= lower;
	return counter;
}

static u64 gt_counter_read(void)
{
	return _gt_counter_read();
}

gt_starting_cpu(), gt_dying_cpu()

起動時CPUでclock eventを登録する処理。

gt_starting_cpu()/gt_dying_cpu()@arm_global_timer.c
tatic int gt_starting_cpu(unsigned int cpu)
{
	struct clock_event_device *clk = this_cpu_ptr(gt_evt);

	clk->name = "arm_global_timer";
	clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
		CLOCK_EVT_FEAT_PERCPU;
	clk->set_state_shutdown = gt_clockevent_shutdown;
	clk->set_state_periodic = gt_clockevent_set_periodic;
	clk->set_state_oneshot = gt_clockevent_shutdown;
	clk->set_state_oneshot_stopped = gt_clockevent_shutdown;
	clk->set_next_event = gt_clockevent_set_next_event;
	clk->cpumask = cpumask_of(cpu);
	clk->rating = 300;
	clk->irq = gt_ppi;
	clockevents_config_and_register(clk, gt_clk_rate,
					1, 0xffffffff);
	enable_percpu_irq(clk->irq, IRQ_TYPE_NONE);
	return 0;
}

static int gt_dying_cpu(unsigned int cpu)
{
	struct clock_event_device *clk = this_cpu_ptr(gt_evt);

	gt_clockevent_shutdown(clk);
	disable_percpu_irq(clk->irq);
	return 0;
}

gt_clockevent_shutdown()

gt_clockevent_shutdown()@arm_global_timer.c
static int gt_clockevent_shutdown(struct clock_event_device *evt)
{
	unsigned long ctrl;

	ctrl = readl(gt_base + GT_CONTROL);
	ctrl &= ~(GT_CONTROL_COMP_ENABLE | GT_CONTROL_IRQ_ENABLE |
		  GT_CONTROL_AUTO_INC);
	writel(ctrl, gt_base + GT_CONTROL);
	return 0;
}

gt_compare_set()/gt_clockevent_set_periodic()/gt_clockevent_set_next_event

gt_compare_set()/gt_clockevent_set_periodic()/gt_clockevent_set_next_event@arm_global_timer.c
/**
 * To ensure that updates to comparator value register do not set the
 * Interrupt Status Register proceed as follows:
 * 1. Clear the Comp Enable bit in the Timer Control Register.
 * 2. Write the lower 32-bit Comparator Value Register.
 * 3. Write the upper 32-bit Comparator Value Register.
 * 4. Set the Comp Enable bit and, if necessary, the IRQ enable bit.
 */
static void gt_compare_set(unsigned long delta, int periodic)
{
	u64 counter = gt_counter_read();
	unsigned long ctrl;

	counter += delta;
	ctrl = GT_CONTROL_TIMER_ENABLE;
	writel_relaxed(ctrl, gt_base + GT_CONTROL);
	writel_relaxed(lower_32_bits(counter), gt_base + GT_COMP0);
	writel_relaxed(upper_32_bits(counter), gt_base + GT_COMP1);

	if (periodic) {
		writel_relaxed(delta, gt_base + GT_AUTO_INC);
		ctrl |= GT_CONTROL_AUTO_INC;
	}

	ctrl |= GT_CONTROL_COMP_ENABLE | GT_CONTROL_IRQ_ENABLE;
	writel_relaxed(ctrl, gt_base + GT_CONTROL);
}

static int gt_clockevent_set_periodic(struct clock_event_device *evt)
{
	gt_compare_set(DIV_ROUND_CLOSEST(gt_clk_rate, HZ), 1);
	return 0;
}

static int gt_clockevent_set_next_event(unsigned long evt,
					struct clock_event_device *unused)
{
	gt_compare_set(evt, 0);
	return 0;
}

gt_delay_timer_init()

  • クロックレートを指定
  • delay timerの登録
gt_delay_timer_init()@arm_global_timer.c
static unsigned long gt_read_long(void)
{
	return readl_relaxed(gt_base + GT_COUNTER0);
}

static struct delay_timer gt_delay_timer = {
	.read_current_timer = gt_read_long,
};

static void __init gt_delay_timer_init(void)
{
	gt_delay_timer.freq = gt_clk_rate;
	register_current_timer_delay(&gt_delay_timer);
}

まとめ

大体、下記の内容に分かれるっぽい。

  • 割り込み登録
  • clock sourceの登録
  • clock eventの登録
  • delay timerの登録

うーん、これなら自分でタイマー作れる・・・かも?という素朴な感想に至りました。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?