Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/acpica/acpi_hpet.c
Show First 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | |||||
#include <contrib/dev/acpica/include/acpi.h> | #include <contrib/dev/acpica/include/acpi.h> | ||||
#include <contrib/dev/acpica/include/accommon.h> | #include <contrib/dev/acpica/include/accommon.h> | ||||
#include <dev/acpica/acpivar.h> | #include <dev/acpica/acpivar.h> | ||||
#include <dev/acpica/acpi_hpet.h> | #include <dev/acpica/acpi_hpet.h> | ||||
#ifdef DEV_APIC | #ifdef DEV_APIC | ||||
#include "pcib_if.h" | #include <x86/apicreg.h> | ||||
#ifdef SMP | |||||
#include <x86/x86_smp.h> | |||||
#endif | #endif | ||||
#include <x86/x86_var.h> | |||||
#include "pcib_if.h" | |||||
#endif /* DEV_APIC */ | |||||
#define HPET_VENDID_AMD 0x4353 | #define HPET_VENDID_AMD 0x4353 | ||||
#define HPET_VENDID_AMD2 0x1022 | #define HPET_VENDID_AMD2 0x1022 | ||||
#define HPET_VENDID_INTEL 0x8086 | #define HPET_VENDID_INTEL 0x8086 | ||||
#define HPET_VENDID_NVIDIA 0x10de | #define HPET_VENDID_NVIDIA 0x10de | ||||
#define HPET_VENDID_SW 0x1166 | #define HPET_VENDID_SW 0x1166 | ||||
ACPI_SERIAL_DECL(hpet, "ACPI HPET support"); | ACPI_SERIAL_DECL(hpet, "ACPI HPET support"); | ||||
Show All 37 Lines | #define TIMER_ONESHOT 2 | ||||
int pcpu_slaves[MAXCPU]; | int pcpu_slaves[MAXCPU]; | ||||
struct resource *intr_res; | struct resource *intr_res; | ||||
void *intr_handle; | void *intr_handle; | ||||
uint32_t caps; | uint32_t caps; | ||||
uint32_t vectors; | uint32_t vectors; | ||||
uint32_t div; | uint32_t div; | ||||
uint32_t next; | uint32_t next; | ||||
char name[8]; | char name[8]; | ||||
bool nmi; | |||||
} t[32]; | } t[32]; | ||||
int num_timers; | int num_timers; | ||||
struct cdev *pdev; | struct cdev *pdev; | ||||
int mmap_allow; | int mmap_allow; | ||||
int mmap_allow_write; | int mmap_allow_write; | ||||
}; | }; | ||||
static d_open_t hpet_open; | static d_open_t hpet_open; | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | hpet_start(struct eventtimer *et, sbintime_t first, sbintime_t period) | ||||
t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; | t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; | ||||
if (period != 0) { | if (period != 0) { | ||||
t->mode = TIMER_PERIODIC; | t->mode = TIMER_PERIODIC; | ||||
t->div = (sc->freq * period) >> 32; | t->div = (sc->freq * period) >> 32; | ||||
} else { | } else { | ||||
t->mode = TIMER_ONESHOT; | t->mode = TIMER_ONESHOT; | ||||
t->div = 0; | t->div = 0; | ||||
} | } | ||||
if (first != 0) | KASSERT(!t->nmi || t->mode == TIMER_ONESHOT, | ||||
("NMI timer started in periodic mode")); | |||||
if (first != 0) { | |||||
fdiv = (sc->freq * first) >> 32; | fdiv = (sc->freq * first) >> 32; | ||||
else | /* Save calculated timeout for the sake of resume. */ | ||||
if (t->nmi) | |||||
t->div = fdiv; | |||||
} else { | |||||
fdiv = t->div; | fdiv = t->div; | ||||
} | |||||
if (t->irq < 0) | if (t->irq < 0) | ||||
bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num); | bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num); | ||||
t->caps |= HPET_TCNF_INT_ENB; | t->caps |= HPET_TCNF_INT_ENB; | ||||
now = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); | now = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); | ||||
restart: | restart: | ||||
t->next = now + fdiv; | t->next = now + fdiv; | ||||
if (t->mode == TIMER_PERIODIC && (t->caps & HPET_TCAP_PER_INT)) { | if (t->mode == TIMER_PERIODIC && (t->caps & HPET_TCAP_PER_INT)) { | ||||
t->caps |= HPET_TCNF_TYPE; | t->caps |= HPET_TCNF_TYPE; | ||||
Show All 27 Lines | hpet_stop(struct eventtimer *et) | ||||
t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; | t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; | ||||
t->mode = TIMER_STOPPED; | t->mode = TIMER_STOPPED; | ||||
t->caps &= ~(HPET_TCNF_INT_ENB | HPET_TCNF_TYPE); | t->caps &= ~(HPET_TCNF_INT_ENB | 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_CAP_CNF(t->num), t->caps); | ||||
return (0); | return (0); | ||||
} | } | ||||
#if defined(DEV_APIC) | |||||
static int | static int | ||||
hpet_fsb_setup(struct hpet_timer *t) | |||||
{ | |||||
struct hpet_softc *sc = t->sc; | |||||
uint64_t addr; | |||||
uint32_t data; | |||||
int err; | |||||
err = PCIB_MAP_MSI(device_get_parent(device_get_parent(sc->dev)), | |||||
sc->dev, t->irq, &addr, &data); | |||||
if (err != 0) | |||||
return (err); | |||||
/* | |||||
* If NMI mode is disabled, then use the returned values as is. | |||||
* Otherwise, directly configure NMI delivery mode. | |||||
* Destination ID is either set to BSP or to broadcast. | |||||
* This code has some direct knowledge of MSI internals. | |||||
*/ | |||||
if (t->nmi) { | |||||
data = IOART_TRGREDG; | |||||
data |= amd_intr_delmode_bug ? IOART_DELRSV1 : IOART_DELNMI; | |||||
#ifdef SMP | |||||
addr &= ~0x000ff000u; | |||||
addr |= (nmi_is_broadcast ? 0xff : boot_cpu_id) << 12; | |||||
#endif | |||||
} | |||||
bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(t->num), addr); | |||||
bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(t->num), data); | |||||
kib: This requires coordination with the interrupt remapping code in the dmar driver. | |||||
Not Done Inline ActionsYeah, I guess. As I noted in the request message this code directly messes with MSI internals. avg: Yeah, I guess. As I noted in the request message this code directly messes with MSI internals. | |||||
return (0); | |||||
} | |||||
#endif | |||||
static int | |||||
hpet_set_nmi_mode(struct eventtimer *et, boolean_t enable) | |||||
{ | |||||
#if defined(DEV_APIC) | |||||
struct hpet_timer *mt = (struct hpet_timer *)et->et_priv; | |||||
struct hpet_softc *sc = mt->sc; | |||||
struct hpet_timer *t; | |||||
int err; | |||||
t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; | |||||
if ((t->caps & HPET_TCNF_FSB_EN) == 0) | |||||
return (ENOTSUP); | |||||
if (t->nmi == enable) | |||||
return (0); | |||||
t->nmi = enable; | |||||
err = hpet_fsb_setup(t); | |||||
if (err != 0) | |||||
t->nmi = !enable; | |||||
return (err); | |||||
#else /* DEV_APIC */ | |||||
return (ENOTSUP); | |||||
#endif /* DEV_APIC */ | |||||
} | |||||
static int | |||||
hpet_check_nmi(struct eventtimer *et) | |||||
{ | |||||
#if defined(DEV_APIC) | |||||
struct hpet_timer *mt = (struct hpet_timer *)et->et_priv; | |||||
struct hpet_softc *sc = mt->sc; | |||||
struct hpet_timer *t; | |||||
uint32_t val; | |||||
t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; | |||||
if ((t->caps & HPET_TCNF_FSB_EN) == 0) | |||||
return (ENOTSUP); | |||||
if (!t->nmi) | |||||
return (ENOENT); | |||||
val = bus_read_4(sc->mem_res, HPET_ISR); | |||||
if ((val & (1 << t->num)) == 0) | |||||
return (ENOENT); | |||||
/* | |||||
* Clear the interrupt stats bit as well as interrupt enable bit | |||||
* to avoid another NMI after the counter wrap-around. | |||||
* With a 32-bit counter and a typical frequency the wrap-around | |||||
* happens in less than 5 minutes. | |||||
*/ | |||||
bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num); | |||||
t->caps &= ~HPET_TCNF_INT_ENB; | |||||
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps); | |||||
return (0); | |||||
#else /* DEV_APIC */ | |||||
return (ENOTSUP); | |||||
#endif /* DEV_APIC */ | |||||
} | |||||
static int | |||||
hpet_intr_single(void *arg) | hpet_intr_single(void *arg) | ||||
{ | { | ||||
struct hpet_timer *t = (struct hpet_timer *)arg; | struct hpet_timer *t = (struct hpet_timer *)arg; | ||||
struct hpet_timer *mt; | struct hpet_timer *mt; | ||||
struct hpet_softc *sc = t->sc; | struct hpet_softc *sc = t->sc; | ||||
uint32_t now; | uint32_t now; | ||||
if (t->mode == TIMER_STOPPED) | if (t->mode == TIMER_STOPPED) | ||||
▲ Show 20 Lines • Show All 464 Lines • ▼ Show 20 Lines | for (i = 0; i < num_timers; i++) { | ||||
t->caps &= ~(HPET_TCNF_VAL_SET | HPET_TCNF_INT_ENB); | t->caps &= ~(HPET_TCNF_VAL_SET | HPET_TCNF_INT_ENB); | ||||
t->caps &= ~(HPET_TCNF_INT_TYPE); | t->caps &= ~(HPET_TCNF_INT_TYPE); | ||||
t->caps |= HPET_TCNF_32MODE; | t->caps |= HPET_TCNF_32MODE; | ||||
if (t->irq >= 0 && sc->legacy_route && i < 2) { | if (t->irq >= 0 && sc->legacy_route && i < 2) { | ||||
/* Legacy route doesn't need more configuration. */ | /* Legacy route doesn't need more configuration. */ | ||||
} else | } else | ||||
#ifdef DEV_APIC | #ifdef DEV_APIC | ||||
if ((t->caps & HPET_TCAP_FSB_INT_DEL) && t->irq >= 0) { | if ((t->caps & HPET_TCAP_FSB_INT_DEL) && t->irq >= 0) { | ||||
uint64_t addr; | if (hpet_fsb_setup(t) == 0) | ||||
uint32_t data; | |||||
if (PCIB_MAP_MSI( | |||||
device_get_parent(device_get_parent(dev)), dev, | |||||
t->irq, &addr, &data) == 0) { | |||||
bus_write_4(sc->mem_res, | |||||
HPET_TIMER_FSB_ADDR(i), addr); | |||||
bus_write_4(sc->mem_res, | |||||
HPET_TIMER_FSB_VAL(i), data); | |||||
t->caps |= HPET_TCNF_FSB_EN; | t->caps |= HPET_TCNF_FSB_EN; | ||||
} else | else | ||||
t->irq = -2; | t->irq = -2; | ||||
} else | } else | ||||
#endif | #endif | ||||
if (t->irq >= 0) | if (t->irq >= 0) | ||||
t->caps |= (t->irq << 9); | t->caps |= (t->irq << 9); | ||||
else if (sc->irq >= 0 && (t->vectors & (1 << sc->irq))) | else if (sc->irq >= 0 && (t->vectors & (1 << sc->irq))) | ||||
t->caps |= (sc->irq << 9) | HPET_TCNF_INT_TYPE; | t->caps |= (sc->irq << 9) | HPET_TCNF_INT_TYPE; | ||||
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), t->caps); | bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), t->caps); | ||||
/* Skip event timers without set up IRQ. */ | /* Skip event timers without set up IRQ. */ | ||||
if (t->irq < 0 && | if (t->irq < 0 && | ||||
(sc->irq < 0 || (t->vectors & (1 << sc->irq)) == 0)) | (sc->irq < 0 || (t->vectors & (1 << sc->irq)) == 0)) | ||||
continue; | continue; | ||||
/* Announce the reset. */ | /* Announce the reset. */ | ||||
if (maxhpetet == 0) | if (maxhpetet == 0) | ||||
t->et.et_name = "HPET"; | t->et.et_name = "HPET"; | ||||
else { | else { | ||||
sprintf(t->name, "HPET%d", maxhpetet); | sprintf(t->name, "HPET%d", maxhpetet); | ||||
t->et.et_name = t->name; | t->et.et_name = t->name; | ||||
} | } | ||||
t->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT; | t->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT; | ||||
#if defined(DEV_APIC) | |||||
if ((t->caps & HPET_TCNF_FSB_EN) != 0) | |||||
t->et.et_flags |= ET_FLAGS_NMI; | |||||
#endif | |||||
t->et.et_quality = 450; | t->et.et_quality = 450; | ||||
if (t->pcpu_master >= 0) { | if (t->pcpu_master >= 0) { | ||||
t->et.et_flags |= ET_FLAGS_PERCPU; | t->et.et_flags |= ET_FLAGS_PERCPU; | ||||
t->et.et_quality += 100; | t->et.et_quality += 100; | ||||
} else if (mp_ncpus >= 8) | } else if (mp_ncpus >= 8) | ||||
t->et.et_quality -= 100; | t->et.et_quality -= 100; | ||||
if ((t->caps & HPET_TCAP_PER_INT) == 0) | if ((t->caps & HPET_TCAP_PER_INT) == 0) | ||||
t->et.et_quality -= 10; | t->et.et_quality -= 10; | ||||
t->et.et_frequency = sc->freq; | t->et.et_frequency = sc->freq; | ||||
t->et.et_min_period = | t->et.et_min_period = | ||||
((uint64_t)(HPET_MIN_CYCLES * 2) << 32) / sc->freq; | ((uint64_t)(HPET_MIN_CYCLES * 2) << 32) / sc->freq; | ||||
t->et.et_max_period = (0xfffffffeLLU << 32) / sc->freq; | t->et.et_max_period = (0xfffffffeLLU << 32) / sc->freq; | ||||
t->et.et_start = hpet_start; | t->et.et_start = hpet_start; | ||||
t->et.et_stop = hpet_stop; | t->et.et_stop = hpet_stop; | ||||
t->et.et_set_nmi_mode = hpet_set_nmi_mode; | |||||
t->et.et_check_nmi = hpet_check_nmi; | |||||
t->et.et_priv = &sc->t[i]; | t->et.et_priv = &sc->t[i]; | ||||
if (t->pcpu_master < 0 || t->pcpu_master == i) { | if (t->pcpu_master < 0 || t->pcpu_master == i) { | ||||
et_register(&t->et); | et_register(&t->et); | ||||
maxhpetet++; | maxhpetet++; | ||||
} | } | ||||
} | } | ||||
acpi_GetInteger(sc->handle, "_UID", &sc->acpi_uid); | acpi_GetInteger(sc->handle, "_UID", &sc->acpi_uid); | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | hpet_resume(device_t dev) | ||||
/* Re-enable the timer after a resume to keep the clock advancing. */ | /* Re-enable the timer after a resume to keep the clock advancing. */ | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
hpet_enable(sc); | hpet_enable(sc); | ||||
/* Restart event timers that were running on suspend. */ | /* Restart event timers that were running on suspend. */ | ||||
for (i = 0; i < sc->num_timers; i++) { | for (i = 0; i < sc->num_timers; i++) { | ||||
t = &sc->t[i]; | t = &sc->t[i]; | ||||
#ifdef DEV_APIC | #ifdef DEV_APIC | ||||
if (t->irq >= 0 && (sc->legacy_route == 0 || i >= 2)) { | if ((t->caps & HPET_TCNF_FSB_EN) != 0) | ||||
uint64_t addr; | (void)hpet_fsb_setup(t); | ||||
uint32_t data; | |||||
if (PCIB_MAP_MSI( | |||||
device_get_parent(device_get_parent(dev)), dev, | |||||
t->irq, &addr, &data) == 0) { | |||||
bus_write_4(sc->mem_res, | |||||
HPET_TIMER_FSB_ADDR(i), addr); | |||||
bus_write_4(sc->mem_res, | |||||
HPET_TIMER_FSB_VAL(i), data); | |||||
} | |||||
} | |||||
#endif | #endif | ||||
if (t->mode == TIMER_STOPPED) | if (t->mode == TIMER_STOPPED) | ||||
continue; | continue; | ||||
t->next = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); | t->next = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); | ||||
if (t->mode == TIMER_PERIODIC && | if (t->mode == TIMER_PERIODIC && | ||||
(t->caps & HPET_TCAP_PER_INT) != 0) { | (t->caps & HPET_TCAP_PER_INT) != 0) { | ||||
t->caps |= HPET_TCNF_TYPE; | t->caps |= HPET_TCNF_TYPE; | ||||
t->next += t->div; | t->next += t->div; | ||||
bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), | bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), | ||||
t->caps | HPET_TCNF_VAL_SET); | t->caps | HPET_TCNF_VAL_SET); | ||||
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), | bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), | ||||
t->next); | t->next); | ||||
bus_read_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num)); | bus_read_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num)); | ||||
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), | bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), | ||||
t->div); | t->div); | ||||
} else { | } else { | ||||
t->next += sc->freq / 1024; | t->next += t->nmi ? t->div : sc->freq / 1024; | ||||
bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), | bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), | ||||
t->next); | t->next); | ||||
} | } | ||||
bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num); | 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); | bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps); | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
Show All 28 Lines | |||||
} | } | ||||
#ifdef DEV_APIC | #ifdef DEV_APIC | ||||
static int | static int | ||||
hpet_remap_intr(device_t dev, device_t child, u_int irq) | hpet_remap_intr(device_t dev, device_t child, u_int irq) | ||||
{ | { | ||||
struct hpet_softc *sc = device_get_softc(dev); | struct hpet_softc *sc = device_get_softc(dev); | ||||
struct hpet_timer *t; | struct hpet_timer *t; | ||||
uint64_t addr; | |||||
uint32_t data; | |||||
int error, i; | int error, i; | ||||
for (i = 0; i < sc->num_timers; i++) { | for (i = 0; i < sc->num_timers; i++) { | ||||
t = &sc->t[i]; | t = &sc->t[i]; | ||||
if (t->irq != irq) | if (t->irq != irq) | ||||
continue; | continue; | ||||
error = PCIB_MAP_MSI( | |||||
device_get_parent(device_get_parent(dev)), dev, | |||||
irq, &addr, &data); | |||||
if (error) | |||||
return (error); | |||||
hpet_disable(sc); /* Stop timer to avoid interrupt loss. */ | hpet_disable(sc); /* Stop timer to avoid interrupt loss. */ | ||||
bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(i), addr); | error = hpet_fsb_setup(t); | ||||
bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(i), data); | |||||
hpet_enable(sc); | hpet_enable(sc); | ||||
return (0); | return (error); | ||||
} | } | ||||
return (ENOENT); | return (ENOENT); | ||||
} | } | ||||
#endif | #endif | ||||
static device_method_t hpet_methods[] = { | static device_method_t hpet_methods[] = { | ||||
/* Device interface */ | /* Device interface */ | ||||
DEVMETHOD(device_identify, hpet_identify), | DEVMETHOD(device_identify, hpet_identify), | ||||
Show All 21 Lines |
This requires coordination with the interrupt remapping code in the dmar driver.