diff options
-rw-r--r-- | sys/amd64/amd64/mp_machdep.c | 3 | ||||
-rw-r--r-- | sys/amd64/include/apicvar.h | 7 | ||||
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/conf/files.amd64 | 1 | ||||
-rw-r--r-- | sys/conf/files.i386 | 1 | ||||
-rw-r--r-- | sys/dev/acpica/acpi_hpet.c | 519 | ||||
-rw-r--r-- | sys/dev/acpica/acpi_hpet.h | 2 | ||||
-rw-r--r-- | sys/i386/i386/mp_machdep.c | 3 | ||||
-rw-r--r-- | sys/i386/include/apicvar.h | 7 | ||||
-rw-r--r-- | sys/isa/rtc.h | 4 | ||||
-rw-r--r-- | sys/kern/kern_clock.c | 12 | ||||
-rw-r--r-- | sys/kern/kern_et.c | 232 | ||||
-rw-r--r-- | sys/pc98/cbus/clock.c | 61 | ||||
-rw-r--r-- | sys/sys/systm.h | 2 | ||||
-rw-r--r-- | sys/sys/timeet.h | 103 | ||||
-rw-r--r-- | sys/x86/isa/atrtc.c | 143 | ||||
-rw-r--r-- | sys/x86/isa/clock.c | 334 | ||||
-rw-r--r-- | sys/x86/x86/local_apic.c | 213 | ||||
-rw-r--r-- | sys/x86/x86/timeevents.c | 509 |
19 files changed, 1662 insertions, 495 deletions
diff --git a/sys/amd64/amd64/mp_machdep.c b/sys/amd64/amd64/mp_machdep.c index 0de3549..fa26c7e 100644 --- a/sys/amd64/amd64/mp_machdep.c +++ b/sys/amd64/amd64/mp_machdep.c @@ -706,10 +706,11 @@ init_secondary(void) load_es(_udatasel); load_fs(_ufssel); mtx_unlock_spin(&ap_boot_mtx); - /* wait until all the AP's are up */ while (smp_started == 0) ia32_pause(); + /* Start per-CPU event timers. */ + cpu_initclocks_ap(); sched_throw(NULL); diff --git a/sys/amd64/include/apicvar.h b/sys/amd64/include/apicvar.h index 4968842..2ebf7c2 100644 --- a/sys/amd64/include/apicvar.h +++ b/sys/amd64/include/apicvar.h @@ -158,12 +158,6 @@ #define APIC_BUS_PCI 2 #define APIC_BUS_MAX APIC_BUS_PCI -enum lapic_clock { - LAPIC_CLOCK_NONE, - LAPIC_CLOCK_HARDCLOCK, - LAPIC_CLOCK_ALL -}; - /* * An APIC enumerator is a psuedo bus driver that enumerates APIC's including * CPU's and I/O APIC's. @@ -234,7 +228,6 @@ int lapic_set_lvt_triggermode(u_int apic_id, u_int lvt, enum intr_trigger trigger); void lapic_set_tpr(u_int vector); void lapic_setup(int boot); -enum lapic_clock lapic_setup_clock(enum lapic_clock srcsdes); #endif /* !LOCORE */ #endif /* _MACHINE_APICVAR_H_ */ diff --git a/sys/conf/files b/sys/conf/files index a749e11..8949dd7 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2085,6 +2085,7 @@ kern/kern_context.c standard kern/kern_descrip.c standard kern/kern_dtrace.c optional kdtrace_hooks kern/kern_environment.c standard +kern/kern_et.c standard kern/kern_event.c standard kern/kern_exec.c standard kern/kern_exit.c standard diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 index 194b1aa..c0636a9 100644 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -297,6 +297,7 @@ x86/cpufreq/p4tcc.c optional cpufreq x86/isa/atpic.c optional atpic isa x86/isa/atrtc.c standard x86/isa/clock.c standard +x86/x86/timeevents.c standard x86/isa/elcr.c standard x86/isa/isa.c standard x86/isa/isa_dma.c standard diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 index 1b5c6d7..9cb19ea 100644 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -381,6 +381,7 @@ x86/cpufreq/smist.c optional cpufreq x86/isa/atpic.c optional atpic x86/isa/atrtc.c optional atpic x86/isa/clock.c optional native +x86/x86/timeevents.c standard x86/isa/elcr.c standard x86/isa/isa.c optional isa x86/isa/isa_dma.c optional isa diff --git a/sys/dev/acpica/acpi_hpet.c b/sys/dev/acpica/acpi_hpet.c index b3f666f..69da765 100644 --- a/sys/dev/acpica/acpi_hpet.c +++ b/sys/dev/acpica/acpi_hpet.c @@ -28,12 +28,21 @@ __FBSDID("$FreeBSD$"); #include "opt_acpi.h" +#ifdef __amd64__ +#define DEV_APIC +#else +#include "opt_apic.h" +#endif #include <sys/param.h> #include <sys/bus.h> #include <sys/kernel.h> #include <sys/module.h> +#include <sys/proc.h> #include <sys/rman.h> #include <sys/time.h> +#include <sys/smp.h> +#include <sys/sysctl.h> +#include <sys/timeet.h> #include <sys/timetc.h> #include <contrib/dev/acpica/include/acpi.h> @@ -42,46 +51,69 @@ __FBSDID("$FreeBSD$"); #include <dev/acpica/acpivar.h> #include <dev/acpica/acpi_hpet.h> +#ifdef DEV_APIC +#include "pcib_if.h" +#endif + #define HPET_VENDID_AMD 0x4353 #define HPET_VENDID_INTEL 0x8086 ACPI_SERIAL_DECL(hpet, "ACPI HPET support"); -static devclass_t acpi_hpet_devclass; +static devclass_t hpet_devclass; /* ACPI CA debugging */ #define _COMPONENT ACPI_TIMER ACPI_MODULE_NAME("HPET") -struct acpi_hpet_softc { +struct hpet_softc { device_t dev; + int mem_rid; + int intr_rid; + int irq; + int useirq; struct resource *mem_res; + struct resource *intr_res; + void *intr_handle; ACPI_HANDLE handle; + uint64_t freq; + struct timecounter tc; + struct hpet_timer { + struct eventtimer et; + struct hpet_softc *sc; + int num; + int mode; + int intr_rid; + int irq; + int pcpu_master; + int pcpu_slaves[MAXCPU]; + struct resource *intr_res; + void *intr_handle; + uint32_t caps; + uint32_t vectors; + uint32_t div; + uint32_t last; + char name[8]; + } t[32]; + int num_timers; }; static u_int hpet_get_timecount(struct timecounter *tc); -static void acpi_hpet_test(struct acpi_hpet_softc *sc); +static void hpet_test(struct hpet_softc *sc); static char *hpet_ids[] = { "PNP0103", NULL }; -struct timecounter hpet_timecounter = { - .tc_get_timecount = hpet_get_timecount, - .tc_counter_mask = ~0u, - .tc_name = "HPET", - .tc_quality = 900, -}; - static u_int hpet_get_timecount(struct timecounter *tc) { - struct acpi_hpet_softc *sc; + struct hpet_softc *sc; sc = tc->tc_priv; return (bus_read_4(sc->mem_res, HPET_MAIN_COUNTER)); } static void -hpet_enable(struct acpi_hpet_softc *sc) +hpet_enable(struct hpet_softc *sc) { uint32_t val; @@ -92,7 +124,7 @@ hpet_enable(struct acpi_hpet_softc *sc) } static void -hpet_disable(struct acpi_hpet_softc *sc) +hpet_disable(struct hpet_softc *sc) { uint32_t val; @@ -101,8 +133,113 @@ hpet_disable(struct acpi_hpet_softc *sc) bus_write_4(sc->mem_res, HPET_CONFIG, val); } +static int +hpet_start(struct eventtimer *et, + struct bintime *first, struct bintime *period) +{ + struct hpet_timer *mt = (struct hpet_timer *)et->et_priv; + struct hpet_timer *t; + struct hpet_softc *sc = mt->sc; + uint32_t fdiv; + + t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; + if (period != NULL) { + t->mode = 1; + t->div = (sc->freq * (period->frac >> 32)) >> 32; + if (period->sec != 0) + t->div += sc->freq * period->sec; + if (first == NULL) + first = period; + } else { + t->mode = 2; + t->div = 0; + } + fdiv = (sc->freq * (first->frac >> 32)) >> 32; + if (first->sec != 0) + fdiv += sc->freq * first->sec; + t->last = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); + 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); + } else { + bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), + t->last + fdiv); + } + 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); +} + +static int +hpet_stop(struct eventtimer *et) +{ + struct hpet_timer *mt = (struct hpet_timer *)et->et_priv; + struct hpet_timer *t; + struct hpet_softc *sc = mt->sc; + + t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; + t->mode = 0; + t->caps &= ~(HPET_TCNF_INT_ENB | HPET_TCNF_TYPE); + bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps); + return (0); +} + +static int +hpet_intr_single(void *arg) +{ + struct hpet_timer *t = (struct hpet_timer *)arg; + struct hpet_timer *mt; + struct hpet_softc *sc = t->sc; + uint32_t now; + + if (t->mode == 1 && + (t->caps & HPET_TCAP_PER_INT) == 0) { + t->last += t->div; + now = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); + if ((int32_t)(now - (t->last + t->div / 2)) > 0) + t->last = now - t->div / 2; + bus_write_4(sc->mem_res, + HPET_TIMER_COMPARATOR(t->num), t->last + t->div); + } else if (t->mode == 2) + t->mode = 0; + mt = (t->pcpu_master < 0) ? t : &sc->t[t->pcpu_master]; + if (mt->et.et_active) { + mt->et.et_event_cb(&mt->et, + mt->et.et_arg ? mt->et.et_arg : curthread->td_intr_frame); + } + return (FILTER_HANDLED); +} + +static int +hpet_intr(void *arg) +{ + struct hpet_softc *sc = (struct hpet_softc *)arg; + int i; + uint32_t val; + + val = bus_read_4(sc->mem_res, HPET_ISR); + if (val) { + bus_write_4(sc->mem_res, HPET_ISR, val); + val &= sc->useirq; + for (i = 0; i < sc->num_timers; i++) { + if ((val & (1 << i)) == 0) + continue; + hpet_intr_single(&sc->t[i]); + } + return (FILTER_HANDLED); + } + return (FILTER_STRAY); +} + static ACPI_STATUS -acpi_hpet_find(ACPI_HANDLE handle, UINT32 level, void *context, +hpet_find(ACPI_HANDLE handle, UINT32 level, void *context, void **status) { char **ids; @@ -115,16 +252,15 @@ acpi_hpet_find(ACPI_HANDLE handle, UINT32 level, void *context, } if (*ids == NULL) return (AE_OK); - if (ACPI_FAILURE(acpi_GetInteger(handle, "_UID", &uid))) - uid = 0; - if (id == uid) + if (ACPI_FAILURE(acpi_GetInteger(handle, "_UID", &uid)) || + id == uid) *((int *)status) = 1; return (AE_OK); } /* Discover the HPET via the ACPI table of the same name. */ static void -acpi_hpet_identify(driver_t *driver, device_t parent) +hpet_identify(driver_t *driver, device_t parent) { ACPI_TABLE_HPET *hpet; ACPI_STATUS status; @@ -132,7 +268,7 @@ acpi_hpet_identify(driver_t *driver, device_t parent) int i, found; /* Only one HPET device can be added. */ - if (devclass_get_device(acpi_hpet_devclass, 0)) + if (devclass_get_device(hpet_devclass, 0)) return; for (i = 1; ; i++) { /* Search for HPET table. */ @@ -142,12 +278,12 @@ acpi_hpet_identify(driver_t *driver, device_t parent) /* Search for HPET device with same ID. */ found = 0; AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - 100, acpi_hpet_find, NULL, (void *)(uintptr_t)hpet->Sequence, (void *)&found); + 100, hpet_find, NULL, (void *)(uintptr_t)hpet->Sequence, (void *)&found); /* If found - let it be probed in normal way. */ if (found) continue; /* If not - create it from table info. */ - child = BUS_ADD_CHILD(parent, ACPI_DEV_BASE_ORDER, "acpi_hpet", 0); + child = BUS_ADD_CHILD(parent, ACPI_DEV_BASE_ORDER, "hpet", 0); if (child == NULL) { printf("%s: can't add child\n", __func__); continue; @@ -158,7 +294,7 @@ acpi_hpet_identify(driver_t *driver, device_t parent) } static int -acpi_hpet_probe(device_t dev) +hpet_probe(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); @@ -173,13 +309,15 @@ acpi_hpet_probe(device_t dev) } static int -acpi_hpet_attach(device_t dev) +hpet_attach(device_t dev) { - struct acpi_hpet_softc *sc; - int rid, num_timers; - uint32_t val, val2; - uintmax_t freq; - uint16_t vendor; + struct hpet_softc *sc; + struct hpet_timer *t; + 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; + uint16_t vendor, rev; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); @@ -187,8 +325,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); @@ -213,31 +351,53 @@ acpi_hpet_attach(device_t dev) return (ENXIO); } - freq = (1000000000000000LL + val / 2) / val; + sc->freq = (1000000000000000LL + val / 2) / val; + val = bus_read_4(sc->mem_res, HPET_CAPABILITIES); + vendor = (val & HPET_CAP_VENDOR_ID) >> 16; + rev = val & HPET_CAP_REV_ID; + num_timers = 1 + ((val & HPET_CAP_NUM_TIM) >> 8); + /* + * 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. + */ + if (vendor == HPET_VENDID_AMD && rev < 0x10 && num_timers > 0) + num_timers--; + sc->num_timers = num_timers; 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--; 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, - (val & HPET_CAP_LEG_RT) ? " legacy_route" : "", - (val & HPET_CAP_COUNT_SIZE) ? " 64-bit" : ""); + "vendor 0x%x, rev 0x%x, %jdHz%s, %d timers,%s\n", + vendor, rev, + sc->freq, (val & HPET_CAP_COUNT_SIZE) ? " 64bit" : "", + num_timers, (val & HPET_CAP_LEG_RT) ? " legacy route" : ""); + } + num_msi = 0; + for (i = 0; i < num_timers; i++) { + t = &sc->t[i]; + t->sc = sc; + t->num = i; + t->mode = 0; + t->intr_rid = -1; + t->irq = -1; + t->pcpu_master = -1; + t->caps = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i)); + t->vectors = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i) + 4); + if (bootverbose) { + device_printf(dev, + " t%d: irqs 0x%08x (%d)%s%s%s\n", i, + t->vectors, (t->caps & HPET_TCNF_INT_ROUTE) >> 9, + (t->caps & HPET_TCAP_FSB_INT_DEL) ? ", MSI" : "", + (t->caps & HPET_TCAP_SIZE) ? ", 64bit" : "", + (t->caps & HPET_TCAP_PER_INT) ? ", periodic" : ""); + } +#ifdef DEV_APIC + if (t->caps & HPET_TCAP_FSB_INT_DEL) + num_msi++; +#endif } - if (testenv("debug.acpi.hpet_test")) - acpi_hpet_test(sc); - + hpet_test(sc); /* * Don't attach if the timer never increments. Since the spec * requires it to be at least 10 MHz, it has to change in 1 us. @@ -253,15 +413,154 @@ acpi_hpet_attach(device_t dev) } /* Announce first HPET as timecounter. */ if (device_get_unit(dev) == 0) { - hpet_timecounter.tc_frequency = freq; - hpet_timecounter.tc_priv = sc; - tc_init(&hpet_timecounter); + sc->tc.tc_get_timecount = hpet_get_timecount, + sc->tc.tc_counter_mask = ~0u, + sc->tc.tc_name = "HPET", + sc->tc.tc_quality = 900, + sc->tc.tc_frequency = sc->freq; + sc->tc.tc_priv = sc; + tc_init(&sc->tc); + } + /* If not disabled - setup and announce event timers. */ + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "clock", &i) == 0 && i == 0) + return (0); + num_percpu_et = min(num_msi / mp_ncpus, 2); + num_percpu_t = num_percpu_et * mp_ncpus; + cur_cpu = CPU_FIRST(); + pcpu_master = 0; + /* Find common legacy IRQ vectors for all timers. */ + cvectors = 0xffff0000; + /* + * HPETs in AMD chipsets before SB800 have problems with IRQs >= 16 + * Lower are also not always working for different reasons. + * SB800 fixed it, but seems do not implements level triggering + * properly, that makes it very unreliable - it freezes after any + * interrupt loss. Avoid legacy IRQs for AMD. + */ + if (vendor == HPET_VENDID_AMD) + cvectors = 0x00000000; + sc->useirq = 0; + for (i = 0; i < num_timers; i++) { + t = &sc->t[i]; +#ifdef DEV_APIC + if (t->caps & HPET_TCAP_FSB_INT_DEL) { + if ((j = PCIB_ALLOC_MSIX( + device_get_parent(device_get_parent(dev)), dev, + &t->irq))) { + device_printf(dev, + "Can't allocate interrupt: %d.\n", j); + } else if (!(t->intr_res = + bus_alloc_resource(dev, SYS_RES_IRQ, &t->intr_rid, + t->irq, t->irq, 1, RF_SHAREABLE | RF_ACTIVE))) { + device_printf(dev, "Can't map interrupt.\n"); + } else if ((bus_setup_intr(dev, t->intr_res, + INTR_MPSAFE | INTR_TYPE_CLK, + (driver_filter_t *)hpet_intr_single, NULL, + t, &t->intr_handle))) { + device_printf(dev, "Can't setup interrupt.\n"); + } else { + bus_describe_intr(dev, t->intr_res, + t->intr_handle, "t%d", i); + num_msi++; + if (num_percpu_t > 0) { + if (cur_cpu == CPU_FIRST()) + pcpu_master = i; + t->pcpu_master = pcpu_master; + sc->t[pcpu_master]. + pcpu_slaves[cur_cpu] = i; + bus_bind_intr(dev, t->intr_res, cur_cpu); + cur_cpu = CPU_NEXT(cur_cpu); + num_percpu_t--; + } + } + } else +#endif + if ((cvectors & t->vectors) != 0) { + cvectors &= t->vectors; + sc->useirq |= (1 << i); + } + } + bus_write_4(sc->mem_res, HPET_ISR, 0xffffffff); + sc->irq = -1; + sc->intr_rid = -1; + /* If at least one timer needs legacy IRQ - setup it. */ + if (sc->useirq) { + j = i = fls(cvectors) - 1; + while (j > 0 && (cvectors & (1 << (j - 1))) != 0) + j--; + if (!(sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ, + &sc->intr_rid, j, i, 1, RF_SHAREABLE | RF_ACTIVE))) + device_printf(dev,"Can't map interrupt.\n"); + else if ((bus_setup_intr(dev, sc->intr_res, + INTR_MPSAFE | INTR_TYPE_CLK, + (driver_filter_t *)hpet_intr, NULL, + sc, &sc->intr_handle))) { + device_printf(dev, "Can't setup interrupt.\n"); + } else { + sc->irq = rman_get_start(sc->intr_res); + /* Bind IRQ to BSP to avoid live migration. */ + bus_bind_intr(dev, sc->intr_res, CPU_FIRST()); + } + } + /* Program and announce event timers. */ + for (i = 0; i < num_timers; i++) { + t = &sc->t[i]; + t->caps &= ~(HPET_TCNF_FSB_EN | HPET_TCNF_INT_ROUTE); + t->caps &= ~(HPET_TCNF_VAL_SET | HPET_TCNF_INT_ENB); + t->caps |= HPET_TCNF_32MODE; +#ifdef DEV_APIC + if (t->irq >= 0) { + uint64_t addr; + 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; + } else + t->irq = -2; + } else +#endif + 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. */ + if (t->irq < 0 && + (sc->irq < 0 || (t->vectors & (1 << sc->irq)) == 0)) + continue; + /* Announce the reset. */ + if (maxhpetet == 0) + t->et.et_name = "HPET"; + else { + sprintf(t->name, "HPET%d", maxhpetet); + t->et.et_name = t->name; + } + t->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT; + t->et.et_quality = 450; + if (t->pcpu_master >= 0) { + t->et.et_flags |= ET_FLAGS_PERCPU; + t->et.et_quality += 100; + } + if ((t->caps & HPET_TCAP_PER_INT) == 0) + t->et.et_quality -= 10; + t->et.et_frequency = sc->freq; + t->et.et_start = hpet_start; + t->et.et_stop = hpet_stop; + t->et.et_priv = &sc->t[i]; + if (t->pcpu_master < 0 || t->pcpu_master == i) { + et_register(&t->et); + maxhpetet++; + } } return (0); } static int -acpi_hpet_detach(device_t dev) +hpet_detach(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); @@ -270,9 +569,9 @@ acpi_hpet_detach(device_t dev) } static int -acpi_hpet_suspend(device_t dev) +hpet_suspend(device_t dev) { - struct acpi_hpet_softc *sc; + struct hpet_softc *sc; /* * Disable the timer during suspend. The timer will not lose @@ -286,20 +585,58 @@ acpi_hpet_suspend(device_t dev) } static int -acpi_hpet_resume(device_t dev) +hpet_resume(device_t dev) { - struct acpi_hpet_softc *sc; + struct hpet_softc *sc; + struct hpet_timer *t; + int i; /* Re-enable the timer after a resume to keep the clock advancing. */ sc = device_get_softc(dev); hpet_enable(sc); - + /* Restart event timers that were running on suspend. */ + for (i = 0; i < sc->num_timers; i++) { + t = &sc->t[i]; +#ifdef DEV_APIC + if (t->irq >= 0) { + uint64_t addr; + 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 + if (t->mode == 0) + continue; + t->last = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); + 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 + t->div); + bus_read_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num)); + 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 + sc->freq / 1024); + } + 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); } /* Print some basic latency/rate information to assist in debugging. */ static void -acpi_hpet_test(struct acpi_hpet_softc *sc) +hpet_test(struct hpet_softc *sc) { int i; uint32_t u1, u2; @@ -326,24 +663,56 @@ acpi_hpet_test(struct acpi_hpet_softc *sc) device_printf(sc->dev, "time per call: %ld ns\n", ts.tv_nsec / 1000); } -static device_method_t acpi_hpet_methods[] = { +#ifdef DEV_APIC +static int +hpet_remap_intr(device_t dev, device_t child, u_int irq) +{ + struct hpet_softc *sc = device_get_softc(dev); + struct hpet_timer *t; + uint64_t addr; + uint32_t data; + int error, i; + + for (i = 0; i < sc->num_timers; i++) { + t = &sc->t[i]; + if (t->irq != irq) + 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. */ + bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(i), addr); + bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(i), data); + hpet_enable(sc); + return (0); + } + return (ENOENT); +} +#endif + +static device_method_t hpet_methods[] = { /* Device interface */ - DEVMETHOD(device_identify, acpi_hpet_identify), - DEVMETHOD(device_probe, acpi_hpet_probe), - DEVMETHOD(device_attach, acpi_hpet_attach), - DEVMETHOD(device_detach, acpi_hpet_detach), - DEVMETHOD(device_suspend, acpi_hpet_suspend), - DEVMETHOD(device_resume, acpi_hpet_resume), + DEVMETHOD(device_identify, hpet_identify), + DEVMETHOD(device_probe, hpet_probe), + DEVMETHOD(device_attach, hpet_attach), + DEVMETHOD(device_detach, hpet_detach), + DEVMETHOD(device_suspend, hpet_suspend), + DEVMETHOD(device_resume, hpet_resume), + +#ifdef DEV_APIC + DEVMETHOD(bus_remap_intr, hpet_remap_intr), +#endif {0, 0} }; -static driver_t acpi_hpet_driver = { - "acpi_hpet", - acpi_hpet_methods, - sizeof(struct acpi_hpet_softc), +static driver_t hpet_driver = { + "hpet", + hpet_methods, + sizeof(struct hpet_softc), }; - -DRIVER_MODULE(acpi_hpet, acpi, acpi_hpet_driver, acpi_hpet_devclass, 0, 0); -MODULE_DEPEND(acpi_hpet, acpi, 1, 1, 1); +DRIVER_MODULE(hpet, acpi, hpet_driver, hpet_devclass, 0, 0); +MODULE_DEPEND(hpet, acpi, 1, 1, 1); diff --git a/sys/dev/acpica/acpi_hpet.h b/sys/dev/acpica/acpi_hpet.h index 11372e1..8ca40cd 100644 --- a/sys/dev/acpica/acpi_hpet.h +++ b/sys/dev/acpica/acpi_hpet.h @@ -57,7 +57,7 @@ #define HPET_TCAP_PER_INT 0x00000010 /* Supports periodic interrupts */ #define HPET_TCNF_TYPE 0x00000008 /* 1 = periodic, 0 = one-shot */ #define HPET_TCNF_INT_ENB 0x00000004 -#define HPET_TCNT_INT_TYPE 0x00000002 /* 1 = level triggered, 0 = edge */ +#define HPET_TCNF_INT_TYPE 0x00000002 /* 1 = level triggered, 0 = edge */ #define HPET_TIMER_COMPARATOR(x) ((x) * 0x20 + 0x108) #define HPET_TIMER_FSB_VAL(x) ((x) * 0x20 + 0x110) #define HPET_TIMER_FSB_ADDR(x) ((x) * 0x20 + 0x114) diff --git a/sys/i386/i386/mp_machdep.c b/sys/i386/i386/mp_machdep.c index 70f69271..4e3ec3e 100644 --- a/sys/i386/i386/mp_machdep.c +++ b/sys/i386/i386/mp_machdep.c @@ -728,10 +728,11 @@ init_secondary(void) } mtx_unlock_spin(&ap_boot_mtx); - /* wait until all the AP's are up */ while (smp_started == 0) ia32_pause(); + /* Start per-CPU event timers. */ + cpu_initclocks_ap(); /* enter the scheduler */ sched_throw(NULL); diff --git a/sys/i386/include/apicvar.h b/sys/i386/include/apicvar.h index f917357..cada017 100644 --- a/sys/i386/include/apicvar.h +++ b/sys/i386/include/apicvar.h @@ -187,12 +187,6 @@ #define APIC_BUS_PCI 2 #define APIC_BUS_MAX APIC_BUS_PCI -enum lapic_clock { - LAPIC_CLOCK_NONE, - LAPIC_CLOCK_HARDCLOCK, - LAPIC_CLOCK_ALL -}; - /* * An APIC enumerator is a psuedo bus driver that enumerates APIC's including * CPU's and I/O APIC's. @@ -263,7 +257,6 @@ int lapic_set_lvt_triggermode(u_int apic_id, u_int lvt, enum intr_trigger trigger); void lapic_set_tpr(u_int vector); void lapic_setup(int boot); -enum lapic_clock lapic_setup_clock(enum lapic_clock srcsdes); #endif /* !LOCORE */ #endif /* _MACHINE_APICVAR_H_ */ diff --git a/sys/isa/rtc.h b/sys/isa/rtc.h index 018a4ed..8d86981 100644 --- a/sys/isa/rtc.h +++ b/sys/isa/rtc.h @@ -114,11 +114,7 @@ #ifdef _KERNEL extern struct mtx clock_lock; extern int atrtcclock_disable; -int atrtc_setup_clock(void); int rtcin(int reg); -void atrtc_start(void); -void atrtc_rate(unsigned rate); -void atrtc_enable_intr(void); void atrtc_restore(void); void writertc(int reg, u_char val); #endif diff --git a/sys/kern/kern_clock.c b/sys/kern/kern_clock.c index 7e93ddd..64f0b0e 100644 --- a/sys/kern/kern_clock.c +++ b/sys/kern/kern_clock.c @@ -392,7 +392,7 @@ initclocks(dummy) * Set divisors to 1 (normal case) and let the machine-specific * code do its bit. */ - mtx_init(&time_lock, "time lock", NULL, MTX_SPIN); + mtx_init(&time_lock, "time lock", NULL, MTX_DEF); cpu_initclocks(); /* @@ -449,7 +449,7 @@ timer2clock(int usermode, uintfptr_t pc) *cnt -= t2hz; if (*cnt >= t2hz) *cnt = 0; - profclock(usermode, pc); + profclock(usermode, pc); } } @@ -599,10 +599,10 @@ startprofclock(p) return; if ((p->p_flag & P_PROFIL) == 0) { p->p_flag |= P_PROFIL; - mtx_lock_spin(&time_lock); + mtx_lock(&time_lock); if (++profprocs == 1) cpu_startprofclock(); - mtx_unlock_spin(&time_lock); + mtx_unlock(&time_lock); } } @@ -626,10 +626,10 @@ stopprofclock(p) if ((p->p_flag & P_PROFIL) == 0) return; p->p_flag &= ~P_PROFIL; - mtx_lock_spin(&time_lock); + mtx_lock(&time_lock); if (--profprocs == 0) cpu_stopprofclock(); - mtx_unlock_spin(&time_lock); + mtx_unlock(&time_lock); } } diff --git a/sys/kern/kern_et.c b/sys/kern/kern_et.c new file mode 100644 index 0000000..52fb705 --- /dev/null +++ b/sys/kern/kern_et.c @@ -0,0 +1,232 @@ +/*- + * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/queue.h> +#include <sys/timeet.h> + +SLIST_HEAD(et_eventtimers_list, eventtimer); +static struct et_eventtimers_list eventtimers = SLIST_HEAD_INITIALIZER(et_eventtimers); + +struct mtx et_eventtimers_mtx; +MTX_SYSINIT(et_eventtimers_init, &et_eventtimers_mtx, "et_mtx", MTX_SPIN); + +SYSCTL_NODE(_kern, OID_AUTO, eventtimer, CTLFLAG_RW, 0, "Event timers"); +SYSCTL_NODE(_kern_eventtimer, OID_AUTO, et, CTLFLAG_RW, 0, ""); + +/* + * Register a new event timer hardware. + */ +int +et_register(struct eventtimer *et) +{ + struct eventtimer *tmp, *next; + + if (et->et_quality >= 0 || bootverbose) { + printf("Event timer \"%s\" frequency %ju Hz quality %d\n", + et->et_name, (uintmax_t)et->et_frequency, + et->et_quality); + } + et->et_sysctl = SYSCTL_ADD_NODE(NULL, + SYSCTL_STATIC_CHILDREN(_kern_eventtimer_et), OID_AUTO, et->et_name, + CTLFLAG_RW, 0, "event timer description"); + SYSCTL_ADD_UINT(NULL, SYSCTL_CHILDREN(et->et_sysctl), OID_AUTO, + "flags", CTLFLAG_RD, &(et->et_flags), 0, + "Event timer capabilities"); + SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(et->et_sysctl), OID_AUTO, + "frequency", CTLFLAG_RD, &(et->et_frequency), 0, + "Event timer base frequency"); + SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(et->et_sysctl), OID_AUTO, + "quality", CTLFLAG_RD, &(et->et_quality), 0, + "Goodness of event timer"); + ET_LOCK(); + if (SLIST_EMPTY(&eventtimers) || + SLIST_FIRST(&eventtimers)->et_quality < et->et_quality) { + SLIST_INSERT_HEAD(&eventtimers, et, et_all); + } else { + SLIST_FOREACH(tmp, &eventtimers, et_all) { + next = SLIST_NEXT(tmp, et_all); + if (next == NULL || next->et_quality < et->et_quality) { + SLIST_INSERT_AFTER(tmp, et, et_all); + break; + } + } + } + ET_UNLOCK(); + return (0); +} + +/* + * Deregister event timer hardware. + */ +int +et_deregister(struct eventtimer *et) +{ + int err = 0; + + if (et->et_deregister_cb != NULL) { + if ((err = et->et_deregister_cb(et, et->et_arg)) != 0) + return (err); + } + + ET_LOCK(); + SLIST_REMOVE(&eventtimers, et, eventtimer, et_all); + ET_UNLOCK(); + sysctl_remove_oid(et->et_sysctl, 1, 1); + return (0); +} + +/* + * Find free event timer hardware with specified parameters. + */ +struct eventtimer * +et_find(const char *name, int check, int want) +{ + struct eventtimer *et = NULL; + + SLIST_FOREACH(et, &eventtimers, et_all) { + if (et->et_active) + continue; + if (name != NULL && strcasecmp(et->et_name, name) != 0) + continue; + if (name == NULL && et->et_quality < 0) + continue; + if ((et->et_flags & check) != want) + continue; + break; + } + return (et); +} + +/* + * Initialize event timer hardware. Set callbacks. + */ +int +et_init(struct eventtimer *et, et_event_cb_t *event, + et_deregister_cb_t *deregister, void *arg) +{ + + if (event == NULL) + return (EINVAL); + if (et->et_active) + return (EBUSY); + + et->et_active = 1; + et->et_event_cb = event; + et->et_deregister_cb = deregister; + et->et_arg = arg; + return (0); +} + +/* + * Start event timer hardware. + * first - delay before first tick. + * period - period of subsequent periodic ticks. + */ +int +et_start(struct eventtimer *et, + struct bintime *first, struct bintime *period) +{ + + if (!et->et_active) + return (ENXIO); + if (first == NULL && period == NULL) + return (EINVAL); + if ((et->et_flags & ET_FLAGS_PERIODIC) == 0 && + period != NULL) + return (ENODEV); + if ((et->et_flags & ET_FLAGS_ONESHOT) == 0 && + period == NULL) + return (ENODEV); + if (et->et_start) + return (et->et_start(et, first, period)); + return (0); +} + +/* Stop event timer hardware. */ +int +et_stop(struct eventtimer *et) +{ + + if (!et->et_active) + return (ENXIO); + if (et->et_stop) + return (et->et_stop(et)); + return (0); +} + +/* Mark event timer hardware as broken. */ +int +et_ban(struct eventtimer *et) +{ + + et->et_flags &= ~(ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT); + return (0); +} + +/* Free event timer hardware. */ +int +et_free(struct eventtimer *et) +{ + + if (!et->et_active) + return (ENXIO); + + et->et_active = 0; + return (0); +} + +/* Report list of supported event timers hardware via sysctl. */ +static int +sysctl_kern_eventtimer_choice(SYSCTL_HANDLER_ARGS) +{ + char buf[512], *spc; + struct eventtimer *et; + int error, off; + + spc = ""; + error = 0; + off = 0; + ET_LOCK(); + SLIST_FOREACH(et, &eventtimers, et_all) { + off += snprintf(buf + off, sizeof(buf) - off, "%s%s(%d)", + spc, et->et_name, et->et_quality); + spc = " "; + } + ET_UNLOCK(); + error = SYSCTL_OUT(req, buf, strlen(buf)); + return (error); +} +SYSCTL_PROC(_kern_eventtimer, OID_AUTO, choice, + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + 0, 0, sysctl_kern_eventtimer_choice, "A", "Present event timers"); + diff --git a/sys/pc98/cbus/clock.c b/sys/pc98/cbus/clock.c index 2f2c08c..2596571 100644 --- a/sys/pc98/cbus/clock.c +++ b/sys/pc98/cbus/clock.c @@ -93,16 +93,12 @@ TUNABLE_INT("hw.i8254.freq", &i8254_freq); int i8254_max_count; static int i8254_real_max_count; -static int lapic_allclocks = 1; -TUNABLE_INT("machdep.lapic_allclocks", &lapic_allclocks); - static struct mtx clock_lock; static struct intsrc *i8254_intsrc; static u_int32_t i8254_lastcount; static u_int32_t i8254_offset; static int (*i8254_pending)(struct intsrc *); static int i8254_ticked; -static enum lapic_clock using_lapic_timer = LAPIC_CLOCK_NONE; /* Values for timerX_state: */ #define RELEASED 0 @@ -113,7 +109,9 @@ static enum lapic_clock using_lapic_timer = LAPIC_CLOCK_NONE; static u_char timer1_state; static unsigned i8254_get_timecount(struct timecounter *tc); +#if 0 static unsigned i8254_simple_get_timecount(struct timecounter *tc); +#endif static void set_i8254_freq(u_int freq, int intr_freq); static struct timecounter i8254_timecounter = { @@ -155,8 +153,6 @@ clkintr(struct trapframe *frame) clkintr_pending = 0; mtx_unlock_spin(&clock_lock); } - KASSERT(using_lapic_timer == LAPIC_CLOCK_NONE, - ("clk interrupt enabled with lapic timer")); #ifdef KDTRACE_HOOKS /* @@ -352,7 +348,7 @@ set_i8254_freq(u_int freq, int intr_freq) i8254_timecounter.tc_frequency = freq; mtx_lock_spin(&clock_lock); i8254_freq = freq; - if (using_lapic_timer != LAPIC_CLOCK_NONE) + if (intr_freq == 0) new_i8254_real_max_count = 0x10000; else new_i8254_real_max_count = TIMER_DIV(intr_freq); @@ -425,45 +421,30 @@ startrtclock() void cpu_initclocks() { -#if defined(DEV_APIC) - enum lapic_clock tlsca; - tlsca = lapic_allclocks == 0 ? LAPIC_CLOCK_HARDCLOCK : LAPIC_CLOCK_ALL; - using_lapic_timer = lapic_setup_clock(tlsca); -#endif - /* - * If we aren't using the local APIC timer to drive the kernel - * clocks, setup the interrupt handler for the 8254 timer 0 so - * that it can drive hardclock(). Otherwise, change the 8254 - * timecounter to user a simpler algorithm. - */ - if (using_lapic_timer == LAPIC_CLOCK_NONE) { - timer1hz = hz; - intr_add_handler("clk", 0, (driver_filter_t *)clkintr, NULL, - NULL, INTR_TYPE_CLK, NULL); - i8254_intsrc = intr_lookup_source(0); - if (i8254_intsrc != NULL) - i8254_pending = - i8254_intsrc->is_pic->pic_source_pending; - } else { - i8254_timecounter.tc_get_timecount = - i8254_simple_get_timecount; - i8254_timecounter.tc_counter_mask = 0xffff; - set_i8254_freq(i8254_freq, hz); - } - if (using_lapic_timer != LAPIC_CLOCK_ALL) { - profhz = hz; - if (hz < 128) - stathz = hz; - else - stathz = hz / (hz / 128); - } + timer1hz = hz; + intr_add_handler("clk", 0, (driver_filter_t *)clkintr, NULL, + NULL, INTR_TYPE_CLK, NULL); + i8254_intsrc = intr_lookup_source(0); + if (i8254_intsrc != NULL) + i8254_pending = + i8254_intsrc->is_pic->pic_source_pending; + profhz = hz; + if (hz < 128) + stathz = hz; + else + stathz = hz / (hz / 128); timer2hz = 0; init_TSC_tc(); } void +cpu_initclocks_ap(void) +{ +} + +void cpu_startprofclock(void) { } @@ -493,12 +474,14 @@ sysctl_machdep_i8254_freq(SYSCTL_HANDLER_ARGS) SYSCTL_PROC(_machdep, OID_AUTO, i8254_freq, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(u_int), sysctl_machdep_i8254_freq, "IU", ""); +#if 0 static unsigned i8254_simple_get_timecount(struct timecounter *tc) { return (i8254_max_count - getit()); } +#endif static unsigned i8254_get_timecount(struct timecounter *tc) diff --git a/sys/sys/systm.h b/sys/sys/systm.h index 8d245a3..a1df1c8 100644 --- a/sys/sys/systm.h +++ b/sys/sys/systm.h @@ -280,6 +280,8 @@ void adjust_timeout_calltodo(struct timeval *time_change); /* Initialize the world */ void consinit(void); void cpu_initclocks(void); +void cpu_initclocks_bsp(void); +void cpu_initclocks_ap(void); void usrinfoinit(void); /* Finalize the world */ diff --git a/sys/sys/timeet.h b/sys/sys/timeet.h new file mode 100644 index 0000000..bafa55a --- /dev/null +++ b/sys/sys/timeet.h @@ -0,0 +1,103 @@ +/*- + * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _SYS_TIMEEC_H_ +#define _SYS_TIMEEC_H_ + +#ifndef _KERNEL +#error "no user-serviceable parts inside" +#endif + +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/queue.h> +#include <sys/time.h> + +/* + * `struct eventtimer' is the interface between the hardware which implements + * a event timer and the MI code which uses this to receive time events. + */ + +struct eventtimer; +typedef int et_start_t(struct eventtimer *et, + struct bintime *first, struct bintime *period); +typedef int et_stop_t(struct eventtimer *et); +typedef void et_event_cb_t(struct eventtimer *et, void *arg); +typedef int et_deregister_cb_t(struct eventtimer *et, void *arg); + +struct eventtimer { + SLIST_ENTRY(eventtimer) et_all; + /* Pointer to the next event timer. */ + char *et_name; + /* Name of the event timer. */ + int et_flags; + /* Set of capabilities flags: */ +#define ET_FLAGS_PERIODIC 1 +#define ET_FLAGS_ONESHOT 2 +#define ET_FLAGS_PERCPU 4 +#define ET_FLAGS_C3STOP 8 + int et_quality; + /* + * Used to determine if this timecounter is better than + * another timecounter. Higher means better. + */ + int et_active; + u_int64_t et_frequency; + /* Base frequency in Hz. */ + et_start_t *et_start; + et_stop_t *et_stop; + et_event_cb_t *et_event_cb; + et_deregister_cb_t *et_deregister_cb; + void *et_arg; + void *et_priv; + struct sysctl_oid *et_sysctl; + /* Pointer to the event timer's private parts. */ +}; + +extern struct mtx et_eventtimers_mtx; +#define ET_LOCK() mtx_lock_spin(&et_eventtimers_mtx) +#define ET_UNLOCK() mtx_unlock_spin(&et_eventtimers_mtx) + +/* Driver API */ +int et_register(struct eventtimer *et); +int et_deregister(struct eventtimer *et); +/* Consumer API */ +struct eventtimer *et_find(const char *name, int check, int want); +int et_init(struct eventtimer *et, et_event_cb_t *event, + et_deregister_cb_t *deregister, void *arg); +int et_start(struct eventtimer *et, + struct bintime *first, struct bintime *period); +int et_stop(struct eventtimer *et); +int et_ban(struct eventtimer *et); +int et_free(struct eventtimer *et); + +#ifdef SYSCTL_DECL +SYSCTL_DECL(_kern_eventtimer); +#endif +#endif /* !_SYS_TIMETC_H_ */ + diff --git a/sys/x86/isa/atrtc.c b/sys/x86/isa/atrtc.c index 777c720..a7cd7f1 100644 --- a/sys/x86/isa/atrtc.c +++ b/sys/x86/isa/atrtc.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2008 Poul-Henning Kamp + * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,18 +40,21 @@ __FBSDID("$FreeBSD$"); #include <sys/mutex.h> #include <sys/kernel.h> #include <sys/module.h> +#include <sys/proc.h> +#include <sys/rman.h> +#include <sys/timeet.h> #include <isa/rtc.h> #ifdef DEV_ISA #include <isa/isareg.h> #include <isa/isavar.h> #endif +#include <machine/intr_machdep.h> +#include "clock_if.h" #define RTC_LOCK mtx_lock_spin(&clock_lock) #define RTC_UNLOCK mtx_unlock_spin(&clock_lock) -int atrtcclock_disable = 0; - static int rtc_reg = -1; static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; static u_char rtc_statusb = RTCSB_24HR; @@ -98,7 +102,7 @@ readrtc(int port) return(bcd2bin(rtcin(port))); } -void +static void atrtc_start(void) { @@ -106,7 +110,7 @@ atrtc_start(void) writertc(RTC_STATUSB, RTCSB_24HR); } -void +static void atrtc_rate(unsigned rate) { @@ -114,7 +118,7 @@ atrtc_rate(unsigned rate) writertc(RTC_STATUSA, rtc_statusa); } -void +static void atrtc_enable_intr(void) { @@ -123,6 +127,15 @@ atrtc_enable_intr(void) rtcin(RTC_INTR); } +static void +atrtc_disable_intr(void) +{ + + rtc_statusb &= ~RTCSB_PINTR; + writertc(RTC_STATUSB, rtc_statusb); + rtcin(RTC_INTR); +} + void atrtc_restore(void) { @@ -135,41 +148,74 @@ atrtc_restore(void) rtcin(RTC_INTR); } -int -atrtc_setup_clock(void) -{ - int diag; - - if (atrtcclock_disable) - return (0); - - diag = rtcin(RTC_DIAG); - if (diag != 0) { - printf("RTC BIOS diagnostic error %b\n", - diag, RTCDG_BITS); - return (0); - } - - stathz = RTC_NOPROFRATE; - profhz = RTC_PROFRATE; - - return (1); -} - /********************************************************************** * RTC driver for subr_rtc */ -#include "clock_if.h" - -#include <sys/rman.h> - struct atrtc_softc { int port_rid, intr_rid; struct resource *port_res; struct resource *intr_res; + void *intr_handler; + struct eventtimer et; }; +static int +rtc_start(struct eventtimer *et, + struct bintime *first, struct bintime *period) +{ + + atrtc_rate(max(fls((period->frac + (period->frac >> 1)) >> 32) - 17, 1)); + atrtc_enable_intr(); + return (0); +} + +static int +rtc_stop(struct eventtimer *et) +{ + + atrtc_disable_intr(); + return (0); +} + +/* + * This routine receives statistical clock interrupts from the RTC. + * As explained above, these occur at 128 interrupts per second. + * When profiling, we receive interrupts at a rate of 1024 Hz. + * + * This does not actually add as much overhead as it sounds, because + * when the statistical clock is active, the hardclock driver no longer + * needs to keep (inaccurate) statistics on its own. This decouples + * statistics gathering from scheduling interrupts. + * + * The RTC chip requires that we read status register C (RTC_INTR) + * to acknowledge an interrupt, before it will generate the next one. + * Under high interrupt load, rtcintr() can be indefinitely delayed and + * the clock can tick immediately after the read from RTC_INTR. In this + * case, the mc146818A interrupt signal will not drop for long enough + * to register with the 8259 PIC. If an interrupt is missed, the stat + * clock will halt, considerably degrading system performance. This is + * why we use 'while' rather than a more straightforward 'if' below. + * Stat clock ticks can still be lost, causing minor loss of accuracy + * in the statistics, but the stat clock will no longer stop. + */ +static int +rtc_intr(void *arg) +{ + struct atrtc_softc *sc = (struct atrtc_softc *)arg; + int flag = 0; + + while (rtcin(RTC_INTR) & RTCIR_PERIOD) { + flag = 1; + if (sc->et.et_active) { + sc->et.et_event_cb(&sc->et, + sc->et.et_arg ? sc->et.et_arg : + curthread->td_intr_frame); + } + } + return(flag ? FILTER_HANDLED : FILTER_STRAY); +} + /* * Attach to the ISA PnP descriptors for the timer and realtime clock. */ @@ -196,23 +242,42 @@ static int atrtc_attach(device_t dev) { struct atrtc_softc *sc; - int i; + int i, diag, haveirq = 0; - /* - * Not that we need them or anything, but grab our resources - * so they show up, correctly attributed, in the big picture. - */ - sc = device_get_softc(dev); if (!(sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port_rid, IO_RTC, IO_RTC + 1, 2, RF_ACTIVE))) device_printf(dev,"Warning: Couldn't map I/O.\n"); if (!(sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->intr_rid, 8, 8, 1, RF_ACTIVE))) - device_printf(dev,"Warning: Couldn't map Interrupt.\n"); + device_printf(dev,"Couldn't map Interrupt.\n"); + else if ((bus_setup_intr(dev, sc->intr_res, + INTR_MPSAFE | INTR_TYPE_CLK, (driver_filter_t *)rtc_intr, NULL, + sc, &sc->intr_handler))) { + device_printf(dev, "Can't setup interrupt.\n"); + } else { + haveirq = 1; + /* Bind IRQ to BSP to avoid live migration. */ + bus_bind_intr(dev, sc->intr_res, 0); + } + diag = rtcin(RTC_DIAG); + if (diag != 0) + printf("RTC BIOS diagnostic error %b\n", diag, RTCDG_BITS); + atrtc_start(); clock_register(dev, 1000000); - if (resource_int_value("atrtc", 0, "clock", &i) == 0 && i == 0) - atrtcclock_disable = 1; + bzero(&sc->et, sizeof(struct eventtimer)); + if (haveirq && + (resource_int_value(device_get_name(dev), device_get_unit(dev), + "clock", &i) != 0 || i != 0)) { + sc->et.et_name = "RTC"; + sc->et.et_flags = ET_FLAGS_PERIODIC; + sc->et.et_quality = 0; + sc->et.et_frequency = 32768; + sc->et.et_start = rtc_start; + sc->et.et_stop = rtc_stop; + sc->et.et_priv = dev; + et_register(&sc->et); + } return(0); } diff --git a/sys/x86/isa/clock.c b/sys/x86/isa/clock.c index b0d49d5..5929f5b 100644 --- a/sys/x86/isa/clock.c +++ b/sys/x86/isa/clock.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 1990 The Regents of the University of California. + * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org> * All rights reserved. * * This code is derived from software contributed to Berkeley by @@ -39,11 +40,7 @@ __FBSDID("$FreeBSD$"); * Routines to handle clock hardware. */ -#ifndef __amd64__ -#include "opt_apic.h" -#endif #include "opt_clock.h" -#include "opt_kdtrace.h" #include "opt_isa.h" #include "opt_mca.h" @@ -54,21 +51,20 @@ __FBSDID("$FreeBSD$"); #include <sys/kdb.h> #include <sys/mutex.h> #include <sys/proc.h> -#include <sys/timetc.h> #include <sys/kernel.h> #include <sys/module.h> +#include <sys/rman.h> #include <sys/sched.h> #include <sys/smp.h> #include <sys/sysctl.h> +#include <sys/timeet.h> +#include <sys/timetc.h> #include <machine/clock.h> #include <machine/cpu.h> #include <machine/intr_machdep.h> -#include <machine/md_var.h> -#include <machine/apicvar.h> #include <machine/ppireg.h> #include <machine/timerreg.h> -#include <machine/smp.h> #include <isa/rtc.h> #ifdef DEV_ISA @@ -80,12 +76,6 @@ __FBSDID("$FreeBSD$"); #include <i386/bios/mca_machdep.h> #endif -#ifdef KDTRACE_HOOKS -#include <sys/dtrace_bsd.h> -#endif - -#define TIMER_DIV(x) ((i8254_freq + (x) / 2) / (x)) - int clkintr_pending; #ifndef TIMER_FREQ #define TIMER_FREQ 1193182 @@ -95,17 +85,23 @@ TUNABLE_INT("hw.i8254.freq", &i8254_freq); int i8254_max_count; static int i8254_real_max_count; -static int lapic_allclocks = 1; -TUNABLE_INT("machdep.lapic_allclocks", &lapic_allclocks); - struct mtx clock_lock; static struct intsrc *i8254_intsrc; -static u_int32_t i8254_lastcount; -static u_int32_t i8254_offset; +static uint16_t i8254_lastcount; +static uint16_t i8254_offset; static int (*i8254_pending)(struct intsrc *); static int i8254_ticked; -static int using_atrtc_timer; -static enum lapic_clock using_lapic_timer = LAPIC_CLOCK_NONE; + +struct attimer_softc { + int intr_en; + int intr_rid; + struct resource *intr_res; + void *intr_handler; + struct timecounter tc; + struct eventtimer et; + uint32_t intr_period; +}; +static struct attimer_softc *attimer_sc = NULL; /* Values for timerX_state: */ #define RELEASED 0 @@ -116,39 +112,14 @@ static enum lapic_clock using_lapic_timer = LAPIC_CLOCK_NONE; static u_char timer2_state; static unsigned i8254_get_timecount(struct timecounter *tc); -static unsigned i8254_simple_get_timecount(struct timecounter *tc); -static void set_i8254_freq(u_int freq, int intr_freq); - -static struct timecounter i8254_timecounter = { - i8254_get_timecount, /* get_timecount */ - 0, /* no poll_pps */ - ~0u, /* counter_mask */ - 0, /* frequency */ - "i8254", /* name */ - 0 /* quality */ -}; - -int -hardclockintr(struct trapframe *frame) -{ - - timer1clock(TRAPF_USERMODE(frame), TRAPF_PC(frame)); - return (FILTER_HANDLED); -} - -int -statclockintr(struct trapframe *frame) -{ - - timer2clock(TRAPF_USERMODE(frame), TRAPF_PC(frame)); - return (FILTER_HANDLED); -} +static void set_i8254_freq(u_int freq, uint32_t intr_period); static int -clkintr(struct trapframe *frame) +clkintr(void *arg) { + struct attimer_softc *sc = (struct attimer_softc *)arg; - if (timecounter->tc_get_timecount == i8254_get_timecount) { + if (sc->intr_period != 0) { mtx_lock_spin(&clock_lock); if (i8254_ticked) i8254_ticked = 0; @@ -159,25 +130,11 @@ clkintr(struct trapframe *frame) clkintr_pending = 0; mtx_unlock_spin(&clock_lock); } - KASSERT(using_lapic_timer == LAPIC_CLOCK_NONE, - ("clk interrupt enabled with lapic timer")); -#ifdef KDTRACE_HOOKS - /* - * If the DTrace hooks are configured and a callback function - * has been registered, then call it to process the high speed - * timers. - */ - int cpu = PCPU_GET(cpuid); - if (cyclic_clock_func[cpu] != NULL) - (*cyclic_clock_func[cpu])(frame); -#endif - -#ifdef SMP - if (smp_started) - ipi_all_but_self(IPI_HARDCLOCK); -#endif - hardclockintr(frame); + if (sc && sc->et.et_active) { + sc->et.et_event_cb(&sc->et, + sc->et.et_arg ? sc->et.et_arg : curthread->td_intr_frame); + } #ifdef DEV_MCA /* Reset clock interrupt by asserting bit 7 of port 0x61 */ @@ -233,43 +190,6 @@ timer_spkr_setfreq(int freq) mtx_unlock_spin(&clock_lock); } -/* - * This routine receives statistical clock interrupts from the RTC. - * As explained above, these occur at 128 interrupts per second. - * When profiling, we receive interrupts at a rate of 1024 Hz. - * - * This does not actually add as much overhead as it sounds, because - * when the statistical clock is active, the hardclock driver no longer - * needs to keep (inaccurate) statistics on its own. This decouples - * statistics gathering from scheduling interrupts. - * - * The RTC chip requires that we read status register C (RTC_INTR) - * to acknowledge an interrupt, before it will generate the next one. - * Under high interrupt load, rtcintr() can be indefinitely delayed and - * the clock can tick immediately after the read from RTC_INTR. In this - * case, the mc146818A interrupt signal will not drop for long enough - * to register with the 8259 PIC. If an interrupt is missed, the stat - * clock will halt, considerably degrading system performance. This is - * why we use 'while' rather than a more straightforward 'if' below. - * Stat clock ticks can still be lost, causing minor loss of accuracy - * in the statistics, but the stat clock will no longer stop. - */ -static int -rtcintr(struct trapframe *frame) -{ - int flag = 0; - - while (rtcin(RTC_INTR) & RTCIR_PERIOD) { - flag = 1; -#ifdef SMP - if (smp_started) - ipi_all_but_self(IPI_STATCLOCK); -#endif - statclockintr(frame); - } - return(flag ? FILTER_HANDLED : FILTER_STRAY); -} - static int getit(void) { @@ -405,17 +325,18 @@ DELAY(int n) } static void -set_i8254_freq(u_int freq, int intr_freq) +set_i8254_freq(u_int freq, uint32_t intr_period) { int new_i8254_real_max_count; - i8254_timecounter.tc_frequency = freq; mtx_lock_spin(&clock_lock); i8254_freq = freq; - if (using_lapic_timer != LAPIC_CLOCK_NONE) + if (intr_period == 0) new_i8254_real_max_count = 0x10000; - else - new_i8254_real_max_count = TIMER_DIV(intr_freq); + else { + new_i8254_real_max_count = + min(((uint64_t)i8254_freq * intr_period) >> 32, 0x10000); + } if (new_i8254_real_max_count != i8254_real_max_count) { i8254_real_max_count = new_i8254_real_max_count; if (i8254_real_max_count == 0x10000) @@ -465,113 +386,22 @@ i8254_init(void) { mtx_init(&clock_lock, "clk", NULL, MTX_SPIN | MTX_NOPROFILE); - set_i8254_freq(i8254_freq, hz); + set_i8254_freq(i8254_freq, 0); } void startrtclock() { - atrtc_start(); - - set_i8254_freq(i8254_freq, hz); - tc_init(&i8254_timecounter); - init_TSC(); } -/* - * Start both clocks running. - */ void -cpu_initclocks() +cpu_initclocks(void) { -#if defined(__amd64__) || defined(DEV_APIC) - enum lapic_clock tlsca; -#endif - int tasc; - - /* Initialize RTC. */ - atrtc_start(); - tasc = atrtc_setup_clock(); - - /* - * If the atrtc successfully initialized and the users didn't force - * otherwise use the LAPIC in order to cater hardclock only, otherwise - * take in charge all the clock sources. - */ -#if defined(__amd64__) || defined(DEV_APIC) - tlsca = (lapic_allclocks == 0 && tasc != 0) ? LAPIC_CLOCK_HARDCLOCK : - LAPIC_CLOCK_ALL; - using_lapic_timer = lapic_setup_clock(tlsca); -#endif - /* - * If we aren't using the local APIC timer to drive the kernel - * clocks, setup the interrupt handler for the 8254 timer 0 so - * that it can drive hardclock(). Otherwise, change the 8254 - * timecounter to user a simpler algorithm. - */ - if (using_lapic_timer == LAPIC_CLOCK_NONE) { - timer1hz = hz; - intr_add_handler("clk", 0, (driver_filter_t *)clkintr, NULL, - NULL, INTR_TYPE_CLK, NULL); - i8254_intsrc = intr_lookup_source(0); - if (i8254_intsrc != NULL) - i8254_pending = - i8254_intsrc->is_pic->pic_source_pending; - } else { - i8254_timecounter.tc_get_timecount = - i8254_simple_get_timecount; - i8254_timecounter.tc_counter_mask = 0xffff; - set_i8254_freq(i8254_freq, hz); - } - - /* - * If the separate statistics clock hasn't been explicility disabled - * and we aren't already using the local APIC timer to drive the - * kernel clocks, then setup the RTC to periodically interrupt to - * drive statclock() and profclock(). - */ - if (using_lapic_timer != LAPIC_CLOCK_ALL) { - using_atrtc_timer = tasc; - if (using_atrtc_timer) { - timer2hz = RTC_NOPROFRATE; - /* Enable periodic interrupts from the RTC. */ - intr_add_handler("rtc", 8, - (driver_filter_t *)rtcintr, NULL, NULL, - INTR_TYPE_CLK, NULL); - atrtc_enable_intr(); - } else { - profhz = hz; - if (hz < 128) - stathz = hz; - else - stathz = hz / (hz / 128); - timer2hz = 0; - } - } init_TSC_tc(); -} - -void -cpu_startprofclock(void) -{ - - if (using_lapic_timer == LAPIC_CLOCK_ALL || !using_atrtc_timer) - return; - atrtc_rate(RTCSA_PROF); - timer2hz = RTC_PROFRATE; -} - -void -cpu_stopprofclock(void) -{ - - if (using_lapic_timer == LAPIC_CLOCK_ALL || !using_atrtc_timer) - return; - atrtc_rate(RTCSA_NOPROF); - timer2hz = RTC_NOPROFRATE; + cpu_initclocks_bsp(); } static int @@ -586,8 +416,14 @@ sysctl_machdep_i8254_freq(SYSCTL_HANDLER_ARGS) */ freq = i8254_freq; error = sysctl_handle_int(oidp, &freq, 0, req); - if (error == 0 && req->newptr != NULL) - set_i8254_freq(freq, hz); + if (error == 0 && req->newptr != NULL) { + if (attimer_sc) { + set_i8254_freq(freq, attimer_sc->intr_period); + attimer_sc->tc.tc_frequency = freq; + } else { + set_i8254_freq(freq, 0); + } + } return (error); } @@ -595,19 +431,17 @@ SYSCTL_PROC(_machdep, OID_AUTO, i8254_freq, CTLTYPE_INT | CTLFLAG_RW, 0, sizeof(u_int), sysctl_machdep_i8254_freq, "IU", ""); static unsigned -i8254_simple_get_timecount(struct timecounter *tc) -{ - - return (i8254_max_count - getit()); -} - -static unsigned i8254_get_timecount(struct timecounter *tc) { + device_t dev = (device_t)tc->tc_priv; + struct attimer_softc *sc = device_get_softc(dev); register_t flags; - u_int count; + uint16_t count; u_int high, low; + if (sc->intr_period == 0) + return (i8254_max_count - getit()); + #ifdef __amd64__ flags = read_rflags(); #else @@ -635,6 +469,33 @@ i8254_get_timecount(struct timecounter *tc) return (count); } +static int +attimer_start(struct eventtimer *et, + struct bintime *first, struct bintime *period) +{ + device_t dev = (device_t)et->et_priv; + struct attimer_softc *sc = device_get_softc(dev); + + sc->intr_period = period->frac >> 32; + set_i8254_freq(i8254_freq, sc->intr_period); + if (!sc->intr_en) { + i8254_intsrc->is_pic->pic_enable_source(i8254_intsrc); + sc->intr_en = 1; + } + return (0); +} + +static int +attimer_stop(struct eventtimer *et) +{ + device_t dev = (device_t)et->et_priv; + struct attimer_softc *sc = device_get_softc(dev); + + sc->intr_period = 0; + set_i8254_freq(i8254_freq, sc->intr_period); + return (0); +} + #ifdef DEV_ISA /* * Attach to the ISA PnP descriptors for the timer @@ -650,14 +511,53 @@ attimer_probe(device_t dev) int result; result = ISA_PNP_PROBE(device_get_parent(dev), dev, attimer_ids); - if (result <= 0) - device_quiet(dev); return(result); } static int attimer_attach(device_t dev) { + struct attimer_softc *sc; + int i; + + attimer_sc = sc = device_get_softc(dev); + bzero(sc, sizeof(struct attimer_softc)); + if (!(sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ, + &sc->intr_rid, 0, 0, 1, RF_ACTIVE))) + device_printf(dev,"Warning: Couldn't map Interrupt.\n"); + i8254_intsrc = intr_lookup_source(0); + if (i8254_intsrc != NULL) + i8254_pending = i8254_intsrc->is_pic->pic_source_pending; + set_i8254_freq(i8254_freq, 0); + sc->tc.tc_get_timecount = i8254_get_timecount; + sc->tc.tc_counter_mask = 0xffff; + sc->tc.tc_frequency = i8254_freq; + sc->tc.tc_name = "i8254"; + sc->tc.tc_quality = 0; + sc->tc.tc_priv = dev; + tc_init(&sc->tc); + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "clock", &i) != 0 || i != 0) { + /* Dirty hack, to make bus_setup_intr to not enable source. */ + i8254_intsrc->is_handlers++; + if ((bus_setup_intr(dev, sc->intr_res, + INTR_MPSAFE | INTR_TYPE_CLK, + (driver_filter_t *)clkintr, NULL, + sc, &sc->intr_handler))) { + device_printf(dev, "Can't setup interrupt.\n"); + } else { + i8254_intsrc->is_pic->pic_enable_intr(i8254_intsrc); + sc->et.et_name = "i8254"; + sc->et.et_flags = ET_FLAGS_PERIODIC; + sc->et.et_quality = 100; + sc->et.et_frequency = i8254_freq; + sc->et.et_start = attimer_start; + sc->et.et_stop = attimer_stop; + sc->et.et_priv = dev; + et_register(&sc->et); + } + i8254_intsrc->is_handlers--; + } return(0); } @@ -683,7 +583,7 @@ static device_method_t attimer_methods[] = { static driver_t attimer_driver = { "attimer", attimer_methods, - 1, /* no softc */ + sizeof(struct attimer_softc), }; static devclass_t attimer_devclass; diff --git a/sys/x86/x86/local_apic.c b/sys/x86/x86/local_apic.c index 7fec4f6..d1a8439 100644 --- a/sys/x86/x86/local_apic.c +++ b/sys/x86/x86/local_apic.c @@ -49,6 +49,7 @@ __FBSDID("$FreeBSD$"); #include <sys/proc.h> #include <sys/sched.h> #include <sys/smp.h> +#include <sys/timeet.h> #include <vm/vm.h> #include <vm/pmap.h> @@ -79,11 +80,6 @@ __FBSDID("$FreeBSD$"); #define GSEL_APIC GSEL(GCODE_SEL, SEL_KPL) #endif -#ifdef KDTRACE_HOOKS -#include <sys/dtrace_bsd.h> -cyclic_clock_func_t cyclic_clock_func[MAXCPU]; -#endif - /* Sanity checks on IDT vectors. */ CTASSERT(APIC_IO_INTS + APIC_NUM_IOINTS == APIC_TIMER_INT); CTASSERT(APIC_TIMER_INT < APIC_LOCAL_INTS); @@ -119,6 +115,8 @@ struct lapic { u_int la_cluster_id:2; u_int la_present:1; u_long *la_timer_count; + u_long la_timer_period; + u_int la_timer_mode; /* Include IDT_SYSCALL to make indexing easier. */ int la_ioint_irqs[APIC_NUM_IOINTS + 1]; } static lapics[MAX_APIC_ID + 1]; @@ -155,16 +153,20 @@ extern inthand_t IDTVEC(rsvd); volatile lapic_t *lapic; vm_paddr_t lapic_paddr; -static u_long lapic_timer_divisor, lapic_timer_period, lapic_timer_hz; -static enum lapic_clock clockcoverage; +static u_long lapic_timer_divisor; +static struct eventtimer lapic_et; static void lapic_enable(void); static void lapic_resume(struct pic *pic); static void lapic_timer_enable_intr(void); static void lapic_timer_oneshot(u_int count); static void lapic_timer_periodic(u_int count); +static void lapic_timer_stop(void); static void lapic_timer_set_divisor(u_int divisor); static uint32_t lvt_mode(struct lapic *la, u_int pin, uint32_t value); +static int lapic_et_start(struct eventtimer *et, + struct bintime *first, struct bintime *period); +static int lapic_et_stop(struct eventtimer *et); struct pic lapic_pic = { .pic_resume = lapic_resume }; @@ -215,6 +217,8 @@ lvt_mode(struct lapic *la, u_int pin, uint32_t value) void lapic_init(vm_paddr_t addr) { + u_int regs[4]; + int i, arat; /* Map the local APIC and setup the spurious interrupt handler. */ KASSERT(trunc_page(addr) == addr, @@ -240,6 +244,30 @@ lapic_init(vm_paddr_t addr) /* Local APIC CMCI. */ setidt(APIC_CMC_INT, IDTVEC(cmcint), SDT_APICT, SEL_KPL, GSEL_APIC); + + if ((resource_int_value("apic", 0, "clock", &i) != 0 || i != 0)) { + arat = 0; + /* Intel CPUID 0x06 EAX[2] set if APIC timer runs in C3. */ + if (cpu_vendor_id == CPU_VENDOR_INTEL && cpu_high >= 6) { + do_cpuid(0x06, regs); + if (regs[0] & 0x4) + arat = 1; + } + bzero(&lapic_et, sizeof(lapic_et)); + lapic_et.et_name = "LAPIC"; + lapic_et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT | + ET_FLAGS_PERCPU; + lapic_et.et_quality = 600; + if (!arat) { + lapic_et.et_flags |= ET_FLAGS_C3STOP; + lapic_et.et_quality -= 100; + } + lapic_et.et_frequency = 0; + lapic_et.et_start = lapic_et_start; + lapic_et.et_stop = lapic_et_stop; + lapic_et.et_priv = NULL; + et_register(&lapic_et); + } } /* @@ -328,16 +356,19 @@ lapic_setup(int boot) /* Program timer LVT and setup handler. */ lapic->lvt_timer = lvt_mode(la, LVT_TIMER, lapic->lvt_timer); if (boot) { - snprintf(buf, sizeof(buf), "cpu%d: timer", PCPU_GET(cpuid)); + snprintf(buf, sizeof(buf), "cpu%d:timer", PCPU_GET(cpuid)); intrcnt_add(buf, &la->la_timer_count); } - /* We don't setup the timer during boot on the BSP until later. */ - if (!(boot && PCPU_GET(cpuid) == 0) && lapic_timer_hz != 0) { - KASSERT(lapic_timer_period != 0, ("lapic%u: zero divisor", + /* Setup the timer if configured. */ + if (la->la_timer_mode != 0) { + KASSERT(la->la_timer_period != 0, ("lapic%u: zero divisor", lapic_id())); lapic_timer_set_divisor(lapic_timer_divisor); - lapic_timer_periodic(lapic_timer_period); + if (la->la_timer_mode == 1) + lapic_timer_periodic(la->la_timer_period); + else + lapic_timer_oneshot(la->la_timer_period); lapic_timer_enable_intr(); } @@ -436,88 +467,66 @@ lapic_disable_pmc(void) #endif } -/* - * Called by cpu_initclocks() on the BSP to setup the local APIC timer so - * that it can drive hardclock, statclock, and profclock. - */ -enum lapic_clock -lapic_setup_clock(enum lapic_clock srcsdes) +static int +lapic_et_start(struct eventtimer *et, + struct bintime *first, struct bintime *period) { + struct lapic *la; u_long value; - int i; - - /* lapic_setup_clock() should not be called with LAPIC_CLOCK_NONE. */ - MPASS(srcsdes != LAPIC_CLOCK_NONE); - - /* Can't drive the timer without a local APIC. */ - if (lapic == NULL || - (resource_int_value("apic", 0, "clock", &i) == 0 && i == 0)) { - clockcoverage = LAPIC_CLOCK_NONE; - return (clockcoverage); - } - /* Start off with a divisor of 2 (power on reset default). */ - lapic_timer_divisor = 2; - - /* Try to calibrate the local APIC timer. */ - do { - lapic_timer_set_divisor(lapic_timer_divisor); - lapic_timer_oneshot(APIC_TIMER_MAX_COUNT); - DELAY(2000000); - value = APIC_TIMER_MAX_COUNT - lapic->ccr_timer; - if (value != APIC_TIMER_MAX_COUNT) - break; - lapic_timer_divisor <<= 1; - } while (lapic_timer_divisor <= 128); - if (lapic_timer_divisor > 128) - panic("lapic: Divisor too big"); - value /= 2; - if (bootverbose) - printf("lapic: Divisor %lu, Frequency %lu Hz\n", - lapic_timer_divisor, value); - - /* - * We want to run stathz in the neighborhood of 128hz. We would - * like profhz to run as often as possible, so we let it run on - * each clock tick. We try to honor the requested 'hz' value as - * much as possible. - * - * If 'hz' is above 1500, then we just let the lapic timer - * (and profhz) run at hz. If 'hz' is below 1500 but above - * 750, then we let the lapic timer run at 2 * 'hz'. If 'hz' - * is below 750 then we let the lapic timer run at 4 * 'hz'. - * - * Please note that stathz and profhz are set only if all the - * clocks are handled through the local APIC. - */ - if (srcsdes == LAPIC_CLOCK_ALL) { - if (hz >= 1500) - lapic_timer_hz = hz; - else if (hz >= 750) - lapic_timer_hz = hz * 2; - else - lapic_timer_hz = hz * 4; - } else - lapic_timer_hz = hz; - lapic_timer_period = value / lapic_timer_hz; - timer1hz = lapic_timer_hz; - if (srcsdes == LAPIC_CLOCK_ALL) { - if (lapic_timer_hz < 128) - stathz = lapic_timer_hz; - else - stathz = lapic_timer_hz / (lapic_timer_hz / 128); - profhz = lapic_timer_hz; - timer2hz = 0; + if (et->et_frequency == 0) { + /* Start off with a divisor of 2 (power on reset default). */ + lapic_timer_divisor = 2; + /* Try to calibrate the local APIC timer. */ + do { + lapic_timer_set_divisor(lapic_timer_divisor); + lapic_timer_oneshot(APIC_TIMER_MAX_COUNT); + DELAY(1000000); + value = APIC_TIMER_MAX_COUNT - lapic->ccr_timer; + if (value != APIC_TIMER_MAX_COUNT) + break; + lapic_timer_divisor <<= 1; + } while (lapic_timer_divisor <= 128); + if (lapic_timer_divisor > 128) + panic("lapic: Divisor too big"); + if (bootverbose) + printf("lapic: Divisor %lu, Frequency %lu Hz\n", + lapic_timer_divisor, value); + et->et_frequency = value; } - + la = &lapics[lapic_id()]; /* * Start up the timer on the BSP. The APs will kick off their * timer during lapic_setup(). */ - lapic_timer_periodic(lapic_timer_period); + lapic_timer_set_divisor(lapic_timer_divisor); + if (period != NULL) { + la->la_timer_mode = 1; + la->la_timer_period = + (et->et_frequency * (period->frac >> 32)) >> 32; + if (period->sec != 0) + la->la_timer_period += et->et_frequency * period->sec; + lapic_timer_periodic(la->la_timer_period); + } else { + la->la_timer_mode = 2; + la->la_timer_period = + (et->et_frequency * (first->frac >> 32)) >> 32; + if (first->sec != 0) + la->la_timer_period += et->et_frequency * first->sec; + lapic_timer_oneshot(la->la_timer_period); + } lapic_timer_enable_intr(); - clockcoverage = srcsdes; - return (srcsdes); + return (0); +} + +static int +lapic_et_stop(struct eventtimer *et) +{ + struct lapic *la = &lapics[lapic_id()]; + + la->la_timer_mode = 0; + lapic_timer_stop(); + return (0); } void @@ -763,6 +772,8 @@ void lapic_handle_timer(struct trapframe *frame) { struct lapic *la; + struct trapframe *oldframe; + struct thread *td; /* Send EOI first thing. */ lapic_eoi(); @@ -787,19 +798,14 @@ lapic_handle_timer(struct trapframe *frame) la = &lapics[PCPU_GET(apic_id)]; (*la->la_timer_count)++; critical_enter(); - -#ifdef KDTRACE_HOOKS - /* - * If the DTrace hooks are configured and a callback function - * has been registered, then call it to process the high speed - * timers. - */ - int cpu = PCPU_GET(cpuid); - if (cyclic_clock_func[cpu] != NULL) - (*cyclic_clock_func[cpu])(frame); -#endif - - timer1clock(TRAPF_USERMODE(frame), TRAPF_PC(frame)); + if (lapic_et.et_active) { + td = curthread; + oldframe = td->td_intr_frame; + td->td_intr_frame = frame; + lapic_et.et_event_cb(&lapic_et, + lapic_et.et_arg ? lapic_et.et_arg : frame); + td->td_intr_frame = oldframe; + } critical_exit(); } @@ -838,6 +844,17 @@ lapic_timer_periodic(u_int count) } static void +lapic_timer_stop(void) +{ + u_int32_t value; + + value = lapic->lvt_timer; + value &= ~APIC_LVTT_TM; + value &= ~APIC_LVT_M; + lapic->lvt_timer = value; +} + +static void lapic_timer_enable_intr(void) { u_int32_t value; diff --git a/sys/x86/x86/timeevents.c b/sys/x86/x86/timeevents.c new file mode 100644 index 0000000..d31af5d --- /dev/null +++ b/sys/x86/x86/timeevents.c @@ -0,0 +1,509 @@ +/*- + * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Common routines to manage event timers hardware. + */ + +/* XEN has own timer routines now. */ +#ifndef XEN + +#include "opt_clock.h" +#include "opt_kdtrace.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/lock.h> +#include <sys/kdb.h> +#include <sys/mutex.h> +#include <sys/proc.h> +#include <sys/kernel.h> +#include <sys/sched.h> +#include <sys/smp.h> +#include <sys/sysctl.h> +#include <sys/timeet.h> + +#include <machine/atomic.h> +#include <machine/clock.h> +#include <machine/cpu.h> +#include <machine/intr_machdep.h> +#include <machine/md_var.h> +#include <machine/smp.h> + +#ifdef KDTRACE_HOOKS +#include <sys/dtrace_bsd.h> +cyclic_clock_func_t cyclic_clock_func[MAXCPU]; +#endif + +static void cpu_restartclocks(void); +static void timercheck(void); +inline static int doconfigtimer(int i); +static void configtimer(int i); + +static struct eventtimer *timer[2] = { NULL, NULL }; +static int timertest = 0; +static int timerticks[2] = { 0, 0 }; +static int profiling_on = 0; +static struct bintime timerperiod[2]; + +static char timername[2][32]; +TUNABLE_STR("kern.eventtimer.timer1", timername[0], sizeof(*timername)); +TUNABLE_STR("kern.eventtimer.timer2", timername[1], sizeof(*timername)); + +static u_int singlemul = 0; +TUNABLE_INT("kern.eventtimer.singlemul", &singlemul); +SYSCTL_INT(_kern_eventtimer, OID_AUTO, singlemul, CTLFLAG_RW, &singlemul, + 0, "Multiplier, used in single timer mode"); + +typedef u_int tc[2]; +static DPCPU_DEFINE(tc, configtimer); + +#define FREQ2BT(freq, bt) \ +{ \ + (bt)->sec = 0; \ + (bt)->frac = ((uint64_t)0x8000000000000000 / (freq)) << 1; \ +} +#define BT2FREQ(bt, freq) \ +{ \ + *(freq) = ((uint64_t)0x8000000000000000 + ((bt)->frac >> 2)) / \ + ((bt)->frac >> 1); \ +} + +/* Per-CPU timer1 handler. */ +static int +hardclockhandler(struct trapframe *frame) +{ + +#ifdef KDTRACE_HOOKS + /* + * If the DTrace hooks are configured and a callback function + * has been registered, then call it to process the high speed + * timers. + */ + int cpu = curcpu; + if (cyclic_clock_func[cpu] != NULL) + (*cyclic_clock_func[cpu])(frame); +#endif + + timer1clock(TRAPF_USERMODE(frame), TRAPF_PC(frame)); + return (FILTER_HANDLED); +} + +/* Per-CPU timer2 handler. */ +static int +statclockhandler(struct trapframe *frame) +{ + + timer2clock(TRAPF_USERMODE(frame), TRAPF_PC(frame)); + return (FILTER_HANDLED); +} + +/* timer1 broadcast IPI handler. */ +int +hardclockintr(struct trapframe *frame) +{ + + if (doconfigtimer(0)) + return (FILTER_HANDLED); + return (hardclockhandler(frame)); +} + +/* timer2 broadcast IPI handler. */ +int +statclockintr(struct trapframe *frame) +{ + + if (doconfigtimer(1)) + return (FILTER_HANDLED); + return (statclockhandler(frame)); +} + +/* timer1 callback. */ +static void +timer1cb(struct eventtimer *et, void *arg) +{ + +#ifdef SMP + /* Broadcast interrupt to other CPUs for non-per-CPU timers */ + if (smp_started && (et->et_flags & ET_FLAGS_PERCPU) == 0) + ipi_all_but_self(IPI_HARDCLOCK); +#endif + if (timertest) { + if ((et->et_flags & ET_FLAGS_PERCPU) == 0 || curcpu == 0) { + timerticks[0]++; + if (timerticks[0] >= timer1hz) { + ET_LOCK(); + timercheck(); + ET_UNLOCK(); + } + } + } + hardclockhandler((struct trapframe *)arg); +} + +/* timer2 callback. */ +static void +timer2cb(struct eventtimer *et, void *arg) +{ + +#ifdef SMP + /* Broadcast interrupt to other CPUs for non-per-CPU timers */ + if (smp_started && (et->et_flags & ET_FLAGS_PERCPU) == 0) + ipi_all_but_self(IPI_STATCLOCK); +#endif + if (timertest) { + if ((et->et_flags & ET_FLAGS_PERCPU) == 0 || curcpu == 0) { + timerticks[1]++; + if (timerticks[1] >= timer2hz * 2) { + ET_LOCK(); + timercheck(); + ET_UNLOCK(); + } + } + } + statclockhandler((struct trapframe *)arg); +} + +/* + * Check that both timers are running with at least 1/4 of configured rate. + * If not - replace the broken one. + */ +static void +timercheck(void) +{ + + if (!timertest) + return; + timertest = 0; + if (timerticks[0] * 4 < timer1hz) { + printf("Event timer \"%s\" is dead.\n", timer[0]->et_name); + timer1hz = 0; + configtimer(0); + et_ban(timer[0]); + et_free(timer[0]); + timer[0] = et_find(NULL, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + if (timer[0] == NULL) { + timer2hz = 0; + configtimer(1); + et_free(timer[1]); + timer[1] = NULL; + timer[0] = timer[1]; + } + et_init(timer[0], timer1cb, NULL, NULL); + cpu_restartclocks(); + return; + } + if (timerticks[1] * 4 < timer2hz) { + printf("Event timer \"%s\" is dead.\n", timer[1]->et_name); + timer2hz = 0; + configtimer(1); + et_ban(timer[1]); + et_free(timer[1]); + timer[1] = et_find(NULL, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + if (timer[1] != NULL) + et_init(timer[1], timer2cb, NULL, NULL); + cpu_restartclocks(); + return; + } +} + +/* + * Reconfigure specified per-CPU timer on other CPU. Called from IPI handler. + */ +inline static int +doconfigtimer(int i) +{ + tc *conf; + + conf = DPCPU_PTR(configtimer); + if (atomic_load_acq_int(*conf + i)) { + if (i == 0 ? timer1hz : timer2hz) + et_start(timer[i], NULL, &timerperiod[i]); + else + et_stop(timer[i]); + atomic_store_rel_int(*conf + i, 0); + return (1); + } + return (0); +} + +/* + * Reconfigure specified timer. + * For per-CPU timers use IPI to make other CPUs to reconfigure. + */ +static void +configtimer(int i) +{ +#ifdef SMP + tc *conf; + int cpu; + + critical_enter(); +#endif + /* Start/stop global timer or per-CPU timer of this CPU. */ + if (i == 0 ? timer1hz : timer2hz) + et_start(timer[i], NULL, &timerperiod[i]); + else + et_stop(timer[i]); +#ifdef SMP + if ((timer[i]->et_flags & ET_FLAGS_PERCPU) == 0 || !smp_started) { + critical_exit(); + return; + } + /* Set reconfigure flags for other CPUs. */ + CPU_FOREACH(cpu) { + conf = DPCPU_ID_PTR(cpu, configtimer); + atomic_store_rel_int(*conf + i, (cpu == curcpu) ? 0 : 1); + } + /* Send reconfigure IPI. */ + ipi_all_but_self(i == 0 ? IPI_HARDCLOCK : IPI_STATCLOCK); + /* Wait for reconfiguration completed. */ +restart: + cpu_spinwait(); + CPU_FOREACH(cpu) { + if (cpu == curcpu) + continue; + conf = DPCPU_ID_PTR(cpu, configtimer); + if (atomic_load_acq_int(*conf + i)) + goto restart; + } + critical_exit(); +#endif +} + +/* + * Configure and start event timers. + */ +void +cpu_initclocks_bsp(void) +{ + int base, div; + + timer[0] = et_find(timername[0], ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + if (timer[0] == NULL) + timer[0] = et_find(NULL, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + et_init(timer[0], timer1cb, NULL, NULL); + timer[1] = et_find(timername[1][0] ? timername[1] : NULL, + ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + if (timer[1]) + et_init(timer[1], timer2cb, NULL, NULL); + /* + * We honor the requested 'hz' value. + * We want to run stathz in the neighborhood of 128hz. + * We would like profhz to run as often as possible. + */ + if (singlemul == 0) { + if (hz >= 1500 || (hz % 128) == 0) + singlemul = 1; + else if (hz >= 750) + singlemul = 2; + else + singlemul = 4; + } + if (timer[1] == NULL) { + base = hz * singlemul; + if (base < 128) + stathz = base; + else { + div = base / 128; + if (div % 2 == 0) + div++; + stathz = base / div; + } + profhz = stathz; + while ((profhz + stathz) <= 8192) + profhz += stathz; + } else { + stathz = 128; + profhz = stathz * 64; + } + ET_LOCK(); + cpu_restartclocks(); + ET_UNLOCK(); +} + +/* Start per-CPU event timers on APs. */ +void +cpu_initclocks_ap(void) +{ + + ET_LOCK(); + if (timer[0]->et_flags & ET_FLAGS_PERCPU) + et_start(timer[0], NULL, &timerperiod[0]); + if (timer[1] && timer[1]->et_flags & ET_FLAGS_PERCPU) + et_start(timer[1], NULL, &timerperiod[1]); + ET_UNLOCK(); +} + +/* Reconfigure and restart event timers after configuration changes. */ +static void +cpu_restartclocks(void) +{ + + /* Stop all event timers. */ + timertest = 0; + if (timer1hz) { + timer1hz = 0; + configtimer(0); + } + if (timer[1] && timer2hz) { + timer2hz = 0; + configtimer(1); + } + /* Calculate new event timers parameters. */ + if (timer[1] == NULL) { + timer1hz = hz * singlemul; + while (timer1hz < (profiling_on ? profhz : stathz)) + timer1hz += hz; + timer2hz = 0; + } else { + timer1hz = hz; + timer2hz = profiling_on ? profhz : stathz; + } + printf("Starting kernel event timers: %s @ %dHz, %s @ %dHz\n", + timer[0]->et_name, timer1hz, + timer[1] ? timer[1]->et_name : "NONE", timer2hz); + /* Restart event timers. */ + FREQ2BT(timer1hz, &timerperiod[0]); + configtimer(0); + if (timer[1]) { + timerticks[0] = 0; + timerticks[1] = 0; + FREQ2BT(timer2hz, &timerperiod[1]); + configtimer(1); + timertest = 1; + } +} + +/* Switch to profiling clock rates. */ +void +cpu_startprofclock(void) +{ + + ET_LOCK(); + profiling_on = 1; + cpu_restartclocks(); + ET_UNLOCK(); +} + +/* Switch to regular clock rates. */ +void +cpu_stopprofclock(void) +{ + + ET_LOCK(); + profiling_on = 0; + cpu_restartclocks(); + ET_UNLOCK(); +} + +/* Report or change the active event timers hardware. */ +static int +sysctl_kern_eventtimer_timer1(SYSCTL_HANDLER_ARGS) +{ + char buf[32]; + struct eventtimer *et; + int error; + + ET_LOCK(); + et = timer[0]; + snprintf(buf, sizeof(buf), "%s", et->et_name); + ET_UNLOCK(); + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + ET_LOCK(); + et = timer[0]; + if (error != 0 || req->newptr == NULL || + strcmp(buf, et->et_name) == 0) { + ET_UNLOCK(); + return (error); + } + et = et_find(buf, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + if (et == NULL) { + ET_UNLOCK(); + return (ENOENT); + } + timer1hz = 0; + configtimer(0); + et_free(timer[0]); + timer[0] = et; + et_init(timer[0], timer1cb, NULL, NULL); + cpu_restartclocks(); + ET_UNLOCK(); + return (error); +} +SYSCTL_PROC(_kern_eventtimer, OID_AUTO, timer1, + CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + 0, 0, sysctl_kern_eventtimer_timer1, "A", "Primary event timer"); + +static int +sysctl_kern_eventtimer_timer2(SYSCTL_HANDLER_ARGS) +{ + char buf[32]; + struct eventtimer *et; + int error; + + ET_LOCK(); + et = timer[1]; + if (et == NULL) + snprintf(buf, sizeof(buf), "NONE"); + else + snprintf(buf, sizeof(buf), "%s", et->et_name); + ET_UNLOCK(); + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + ET_LOCK(); + et = timer[1]; + if (error != 0 || req->newptr == NULL || + strcmp(buf, et ? et->et_name : "NONE") == 0) { + ET_UNLOCK(); + return (error); + } + et = et_find(buf, ET_FLAGS_PERIODIC, ET_FLAGS_PERIODIC); + if (et == NULL && strcasecmp(buf, "NONE") != 0) { + ET_UNLOCK(); + return (ENOENT); + } + if (timer[1] != NULL) { + timer2hz = 0; + configtimer(1); + et_free(timer[1]); + } + timer[1] = et; + if (timer[1] != NULL) + et_init(timer[1], timer2cb, NULL, NULL); + cpu_restartclocks(); + ET_UNLOCK(); + return (error); +} +SYSCTL_PROC(_kern_eventtimer, OID_AUTO, timer2, + CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + 0, 0, sysctl_kern_eventtimer_timer2, "A", "Secondary event timer"); + +#endif + |