/* $NetBSD: lockstat.c,v 1.10 2019/02/12 14:31:45 rin Exp $ */
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: lockstat.c,v 1.10 2019/02/12 14:31:45 rin Exp $");
#include <sys/types.h>
#include <sys/proc.h>
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/dtrace.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <sys/xcall.h>
#include <sys/atomic.h>
#define NLOCKSTAT 1
#include <dev/lockstat.h>
typedef struct lockstat_probe {
const char *lsp_func;
const char *lsp_name;
int lsp_probe;
dtrace_id_t lsp_id;
} lockstat_probe_t;
lockstat_probe_t lockstat_probes[] = {
{ "mutex_spin", "spin", LB_SPIN_MUTEX | LB_SPIN, 0 },
{ "mutex_adaptive", "sleep", LB_ADAPTIVE_MUTEX | LB_SLEEP1, 0 },
{ "mutex_adaptive", "spin", LB_ADAPTIVE_MUTEX | LB_SPIN, 0 },
{ "rwlock", "sleep_writer", LB_RWLOCK | LB_SLEEP1, 0 },
{ "rwlock", "sleep_reader", LB_RWLOCK | LB_SLEEP2, 0 },
{ "rwlock", "spin", LB_RWLOCK | LB_SPIN, 0 },
{ "kernel", "spin", LB_KERNEL_LOCK | LB_SPIN, 0 },
{ "lwp", "spin", LB_NOPREEMPT | LB_SPIN, 0 },
{ NULL, NULL, 0, 0 },
};
static dtrace_provider_id_t lockstat_id;
static size_t lockstat_dtrace_count;
/*ARGSUSED*/
static int
lockstat_enable(void *arg, dtrace_id_t id, void *parg)
{
lockstat_probe_t *probe = parg;
ASSERT(!lockstat_probemap[LS_COMPRESS(probe->lsp_probe)]);
lockstat_probemap[LS_COMPRESS(probe->lsp_probe)] = id;
if (lockstat_dtrace_count++ == 0) {
lockstat_dtrace_enabled = LB_DTRACE;
LOCKSTAT_ENABLED_UPDATE();
}
return 0;
}
/*ARGSUSED*/
static void
lockstat_disable(void *arg, dtrace_id_t id __unused, void *parg)
{
lockstat_probe_t *probe = parg;
ASSERT(lockstat_probemap[LS_COMPRESS(probe->lsp_probe)]);
if (--lockstat_dtrace_count == 0) {
lockstat_dtrace_enabled = 0;
LOCKSTAT_ENABLED_UPDATE();
}
lockstat_probemap[LS_COMPRESS(probe->lsp_probe)] = 0;
}
/*ARGSUSED*/
static void
lockstat_provide(void *arg, dtrace_probedesc_t *desc)
{
int i = 0;
for (i = 0; lockstat_probes[i].lsp_func != NULL; i++) {
lockstat_probe_t *probe = &lockstat_probes[i];
if (dtrace_probe_lookup(lockstat_id, __UNCONST("kernel"),
__UNCONST(probe->lsp_func), __UNCONST(probe->lsp_name))
!= 0)
continue;
ASSERT(!probe->lsp_id);
probe->lsp_id = dtrace_probe_create(lockstat_id,
__UNCONST("kernel"), __UNCONST(probe->lsp_func),
__UNCONST(probe->lsp_name), 1, probe);
}
}
/*ARGSUSED*/
static void
lockstat_destroy(void *arg, dtrace_id_t id, void *parg)
{
lockstat_probe_t *probe = parg;
ASSERT(!lockstat_probemap[probe->lsp_probe]);
probe->lsp_id = 0;
}
static dtrace_pattr_t lockstat_attr = {
{ DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON },
{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
{ DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN },
{ DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON },
{ DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON },
};
static dtrace_pops_t lockstat_pops = {
lockstat_provide,
NULL,
lockstat_enable,
lockstat_disable,
NULL,
NULL,
NULL,
NULL,
NULL,
lockstat_destroy
};
static void
lockstat_barrier_xc(void *arg0 __unused, void *arg1 __unused)
{
membar_consumer();
}
typedef void (*dtrace_probe_func_t)(dtrace_id_t, uintptr_t, uintptr_t,
uintptr_t, uintptr_t, uintptr_t);
static bool
lockstat_cas_probe(dtrace_probe_func_t old, dtrace_probe_func_t new)
{
ASSERT(kernconfig_is_held());
if (lockstat_probe_func != old)
return false;
lockstat_probe_func = new;
membar_producer();
xc_wait(xc_broadcast(0, lockstat_barrier_xc, NULL, NULL));
return true;
}
static int
lockstat_init(void)
{
int error;
bool ok;
/* Install the probe function. */
ok = lockstat_cas_probe(lockstat_probe_stub, dtrace_probe);
if (!ok) {
printf("dtrace_lockstat: lockstat probe already installed\n");
error = EEXIST;
goto fail0;
}
/* Everything is in place. Register a dtrace provider. */
ASSERT(lockstat_id == 0);
error = dtrace_register("lockstat", &lockstat_attr, DTRACE_PRIV_USER,
NULL, &lockstat_pops, NULL, &lockstat_id);
if (error) {
printf("dtrace_lockstat: failed to register dtrace provider"
": %d\n", error);
goto fail1;
}
/* Success! */
return 0;
fail1: ok = lockstat_cas_probe(dtrace_probe, lockstat_probe_stub);
ASSERT(ok);
fail0: ASSERT(error);
return error;
}
static int
lockstat_fini(void)
{
int error;
bool ok __debugused;
error = dtrace_unregister(lockstat_id);
if (error) {
printf("dtrace_lockstat: failed to unregister dtrace provider"
": %d\n",
error);
return error;
}
/* Unhook the probe. */
ok = lockstat_cas_probe(dtrace_probe, lockstat_probe_stub);
ASSERT(ok);
/* Success! */
return 0;
}
static int
dtrace_lockstat_modcmd(modcmd_t cmd, void *data)
{
switch (cmd) {
case MODULE_CMD_INIT:
return lockstat_init();
case MODULE_CMD_FINI:
return lockstat_fini();
default:
return ENOTTY;
}
}
MODULE(MODULE_CLASS_MISC, dtrace_lockstat, "dtrace");