Several improvements to HPET driver:

- Add special check for case when time expires before being programmed.
This fixes interrupt loss and respectively timer death on attempt to
program very short interval. Increase minimal supported period to more
realistic value.
 - Add support for hint.hpet.X.allowed_irqs tunable, allowing manually
specify which interrupts driver allowed to use. Unluckily, many BIOSes
program wrong allowed interrupts mask, so driver tries to stay on safe
side by not using unshareable ISA IRQs. This option gives control over
this limitation, allowing more per-CPU timers to be provided, when FSB
interrupts are not supported. Value of this tunable is bitmask.
 - Do not use regular interrupts on virtual machines. QEMU and VirtualBox
do not support them properly, that may cause problems. Stay safe by default.
Same time both QEMU and VirtualBox work fine in legacy_route mode.
VirtualBox also works fine if manually specify allowed ISA IRQs with above.
This commit is contained in:
mav 2010-09-05 19:24:32 +00:00
parent ac49a0b859
commit ece17c923a

View File

@ -74,6 +74,7 @@ struct hpet_softc {
int irq;
int useirq;
int legacy_route;
uint32_t allowed_irqs;
struct resource *mem_res;
struct resource *intr_res;
void *intr_handle;
@ -146,7 +147,7 @@ hpet_start(struct eventtimer *et,
struct hpet_timer *mt = (struct hpet_timer *)et->et_priv;
struct hpet_timer *t;
struct hpet_softc *sc = mt->sc;
uint32_t fdiv;
uint32_t fdiv, cmp;
t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]];
if (period != NULL) {
@ -164,23 +165,31 @@ hpet_start(struct eventtimer *et,
fdiv += sc->freq * first->sec;
} else
fdiv = t->div;
if (t->irq < 0)
bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num);
t->caps |= HPET_TCNF_INT_ENB;
t->last = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
restart:
cmp = t->last + fdiv;
if (t->mode == 1 && (t->caps & HPET_TCAP_PER_INT)) {
t->caps |= HPET_TCNF_TYPE;
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num),
t->caps | HPET_TCNF_VAL_SET);
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num),
t->last + fdiv);
bus_read_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num));
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num),
t->div);
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), cmp);
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->div);
} else {
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num),
t->last + fdiv);
t->caps &= ~HPET_TCNF_TYPE;
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps);
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), cmp);
}
if (fdiv < 5000) {
bus_read_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num));
t->last = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER);
if ((int32_t)(t->last - cmp) < 0) {
fdiv *= 2;
goto restart;
}
}
t->caps |= HPET_TCNF_INT_ENB;
bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num);
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps);
return (0);
}
@ -321,7 +330,7 @@ hpet_attach(device_t dev)
int i, j, num_msi, num_timers, num_percpu_et, num_percpu_t, cur_cpu;
int pcpu_master;
static int maxhpetet = 0;
uint32_t val, val2, cvectors;
uint32_t val, val2, cvectors, dvectors;
uint16_t vendor, rev;
ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
@ -438,10 +447,9 @@ hpet_attach(device_t dev)
sc->t[1].vectors = 0;
}
num_msi = 0;
sc->useirq = 0;
/* Find common legacy IRQ vectors for all timers. */
cvectors = 0xffff0000;
/* Check what IRQs we want use. */
/* By default allow any PCI IRQs. */
sc->allowed_irqs = 0xffff0000;
/*
* HPETs in AMD chipsets before SB800 have problems with IRQs >= 16
* Lower are also not always working for different reasons.
@ -450,7 +458,25 @@ hpet_attach(device_t dev)
* interrupt loss. Avoid legacy IRQs for AMD.
*/
if (vendor == HPET_VENDID_AMD)
cvectors = 0x00000000;
sc->allowed_irqs = 0x00000000;
/*
* Neither QEMU nor VirtualBox report supported IRQs correctly.
* The only way to use HPET there is to specify IRQs manually
* and/or use legacy_route. Legacy_route mode work on both.
*/
if (vm_guest)
sc->allowed_irqs = 0x00000000;
/* Let user override. */
resource_int_value(device_get_name(dev), device_get_unit(dev),
"allowed_irqs", &sc->allowed_irqs);
num_msi = 0;
sc->useirq = 0;
/* Find IRQ vectors for all timers. */
cvectors = sc->allowed_irqs & 0xffff0000;
dvectors = sc->allowed_irqs & 0x0000ffff;
if (sc->legacy_route)
dvectors &= 0x0000fefe;
for (i = 0; i < num_timers; i++) {
t = &sc->t[i];
if (sc->legacy_route && i < 2)
@ -465,6 +491,10 @@ hpet_attach(device_t dev)
}
}
#endif
else if (dvectors & t->vectors) {
t->irq = ffs(dvectors & t->vectors) - 1;
dvectors &= ~(1 << t->irq);
}
if (t->irq >= 0) {
if (!(t->intr_res =
bus_alloc_resource(dev, SYS_RES_IRQ, &t->intr_rid,
@ -495,7 +525,7 @@ hpet_attach(device_t dev)
if (sc->legacy_route)
hpet_enable(sc);
/* Group timers for per-CPU operation. */
num_percpu_et = min(num_msi / mp_ncpus, 2);
num_percpu_et = min(num_msi / mp_ncpus, 1);
num_percpu_t = num_percpu_et * mp_ncpus;
pcpu_master = 0;
cur_cpu = CPU_FIRST();
@ -510,7 +540,8 @@ hpet_attach(device_t dev)
bus_bind_intr(dev, t->intr_res, cur_cpu);
cur_cpu = CPU_NEXT(cur_cpu);
num_percpu_t--;
}
} else if (t->irq >= 0)
bus_bind_intr(dev, t->intr_res, CPU_FIRST());
}
bus_write_4(sc->mem_res, HPET_ISR, 0xffffffff);
sc->irq = -1;
@ -545,7 +576,7 @@ hpet_attach(device_t dev)
/* Legacy route doesn't need more configuration. */
} else
#ifdef DEV_APIC
if (t->irq >= 0) {
if ((t->caps & HPET_TCAP_FSB_INT_DEL) && t->irq >= 0) {
uint64_t addr;
uint32_t data;
@ -561,7 +592,9 @@ hpet_attach(device_t dev)
t->irq = -2;
} else
#endif
if (sc->irq >= 0 && (t->vectors & (1 << sc->irq)))
if (t->irq >= 0)
t->caps |= (t->irq << 9);
else if (sc->irq >= 0 && (t->vectors & (1 << sc->irq)))
t->caps |= (sc->irq << 9) | HPET_TCNF_INT_TYPE;
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), t->caps);
/* Skip event timers without set up IRQ. */
@ -585,7 +618,7 @@ hpet_attach(device_t dev)
t->et.et_quality -= 10;
t->et.et_frequency = sc->freq;
t->et.et_min_period.sec = 0;
t->et.et_min_period.frac = 0x00004000LLU << 32;
t->et.et_min_period.frac = 0x00008000LLU << 32;
t->et.et_max_period.sec = 0xfffffffeLLU / sc->freq;
t->et.et_max_period.frac =
((0xfffffffeLLU << 32) / sc->freq) << 32;