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:
parent
ac49a0b859
commit
ece17c923a
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user