timecounter: Lock the timecounter list

Timecounter registration is dynamic, i.e., there is no requirement that
timecounters must be registered during single-threaded boot.  Loadable
drivers may in principle register timecounters (which can be switched to
automatically).  Timecounters cannot be unregistered, though this could
be implemented.

Registered timecounters belong to a global linked list.  Add a mutex to
synchronize insertions and the traversals done by (mpsafe) sysctl
handlers.  No functional change intended.

Reviewed by:	imp, kib
MFC after:	2 weeks
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D32511
This commit is contained in:
Mark Johnston 2021-10-16 09:46:55 -04:00
parent 06ebadc5f5
commit 621fd9dcb2

View File

@ -97,6 +97,10 @@ 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;
@ -1188,8 +1192,6 @@ tc_init(struct timecounter *tc)
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.
*/ */
@ -1211,6 +1213,11 @@ tc_init(struct timecounter *tc)
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.
@ -1218,22 +1225,24 @@ tc_init(struct timecounter *tc)
* 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. */
@ -1479,16 +1488,22 @@ sysctl_kern_timecounter_hardware(SYSCTL_HANDLER_ARGS)
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;
@ -1506,11 +1521,11 @@ sysctl_kern_timecounter_hardware(SYSCTL_HANDLER_ARGS)
* 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",
@ -1524,12 +1539,17 @@ sysctl_kern_timecounter_choice(SYSCTL_HANDLER_ARGS)
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);
} }
mtx_unlock(&tc_lock);
error = sbuf_finish(&sb); error = sbuf_finish(&sb);
sbuf_delete(&sb); sbuf_delete(&sb);
return (error); return (error);