diff options
author | Nils Carlson <nils.carlson@ericsson.com> | 2011-06-15 18:08:54 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-06-15 23:04:02 -0400 |
commit | 273ef9509b7903e50f36aaf9f1d5dc9087fca506 (patch) | |
tree | 4d1800cadbb85647b4db5cf5cb855dc43465536c /drivers/char/hpet.c | |
parent | 31b5f8eeece4c0d70b649bfac7759cf7e3f915dd (diff) |
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: Nils 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: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char/hpet.c')
-rw-r--r-- | drivers/char/hpet.c | 25 |
1 files changed, 23 insertions, 2 deletions
diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c index 051474c65b78..34d6a1cab8de 100644 --- a/drivers/char/hpet.c +++ b/drivers/char/hpet.c | |||
@@ -163,11 +163,32 @@ static irqreturn_t hpet_interrupt(int irq, void *data) | |||
163 | * This has the effect of treating non-periodic like periodic. | 163 | * This has the effect of treating non-periodic like periodic. |
164 | */ | 164 | */ |
165 | if ((devp->hd_flags & (HPET_IE | HPET_PERIODIC)) == HPET_IE) { | 165 | if ((devp->hd_flags & (HPET_IE | HPET_PERIODIC)) == HPET_IE) { |
166 | unsigned long m, t; | 166 | unsigned long m, t, mc, base, k; |
167 | struct hpet __iomem *hpet = devp->hd_hpet; | ||
168 | struct hpets *hpetp = devp->hd_hpets; | ||
167 | 169 | ||
168 | t = devp->hd_ireqfreq; | 170 | t = devp->hd_ireqfreq; |
169 | m = read_counter(&devp->hd_timer->hpet_compare); | 171 | m = read_counter(&devp->hd_timer->hpet_compare); |
170 | write_counter(t + m, &devp->hd_timer->hpet_compare); | 172 | mc = read_counter(&hpet->hpet_mc); |
173 | /* The time for the next interrupt would logically be t + m, | ||
174 | * however, if we are very unlucky and the interrupt is delayed | ||
175 | * for longer than t then we will completely miss the next | ||
176 | * interrupt if we set t + m and an application will hang. | ||
177 | * Therefore we need to make a more complex computation assuming | ||
178 | * that there exists a k for which the following is true: | ||
179 | * k * t + base < mc + delta | ||
180 | * (k + 1) * t + base > mc + delta | ||
181 | * where t is the interval in hpet ticks for the given freq, | ||
182 | * base is the theoretical start value 0 < base < t, | ||
183 | * mc is the main counter value at the time of the interrupt, | ||
184 | * delta is the time it takes to write the a value to the | ||
185 | * comparator. | ||
186 | * k may then be computed as (mc - base + delta) / t . | ||
187 | */ | ||
188 | base = mc % t; | ||
189 | k = (mc - base + hpetp->hp_delta) / t; | ||
190 | write_counter(t * (k + 1) + base, | ||
191 | &devp->hd_timer->hpet_compare); | ||
171 | } | 192 | } |
172 | 193 | ||
173 | if (devp->hd_flags & HPET_SHARED_IRQ) | 194 | if (devp->hd_flags & HPET_SHARED_IRQ) |