/*-
* Copyright (c) 2006, 2007, 2019 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* 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.
* 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.
*/
/*
* Lock statistics driver, providing kernel support for the lockstat(8)
* command.
*
* We use a global lock word (lockstat_lock) to track device opens.
* Only one thread can hold the device at a time, providing a global lock.
*
* XXX Timings for contention on sleep locks are currently incorrect.
* XXX Convert this to use timecounters!
*/
/*
* Prepare the per-CPU tables for use, or clear down tables when tracing is
* stopped.
*/
void
lockstat_init_tables(lsenable_t *le)
{
int i, per, slop, cpuno;
CPU_INFO_ITERATOR cii;
struct cpu_info *ci;
lscpu_t *lc;
lsbuf_t *lb;
/*
* Ensure everything is initialized on all CPUs, by issuing a
* null xcall with the side effect of a release barrier on this
* CPU and an acquire barrier on all other CPUs, before they
* can witness any flags set in lockstat_dev_enabled -- this
* way we don't need to add any barriers in lockstat_event.
*/
xc_barrier(0);
/*
* Start timing after the xcall, so we don't spuriously count
* xcall communication time, but before flipping the switch, so
* we don't dirty sample with locks taken in the timecounter.
*/
getnanotime(&lockstat_stime);
/*
* Disable and wait for other CPUs to exit lockstat_event().
*/
LOCKSTAT_ENABLED_UPDATE_BEGIN();
atomic_store_relaxed(&lockstat_dev_enabled, 0);
LOCKSTAT_ENABLED_UPDATE_END();
getnanotime(&ts);
xc_barrier(0);
/*
* Did we run out of buffers while tracing?
*/
overflow = 0;
for (CPU_INFO_FOREACH(cii, ci))
overflow += ((lscpu_t *)ci->ci_lockstat)->lc_overflow;
/* Run through all LWPs and clear the slate for the next run. */
mutex_enter(&proc_lock);
LIST_FOREACH(l, &alllwp, l_list) {
l->l_pfailaddr = 0;
l->l_pfailtime = 0;
l->l_pfaillock = 0;
}
mutex_exit(&proc_lock);
if (ld == NULL)
return error;
/*
* Fill out the disable struct for the caller.
*/
timespecsub(&ts, &lockstat_stime, &ld->ld_time);
ld->ld_size = lockstat_sizeb;
cpuno = 0;
for (CPU_INFO_FOREACH(cii, ci)) {
if (cpuno >= sizeof(ld->ld_freq) / sizeof(ld->ld_freq[0])) {
log(LOG_WARNING, "lockstat: too many CPUs\n");
break;
}
ld->ld_freq[cpuno++] = cpu_frequency(ci);
}
return error;
}
/*
* Allocate buffers for lockstat_start().
*/
int
lockstat_alloc(lsenable_t *le)
{
lsbuf_t *lb;
size_t sz;
/*
* Find the table for this lock+callsite pair, and try to locate a
* buffer with the same key.
*/
s = splhigh();
lc = curcpu()->ci_lockstat;
ll = &lc->lc_hash[LOCKSTAT_HASH(lock ^ callsite)];
event = (flags & LB_EVENT_MASK) - 1;
if (lb != NULL) {
/*
* We found a record. Move it to the front of the list, as
* we're likely to hit it again soon.
*/
if (lb != LIST_FIRST(ll)) {
LIST_REMOVE(lb, lb_chain.list);
LIST_INSERT_HEAD(ll, lb, lb_chain.list);
}
lb->lb_counts[event] += count;
lb->lb_times[event] += cycles;
} else if ((lb = SLIST_FIRST(&lc->lc_free)) != NULL) {
/*
* Pinch a new buffer and fill it out.
*/
SLIST_REMOVE_HEAD(&lc->lc_free, lb_chain.slist);
LIST_INSERT_HEAD(ll, lb, lb_chain.list);
lb->lb_flags = (uint16_t)flags;
lb->lb_lock = lock;
lb->lb_callsite = callsite;
lb->lb_counts[event] = count;
lb->lb_times[event] = cycles;
} else {
/*
* We didn't find a buffer and there were none free.
* lockstat_stop() will notice later on and report the
* error.
*/
lc->lc_overflow++;
}
splx(s);
}
/*
* Accept an open() on /dev/lockstat.
*/
int
lockstat_open(dev_t dev, int flag, int mode, lwp_t *l)
{
if (!__cpu_simple_lock_try(&lockstat_lock))
return EBUSY;
lockstat_lwp = curlwp;
return 0;
}
/*
* Accept the last close() on /dev/lockstat.
*/
int
lockstat_close(dev_t dev, int flag, int mode, lwp_t *l)
{