diff options
author | mav <mav@FreeBSD.org> | 2010-06-20 21:33:29 +0000 |
---|---|---|
committer | mav <mav@FreeBSD.org> | 2010-06-20 21:33:29 +0000 |
commit | d1175426d7bcb8aeacdb7416e281e63e668da297 (patch) | |
tree | 0950027f8a4d7f49ad3b3ae792b390e08763c5c5 | |
parent | 75f3660605fcdf678edb8498311bba6ec652af82 (diff) | |
download | FreeBSD-src-d1175426d7bcb8aeacdb7416e281e63e668da297.zip FreeBSD-src-d1175426d7bcb8aeacdb7416e281e63e668da297.tar.gz |
Implement new event timers infrastructure. It provides unified APIs for
writing event timer drivers, for choosing best possible drivers by machine
independent code and for operating them to supply kernel with hardclock(),
statclock() and profclock() events in unified fashion on various hardware.
Infrastructure provides support for both per-CPU (independent for every CPU
core) and global timers in periodic and one-shot modes. MI management code
at this moment uses only periodic mode, but one-shot mode use planned for
later, as part of tickless kernel project.
For this moment infrastructure used on i386 and amd64 architectures. Other
archs are welcome to follow, while their current operation should not be
affected.
This patch updates existing drivers (i8254, RTC and LAPIC) for the new
order, and adds event timers support into the HPET driver. These drivers
have different capabilities:
LAPIC - per-CPU timer, supports periodic and one-shot operation, may
freeze in C3 state, calibrated on first use, so may be not exactly precise.
HPET - depending on hardware can work as per-CPU or global, supports
periodic and one-shot operation, usually provides several event timers.
i8254 - global, limited to periodic mode, because same hardware used also
as time counter.
RTC - global, supports only periodic mode, set of frequencies in Hz
limited by powers of 2.
Depending on hardware capabilities, drivers preferred in following orders,
either LAPIC, HPETs, i8254, RTC or HPETs, LAPIC, i8254, RTC.
User may explicitly specify wanted timers via loader tunables or sysctls:
kern.eventtimer.timer1 and kern.eventtimer.timer2.
If requested driver is unavailable or unoperational, system will try to
replace it. If no more timers available or "NONE" specified for second,
system will operate using only one timer, multiplying it's frequency by few
times and uing respective dividers to honor hz, stathz and profhz values,
set during initial setup.
-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 + |