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.
This commit is contained in:
Alexander Motin 2010-06-20 21:33:29 +00:00
parent 976cc6975b
commit 875b8844be
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=209371
19 changed files with 1662 additions and 495 deletions

View File

@ -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);

View File

@ -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_ */

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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_ */

View File

@ -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

View File

@ -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);
}
}

232
sys/kern/kern_et.c Normal file
View File

@ -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");

View File

@ -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,44 +421,29 @@ 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)

View File

@ -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 */

103
sys/sys/timeet.h Normal file
View File

@ -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_ */

View File

@ -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);
}

View File

@ -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,28 +416,32 @@ 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);
}
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;

View File

@ -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);
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;
}
/* 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;
}
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();
}
@ -837,6 +843,17 @@ lapic_timer_periodic(u_int count)
lapic->icr_timer = 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)
{

509
sys/x86/x86/timeevents.c Normal file
View File

@ -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