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:
parent
976cc6975b
commit
875b8844be
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=209371
@ -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);
|
||||
|
||||
|
@ -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_ */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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_ */
|
||||
|
@ -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
|
||||
|
@ -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
232
sys/kern/kern_et.c
Normal 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");
|
||||
|
@ -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)
|
||||
|
@ -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
103
sys/sys/timeet.h
Normal 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_ */
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
509
sys/x86/x86/timeevents.c
Normal 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
|
||||
|
Loading…
Reference in New Issue
Block a user