freebsd-nq/sys/kern/kern_et.c
Alexander Motin 875b8844be 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.
2010-06-20 21:33:29 +00:00

233 lines
6.0 KiB
C

/*-
* 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");