Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 273ef950 authored by Nils Carlson's avatar Nils Carlson Committed by Linus Torvalds
Browse files

drivers/char/hpet.c: fix periodic-emulation for delayed interrupts



When interrupts are delayed due to interrupt masking or due to other
interrupts being serviced the HPET periodic-emuation would fail.  This
happened because given an interval t and a time for the current interrupt
m we would compute the next time as t + m.  This works until we are
delayed for > t, in which case we would be writing a new value which is in
fact in the past.

This can be solved by computing the next time instead as (k * t) + m where
k is large enough to be in the future.  The exact computation of k is
described in a comment to the code.

More detail:

Assuming an interval of 5 between each expected interrupt we have a normal
case of

t0: interrupt, read t0 from comparator, set next interrupt t0 + 5
t5: interrupt, read t5 from comparator, set next interrupt t5 + 5
t10: interrupt, read t10 from comparator, set next interrupt t10 + 5
...

So, what happens when the interrupt is serviced too late?

t0: interrupt, read t0 from comparator, set next interrupt t0 + 5
t11: delayed interrupt serviced, read t5 from comparator, set next
interrupt t5 + 5, which is in the past!
... counter loops ...
t10: Much much later, get the next interrupt.

This can happen either because we have interrupts masked for too long
(some stupid driver goes on a printk rampage) or just because we are
pushing the limits of the interval (too small a period), or both most
probably.

My solution is to read the main counter as well and set the next interrupt
to occur at the right interval, for example:

t0: interrupt, read t0 from comparator, set next interrupt t0 + 5
t11: delayed interrupt serviced, read t5 from comparator, set next
interrupt t15 as t10 has been missed.
t15: back on track.

Signed-off-by: default avatarNils Carlson <nils.carlson@ericsson.com>
Cc: John Stultz <john.stultz@linaro.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 31b5f8ee
Loading
Loading
Loading
Loading
+23 −2
Original line number Diff line number Diff line
@@ -163,11 +163,32 @@ static irqreturn_t hpet_interrupt(int irq, void *data)
	 * This has the effect of treating non-periodic like periodic.
	 */
	if ((devp->hd_flags & (HPET_IE | HPET_PERIODIC)) == HPET_IE) {
		unsigned long m, t;
		unsigned long m, t, mc, base, k;
		struct hpet __iomem *hpet = devp->hd_hpet;
		struct hpets *hpetp = devp->hd_hpets;

		t = devp->hd_ireqfreq;
		m = read_counter(&devp->hd_timer->hpet_compare);
		write_counter(t + m, &devp->hd_timer->hpet_compare);
		mc = read_counter(&hpet->hpet_mc);
		/* The time for the next interrupt would logically be t + m,
		 * however, if we are very unlucky and the interrupt is delayed
		 * for longer than t then we will completely miss the next
		 * interrupt if we set t + m and an application will hang.
		 * Therefore we need to make a more complex computation assuming
		 * that there exists a k for which the following is true:
		 * k * t + base < mc + delta
		 * (k + 1) * t + base > mc + delta
		 * where t is the interval in hpet ticks for the given freq,
		 * base is the theoretical start value 0 < base < t,
		 * mc is the main counter value at the time of the interrupt,
		 * delta is the time it takes to write the a value to the
		 * comparator.
		 * k may then be computed as (mc - base + delta) / t .
		 */
		base = mc % t;
		k = (mc - base + hpetp->hp_delta) / t;
		write_counter(t * (k + 1) + base,
			      &devp->hd_timer->hpet_compare);
	}

	if (devp->hd_flags & HPET_SHARED_IRQ)