diff --git a/sys/dev/acpica/acpi_hpet.c b/sys/dev/acpica/acpi_hpet.c index a98a826..18cb843 100644 --- a/sys/dev/acpica/acpi_hpet.c +++ b/sys/dev/acpica/acpi_hpet.c @@ -42,9 +42,14 @@ __FBSDID("$FreeBSD$"); #include #include +#include +#include + #define HPET_VENDID_AMD 0x4353 #define HPET_VENDID_INTEL 0x8086 +#define HPET_MAX_TIMER_CNT 8 + ACPI_SERIAL_DECL(hpet, "ACPI HPET support"); static devclass_t acpi_hpet_devclass; @@ -53,10 +58,28 @@ static devclass_t acpi_hpet_devclass; #define _COMPONENT ACPI_TIMER ACPI_MODULE_NAME("HPET") +struct acpi_hpet_softc; +struct hpet_timer +{ + struct acpi_hpet_softc *sc; + struct resource *irq_res; + driver_filter_t *irq_func; + void *irq_handle; + void *irq_cookie; + int irq_rid; + int id; + int periodic; + int running; +}; + struct acpi_hpet_softc { device_t dev; - struct resource *mem_res; ACPI_HANDLE handle; + struct resource *mem_res; + int mem_rid; + int ntimers; + uintmax_t freq; + struct hpet_timer timers[HPET_MAX_TIMER_CNT]; }; static u_int hpet_get_timecount(struct timecounter *tc); @@ -71,6 +94,8 @@ struct timecounter hpet_timecounter = { .tc_quality = 900, }; +static struct acpi_hpet_softc *the_hpet; + static u_int hpet_get_timecount(struct timecounter *tc) { @@ -86,7 +111,6 @@ hpet_enable(struct acpi_hpet_softc *sc) uint32_t val; val = bus_read_4(sc->mem_res, HPET_CONFIG); - val &= ~HPET_CNF_LEG_RT; val |= HPET_CNF_ENABLE; bus_write_4(sc->mem_res, HPET_CONFIG, val); } @@ -154,13 +178,250 @@ acpi_hpet_probe(device_t dev) return (0); } +#if 0 +static int +hpet_intr_fsb(void *arg) +{ + struct hpet_timer *timer = arg; + + timer->irq_func(timer->irq_cookie); + return (FILTER_HANDLED); +} +#endif + +static int +hpet_intr_pci(void *arg) +{ + struct hpet_timer *timer = arg; + uint32_t val; + + val = bus_read_4(timer->sc->mem_res, HPET_ISR); + val &= 1 << timer->id; + if (val) { + bus_write_4(timer->sc->mem_res, HPET_ISR, val); + timer->irq_func(timer->irq_cookie); + return (FILTER_HANDLED); + } + return (FILTER_STRAY); +} + +static void +acpi_hpet_timers_init(struct acpi_hpet_softc *sc) +{ + device_t dev = sc->dev; + struct hpet_timer *timer; + uint64_t addr; + uint32_t val; + uint32_t irq_cap; + uint32_t data; + int irq; + int err; + int i; + + for (i = 0; i < sc->ntimers; i++) { + timer = &sc->timers[i]; + timer->sc = sc; + timer->id = i; + timer->irq_rid = -1; + val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i)); + if ((val & HPET_TCNF_INT_ENB) != 0) { + /* Timers that have been started before us are marked permanently busy. */ + //timer->used = 1; + continue; + } + val |= HPET_TCNF_32MODE; /* set 32-bit mode XXX */ + val &= ~HPET_TCNF_TYPE; /* disable periodity by default */ + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), val); + timer->periodic = ((val & HPET_TCAP_PER_INT) != 0); + if ((val & HPET_TCAP_FSB_INT_DEL) != 0) { + if ((err = fsb_alloc(&irq)) != 0) { + device_printf(dev, "msi_alloc failed, err = %u\n", err); + } else if ((err = fsb_map(irq, &addr, &data)) != 0) { + device_printf(dev, "msi_map failed, err = %u\n", err); + fsb_release(irq); + } else { + timer->irq_rid = 1; + bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(i), data); + bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(i), (uint32_t)addr); + //val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i)); + val |= HPET_TCNF_FSB_EN; + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), val); + } + } + if (timer->irq_rid == -1) { + irq_cap = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i) + 4); + for (irq = 16 /*IOAPIC_ISA_INTS*/; irq < 32; irq++) { + if ((irq_cap & (1U << irq)) == 0) + continue; + timer->irq_rid = 0; + //val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i)); + val &= ~HPET_TCNF_INT_ROUTE; + val |= (uint32_t)irq << 9; + val |= HPET_TCNT_INT_TYPE; /* always level triggered */ + val &= ~HPET_TCNF_FSB_EN; /* disable MSI XXX */ + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), val); + break; + } + } + + if (timer->irq_rid == -1) + continue; + + err = bus_set_resource(dev, SYS_RES_IRQ, timer->irq_rid, irq, 1); + if (err != 0) { + device_printf(dev, "bus_set_resource failed, err = %d\n", err); + if (timer->irq_rid > 0) + fsb_release(irq); + timer->irq_rid = -1; + continue; + } + + timer->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &timer->irq_rid, RF_ACTIVE | RF_SHAREABLE); + if (timer->irq_res == NULL) { + device_printf(dev, "could not allocate interrupt\n"); + bus_delete_resource(dev, SYS_RES_IRQ, timer->irq_rid); + if (timer->irq_rid > 0) + fsb_release(irq); + timer->irq_rid = -1; + continue; + } + + if (timer->irq_rid > 0) + bus_bind_intr(dev, timer->irq_res, boot_cpu_id); + + if (bootverbose) + device_printf(dev, "timer %d using IRQ #%u\n", i, irq); + } +} + +uintmax_t hpet_get_freq(void); +int hpet_get_timer(driver_filter_t handler, void *cookie, int *timer_id); +int hpet_start_timer(int timer_id, uint32_t first, uint32_t period); +int hpet_stop_timer(int timer_id); +int hpet_release_timer(int timer_id); + +uintmax_t +hpet_get_freq() +{ + struct acpi_hpet_softc *sc = the_hpet; + + if (sc == NULL) + return (0); + return (sc->freq); +} + +int +hpet_get_timer(driver_filter_t handler, void *cookie, int *timer_id) +{ + struct acpi_hpet_softc *sc = the_hpet; + struct hpet_timer *timer; + int err, i; + + if (sc == NULL) + return (ENXIO); + for (i = 0; i < sc->ntimers; i++) { + timer = &sc->timers[i]; + if (timer->irq_rid == -1 || timer->irq_func != NULL) + continue; + if (cookie == NULL && timer->irq_rid == 0) + continue; + if ((err = bus_setup_intr(sc->dev, timer->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + timer->irq_rid > 0 ? handler : hpet_intr_pci, NULL, + timer->irq_rid > 0 ? cookie : timer, &timer->irq_handle)) != 0) { + device_printf(sc->dev, "could not set up interrupt\n"); + return (err); +#if 0 + bus_release_resource(sc->dev, SYS_RES_IRQ, timer->irq_rid, timer->irq_res); + bus_delete_resource(sc->dev, SYS_RES_IRQ, timer->irq_rid); + if (timer->irq_rid > 0) + fsb_release(irq); + timer->irq_rid = -1; + timer->irq_res = NULL; + continue; +#endif + } + sc->timers[i].irq_func = handler; + sc->timers[i].irq_cookie = cookie; + *timer_id = i; + return (0); + } + return (EBUSY); +} + +int +hpet_start_timer(int timer_id, uint32_t first, uint32_t period) +{ + struct acpi_hpet_softc *sc = the_hpet; + uint32_t cur; + uint32_t val; + + if (sc == NULL) + return (ENXIO); + if (sc->timers[timer_id].running) + hpet_stop_timer(timer_id); + if (period != 0) { + if (!sc->timers[timer_id].periodic) + return (EOPNOTSUPP); + if (first == 0) + first = period; + val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id)); + val |= HPET_TCNF_TYPE; + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id), val); + cur = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); + val |= HPET_TCNF_VAL_SET; /* setting period */ + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id), val); + bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(timer_id), cur + first); + bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(timer_id), period); + } else { + if (first == 0) + return (EINVAL); + cur = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); + bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(timer_id), cur + first); + } + + sc->timers[timer_id].running = 1; + val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id)); + val |= HPET_TCNF_INT_ENB; + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id), val); + printf("hpet timer started: timer_id = %u, first = %u, period = %u\n", timer_id, first, period); + return (0); +} + +int +hpet_stop_timer(int timer_id) +{ + struct acpi_hpet_softc *sc = the_hpet; + uint32_t val; + + if (sc == NULL) + return (ENXIO); + val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id)); + val &= ~HPET_TCNF_INT_ENB; /* disable intr */ + val &= ~HPET_TCNF_TYPE; /* disable periodity, just in case */ + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(timer_id), val); + sc->timers[timer_id].running = 0; + return (0); +} + +int +hpet_release_timer(int timer_id) +{ + struct acpi_hpet_softc *sc = the_hpet; + + if (sc == NULL) + return (ENXIO); + if (sc->timers[timer_id].running) + return (EBUSY); + sc->timers[timer_id].irq_func = NULL; + sc->timers[timer_id].irq_cookie = NULL; + return (0); +} + static int acpi_hpet_attach(device_t dev) { struct acpi_hpet_softc *sc; - int rid, num_timers; uint32_t val, val2; - uintmax_t freq; uint16_t vendor; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); @@ -169,8 +430,8 @@ acpi_hpet_attach(device_t dev) sc->dev = dev; sc->handle = acpi_get_handle(dev); - rid = 0; - sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + sc->mem_rid = 0; + sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); if (sc->mem_res == NULL) return (ENOMEM); @@ -183,6 +444,13 @@ acpi_hpet_attach(device_t dev) return (ENXIO); } + /* XXX Be sure that legacy mode was not enabled by BIOS. */ + val = bus_read_4(sc->mem_res, HPET_CONFIG); + if (val & HPET_CNF_LEG_RT) { + device_printf(dev, "timer is used in legacy replacement mode\n"); + return (ENXIO); + } + /* Be sure timer is enabled. */ hpet_enable(sc); @@ -195,26 +463,29 @@ acpi_hpet_attach(device_t dev) return (ENXIO); } - freq = (1000000000000000LL + val / 2) / val; - if (bootverbose) { - val = bus_read_4(sc->mem_res, HPET_CAPABILITIES); - - /* - * ATI/AMD violates IA-PC HPET (High Precision Event Timers) - * Specification and provides an off by one number - * of timers/comparators. - * Additionally, they use unregistered value in VENDOR_ID field. - */ - num_timers = 1 + ((val & HPET_CAP_NUM_TIM) >> 8); - vendor = val >> 16; - if (vendor == HPET_VENDID_AMD && num_timers > 0) - num_timers--; + sc->freq = (1000000000000000LL + val / 2) / val; + val = bus_read_4(sc->mem_res, HPET_CAPABILITIES); + + /* + * ATI/AMD violates IA-PC HPET (High Precision Event Timers) + * Specification and provides an off by one number + * of timers/comparators. + * Additionally, they use unregistered value in VENDOR_ID field. + */ + sc->ntimers = 1 + ((val & HPET_CAP_NUM_TIM) >> 8); + vendor = val >> 16; + if (vendor == HPET_VENDID_AMD && sc->ntimers > 0) + sc->ntimers--; + if (bootverbose) device_printf(dev, "vend: 0x%x rev: 0x%x num: %d hz: %jd opts:%s%s\n", vendor, val & HPET_CAP_REV_ID, - num_timers, freq, + sc->ntimers, sc->freq, (val & HPET_CAP_LEG_RT) ? " legacy_route" : "", (val & HPET_CAP_COUNT_SIZE) ? " 64-bit" : ""); + if (sc->ntimers > HPET_MAX_TIMER_CNT) { + sc->ntimers = HPET_MAX_TIMER_CNT; + device_printf(dev, "using only %d timers\n", sc->ntimers); } if (testenv("debug.acpi.hpet_test")) @@ -234,10 +505,13 @@ acpi_hpet_attach(device_t dev) return (ENXIO); } - hpet_timecounter.tc_frequency = freq; + acpi_hpet_timers_init(sc); + + hpet_timecounter.tc_frequency = sc->freq; hpet_timecounter.tc_priv = sc; tc_init(&hpet_timecounter); + the_hpet = sc; return (0); } @@ -248,6 +522,40 @@ acpi_hpet_detach(device_t dev) /* XXX Without a tc_remove() function, we can't detach. */ return (EBUSY); +#if 0 + struct acpi_hpet_softc *sc; + uint32_t val; + + sc = device_get_softc(dev); + hpet_disable(sc); + if (sc->timerid != -1) { + val = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(sc->timerid)); + val &= ~HPET_TCNF_INT_ENB; + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(sc->timerid), val); + } + if (sc->irq_res != NULL) { + if (sc->irq_handle != NULL) { + bus_teardown_intr(dev, sc->irq_res, sc->irq_handle); + sc->irq_handle = NULL; + } + bus_deactivate_resource(dev, SYS_RES_IRQ, sc->irq_rid, + sc->irq_res); + bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, + sc->irq_res); + sc->irq_res = NULL; + } + if (sc->fsb_irq > 0) + fsb_release(sc->fsb_irq); + if (sc->mem_res != NULL) { + bus_deactivate_resource(dev, SYS_RES_MEMORY, sc->mem_rid, + sc->mem_res); + bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, + sc->mem_res); + sc->mem_res = NULL; + } + + return (0); +#endif } static int