Changeset View
Changeset View
Standalone View
Standalone View
sys/kern/kern_tc.c
Show First 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | static struct timehands ths[16] = { | ||||
.th_generation = 1, | .th_generation = 1, | ||||
}, | }, | ||||
}; | }; | ||||
static struct timehands *volatile timehands = &ths[0]; | static struct timehands *volatile timehands = &ths[0]; | ||||
struct timecounter *timecounter = &dummy_timecounter; | struct timecounter *timecounter = &dummy_timecounter; | ||||
static struct timecounter *timecounters = &dummy_timecounter; | static struct timecounter *timecounters = &dummy_timecounter; | ||||
/* Mutex to protect the timecounter list. */ | |||||
static struct mtx tc_lock; | |||||
MTX_SYSINIT(tc_lock, &tc_lock, "tc", MTX_DEF); | |||||
int tc_min_ticktock_freq = 1; | int tc_min_ticktock_freq = 1; | ||||
volatile time_t time_second = 1; | volatile time_t time_second = 1; | ||||
volatile time_t time_uptime = 1; | volatile time_t time_uptime = 1; | ||||
/* | /* | ||||
* The system time is always computed by summing the estimated boot time and the | * The system time is always computed by summing the estimated boot time and the | ||||
* system uptime. The timehands track boot time, but it changes when the system | * system uptime. The timehands track boot time, but it changes when the system | ||||
▲ Show 20 Lines • Show All 1,075 Lines • ▼ Show 20 Lines | if (bootverbose) { | ||||
printf(" -- Insufficient hz, needs at least %u\n", u); | printf(" -- Insufficient hz, needs at least %u\n", u); | ||||
} | } | ||||
} else if (tc->tc_quality >= 0 || bootverbose) { | } else if (tc->tc_quality >= 0 || bootverbose) { | ||||
printf("Timecounter \"%s\" frequency %ju Hz quality %d\n", | printf("Timecounter \"%s\" frequency %ju Hz quality %d\n", | ||||
tc->tc_name, (uintmax_t)tc->tc_frequency, | tc->tc_name, (uintmax_t)tc->tc_frequency, | ||||
tc->tc_quality); | tc->tc_quality); | ||||
} | } | ||||
tc->tc_next = timecounters; | |||||
timecounters = tc; | |||||
/* | /* | ||||
* Set up sysctl tree for this counter. | * Set up sysctl tree for this counter. | ||||
*/ | */ | ||||
tc_root = SYSCTL_ADD_NODE_WITH_LABEL(NULL, | tc_root = SYSCTL_ADD_NODE_WITH_LABEL(NULL, | ||||
SYSCTL_STATIC_CHILDREN(_kern_timecounter_tc), OID_AUTO, tc->tc_name, | SYSCTL_STATIC_CHILDREN(_kern_timecounter_tc), OID_AUTO, tc->tc_name, | ||||
CTLFLAG_RW | CTLFLAG_MPSAFE, 0, | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, | ||||
"timecounter description", "timecounter"); | "timecounter description", "timecounter"); | ||||
SYSCTL_ADD_UINT(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | SYSCTL_ADD_UINT(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | ||||
"mask", CTLFLAG_RD, &(tc->tc_counter_mask), 0, | "mask", CTLFLAG_RD, &(tc->tc_counter_mask), 0, | ||||
"mask for implemented bits"); | "mask for implemented bits"); | ||||
SYSCTL_ADD_PROC(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | SYSCTL_ADD_PROC(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | ||||
"counter", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, tc, | "counter", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, tc, | ||||
sizeof(*tc), sysctl_kern_timecounter_get, "IU", | sizeof(*tc), sysctl_kern_timecounter_get, "IU", | ||||
"current timecounter value"); | "current timecounter value"); | ||||
SYSCTL_ADD_PROC(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | SYSCTL_ADD_PROC(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | ||||
"frequency", CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE, tc, | "frequency", CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_MPSAFE, tc, | ||||
sizeof(*tc), sysctl_kern_timecounter_freq, "QU", | sizeof(*tc), sysctl_kern_timecounter_freq, "QU", | ||||
"timecounter frequency"); | "timecounter frequency"); | ||||
SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | SYSCTL_ADD_INT(NULL, SYSCTL_CHILDREN(tc_root), OID_AUTO, | ||||
"quality", CTLFLAG_RD, &(tc->tc_quality), 0, | "quality", CTLFLAG_RD, &(tc->tc_quality), 0, | ||||
"goodness of time counter"); | "goodness of time counter"); | ||||
mtx_lock(&tc_lock); | |||||
tc->tc_next = timecounters; | |||||
timecounters = tc; | |||||
/* | /* | ||||
* Do not automatically switch if the current tc was specifically | * Do not automatically switch if the current tc was specifically | ||||
* chosen. Never automatically use a timecounter with negative quality. | * chosen. Never automatically use a timecounter with negative quality. | ||||
* Even though we run on the dummy counter, switching here may be | * Even though we run on the dummy counter, switching here may be | ||||
* worse since this timecounter may not be monotonic. | * worse since this timecounter may not be monotonic. | ||||
*/ | */ | ||||
if (tc_chosen) | if (tc_chosen) | ||||
return; | goto unlock; | ||||
if (tc->tc_quality < 0) | if (tc->tc_quality < 0) | ||||
return; | goto unlock; | ||||
if (tc_from_tunable[0] != '\0' && | if (tc_from_tunable[0] != '\0' && | ||||
strcmp(tc->tc_name, tc_from_tunable) == 0) { | strcmp(tc->tc_name, tc_from_tunable) == 0) { | ||||
tc_chosen = 1; | tc_chosen = 1; | ||||
tc_from_tunable[0] = '\0'; | tc_from_tunable[0] = '\0'; | ||||
} else { | } else { | ||||
if (tc->tc_quality < timecounter->tc_quality) | if (tc->tc_quality < timecounter->tc_quality) | ||||
return; | goto unlock; | ||||
if (tc->tc_quality == timecounter->tc_quality && | if (tc->tc_quality == timecounter->tc_quality && | ||||
tc->tc_frequency < timecounter->tc_frequency) | tc->tc_frequency < timecounter->tc_frequency) | ||||
return; | goto unlock; | ||||
} | } | ||||
(void)tc->tc_get_timecount(tc); | (void)tc->tc_get_timecount(tc); | ||||
timecounter = tc; | timecounter = tc; | ||||
unlock: | |||||
mtx_unlock(&tc_lock); | |||||
} | } | ||||
/* Report the frequency of the current timecounter. */ | /* Report the frequency of the current timecounter. */ | ||||
uint64_t | uint64_t | ||||
tc_getfrequency(void) | tc_getfrequency(void) | ||||
{ | { | ||||
return (timehands->th_counter->tc_frequency); | return (timehands->th_counter->tc_frequency); | ||||
▲ Show 20 Lines • Show All 229 Lines • ▼ Show 20 Lines | |||||
/* Report or change the active timecounter hardware. */ | /* Report or change the active timecounter hardware. */ | ||||
static int | static int | ||||
sysctl_kern_timecounter_hardware(SYSCTL_HANDLER_ARGS) | sysctl_kern_timecounter_hardware(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
char newname[32]; | char newname[32]; | ||||
struct timecounter *newtc, *tc; | struct timecounter *newtc, *tc; | ||||
int error; | int error; | ||||
mtx_lock(&tc_lock); | |||||
tc = timecounter; | tc = timecounter; | ||||
strlcpy(newname, tc->tc_name, sizeof(newname)); | strlcpy(newname, tc->tc_name, sizeof(newname)); | ||||
mtx_unlock(&tc_lock); | |||||
error = sysctl_handle_string(oidp, &newname[0], sizeof(newname), req); | error = sysctl_handle_string(oidp, &newname[0], sizeof(newname), req); | ||||
if (error != 0 || req->newptr == NULL) | if (error != 0 || req->newptr == NULL) | ||||
return (error); | return (error); | ||||
mtx_lock(&tc_lock); | |||||
/* Record that the tc in use now was specifically chosen. */ | /* Record that the tc in use now was specifically chosen. */ | ||||
tc_chosen = 1; | tc_chosen = 1; | ||||
if (strcmp(newname, tc->tc_name) == 0) | if (strcmp(newname, tc->tc_name) == 0) { | ||||
mtx_unlock(&tc_lock); | |||||
return (0); | return (0); | ||||
} | |||||
for (newtc = timecounters; newtc != NULL; newtc = newtc->tc_next) { | for (newtc = timecounters; newtc != NULL; newtc = newtc->tc_next) { | ||||
if (strcmp(newname, newtc->tc_name) != 0) | if (strcmp(newname, newtc->tc_name) != 0) | ||||
continue; | continue; | ||||
/* Warm up new timecounter. */ | /* Warm up new timecounter. */ | ||||
(void)newtc->tc_get_timecount(newtc); | (void)newtc->tc_get_timecount(newtc); | ||||
timecounter = newtc; | timecounter = newtc; | ||||
/* | /* | ||||
* The vdso timehands update is deferred until the next | * The vdso timehands update is deferred until the next | ||||
* 'tc_windup()'. | * 'tc_windup()'. | ||||
* | * | ||||
* This is prudent given that 'timekeep_push_vdso()' does not | * This is prudent given that 'timekeep_push_vdso()' does not | ||||
* use any locking and that it can be called in hard interrupt | * use any locking and that it can be called in hard interrupt | ||||
* context via 'tc_windup()'. | * context via 'tc_windup()'. | ||||
*/ | */ | ||||
return (0); | break; | ||||
} | } | ||||
return (EINVAL); | mtx_unlock(&tc_lock); | ||||
return (newtc != NULL ? 0 : EINVAL); | |||||
} | } | ||||
SYSCTL_PROC(_kern_timecounter, OID_AUTO, hardware, | SYSCTL_PROC(_kern_timecounter, OID_AUTO, hardware, | ||||
CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_NOFETCH | CTLFLAG_MPSAFE, 0, 0, | CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_NOFETCH | CTLFLAG_MPSAFE, 0, 0, | ||||
sysctl_kern_timecounter_hardware, "A", | sysctl_kern_timecounter_hardware, "A", | ||||
"Timecounter hardware selected"); | "Timecounter hardware selected"); | ||||
/* Report the available timecounter hardware. */ | /* Report the available timecounter hardware. */ | ||||
static int | static int | ||||
sysctl_kern_timecounter_choice(SYSCTL_HANDLER_ARGS) | sysctl_kern_timecounter_choice(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
struct sbuf sb; | struct sbuf sb; | ||||
struct timecounter *tc; | struct timecounter *tc; | ||||
int error; | int error; | ||||
error = sysctl_wire_old_buffer(req, 0); | |||||
if (error != 0) | |||||
return (error); | |||||
sbuf_new_for_sysctl(&sb, NULL, 0, req); | sbuf_new_for_sysctl(&sb, NULL, 0, req); | ||||
mtx_lock(&tc_lock); | |||||
for (tc = timecounters; tc != NULL; tc = tc->tc_next) { | for (tc = timecounters; tc != NULL; tc = tc->tc_next) { | ||||
if (tc != timecounters) | if (tc != timecounters) | ||||
sbuf_putc(&sb, ' '); | sbuf_putc(&sb, ' '); | ||||
sbuf_printf(&sb, "%s(%d)", tc->tc_name, tc->tc_quality); | sbuf_printf(&sb, "%s(%d)", tc->tc_name, tc->tc_quality); | ||||
kib: Doesn't drain function for sysctl sbufs do copyout? It might do it over the wired userspace… | |||||
Done Inline ActionsThe pages should indeed be wired. Certainly spurious faults are possible, but isn't it legal to hold a mutex there nonetheless? I thought that was the whole point of sbuf_new_for_sysctl(): to permit non-sleepable locks to be held while writing to the output buffer. markj: The pages should indeed be wired. Certainly spurious faults are possible, but isn't it legal to… | |||||
Done Inline ActionsYes, it uses copyout_nofault() in the end. So tc_lock suddently appears before pmap locks on some arches, ok. kib: Yes, it uses copyout_nofault() in the end. So tc_lock suddently appears before pmap locks on… | |||||
Done Inline ActionsYou are right though that an explicit sysctl_wire_old_buffer() call is needed. I thought sbuf_new_for_sysctl() handled it. markj: You are right though that an explicit sysctl_wire_old_buffer() call is needed. I thought… | |||||
} | } | ||||
mtx_unlock(&tc_lock); | |||||
error = sbuf_finish(&sb); | error = sbuf_finish(&sb); | ||||
sbuf_delete(&sb); | sbuf_delete(&sb); | ||||
return (error); | return (error); | ||||
} | } | ||||
SYSCTL_PROC(_kern_timecounter, OID_AUTO, choice, | SYSCTL_PROC(_kern_timecounter, OID_AUTO, choice, | ||||
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, 0, 0, | CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, 0, 0, | ||||
sysctl_kern_timecounter_choice, "A", | sysctl_kern_timecounter_choice, "A", | ||||
▲ Show 20 Lines • Show All 694 Lines • Show Last 20 Lines |
Doesn't drain function for sysctl sbufs do copyout? It might do it over the wired userspace memory, but I do not think it would help against pagefaults while owning the mutex.