[LINUX] Read the implementation of ARM global timer

Now that we've read this far, let's read ARM's global timer implementation little by little.


https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/clocksource/arm_global_timer.c

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 is registered in the following line.

global_timer_of_register()

CPU rivision check

Only available after 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;
	}

Refer to the parameters from the device tree.

The parameters to be referenced are as follows.

No. name Purpose
1 gt_ppi For timer interrupt detection
2 gt_base For register control
3 gt_clk To get the frequency of the timer

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;
	}

Registration of each CPU interrupt

--Calculate the clock rate from the clock obtained earlier. --Secure a different memory area gt_evt for each CPU --Registered for each CPU interrupt --Register 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;
	}

Set the global timer of the CPU at startup.

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()

--Stop CONTROL of Global Timer --Set 0 for both UPPER / LOWER on the counter. --Run Global Timer with CONTROL set to ENABLE -(If CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK is defined) Call sched_clock_register () to register SCHED --Call clocksource_register_hz () to register 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);
}

The following structures are registered in sched and 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 () is an alias of gt_counter_read (). --gt_resume () reactivates the global timer when resuming.

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()

Read the counter value.

--Read the upper 32 bits. --Read the lower 32 bits. --Reread the upper 32 bits again. If this is different from the previous upper 32 bits, the lower 32 bits are read again.

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() The process of registering a clock event on the CPU at startup.

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()

--Specify the clock rate --Registration of 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);
}

Summary

It seems to be roughly divided into the following contents.

--Interrupt registration --Register clock source --Registration of clock event --Registration of delay timer

Well, maybe you can make your own timer ...? I came to the simple impression.

Recommended Posts

Read the implementation of ARM global timer
I read the implementation of golang channel
I read the implementation of range (Objects / rangeobject.c)
Also read the contents of arch / arm / kernel / swp_emulate.c
Othello-From the tic-tac-toe of "Implementation Deep Learning" (3)
The story of manipulating python global variables
Read all the contents of proc / [pid]
Othello-From the tic-tac-toe of "Implementation Deep Learning" (2)
[Python] Read the source code of Bottle Part 2
Why the Python implementation of ISUCON 5 used Bottle
[Python] Read the source code of Bottle Part 1
I read and implemented the Variants of UKR
Template of python script to read the contents of the file
Play with the UI implementation of Pythonista3 [Super Super Introduction]
I followed the implementation of the du command (first half)
About testing in the implementation of machine learning models
Summarize the knowledge of reading Go's HTTP implementation ~ Slice ~
Summarize the knowledge of reading Go's HTTP implementation ~ Channel ~
I followed the implementation of the du command (second half)
DataNitro, implementation of function to read data from sheet
Read the power of the smart meter with M5StickC (BP35C0-J11-T01)
How to access the global variable of the imported module
A reminder about the implementation of recommendations in Python
The beginning of cif2cell
The meaning of self
Read the OpenCV documentation
the zen of Python
Implementation of Fibonacci sequence
Revenge of the Types: Revenge of types
Increase the speed of the Monte Carlo method of Cython cut-out implementation.
A simple Python implementation of the k-nearest neighbor method (k-NN)
Read the GRIB2 file of the Japan Meteorological Agency with pygrib