aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/hwpmc/hwpmc_mod.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/hwpmc/hwpmc_mod.c')
-rw-r--r--sys/dev/hwpmc/hwpmc_mod.c2149
1 files changed, 1265 insertions, 884 deletions
diff --git a/sys/dev/hwpmc/hwpmc_mod.c b/sys/dev/hwpmc/hwpmc_mod.c
index 36e4761adb4e..962c15b44e94 100644
--- a/sys/dev/hwpmc/hwpmc_mod.c
+++ b/sys/dev/hwpmc/hwpmc_mod.c
@@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$");
#include <sys/eventhandler.h>
#include <sys/jail.h>
#include <sys/kernel.h>
+#include <sys/kthread.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/malloc.h>
@@ -39,8 +40,10 @@ __FBSDID("$FreeBSD$");
#include <sys/mutex.h>
#include <sys/pmc.h>
#include <sys/pmckern.h>
+#include <sys/pmclog.h>
#include <sys/proc.h>
#include <sys/queue.h>
+#include <sys/resourcevar.h>
#include <sys/sched.h>
#include <sys/signalvar.h>
#include <sys/smp.h>
@@ -48,7 +51,9 @@ __FBSDID("$FreeBSD$");
#include <sys/sysctl.h>
#include <sys/sysent.h>
#include <sys/systm.h>
+#include <sys/vnode.h>
+#include <machine/atomic.h>
#include <machine/md_var.h>
/*
@@ -135,6 +140,13 @@ static u_long pmc_ownerhashmask;
static LIST_HEAD(pmc_ownerhash, pmc_owner) *pmc_ownerhash;
/*
+ * List of PMC owners with system-wide sampling PMCs.
+ */
+
+static LIST_HEAD(, pmc_owner) pmc_ss_owners;
+
+
+/*
* Prototypes
*/
@@ -144,54 +156,54 @@ static int pmc_debugflags_parse(char *newstr, char *fence);
#endif
static int load(struct module *module, int cmd, void *arg);
-static int pmc_syscall_handler(struct thread *td, void *syscall_args);
-static int pmc_configure_log(struct pmc_owner *po, int logfd);
-static void pmc_log_process_exit(struct pmc *pm, struct pmc_process *pp);
+static int pmc_attach_process(struct proc *p, struct pmc *pm);
static struct pmc *pmc_allocate_pmc_descriptor(void);
-static struct pmc *pmc_find_pmc_descriptor_in_process(struct pmc_owner *po,
- pmc_id_t pmc);
-static void pmc_release_pmc_descriptor(struct pmc *pmc);
+static struct pmc_owner *pmc_allocate_owner_descriptor(struct proc *p);
+static int pmc_attach_one_process(struct proc *p, struct pmc *pm);
static int pmc_can_allocate_rowindex(struct proc *p, unsigned int ri,
int cpu);
-static struct pmc_process *pmc_find_process_descriptor(struct proc *p,
- uint32_t mode);
-static void pmc_remove_process_descriptor(struct pmc_process *pp);
+static int pmc_can_attach(struct pmc *pm, struct proc *p);
+static void pmc_cleanup(void);
+static int pmc_detach_process(struct proc *p, struct pmc *pm);
+static int pmc_detach_one_process(struct proc *p, struct pmc *pm,
+ int flags);
+static void pmc_destroy_owner_descriptor(struct pmc_owner *po);
static struct pmc_owner *pmc_find_owner_descriptor(struct proc *p);
static int pmc_find_pmc(pmc_id_t pmcid, struct pmc **pm);
+static struct pmc *pmc_find_pmc_descriptor_in_process(struct pmc_owner *po,
+ pmc_id_t pmc);
+static struct pmc_process *pmc_find_process_descriptor(struct proc *p,
+ uint32_t mode);
static void pmc_force_context_switch(void);
-static void pmc_remove_owner(struct pmc_owner *po);
-static void pmc_maybe_remove_owner(struct pmc_owner *po);
-static void pmc_unlink_target_process(struct pmc *pmc,
- struct pmc_process *pp);
static void pmc_link_target_process(struct pmc *pm,
struct pmc_process *pp);
-static void pmc_unlink_owner(struct pmc *pmc);
-static void pmc_cleanup(void);
-static void pmc_save_cpu_binding(struct pmc_binding *pb);
-static void pmc_restore_cpu_binding(struct pmc_binding *pb);
-static void pmc_select_cpu(int cpu);
+static void pmc_maybe_remove_owner(struct pmc_owner *po);
+static void pmc_process_csw_in(struct thread *td);
+static void pmc_process_csw_out(struct thread *td);
static void pmc_process_exit(void *arg, struct proc *p);
static void pmc_process_fork(void *arg, struct proc *p1,
struct proc *p2, int n);
-static int pmc_attach_one_process(struct proc *p, struct pmc *pm);
-static int pmc_attach_process(struct proc *p, struct pmc *pm);
-static int pmc_detach_one_process(struct proc *p, struct pmc *pm,
- int flags);
-static int pmc_detach_process(struct proc *p, struct pmc *pm);
+static void pmc_process_samples(int cpu);
+static void pmc_release_pmc_descriptor(struct pmc *pmc);
+static void pmc_remove_owner(struct pmc_owner *po);
+static void pmc_remove_process_descriptor(struct pmc_process *pp);
+static void pmc_restore_cpu_binding(struct pmc_binding *pb);
+static void pmc_save_cpu_binding(struct pmc_binding *pb);
+static void pmc_select_cpu(int cpu);
static int pmc_start(struct pmc *pm);
static int pmc_stop(struct pmc *pm);
-static int pmc_can_attach(struct pmc *pm, struct proc *p);
+static int pmc_syscall_handler(struct thread *td, void *syscall_args);
+static void pmc_unlink_target_process(struct pmc *pmc,
+ struct pmc_process *pp);
/*
* Kernel tunables and sysctl(8) interface.
*/
-#define PMC_SYSCTL_NAME_PREFIX "kern." PMC_MODULE_NAME "."
-
SYSCTL_NODE(_kern, OID_AUTO, hwpmc, CTLFLAG_RW, 0, "HWPMC parameters");
#if DEBUG
-unsigned int pmc_debugflags = PMC_DEBUG_DEFAULT_FLAGS;
+struct pmc_debugflags pmc_debugflags = PMC_DEBUG_DEFAULT_FLAGS;
char pmc_debugstr[PMC_DEBUG_STRSIZE];
TUNABLE_STR(PMC_SYSCTL_NAME_PREFIX "debugflags", pmc_debugstr,
sizeof(pmc_debugstr));
@@ -201,7 +213,7 @@ SYSCTL_PROC(_kern_hwpmc, OID_AUTO, debugflags,
#endif
/*
- * kern.pmc.hashrows -- determines the number of rows in the
+ * kern.hwpmc.hashrows -- determines the number of rows in the
* of the hash table used to look up threads
*/
@@ -211,17 +223,16 @@ SYSCTL_INT(_kern_hwpmc, OID_AUTO, hashsize, CTLFLAG_TUN|CTLFLAG_RD,
&pmc_hashsize, 0, "rows in hash tables");
/*
- * kern.pmc.pcpusize -- the size of each per-cpu
- * area for collection PC samples.
+ * kern.hwpmc.nsamples --- number of PC samples per CPU
*/
-static int pmc_pcpu_buffer_size = PMC_PCPU_BUFFER_SIZE;
-TUNABLE_INT(PMC_SYSCTL_NAME_PREFIX "pcpubuffersize", &pmc_pcpu_buffer_size);
-SYSCTL_INT(_kern_hwpmc, OID_AUTO, pcpubuffersize, CTLFLAG_TUN|CTLFLAG_RD,
- &pmc_pcpu_buffer_size, 0, "size of per-cpu buffer in 4K pages");
+static int pmc_nsamples = PMC_NSAMPLES;
+TUNABLE_INT(PMC_SYSCTL_NAME_PREFIX "nsamples", &pmc_nsamples);
+SYSCTL_INT(_kern_hwpmc, OID_AUTO, nsamples, CTLFLAG_TUN|CTLFLAG_RD,
+ &pmc_nsamples, 0, "number of PC samples per CPU");
/*
- * kern.pmc.mtxpoolsize -- number of mutexes in the mutex pool.
+ * kern.hwpmc.mtxpoolsize -- number of mutexes in the mutex pool.
*/
static int pmc_mtxpool_size = PMC_MTXPOOL_SIZE;
@@ -230,7 +241,6 @@ SYSCTL_INT(_kern_hwpmc, OID_AUTO, mtxpoolsize, CTLFLAG_TUN|CTLFLAG_RD,
&pmc_mtxpool_size, 0, "size of spin mutex pool");
-
/*
* security.bsd.unprivileged_syspmcs -- allow non-root processes to
* allocate system-wide PMCs.
@@ -248,11 +258,11 @@ SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_syspmcs, CTLFLAG_RW,
&pmc_unprivileged_syspmcs, 0,
"allow unprivileged process to allocate system PMCs");
-#if PMC_HASH_USE_CRC32
-
-#define PMC_HASH_PTR(P,M) (crc32(&(P), sizeof((P))) & (M))
-
-#else /* integer multiplication */
+/*
+ * Hash function. Discard the lower 2 bits of the pointer since
+ * these are always zero for our uses. The hash multiplier is
+ * round((2^LONG_BIT) * ((sqrt(5)-1)/2)).
+ */
#if LONG_BIT == 64
#define _PMC_HM 11400714819323198486u
@@ -262,16 +272,8 @@ SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_syspmcs, CTLFLAG_RW,
#error Must know the size of 'long' to compile
#endif
-/*
- * Hash function. Discard the lower 2 bits of the pointer since
- * these are always zero for our uses. The hash multiplier is
- * round((2^LONG_BIT) * ((sqrt(5)-1)/2)).
- */
-
#define PMC_HASH_PTR(P,M) ((((unsigned long) (P) >> 2) * _PMC_HM) & (M))
-#endif
-
/*
* Syscall structures
*/
@@ -300,84 +302,141 @@ DECLARE_MODULE(pmc, pmc_mod, SI_SUB_SMP, SI_ORDER_ANY);
MODULE_VERSION(pmc, PMC_VERSION);
#if DEBUG
+enum pmc_dbgparse_state {
+ PMCDS_WS, /* in whitespace */
+ PMCDS_MAJOR, /* seen a major keyword */
+ PMCDS_MINOR
+};
+
static int
pmc_debugflags_parse(char *newstr, char *fence)
{
char c, *p, *q;
- unsigned int tmpflags;
- int level;
- char tmpbuf[4]; /* 3 character keyword + '\0' */
+ struct pmc_debugflags *tmpflags;
+ int error, found, *newbits, tmp;
+ size_t kwlen;
- tmpflags = 0;
- level = 0xF; /* max verbosity */
+ MALLOC(tmpflags, struct pmc_debugflags *, sizeof(*tmpflags),
+ M_PMC, M_WAITOK|M_ZERO);
p = newstr;
+ error = 0;
- for (; p < fence && (c = *p);) {
+ for (; p < fence && (c = *p); p++) {
- /* skip separators */
- if (c == ' ' || c == '\t' || c == ',') {
- p++; continue;
+ /* skip white space */
+ if (c == ' ' || c == '\t')
+ continue;
+
+ /* look for a keyword followed by "=" */
+ for (q = p; p < fence && (c = *p) && c != '='; p++)
+ ;
+ if (c != '=') {
+ error = EINVAL;
+ goto done;
}
- (void) strlcpy(tmpbuf, p, sizeof(tmpbuf));
+ kwlen = p - q;
+ newbits = NULL;
+
+ /* lookup flag group name */
+#define DBG_SET_FLAG_MAJ(S,F) \
+ if (kwlen == sizeof(S)-1 && strncmp(q, S, kwlen) == 0) \
+ newbits = &tmpflags->pdb_ ## F;
+
+ DBG_SET_FLAG_MAJ("cpu", CPU);
+ DBG_SET_FLAG_MAJ("csw", CSW);
+ DBG_SET_FLAG_MAJ("logging", LOG);
+ DBG_SET_FLAG_MAJ("module", MOD);
+ DBG_SET_FLAG_MAJ("md", MDP);
+ DBG_SET_FLAG_MAJ("owner", OWN);
+ DBG_SET_FLAG_MAJ("pmc", PMC);
+ DBG_SET_FLAG_MAJ("process", PRC);
+ DBG_SET_FLAG_MAJ("sampling", SAM);
+
+ if (newbits == NULL) {
+ error = EINVAL;
+ goto done;
+ }
+
+ p++; /* skip the '=' */
+
+ /* Now parse the individual flags */
+ tmp = 0;
+ newflag:
+ for (q = p; p < fence && (c = *p); p++)
+ if (c == ' ' || c == '\t' || c == ',')
+ break;
+
+ /* p == fence or c == ws or c == "," or c == 0 */
-#define CMP_SET_FLAG_MAJ(S,F) \
- else if (strncmp(tmpbuf, S, 3) == 0) \
- tmpflags |= __PMCDFMAJ(F)
+ if ((kwlen = p - q) == 0) {
+ *newbits = tmp;
+ continue;
+ }
-#define CMP_SET_FLAG_MIN(S,F) \
- else if (strncmp(tmpbuf, S, 3) == 0) \
- tmpflags |= __PMCDFMIN(F)
+ found = 0;
+#define DBG_SET_FLAG_MIN(S,F) \
+ if (kwlen == sizeof(S)-1 && strncmp(q, S, kwlen) == 0) \
+ tmp |= found = (1 << PMC_DEBUG_MIN_ ## F)
+
+ /* a '*' denotes all possible flags in the group */
+ if (kwlen == 1 && *q == '*')
+ tmp = found = ~0;
+ /* look for individual flag names */
+ DBG_SET_FLAG_MIN("allocaterow", ALR);
+ DBG_SET_FLAG_MIN("allocate", ALL);
+ DBG_SET_FLAG_MIN("attach", ATT);
+ DBG_SET_FLAG_MIN("bind", BND);
+ DBG_SET_FLAG_MIN("config", CFG);
+ DBG_SET_FLAG_MIN("exec", EXC);
+ DBG_SET_FLAG_MIN("exit", EXT);
+ DBG_SET_FLAG_MIN("find", FND);
+ DBG_SET_FLAG_MIN("flush", FLS);
+ DBG_SET_FLAG_MIN("fork", FRK);
+ DBG_SET_FLAG_MIN("getbuf", GTB);
+ DBG_SET_FLAG_MIN("hook", PMH);
+ DBG_SET_FLAG_MIN("init", INI);
+ DBG_SET_FLAG_MIN("intr", INT);
+ DBG_SET_FLAG_MIN("linktarget", TLK);
+ DBG_SET_FLAG_MIN("mayberemove", OMR);
+ DBG_SET_FLAG_MIN("ops", OPS);
+ DBG_SET_FLAG_MIN("read", REA);
+ DBG_SET_FLAG_MIN("register", REG);
+ DBG_SET_FLAG_MIN("release", REL);
+ DBG_SET_FLAG_MIN("remove", ORM);
+ DBG_SET_FLAG_MIN("sample", SAM);
+ DBG_SET_FLAG_MIN("scheduleio", SIO);
+ DBG_SET_FLAG_MIN("select", SEL);
+ DBG_SET_FLAG_MIN("signal", SIG);
+ DBG_SET_FLAG_MIN("swi", SWI);
+ DBG_SET_FLAG_MIN("swo", SWO);
+ DBG_SET_FLAG_MIN("start", STA);
+ DBG_SET_FLAG_MIN("stop", STO);
+ DBG_SET_FLAG_MIN("syscall", PMS);
+ DBG_SET_FLAG_MIN("unlinktarget", TUL);
+ DBG_SET_FLAG_MIN("write", WRI);
+ if (found == 0) {
+ /* unrecognized flag name */
+ error = EINVAL;
+ goto done;
+ }
- if (fence - p > 6 && strncmp(p, "level=", 6) == 0) {
- p += 6; /* skip over keyword */
- level = strtoul(p, &q, 16);
+ if (c == 0 || c == ' ' || c == '\t') { /* end of flag group */
+ *newbits = tmp;
+ continue;
}
- CMP_SET_FLAG_MAJ("mod", MOD);
- CMP_SET_FLAG_MAJ("pmc", PMC);
- CMP_SET_FLAG_MAJ("ctx", CTX);
- CMP_SET_FLAG_MAJ("own", OWN);
- CMP_SET_FLAG_MAJ("prc", PRC);
- CMP_SET_FLAG_MAJ("mdp", MDP);
- CMP_SET_FLAG_MAJ("cpu", CPU);
-
- CMP_SET_FLAG_MIN("all", ALL);
- CMP_SET_FLAG_MIN("rel", REL);
- CMP_SET_FLAG_MIN("ops", OPS);
- CMP_SET_FLAG_MIN("ini", INI);
- CMP_SET_FLAG_MIN("fnd", FND);
- CMP_SET_FLAG_MIN("pmh", PMH);
- CMP_SET_FLAG_MIN("pms", PMS);
- CMP_SET_FLAG_MIN("orm", ORM);
- CMP_SET_FLAG_MIN("omr", OMR);
- CMP_SET_FLAG_MIN("tlk", TLK);
- CMP_SET_FLAG_MIN("tul", TUL);
- CMP_SET_FLAG_MIN("ext", EXT);
- CMP_SET_FLAG_MIN("exc", EXC);
- CMP_SET_FLAG_MIN("frk", FRK);
- CMP_SET_FLAG_MIN("att", ATT);
- CMP_SET_FLAG_MIN("swi", SWI);
- CMP_SET_FLAG_MIN("swo", SWO);
- CMP_SET_FLAG_MIN("reg", REG);
- CMP_SET_FLAG_MIN("alr", ALR);
- CMP_SET_FLAG_MIN("rea", REA);
- CMP_SET_FLAG_MIN("wri", WRI);
- CMP_SET_FLAG_MIN("cfg", CFG);
- CMP_SET_FLAG_MIN("sta", STA);
- CMP_SET_FLAG_MIN("sto", STO);
- CMP_SET_FLAG_MIN("int", INT);
- CMP_SET_FLAG_MIN("bnd", BND);
- CMP_SET_FLAG_MIN("sel", SEL);
- else /* unrecognized keyword */
- return EINVAL;
-
- p += 4; /* skip keyword and separator */
+
+ p++;
+ goto newflag;
}
- pmc_debugflags = (tmpflags|level);
+ /* save the new flag set */
+ bcopy(tmpflags, &pmc_debugflags, sizeof(pmc_debugflags));
- return 0;
+ done:
+ FREE(tmpflags, M_PMC);
+ return error;
}
static int
@@ -391,13 +450,13 @@ pmc_debugflags_sysctl_handler(SYSCTL_HANDLER_ARGS)
n = sizeof(pmc_debugstr);
MALLOC(newstr, char *, n, M_PMC, M_ZERO|M_WAITOK);
- (void) strlcpy(newstr, pmc_debugstr, sizeof(pmc_debugstr));
+ (void) strlcpy(newstr, pmc_debugstr, n);
error = sysctl_handle_string(oidp, newstr, n, req);
/* if there is a new string, parse and copy it */
if (error == 0 && req->newptr != NULL) {
- fence = newstr + (n < req->newlen ? n : req->newlen);
+ fence = newstr + (n < req->newlen ? n : req->newlen + 1);
if ((error = pmc_debugflags_parse(newstr, fence)) == 0)
(void) strlcpy(pmc_debugstr, newstr,
sizeof(pmc_debugstr));
@@ -597,53 +656,21 @@ pmc_force_context_switch(void)
}
/*
- * Update the per-pmc histogram
+ * Get the file name for an executable. This is a simple wrapper
+ * around vn_fullpath(9).
*/
-void
-pmc_update_histogram(struct pmc_hw *phw, uintptr_t pc)
-{
- (void) phw;
- (void) pc;
-}
-
-/*
- * Send a signal to a process. This is meant to be invoked from an
- * interrupt handler.
- */
-
-void
-pmc_send_signal(struct pmc *pmc)
+static void
+pmc_getprocname(struct proc *p, char **fullpath, char **freepath)
{
- (void) pmc; /* shutup gcc */
-
-#if 0
- struct proc *proc;
struct thread *td;
- KASSERT(pmc->pm_owner != NULL,
- ("[pmc,%d] No owner for PMC", __LINE__));
-
- KASSERT((pmc->pm_owner->po_flags & PMC_FLAG_IS_OWNER) &&
- (pmc->pm_owner->po_flags & PMC_FLAG_HAS_TS_PMC),
- ("[pmc,%d] interrupting PMC owner has wrong flags 0x%x",
- __LINE__, pmc->pm_owner->po_flags));
-
- proc = pmc->pm_owner->po_owner;
-
- KASSERT(curthread->td_proc == proc,
- ("[pmc,%d] interruping the wrong thread (owner %p, "
- "cur %p)", __LINE__, (void *) proc, curthread->td_proc));
-
- mtx_lock_spin(&sched_lock);
- td = TAILQ_FIRST(&proc->p_threads);
- mtx_unlock_spin(&sched_lock);
- /* XXX RACE HERE: can 'td' disappear now? */
- trapsignal(td, SIGPROF, 0);
- /* XXX rework this to use the regular 'psignal' interface from a
- helper thread */
-#endif
-
+ td = curthread;
+ *fullpath = "unknown";
+ *freepath = NULL;
+ vn_lock(p->p_textvp, LK_EXCLUSIVE | LK_RETRY, td);
+ vn_fullpath(td, p->p_textvp, fullpath, freepath);
+ VOP_UNLOCK(p->p_textvp, 0, td);
}
/*
@@ -653,7 +680,7 @@ pmc_send_signal(struct pmc *pmc)
void
pmc_remove_owner(struct pmc_owner *po)
{
- struct pmc_list *pl, *tmp;
+ struct pmc *pm, *tmp;
sx_assert(&pmc_sx, SX_XLOCKED);
@@ -662,42 +689,23 @@ pmc_remove_owner(struct pmc_owner *po)
/* Remove descriptor from the owner hash table */
LIST_REMOVE(po, po_next);
- /* pass 1: release all owned PMC descriptors */
- LIST_FOREACH_SAFE(pl, &po->po_pmcs, pl_next, tmp) {
-
- PMCDBG(OWN,ORM,2, "pl=%p pmc=%p", pl, pl->pl_pmc);
+ /* release all owned PMC descriptors */
+ LIST_FOREACH_SAFE(pm, &po->po_pmcs, pm_next, tmp) {
+ PMCDBG(OWN,ORM,2, "pmc=%p", pm);
+ KASSERT(pm->pm_owner == po,
+ ("[pmc,%d] owner %p != po %p", __LINE__, pm->pm_owner, po));
- /* remove the associated PMC descriptor, if present */
- if (pl->pl_pmc)
- pmc_release_pmc_descriptor(pl->pl_pmc);
-
- /* remove the linked list entry */
- LIST_REMOVE(pl, pl_next);
- FREE(pl, M_PMC);
- }
-
- /* pass 2: delete the pmc_list chain */
- LIST_FOREACH_SAFE(pl, &po->po_pmcs, pl_next, tmp) {
- KASSERT(pl->pl_pmc == NULL,
- ("[pmc,%d] non-null pmc pointer", __LINE__));
- LIST_REMOVE(pl, pl_next);
- FREE(pl, M_PMC);
+ pmc_release_pmc_descriptor(pm); /* will unlink from the list */
}
+ KASSERT(po->po_sscount == 0,
+ ("[pmc,%d] SS count not zero", __LINE__));
KASSERT(LIST_EMPTY(&po->po_pmcs),
- ("[pmc,%d] PMC list not empty", __LINE__));
-
-
- /*
- * If this process owns a log file used for system wide logging,
- * remove the log file.
- *
- * XXX rework needed.
- */
+ ("[pmc,%d] PMC list not empty", __LINE__));
+ /* de-configure the log file if present */
if (po->po_flags & PMC_PO_OWNS_LOGFILE)
- pmc_configure_log(po, -1);
-
+ pmclog_deconfigure_log(po);
}
/*
@@ -719,7 +727,7 @@ pmc_maybe_remove_owner(struct pmc_owner *po)
if (LIST_EMPTY(&po->po_pmcs) &&
((po->po_flags & PMC_PO_OWNS_LOGFILE) == 0)) {
pmc_remove_owner(po);
- FREE(po, M_PMC);
+ pmc_destroy_owner_descriptor(po);
}
}
@@ -737,7 +745,9 @@ pmc_link_target_process(struct pmc *pm, struct pmc_process *pp)
KASSERT(pm != NULL && pp != NULL,
("[pmc,%d] Null pm %p or pp %p", __LINE__, pm, pp));
-
+ KASSERT(PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)),
+ ("[pmc,%d] Attaching a non-process-virtual pmc=%p to pid=%d",
+ __LINE__, pm, pp->pp_proc->p_pid));
KASSERT(pp->pp_refcnt >= 0 && pp->pp_refcnt < ((int) md->pmd_npmc - 1),
("[pmc,%d] Illegal reference count %d for process record %p",
__LINE__, pp->pp_refcnt, (void *) pp));
@@ -766,6 +776,12 @@ pmc_link_target_process(struct pmc *pm, struct pmc_process *pp)
if (pm->pm_owner->po_owner == pp->pp_proc)
pm->pm_flags |= PMC_F_ATTACHED_TO_OWNER;
+ /*
+ * Initialize the per-process values at this row index.
+ */
+ pp->pp_pmcs[ri].pp_pmcval = PMC_TO_MODE(pm) == PMC_MODE_TS ?
+ pm->pm_sc.pm_reloadcount : 0;
+
pp->pp_refcnt++;
}
@@ -778,6 +794,7 @@ static void
pmc_unlink_target_process(struct pmc *pm, struct pmc_process *pp)
{
int ri;
+ struct proc *p;
struct pmc_target *ptgt;
sx_assert(&pmc_sx, SX_XLOCKED);
@@ -819,36 +836,17 @@ pmc_unlink_target_process(struct pmc *pm, struct pmc_process *pp)
LIST_REMOVE(ptgt, pt_next);
FREE(ptgt, M_PMC);
-}
-
-/*
- * Remove PMC descriptor 'pmc' from the owner descriptor.
- */
-
-void
-pmc_unlink_owner(struct pmc *pm)
-{
- struct pmc_list *pl, *tmp;
- struct pmc_owner *po;
-#if DEBUG
- KASSERT(LIST_EMPTY(&pm->pm_targets),
- ("[pmc,%d] unlinking PMC with targets", __LINE__));
-#endif
-
- po = pm->pm_owner;
-
- KASSERT(po != NULL, ("[pmc,%d] No owner for PMC", __LINE__));
+ /* if the PMC now lacks targets, send the owner a SIGIO */
+ if (LIST_EMPTY(&pm->pm_targets)) {
+ p = pm->pm_owner->po_owner;
+ PROC_LOCK(p);
+ psignal(p, SIGIO);
+ PROC_UNLOCK(p);
- LIST_FOREACH_SAFE(pl, &po->po_pmcs, pl_next, tmp) {
- if (pl->pl_pmc == pm) {
- pl->pl_pmc = NULL;
- pm->pm_owner = NULL;
- return;
- }
+ PMCDBG(PRC,SIG,2, "signalling proc=%p signal=%d", p,
+ SIGIO);
}
-
- KASSERT(0, ("[pmc,%d] couldn't find pmc in owner list", __LINE__));
}
/*
@@ -914,6 +912,7 @@ static int
pmc_attach_one_process(struct proc *p, struct pmc *pm)
{
int ri;
+ char *fullpath, *freepath;
struct pmc_process *pp;
sx_assert(&pmc_sx, SX_XLOCKED);
@@ -931,7 +930,6 @@ pmc_attach_one_process(struct proc *p, struct pmc *pm)
* If not, allocate space for a descriptor and link the
* process descriptor and PMC.
*/
-
ri = PMC_TO_ROWINDEX(pm);
if ((pp = pmc_find_process_descriptor(p, PMC_FLAG_ALLOCATE)) == NULL)
@@ -945,6 +943,19 @@ pmc_attach_one_process(struct proc *p, struct pmc *pm)
pmc_link_target_process(pm, pp);
+ if (PMC_IS_SAMPLING_MODE(PMC_TO_MODE(pm)) &&
+ (pm->pm_flags & PMC_F_ATTACHED_TO_OWNER) == 0)
+ pm->pm_flags |= PMC_F_NEEDS_LOGFILE;
+
+ pm->pm_flags |= PMC_F_ATTACH_DONE; /* mark as attached */
+
+ /* issue an attach event to a configured log file */
+ if (pm->pm_owner->po_flags & PMC_PO_OWNS_LOGFILE) {
+ pmc_getprocname(p, &fullpath, &freepath);
+ pmclog_process_pmcattach(pm, p->p_pid, fullpath);
+ if (freepath)
+ FREE(freepath, M_TEMP);
+ }
/* mark process as using HWPMCs */
PROC_LOCK(p);
p->p_flag |= P_HWPMC;
@@ -1043,12 +1054,15 @@ pmc_detach_one_process(struct proc *p, struct pmc *pm, int flags)
pmc_unlink_target_process(pm, pp);
+ /* Issue a detach entry if a log file is configured */
+ if (pm->pm_owner->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_pmcdetach(pm, p->p_pid);
+
/*
* If there are no PMCs targetting this process, we remove its
* descriptor from the target hash table and unset the P_HWPMC
* flag in the struct proc.
*/
-
KASSERT(pp->pp_refcnt >= 0 && pp->pp_refcnt < (int) md->pmd_npmc,
("[pmc,%d] Illegal refcnt %d for process struct %p",
__LINE__, pp->pp_refcnt, pp));
@@ -1113,194 +1127,314 @@ pmc_detach_process(struct proc *p, struct pmc *pm)
done:
sx_sunlock(&proctree_lock);
+
+ if (LIST_EMPTY(&pm->pm_targets))
+ pm->pm_flags &= ~PMC_F_ATTACH_DONE;
+
return 0;
}
+
/*
- * The 'hook' invoked from the kernel proper
+ * Thread context switch IN
*/
+static void
+pmc_process_csw_in(struct thread *td)
+{
+ int cpu;
+ unsigned int ri;
+ struct pmc *pm;
+ struct proc *p;
+ struct pmc_cpu *pc;
+ struct pmc_hw *phw;
+ struct pmc_process *pp;
+ pmc_value_t newvalue;
-#if DEBUG
-const char *pmc_hooknames[] = {
- "",
- "EXIT",
- "EXEC",
- "FORK",
- "CSW-IN",
- "CSW-OUT"
-};
-#endif
+ p = td->td_proc;
-static int
-pmc_hook_handler(struct thread *td, int function, void *arg)
-{
+ if ((pp = pmc_find_process_descriptor(p, PMC_FLAG_NONE)) == NULL)
+ return;
- KASSERT(td->td_proc->p_flag & P_HWPMC,
- ("[pmc,%d] unregistered thread called pmc_hook()", __LINE__));
+ KASSERT(pp->pp_proc == td->td_proc,
+ ("[pmc,%d] not my thread state", __LINE__));
- PMCDBG(MOD,PMH,1, "hook td=%p func=%d \"%s\" arg=%p", td, function,
- pmc_hooknames[function], arg);
+ critical_enter(); /* no preemption from this point */
- switch (function)
- {
+ cpu = PCPU_GET(cpuid); /* td->td_oncpu is invalid */
- /*
- * Process exit.
- *
- * Remove this process from all hash tables. If this process
- * owned any PMCs, turn off those PMCs and deallocate them,
- * removing any associations with target processes.
- *
- * This function will be called by the last 'thread' of a
- * process.
- *
- */
+ PMCDBG(CSW,SWI,1, "cpu=%d proc=%p (%d, %s) pp=%p", cpu, p,
+ p->p_pid, p->p_comm, pp);
- case PMC_FN_PROCESS_EXIT: /* release PMCs */
- {
- int cpu;
- unsigned int ri;
- struct pmc *pm;
- struct pmc_process *pp;
- struct pmc_owner *po;
- struct proc *p;
- pmc_value_t newvalue, tmp;
+ KASSERT(cpu >= 0 && cpu < mp_ncpus,
+ ("[pmc,%d] wierd CPU id %d", __LINE__, cpu));
- sx_assert(&pmc_sx, SX_XLOCKED);
+ pc = pmc_pcpu[cpu];
- p = (struct proc *) arg;
+ for (ri = 0; ri < md->pmd_npmc; ri++) {
+
+ if ((pm = pp->pp_pmcs[ri].pp_pmc) == NULL)
+ continue;
+
+ KASSERT(PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)),
+ ("[pmc,%d] Target PMC in non-virtual mode (%d)",
+ __LINE__, PMC_TO_MODE(pm)));
+
+ KASSERT(PMC_TO_ROWINDEX(pm) == ri,
+ ("[pmc,%d] Row index mismatch pmc %d != ri %d",
+ __LINE__, PMC_TO_ROWINDEX(pm), ri));
/*
- * Since this code is invoked by the last thread in an
- * exiting process, we would have context switched IN
- * at some prior point. Kernel mode context switches
- * may happen any time, so we want to disable a context
- * switch OUT till we get any PMCs targetting this
- * process off the hardware.
- *
- * We also need to atomically remove this process'
- * entry from our target process hash table, using
- * PMC_FLAG_REMOVE.
+ * Only PMCs that are marked as 'RUNNING' need
+ * be placed on hardware.
*/
- PMCDBG(PRC,EXT,1, "process-exit proc=%p (%d, %s)", p, p->p_pid,
- p->p_comm);
+ if (pm->pm_state != PMC_STATE_RUNNING)
+ continue;
- critical_enter(); /* no preemption */
+ /* increment PMC runcount */
+ atomic_add_rel_32(&pm->pm_runcount, 1);
- cpu = curthread->td_oncpu;
+ /* configure the HWPMC we are going to use. */
+ md->pmd_config_pmc(cpu, ri, pm);
- if ((pp = pmc_find_process_descriptor(p,
- PMC_FLAG_REMOVE)) != NULL) {
+ phw = pc->pc_hwpmcs[ri];
- PMCDBG(PRC,EXT,2,
- "process-exit proc=%p pmc-process=%p", p, pp);
+ KASSERT(phw != NULL,
+ ("[pmc,%d] null hw pointer", __LINE__));
- /*
- * The exiting process could the target of
- * some PMCs which will be running on
- * currently executing CPU.
- *
- * We need to turn these PMCs off like we
- * would do at context switch OUT time.
- */
+ KASSERT(phw->phw_pmc == pm,
+ ("[pmc,%d] hw->pmc %p != pmc %p", __LINE__,
+ phw->phw_pmc, pm));
- for (ri = 0; ri < md->pmd_npmc; ri++) {
+ /*
+ * Write out saved value and start the PMC.
+ *
+ * Sampling PMCs use a per-process value, while
+ * counting mode PMCs use a per-pmc value that is
+ * inherited across descendants.
+ */
+ if (PMC_TO_MODE(pm) == PMC_MODE_TS) {
+ mtx_pool_lock_spin(pmc_mtxpool, pm);
+ newvalue = PMC_PCPU_SAVED(cpu,ri) =
+ pp->pp_pmcs[ri].pp_pmcval;
+ mtx_pool_unlock_spin(pmc_mtxpool, pm);
+ } else {
+ KASSERT(PMC_TO_MODE(pm) == PMC_MODE_TC,
+ ("[pmc,%d] illegal mode=%d", __LINE__,
+ PMC_TO_MODE(pm)));
+ mtx_pool_lock_spin(pmc_mtxpool, pm);
+ newvalue = PMC_PCPU_SAVED(cpu, ri) =
+ pm->pm_gv.pm_savedvalue;
+ mtx_pool_unlock_spin(pmc_mtxpool, pm);
+ }
- /*
- * Pick up the pmc pointer from hardware
- * state similar to the CSW_OUT code.
- */
+ PMCDBG(CSW,SWI,1,"cpu=%d ri=%d new=%jd", cpu, ri, newvalue);
- pm = NULL;
- (void) (*md->pmd_get_config)(cpu, ri, &pm);
+ md->pmd_write_pmc(cpu, ri, newvalue);
+ md->pmd_start_pmc(cpu, ri);
+ }
- PMCDBG(PRC,EXT,2, "ri=%d pm=%p", ri, pm);
+ /*
+ * perform any other architecture/cpu dependent thread
+ * switch-in actions.
+ */
- if (pm == NULL ||
- !PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)))
- continue;
+ (void) (*md->pmd_switch_in)(pc, pp);
- PMCDBG(PRC,EXT,2, "ppmcs[%d]=%p pm=%p "
- "state=%d", ri, pp->pp_pmcs[ri].pp_pmc,
- pm, pm->pm_state);
+ critical_exit();
- KASSERT(PMC_TO_ROWINDEX(pm) == ri,
- ("[pmc,%d] ri mismatch pmc(%d) ri(%d)",
- __LINE__, PMC_TO_ROWINDEX(pm), ri));
+}
- KASSERT(pm == pp->pp_pmcs[ri].pp_pmc,
- ("[pmc,%d] pm %p != pp_pmcs[%d] %p",
- __LINE__, pm, ri,
- pp->pp_pmcs[ri].pp_pmc));
+/*
+ * Thread context switch OUT.
+ */
- (void) md->pmd_stop_pmc(cpu, ri);
+static void
+pmc_process_csw_out(struct thread *td)
+{
+ int cpu;
+ enum pmc_mode mode;
+ unsigned int ri;
+ struct pmc *pm;
+ struct proc *p;
+ struct pmc_cpu *pc;
+ struct pmc_process *pp;
+ int64_t tmp;
+ pmc_value_t newvalue;
- KASSERT(pm->pm_runcount > 0,
- ("[pmc,%d] bad runcount ri %d rc %d",
- __LINE__, ri, pm->pm_runcount));
+ /*
+ * Locate our process descriptor; this may be NULL if
+ * this process is exiting and we have already removed
+ * the process from the target process table.
+ *
+ * Note that due to kernel preemption, multiple
+ * context switches may happen while the process is
+ * exiting.
+ *
+ * Note also that if the target process cannot be
+ * found we still need to deconfigure any PMCs that
+ * are currently running on hardware.
+ */
- if (pm->pm_state == PMC_STATE_RUNNING) {
- md->pmd_read_pmc(cpu, ri, &newvalue);
- tmp = newvalue -
- PMC_PCPU_SAVED(cpu,ri);
+ p = td->td_proc;
+ pp = pmc_find_process_descriptor(p, PMC_FLAG_NONE);
- mtx_pool_lock_spin(pmc_mtxpool, pm);
- pm->pm_gv.pm_savedvalue += tmp;
- pp->pp_pmcs[ri].pp_pmcval += tmp;
- mtx_pool_unlock_spin(pmc_mtxpool, pm);
- }
+ /*
+ * save PMCs
+ */
- atomic_subtract_rel_32(&pm->pm_runcount,1);
+ critical_enter();
- KASSERT((int) pm->pm_runcount >= 0,
- ("[pmc,%d] runcount is %d", __LINE__, ri));
+ cpu = PCPU_GET(cpuid); /* td->td_oncpu is invalid */
- (void) md->pmd_config_pmc(cpu, ri, NULL);
- }
+ PMCDBG(CSW,SWO,1, "cpu=%d proc=%p (%d, %s) pp=%p", cpu, p,
+ p->p_pid, p->p_comm, pp);
- /*
- * Inform the MD layer of this pseudo "context switch
- * out"
- */
+ KASSERT(cpu >= 0 && cpu < mp_ncpus,
+ ("[pmc,%d wierd CPU id %d", __LINE__, cpu));
- (void) md->pmd_switch_out(pmc_pcpu[cpu], pp);
+ pc = pmc_pcpu[cpu];
- critical_exit(); /* ok to be pre-empted now */
+ /*
+ * When a PMC gets unlinked from a target PMC, it will
+ * be removed from the target's pp_pmc[] array.
+ *
+ * However, on a MP system, the target could have been
+ * executing on another CPU at the time of the unlink.
+ * So, at context switch OUT time, we need to look at
+ * the hardware to determine if a PMC is scheduled on
+ * it.
+ */
- /*
- * Unlink this process from the PMCs that are
- * targetting it. Log value at exit() time if
- * requested.
- */
+ for (ri = 0; ri < md->pmd_npmc; ri++) {
- for (ri = 0; ri < md->pmd_npmc; ri++)
- if ((pm = pp->pp_pmcs[ri].pp_pmc) != NULL) {
- if (pm->pm_flags &
- PMC_F_LOG_TC_PROCEXIT)
- pmc_log_process_exit(pm, pp);
- pmc_unlink_target_process(pm, pp);
- }
+ pm = NULL;
+ (void) (*md->pmd_get_config)(cpu, ri, &pm);
- FREE(pp, M_PMC);
+ if (pm == NULL) /* nothing at this row index */
+ continue;
+ mode = PMC_TO_MODE(pm);
+ if (!PMC_IS_VIRTUAL_MODE(mode))
+ continue; /* not a process virtual PMC */
- } else
- critical_exit(); /* pp == NULL */
+ KASSERT(PMC_TO_ROWINDEX(pm) == ri,
+ ("[pmc,%d] ri mismatch pmc(%d) ri(%d)",
+ __LINE__, PMC_TO_ROWINDEX(pm), ri));
+
+ /* Stop hardware if not already stopped */
+ if ((pm->pm_flags & PMC_F_IS_STALLED) == 0)
+ md->pmd_stop_pmc(cpu, ri);
+
+ /* reduce this PMC's runcount */
+ atomic_subtract_rel_32(&pm->pm_runcount, 1);
/*
- * If the process owned PMCs, free them up and free up
- * memory.
+ * If this PMC is associated with this process,
+ * save the reading.
*/
- if ((po = pmc_find_owner_descriptor(p)) != NULL) {
- pmc_remove_owner(po);
- FREE(po, M_PMC);
+ if (pp != NULL && pp->pp_pmcs[ri].pp_pmc != NULL) {
+
+ KASSERT(pm == pp->pp_pmcs[ri].pp_pmc,
+ ("[pmc,%d] pm %p != pp_pmcs[%d] %p", __LINE__,
+ pm, ri, pp->pp_pmcs[ri].pp_pmc));
+
+ KASSERT(pp->pp_refcnt > 0,
+ ("[pmc,%d] pp refcnt = %d", __LINE__,
+ pp->pp_refcnt));
+
+ md->pmd_read_pmc(cpu, ri, &newvalue);
+
+ tmp = newvalue - PMC_PCPU_SAVED(cpu,ri);
+
+ PMCDBG(CSW,SWI,1,"cpu=%d ri=%d tmp=%jd", cpu, ri,
+ tmp);
+
+ if (mode == PMC_MODE_TS) {
+
+ /*
+ * For sampling process-virtual PMCs,
+ * we expect the count to be
+ * decreasing as the 'value'
+ * programmed into the PMC is the
+ * number of events to be seen till
+ * the next sampling interrupt.
+ */
+ if (tmp < 0)
+ tmp += pm->pm_sc.pm_reloadcount;
+ mtx_pool_lock_spin(pmc_mtxpool, pm);
+ pp->pp_pmcs[ri].pp_pmcval -= tmp;
+ if ((int64_t) pp->pp_pmcs[ri].pp_pmcval < 0)
+ pp->pp_pmcs[ri].pp_pmcval +=
+ pm->pm_sc.pm_reloadcount;
+ mtx_pool_unlock_spin(pmc_mtxpool, pm);
+
+ } else {
+
+ /*
+ * For counting process-virtual PMCs,
+ * we expect the count to be
+ * increasing monotonically, modulo a 64
+ * bit wraparound.
+ */
+ KASSERT((int64_t) tmp >= 0,
+ ("[pmc,%d] negative increment cpu=%d "
+ "ri=%d newvalue=%jx saved=%jx "
+ "incr=%jx", __LINE__, cpu, ri,
+ newvalue, PMC_PCPU_SAVED(cpu,ri), tmp));
+
+ mtx_pool_lock_spin(pmc_mtxpool, pm);
+ pm->pm_gv.pm_savedvalue += tmp;
+ pp->pp_pmcs[ri].pp_pmcval += tmp;
+ mtx_pool_unlock_spin(pmc_mtxpool, pm);
+
+ if (pm->pm_flags & PMC_F_LOG_PROCCSW)
+ pmclog_process_proccsw(pm, pp, tmp);
+ }
}
+ /* mark hardware as free */
+ md->pmd_config_pmc(cpu, ri, NULL);
}
- break;
+
+ /*
+ * perform any other architecture/cpu dependent thread
+ * switch out functions.
+ */
+
+ (void) (*md->pmd_switch_out)(pc, pp);
+
+ critical_exit();
+}
+
+/*
+ * The 'hook' invoked from the kernel proper
+ */
+
+
+#if DEBUG
+const char *pmc_hooknames[] = {
+ "",
+ "EXIT",
+ "EXEC",
+ "FORK",
+ "CSW-IN",
+ "CSW-OUT",
+ "SAMPLE"
+};
+#endif
+
+static int
+pmc_hook_handler(struct thread *td, int function, void *arg)
+{
+
+ PMCDBG(MOD,PMH,1, "hook td=%p func=%d \"%s\" arg=%p", td, function,
+ pmc_hooknames[function], arg);
+
+ switch (function)
+ {
/*
* Process exec()
@@ -1309,7 +1443,9 @@ pmc_hook_handler(struct thread *td, int function, void *arg)
case PMC_FN_PROCESS_EXEC:
{
int *credentials_changed;
+ char *fullpath, *freepath;
unsigned int ri;
+ int is_using_hwpmcs;
struct pmc *pm;
struct proc *p;
struct pmc_owner *po;
@@ -1317,16 +1453,32 @@ pmc_hook_handler(struct thread *td, int function, void *arg)
sx_assert(&pmc_sx, SX_XLOCKED);
+ p = td->td_proc;
+ pmc_getprocname(p, &fullpath, &freepath);
+
+ /* Inform owners of SS mode PMCs of the exec event. */
+ LIST_FOREACH(po, &pmc_ss_owners, po_ssnext)
+ if (po->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_procexec(po, p->p_pid, fullpath);
+
+ PROC_LOCK(p);
+ is_using_hwpmcs = p->p_flag & P_HWPMC;
+ PROC_UNLOCK(p);
+
+ if (!is_using_hwpmcs) {
+ if (freepath)
+ FREE(freepath, M_TEMP);
+ break;
+ }
+
/*
* PMCs are not inherited across an exec(): remove any
* PMCs that this process is the owner of.
*/
- p = td->td_proc;
-
if ((po = pmc_find_owner_descriptor(p)) != NULL) {
pmc_remove_owner(po);
- FREE(po, M_PMC);
+ pmc_destroy_owner_descriptor(po);
}
/*
@@ -1337,6 +1489,23 @@ pmc_hook_handler(struct thread *td, int function, void *arg)
if ((pp = pmc_find_process_descriptor(p, 0)) == NULL)
break;
+ /*
+ * Log the exec event to all monitoring owners. Skip
+ * owners who have already recieved the event because
+ * the have system sampling PMCs active.
+ */
+ for (ri = 0; ri < md->pmd_npmc; ri++)
+ if ((pm = pp->pp_pmcs[ri].pp_pmc) != NULL) {
+ po = pm->pm_owner;
+ if (po->po_sscount == 0 &&
+ po->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_procexec(po, p->p_pid,
+ fullpath);
+ }
+
+ if (freepath)
+ FREE(freepath, M_TEMP);
+
credentials_changed = arg;
PMCDBG(PRC,EXC,1, "exec proc=%p (%d, %s) cred-changed=%d",
@@ -1370,304 +1539,45 @@ pmc_hook_handler(struct thread *td, int function, void *arg)
if (pp->pp_refcnt == 0) {
pmc_remove_process_descriptor(pp);
FREE(pp, M_PMC);
- }
- }
- break;
-
- /*
- * Process fork()
- */
-
- case PMC_FN_PROCESS_FORK:
- {
- unsigned int ri;
- uint32_t do_descendants;
- struct pmc *pm;
- struct pmc_process *ppnew, *ppold;
- struct proc *newproc;
-
- sx_assert(&pmc_sx, SX_XLOCKED);
-
- newproc = (struct proc *) arg;
-
- PMCDBG(PMC,FRK,2, "process-fork p1=%p p2=%p",
- curthread->td_proc, newproc);
- /*
- * If the parent process (curthread->td_proc) is a
- * target of any PMCs, look for PMCs that are to be
- * inherited, and link these into the new process
- * descriptor.
- */
-
- if ((ppold = pmc_find_process_descriptor(
- curthread->td_proc, PMC_FLAG_NONE)) == NULL)
break;
-
- do_descendants = 0;
- for (ri = 0; ri < md->pmd_npmc; ri++)
- if ((pm = ppold->pp_pmcs[ri].pp_pmc) != NULL)
- do_descendants |=
- pm->pm_flags & PMC_F_DESCENDANTS;
- if (do_descendants == 0) /* nothing to do */
- break;
-
- if ((ppnew = pmc_find_process_descriptor(newproc,
- PMC_FLAG_ALLOCATE)) == NULL)
- return ENOMEM;
-
- /*
- * Run through all PMCs targeting the old process and
- * attach them to the new process.
- */
-
- for (ri = 0; ri < md->pmd_npmc; ri++)
- if ((pm = ppold->pp_pmcs[ri].pp_pmc) != NULL &&
- pm->pm_flags & PMC_F_DESCENDANTS)
- pmc_link_target_process(pm, ppnew);
-
- /*
- * Now mark the new process as being tracked by this
- * driver.
- */
-
- PROC_LOCK(newproc);
- newproc->p_flag |= P_HWPMC;
- PROC_UNLOCK(newproc);
+ }
}
break;
- /*
- * Thread context switch IN
- */
-
case PMC_FN_CSW_IN:
- {
- int cpu;
- unsigned int ri;
- struct pmc *pm;
- struct proc *p;
- struct pmc_cpu *pc;
- struct pmc_hw *phw;
- struct pmc_process *pp;
- pmc_value_t newvalue;
-
- p = td->td_proc;
-
- if ((pp = pmc_find_process_descriptor(p, PMC_FLAG_NONE)) == NULL)
- break;
-
- KASSERT(pp->pp_proc == td->td_proc,
- ("[pmc,%d] not my thread state", __LINE__));
-
- critical_enter(); /* no preemption on this CPU */
-
- cpu = PCPU_GET(cpuid); /* td->td_oncpu is invalid */
-
- PMCDBG(CTX,SWI,1, "cpu=%d proc=%p (%d, %s) pp=%p", cpu, p,
- p->p_pid, p->p_comm, pp);
-
- KASSERT(cpu >= 0 && cpu < mp_ncpus,
- ("[pmc,%d] wierd CPU id %d", __LINE__, cpu));
-
- pc = pmc_pcpu[cpu];
-
- for (ri = 0; ri < md->pmd_npmc; ri++) {
-
- if ((pm = pp->pp_pmcs[ri].pp_pmc) == NULL)
- continue;
-
- KASSERT(PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)),
- ("[pmc,%d] Target PMC in non-virtual mode (%d)",
- __LINE__, PMC_TO_MODE(pm)));
-
- KASSERT(PMC_TO_ROWINDEX(pm) == ri,
- ("[pmc,%d] Row index mismatch pmc %d != ri %d",
- __LINE__, PMC_TO_ROWINDEX(pm), ri));
-
- /*
- * Only PMCs that are marked as 'RUNNING' need
- * be placed on hardware.
- */
-
- if (pm->pm_state != PMC_STATE_RUNNING)
- continue;
-
- /* increment PMC runcount */
- atomic_add_rel_32(&pm->pm_runcount, 1);
-
- /* configure the HWPMC we are going to use. */
- md->pmd_config_pmc(cpu, ri, pm);
-
- phw = pc->pc_hwpmcs[ri];
-
- KASSERT(phw != NULL,
- ("[pmc,%d] null hw pointer", __LINE__));
-
- KASSERT(phw->phw_pmc == pm,
- ("[pmc,%d] hw->pmc %p != pmc %p", __LINE__,
- phw->phw_pmc, pm));
-
- /* write out saved value and start the PMC */
- mtx_pool_lock_spin(pmc_mtxpool, pm);
- newvalue = PMC_PCPU_SAVED(cpu, ri) =
- pm->pm_gv.pm_savedvalue;
- mtx_pool_unlock_spin(pmc_mtxpool, pm);
-
- md->pmd_write_pmc(cpu, ri, newvalue);
- md->pmd_start_pmc(cpu, ri);
-
- }
-
- /*
- * perform any other architecture/cpu dependent thread
- * switch-in actions.
- */
-
- (void) (*md->pmd_switch_in)(pc, pp);
-
- critical_exit();
+ pmc_process_csw_in(td);
+ break;
- }
- break;
+ case PMC_FN_CSW_OUT:
+ pmc_process_csw_out(td);
+ break;
/*
- * Thread context switch OUT.
+ * Process accumulated PC samples.
+ *
+ * This function is expected to be called by hardclock() for
+ * each CPU that has accumulated PC samples.
+ *
+ * This function is to be executed on the CPU whose samples
+ * are being processed.
*/
-
- case PMC_FN_CSW_OUT:
- {
- int cpu;
- unsigned int ri;
- struct pmc *pm;
- struct proc *p;
- struct pmc_cpu *pc;
- struct pmc_process *pp;
- pmc_value_t newvalue, tmp;
-
- /*
- * Locate our process descriptor; this may be NULL if
- * this process is exiting and we have already removed
- * the process from the target process table.
- *
- * Note that due to kernel preemption, multiple
- * context switches may happen while the process is
- * exiting.
- *
- * Note also that if the target process cannot be
- * found we still need to deconfigure any PMCs that
- * are currently running on hardware.
- */
-
- p = td->td_proc;
- pp = pmc_find_process_descriptor(p, PMC_FLAG_NONE);
-
- /*
- * save PMCs
- */
-
- critical_enter();
-
- cpu = PCPU_GET(cpuid); /* td->td_oncpu is invalid */
-
- PMCDBG(CTX,SWO,1, "cpu=%d proc=%p (%d, %s) pp=%p", cpu, p,
- p->p_pid, p->p_comm, pp);
-
- KASSERT(cpu >= 0 && cpu < mp_ncpus,
- ("[pmc,%d wierd CPU id %d", __LINE__, cpu));
-
- pc = pmc_pcpu[cpu];
+ case PMC_FN_DO_SAMPLES:
/*
- * When a PMC gets unlinked from a target PMC, it will
- * be removed from the target's pp_pmc[] array.
- *
- * However, on a MP system, the target could have been
- * executing on another CPU at the time of the unlink.
- * So, at context switch OUT time, we need to look at
- * the hardware to determine if a PMC is scheduled on
- * it.
+ * Clear the cpu specific bit in the CPU mask before
+ * do the rest of the processing. If the NMI handler
+ * gets invoked after the "atomic_clear_int()" call
+ * below but before "pmc_process_samples()" gets
+ * around to processing the interrupt, then we will
+ * come back here at the next hardclock() tick (and
+ * may find nothing to do if "pmc_process_samples()"
+ * had already processed the interrupt). We don't
+ * lose the interrupt sample.
*/
-
- for (ri = 0; ri < md->pmd_npmc; ri++) {
-
- pm = NULL;
- (void) (*md->pmd_get_config)(cpu, ri, &pm);
-
- if (pm == NULL) /* nothing at this row index */
- continue;
-
- if (!PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)))
- continue; /* not a process virtual PMC */
-
- KASSERT(PMC_TO_ROWINDEX(pm) == ri,
- ("[pmc,%d] ri mismatch pmc(%d) ri(%d)",
- __LINE__, PMC_TO_ROWINDEX(pm), ri));
-
- /* Stop hardware */
- md->pmd_stop_pmc(cpu, ri);
-
- /* reduce this PMC's runcount */
- atomic_subtract_rel_32(&pm->pm_runcount, 1);
-
- /*
- * If this PMC is associated with this process,
- * save the reading.
- */
-
- if (pp != NULL && pp->pp_pmcs[ri].pp_pmc != NULL) {
-
- KASSERT(pm == pp->pp_pmcs[ri].pp_pmc,
- ("[pmc,%d] pm %p != pp_pmcs[%d] %p",
- __LINE__, pm, ri,
- pp->pp_pmcs[ri].pp_pmc));
-
- KASSERT(pp->pp_refcnt > 0,
- ("[pmc,%d] pp refcnt = %d", __LINE__,
- pp->pp_refcnt));
-
- md->pmd_read_pmc(cpu, ri, &newvalue);
-
- tmp = newvalue - PMC_PCPU_SAVED(cpu,ri);
-
- KASSERT((int64_t) tmp >= 0,
- ("[pmc,%d] negative increment cpu=%d "
- "ri=%d newvalue=%jx saved=%jx "
- "incr=%jx", __LINE__, cpu, ri,
- newvalue, PMC_PCPU_SAVED(cpu,ri),
- tmp));
-
- /*
- * Increment the PMC's count and this
- * target process's count by the difference
- * between the current reading and the
- * saved value at context switch in time.
- */
-
- mtx_pool_lock_spin(pmc_mtxpool, pm);
-
- pm->pm_gv.pm_savedvalue += tmp;
- pp->pp_pmcs[ri].pp_pmcval += tmp;
-
- mtx_pool_unlock_spin(pmc_mtxpool, pm);
-
- }
-
- /* mark hardware as free */
- md->pmd_config_pmc(cpu, ri, NULL);
- }
-
- /*
- * perform any other architecture/cpu dependent thread
- * switch out functions.
- */
-
- (void) (*md->pmd_switch_out)(pc, pp);
-
- critical_exit();
-
- }
- break;
+ atomic_clear_int(&pmc_cpumask, (1 << PCPU_GET(cpuid)));
+ pmc_process_samples(PCPU_GET(cpuid));
+ break;
default:
#if DEBUG
@@ -1696,19 +1606,35 @@ pmc_allocate_owner_descriptor(struct proc *p)
/* allocate space for N pointers and one descriptor struct */
MALLOC(po, struct pmc_owner *, sizeof(struct pmc_owner),
- M_PMC, M_WAITOK);
+ M_PMC, M_ZERO|M_WAITOK);
- po->po_flags = 0;
+ po->po_sscount = po->po_error = po->po_flags = 0;
+ po->po_file = NULL;
po->po_owner = p;
+ po->po_kthread = NULL;
LIST_INIT(&po->po_pmcs);
LIST_INSERT_HEAD(poh, po, po_next); /* insert into hash table */
+ TAILQ_INIT(&po->po_logbuffers);
+ mtx_init(&po->po_mtx, "pmc-owner-mtx", "pmc", MTX_SPIN);
+
PMCDBG(OWN,ALL,1, "allocate-owner proc=%p (%d, %s) pmc-owner=%p",
p, p->p_pid, p->p_comm, po);
return po;
}
+static void
+pmc_destroy_owner_descriptor(struct pmc_owner *po)
+{
+
+ PMCDBG(OWN,REL,1, "destroy-owner po=%p proc=%p (%d, %s)",
+ po, po->po_owner, po->po_owner->p_pid, po->po_owner->p_comm);
+
+ mtx_destroy(&po->po_mtx);
+ FREE(po, M_PMC);
+}
+
/*
* find the descriptor corresponding to process 'p', adding or removing it
* as specified by 'mode'.
@@ -1850,6 +1776,31 @@ pmc_destroy_pmc_descriptor(struct pmc *pm)
#endif
}
+static void
+pmc_wait_for_pmc_idle(struct pmc *pm)
+{
+#if DEBUG
+ volatile int maxloop;
+
+ maxloop = 100 * mp_ncpus;
+#endif
+
+ /*
+ * Loop (with a forced context switch) till the PMC's runcount
+ * comes down to zero.
+ */
+ while (atomic_load_acq_32(&pm->pm_runcount) > 0) {
+#if DEBUG
+ maxloop--;
+ KASSERT(maxloop > 0,
+ ("[pmc,%d] (ri%d, rc%d) waiting too long for "
+ "pmc to be free", __LINE__,
+ PMC_TO_ROWINDEX(pm), pm->pm_runcount));
+#endif
+ pmc_force_context_switch();
+ }
+}
+
/*
* This function does the following things:
*
@@ -1865,12 +1816,10 @@ pmc_destroy_pmc_descriptor(struct pmc *pm)
static void
pmc_release_pmc_descriptor(struct pmc *pm)
{
-#if DEBUG
- volatile int maxloop;
-#endif
u_int ri, cpu;
enum pmc_mode mode;
struct pmc_hw *phw;
+ struct pmc_owner *po;
struct pmc_process *pp;
struct pmc_target *ptgt, *tmp;
struct pmc_binding pb;
@@ -1895,21 +1844,21 @@ pmc_release_pmc_descriptor(struct pmc *pm)
* A system mode PMC runs on a specific CPU. Switch
* to this CPU and turn hardware off.
*/
-
pmc_save_cpu_binding(&pb);
cpu = PMC_TO_CPU(pm);
- if (pm->pm_state == PMC_STATE_RUNNING) {
+ pmc_select_cpu(cpu);
- pmc_select_cpu(cpu);
+ /* switch off non-stalled CPUs */
+ if (pm->pm_state == PMC_STATE_RUNNING &&
+ (pm->pm_flags & PMC_F_IS_STALLED) == 0) {
phw = pmc_pcpu[cpu]->pc_hwpmcs[ri];
KASSERT(phw->phw_pmc == pm,
("[pmc, %d] pmc ptr ri(%d) hw(%p) pm(%p)",
__LINE__, ri, phw->phw_pmc, pm));
-
PMCDBG(PMC,REL,2, "stopping cpu=%d ri=%d", cpu, ri);
critical_enter();
@@ -1923,10 +1872,27 @@ pmc_release_pmc_descriptor(struct pmc *pm)
md->pmd_config_pmc(cpu, ri, NULL);
critical_exit();
+ /* adjust the global and process count of SS mode PMCs */
+ if (mode == PMC_MODE_SS && pm->pm_state == PMC_STATE_RUNNING) {
+ po = pm->pm_owner;
+ po->po_sscount--;
+ if (po->po_sscount == 0) {
+ atomic_subtract_rel_int(&pmc_ss_count, 1);
+ LIST_REMOVE(po, po_ssnext);
+ }
+ }
+
pm->pm_state = PMC_STATE_DELETED;
pmc_restore_cpu_binding(&pb);
+ /*
+ * We could have references to this PMC structure in
+ * the per-cpu sample queues. Wait for the queue to
+ * drain.
+ */
+ pmc_wait_for_pmc_idle(pm);
+
} else if (PMC_IS_VIRTUAL_MODE(mode)) {
/*
@@ -1938,30 +1904,11 @@ pmc_release_pmc_descriptor(struct pmc *pm)
*
* Then we wait till all CPUs are done with this PMC.
*/
-
pm->pm_state = PMC_STATE_DELETED;
- /*
- * Wait for the PMCs runcount to come to zero.
- */
-
-#if DEBUG
- maxloop = 100 * mp_ncpus;
-#endif
-
- while (atomic_load_acq_32(&pm->pm_runcount) > 0) {
-
-#if DEBUG
- maxloop--;
- KASSERT(maxloop > 0,
- ("[pmc,%d] (ri%d, rc%d) waiting too long for "
- "pmc to be free", __LINE__,
- PMC_TO_ROWINDEX(pm), pm->pm_runcount));
-#endif
-
- pmc_force_context_switch();
- }
+ /* Wait for the PMCs runcount to come to zero. */
+ pmc_wait_for_pmc_idle(pm);
/*
* At this point the PMC is off all CPUs and cannot be
@@ -1971,7 +1918,6 @@ pmc_release_pmc_descriptor(struct pmc *pm)
* it from the hash table. The module-wide SX lock
* protects us from races.
*/
-
LIST_FOREACH_SAFE(ptgt, &pm->pm_targets, pt_next, tmp) {
pp = ptgt->pt_process;
pmc_unlink_target_process(pm, pp); /* frees 'ptgt' */
@@ -2009,8 +1955,10 @@ pmc_release_pmc_descriptor(struct pmc *pm)
PMC_UNMARK_ROW_THREAD(ri);
/* unlink from the owner's list */
- if (pm->pm_owner)
- pmc_unlink_owner(pm);
+ if (pm->pm_owner) {
+ LIST_REMOVE(pm, pm_next);
+ pm->pm_owner = NULL;
+ }
pmc_destroy_pmc_descriptor(pm);
}
@@ -2022,47 +1970,29 @@ pmc_release_pmc_descriptor(struct pmc *pm)
static int
pmc_register_owner(struct proc *p, struct pmc *pmc)
{
- struct pmc_list *pl;
struct pmc_owner *po;
sx_assert(&pmc_sx, SX_XLOCKED);
- MALLOC(pl, struct pmc_list *, sizeof(struct pmc_list), M_PMC,
- M_WAITOK);
-
- if (pl == NULL)
- return ENOMEM;
-
if ((po = pmc_find_owner_descriptor(p)) == NULL)
- if ((po = pmc_allocate_owner_descriptor(p)) == NULL) {
- FREE(pl, M_PMC);
+ if ((po = pmc_allocate_owner_descriptor(p)) == NULL)
return ENOMEM;
- }
-
- /* XXX is this too restrictive */
- if (PMC_ID_TO_MODE(pmc->pm_id) == PMC_MODE_TS) {
- /* can have only one TS mode PMC per process */
- if (po->po_flags & PMC_PO_HAS_TS_PMC) {
- FREE(pl, M_PMC);
- return EINVAL;
- }
- po->po_flags |= PMC_PO_HAS_TS_PMC;
- }
KASSERT(pmc->pm_owner == NULL,
("[pmc,%d] attempting to own an initialized PMC", __LINE__));
pmc->pm_owner = po;
- pl->pl_pmc = pmc;
-
- LIST_INSERT_HEAD(&po->po_pmcs, pl, pl_next);
+ LIST_INSERT_HEAD(&po->po_pmcs, pmc, pm_next);
PROC_LOCK(p);
p->p_flag |= P_HWPMC;
PROC_UNLOCK(p);
- PMCDBG(PMC,REG,1, "register-owner pmc-owner=%p pl=%p pmc=%p",
- po, pl, pmc);
+ if (po->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_pmcallocate(pmc);
+
+ PMCDBG(PMC,REG,1, "register-owner pmc-owner=%p pmc=%p",
+ po, pmc);
return 0;
}
@@ -2096,7 +2026,6 @@ pmc_can_allocate_rowindex(struct proc *p, unsigned int ri, int cpu)
{
enum pmc_mode mode;
struct pmc *pm;
- struct pmc_list *pl;
struct pmc_owner *po;
struct pmc_process *pp;
@@ -2111,8 +2040,7 @@ pmc_can_allocate_rowindex(struct proc *p, unsigned int ri, int cpu)
* CPU and same RI.
*/
if ((po = pmc_find_owner_descriptor(p)) != NULL)
- LIST_FOREACH(pl, &po->po_pmcs, pl_next) {
- pm = pl->pl_pmc;
+ LIST_FOREACH(pm, &po->po_pmcs, pm_next) {
if (PMC_TO_ROWINDEX(pm) == ri) {
mode = PMC_TO_MODE(pm);
if (PMC_IS_VIRTUAL_MODE(mode))
@@ -2189,15 +2117,15 @@ pmc_can_allocate_row(int ri, enum pmc_mode mode)
static struct pmc *
pmc_find_pmc_descriptor_in_process(struct pmc_owner *po, pmc_id_t pmcid)
{
- struct pmc_list *pl;
+ struct pmc *pm;
KASSERT(PMC_ID_TO_ROWINDEX(pmcid) < md->pmd_npmc,
("[pmc,%d] Illegal pmc index %d (max %d)", __LINE__,
PMC_ID_TO_ROWINDEX(pmcid), md->pmd_npmc));
- LIST_FOREACH(pl, &po->po_pmcs, pl_next)
- if (pl->pl_pmc->pm_id == pmcid)
- return pl->pl_pmc;
+ LIST_FOREACH(pm, &po->po_pmcs, pm_next)
+ if (pm->pm_id == pmcid)
+ return pm;
return NULL;
}
@@ -2232,6 +2160,7 @@ pmc_start(struct pmc *pm)
{
int error, cpu, ri;
enum pmc_mode mode;
+ struct pmc_owner *po;
struct pmc_binding pb;
KASSERT(pm != NULL,
@@ -2243,36 +2172,67 @@ pmc_start(struct pmc *pm)
PMCDBG(PMC,OPS,1, "start pmc=%p mode=%d ri=%d", pm, mode, ri);
- pm->pm_state = PMC_STATE_RUNNING;
+ po = pm->pm_owner;
if (PMC_IS_VIRTUAL_MODE(mode)) {
/*
- * If a PMCATTACH hadn't been done on this
- * PMC, attach this PMC to its owner process.
+ * If a PMCATTACH has never been done on this PMC,
+ * attach it to its owner process.
*/
if (LIST_EMPTY(&pm->pm_targets))
- error = pmc_attach_process(pm->pm_owner->po_owner, pm);
+ error = (pm->pm_flags & PMC_F_ATTACH_DONE) ? ESRCH :
+ pmc_attach_process(po->po_owner, pm);
/*
- * If the PMC is attached to its owner, then force a context
- * switch to ensure that the MD state gets set correctly.
+ * Disallow PMCSTART if a logfile is required but has not
+ * been configured yet.
*/
- if (error == 0 && (pm->pm_flags & PMC_F_ATTACHED_TO_OWNER))
- pmc_force_context_switch();
+
+ if (error == 0 && (pm->pm_flags & PMC_F_NEEDS_LOGFILE) &&
+ (po->po_flags & PMC_PO_OWNS_LOGFILE) == 0)
+ error = EDOOFUS;
/*
- * Nothing further to be done; thread context switch code
- * will start/stop the hardware as appropriate.
+ * If the PMC is attached to its owner, then force a context
+ * switch to ensure that the MD state gets set correctly.
*/
+ if (error == 0) {
+ pm->pm_state = PMC_STATE_RUNNING;
+ if (pm->pm_flags & PMC_F_ATTACHED_TO_OWNER)
+ pmc_force_context_switch();
+ }
+
return error;
+ }
+
+
+ /*
+ * A system-wide PMC.
+ */
+ if ((pm->pm_flags & PMC_F_NEEDS_LOGFILE) &&
+ (po->po_flags & PMC_PO_OWNS_LOGFILE) == 0)
+ return EDOOFUS; /* programming error */
+
+ /*
+ * Add the owner to the global list if this is a system-wide
+ * sampling PMC.
+ */
+
+ if (mode == PMC_MODE_SS) {
+ if (po->po_sscount == 0) {
+ LIST_INSERT_HEAD(&pmc_ss_owners, po, po_ssnext);
+ atomic_add_rel_int(&pmc_ss_count, 1);
+ PMCDBG(PMC,OPS,1, "po=%p in global list", po);
+ }
+ po->po_sscount++;
}
/*
- * A system-wide PMC. Move to the CPU associated with this
+ * Move to the CPU associated with this
* PMC, and start the hardware.
*/
@@ -2290,6 +2250,8 @@ pmc_start(struct pmc *pm)
* so write out the initial value and start the PMC.
*/
+ pm->pm_state = PMC_STATE_RUNNING;
+
critical_enter();
if ((error = md->pmd_write_pmc(cpu, ri,
PMC_IS_SAMPLING_MODE(mode) ?
@@ -2311,6 +2273,7 @@ static int
pmc_stop(struct pmc *pm)
{
int cpu, error, ri;
+ struct pmc_owner *po;
struct pmc_binding pb;
KASSERT(pm != NULL, ("[pmc,%d] null pmc", __LINE__));
@@ -2361,6 +2324,18 @@ pmc_stop(struct pmc *pm)
pmc_restore_cpu_binding(&pb);
+ po = pm->pm_owner;
+
+ /* remove this owner from the global list of SS PMC owners */
+ if (PMC_TO_MODE(pm) == PMC_MODE_SS) {
+ po->po_sscount--;
+ if (po->po_sscount == 0) {
+ atomic_subtract_rel_int(&pmc_ss_count, 1);
+ LIST_REMOVE(po, po_ssnext);
+ PMCDBG(PMC,OPS,2,"po=%p removed from global list", po);
+ }
+ }
+
return error;
}
@@ -2400,6 +2375,8 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
PMC_GET_SX_XLOCK(ENOSYS);
+ DROP_GIANT();
+
is_sx_downgraded = 0;
c = (struct pmc_syscall_args *) syscall_args;
@@ -2437,16 +2414,49 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
/* mark this process as owning a log file */
p = td->td_proc;
if ((po = pmc_find_owner_descriptor(p)) == NULL)
- if ((po = pmc_allocate_owner_descriptor(p)) == NULL)
- return ENOMEM;
+ if ((po = pmc_allocate_owner_descriptor(p)) == NULL) {
+ error = ENOMEM;
+ break;
+ }
+
+ /*
+ * If a valid fd was passed in, try to configure that,
+ * otherwise if 'fd' was less than zero and there was
+ * a log file configured, flush its buffers and
+ * de-configure it.
+ */
+ if (cl.pm_logfd >= 0)
+ error = pmclog_configure_log(po, cl.pm_logfd);
+ else if (po->po_flags & PMC_PO_OWNS_LOGFILE) {
+ pmclog_process_closelog(po);
+ error = pmclog_flush(po);
+ if (error == 0)
+ error = pmclog_deconfigure_log(po);
+ } else
+ error = EINVAL;
+ }
+ break;
- if ((error = pmc_configure_log(po, cl.pm_logfd)) != 0)
+
+ /*
+ * Flush a log file.
+ */
+
+ case PMC_OP_FLUSHLOG:
+ {
+ struct pmc_owner *po;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+
+ if ((po = pmc_find_owner_descriptor(td->td_proc)) == NULL) {
+ error = EINVAL;
break;
+ }
+ error = pmclog_flush(po);
}
break;
-
/*
* Retrieve hardware configuration.
*/
@@ -2486,7 +2496,18 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
case PMC_OP_GETMODULEVERSION:
{
- error = copyout(&_pmc_version.mv_version, arg, sizeof(int));
+ uint32_t cv, modv;
+
+ /* retrieve the client's idea of the ABI version */
+ if ((error = copyin(arg, &cv, sizeof(uint32_t))) != 0)
+ break;
+ /* don't service clients newer than our driver */
+ modv = PMC_VERSION;
+ if ((cv & 0xFFFF0000) > (modv & 0xFFFF0000)) {
+ error = EPROGMISMATCH;
+ break;
+ }
+ error = copyout(&modv, arg, sizeof(int));
}
break;
@@ -2748,8 +2769,15 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
* Look for valid values for 'pm_flags'
*/
- if ((pa.pm_flags & ~(PMC_F_DESCENDANTS|PMC_F_LOG_TC_CSW))
- != 0) {
+ if ((pa.pm_flags & ~(PMC_F_DESCENDANTS | PMC_F_LOG_PROCCSW |
+ PMC_F_LOG_PROCEXIT)) != 0) {
+ error = EINVAL;
+ break;
+ }
+
+ /* process logging options are not allowed for system PMCs */
+ if (PMC_IS_SYSTEM_MODE(mode) && (pa.pm_flags &
+ (PMC_F_LOG_PROCCSW | PMC_F_LOG_PROCEXIT))) {
error = EINVAL;
break;
}
@@ -2759,11 +2787,8 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
* CPU.
*/
- if (PMC_IS_SAMPLING_MODE(mode)) {
+ if (PMC_IS_SAMPLING_MODE(mode))
caps |= PMC_CAP_INTERRUPT;
- error = ENOSYS; /* for snapshot 6 */
- break;
- }
PMCDBG(PMC,ALL,2, "event=%d caps=0x%x mode=%d cpu=%d",
pa.pm_ev, caps, mode, cpu);
@@ -2828,6 +2853,14 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
PMCDBG(PMC,ALL,2, "ev=%d class=%d mode=%d n=%d -> pmcid=%x",
pmc->pm_event, pa.pm_class, mode, n, pmc->pm_id);
+ /* Process mode PMCs with logging enabled need log files */
+ if (pmc->pm_flags & (PMC_F_LOG_PROCEXIT | PMC_F_LOG_PROCCSW))
+ pmc->pm_flags |= PMC_F_NEEDS_LOGFILE;
+
+ /* All system mode sampling PMCs require a log file */
+ if (PMC_IS_SAMPLING_MODE(mode) && PMC_IS_SYSTEM_MODE(mode))
+ pmc->pm_flags |= PMC_F_NEEDS_LOGFILE;
+
/*
* Configure global pmc's immediately
*/
@@ -2999,6 +3032,85 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
/*
+ * Retrieve the MSR number associated with the counter
+ * 'pmc_id'. This allows processes to directly use RDPMC
+ * instructions to read their PMCs, without the overhead of a
+ * system call.
+ */
+
+ case PMC_OP_PMCGETMSR:
+ {
+ int ri;
+ struct pmc *pm;
+ struct pmc_target *pt;
+ struct pmc_op_getmsr gm;
+
+ PMC_DOWNGRADE_SX();
+
+ /* CPU has no 'GETMSR' support */
+ if (md->pmd_get_msr == NULL) {
+ error = ENOSYS;
+ break;
+ }
+
+ if ((error = copyin(arg, &gm, sizeof(gm))) != 0)
+ break;
+
+ if ((error = pmc_find_pmc(gm.pm_pmcid, &pm)) != 0)
+ break;
+
+ /*
+ * The allocated PMC has to be a process virtual PMC,
+ * i.e., of type MODE_T[CS]. Global PMCs can only be
+ * read using the PMCREAD operation since they may be
+ * allocated on a different CPU than the one we could
+ * be running on at the time of the RDPMC instruction.
+ *
+ * The GETMSR operation is not allowed for PMCs that
+ * are inherited across processes.
+ */
+
+ if (!PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)) ||
+ (pm->pm_flags & PMC_F_DESCENDANTS)) {
+ error = EINVAL;
+ break;
+ }
+
+ /*
+ * It only makes sense to use a RDPMC (or its
+ * equivalent instruction on non-x86 architectures) on
+ * a process that has allocated and attached a PMC to
+ * itself. Conversely the PMC is only allowed to have
+ * one process attached to it -- its owner.
+ */
+
+ if ((pt = LIST_FIRST(&pm->pm_targets)) == NULL ||
+ LIST_NEXT(pt, pt_next) != NULL ||
+ pt->pt_process->pp_proc != pm->pm_owner->po_owner) {
+ error = EINVAL;
+ break;
+ }
+
+ ri = PMC_TO_ROWINDEX(pm);
+
+ if ((error = (*md->pmd_get_msr)(ri, &gm.pm_msr)) < 0)
+ break;
+
+ if ((error = copyout(&gm, arg, sizeof(gm))) < 0)
+ break;
+
+ /*
+ * Mark our process as using MSRs. Update machine
+ * state using a forced context switch.
+ */
+
+ pt->pt_process->pp_flags |= PMC_PP_ENABLE_MSR_ACCESS;
+ pmc_force_context_switch();
+
+ }
+ break;
+
+ /*
* Release an allocated PMC
*/
@@ -3166,17 +3278,6 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
sizeof(prw.pm_value))))
break;
- /*
- * send a signal (SIGIO) to the owner if it is trying to read
- * a PMC with no target processes attached.
- */
-
- if (LIST_EMPTY(&pm->pm_targets) &&
- (prw.pm_flags & PMC_F_OLDVALUE)) {
- PROC_LOCK(curthread->td_proc);
- psignal(curthread->td_proc, SIGIO);
- PROC_UNLOCK(curthread->td_proc);
- }
}
break;
@@ -3291,107 +3392,34 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
/*
- * Write a user-entry to the log file.
+ * Flush the per-owner log file and Write a user-entry to the
+ * log file.
*/
case PMC_OP_WRITELOG:
{
+ struct pmc_op_writelog wl;
+ struct pmc_owner *po;
PMC_DOWNGRADE_SX();
- /*
- * flush all per-cpu hash tables
- * append user-log entry
- */
-
- error = ENOSYS;
- }
- break;
-
-
-#if __i386__ || __amd64__
-
- /*
- * Machine dependent operation for i386-class processors.
- *
- * Retrieve the MSR number associated with the counter
- * 'pmc_id'. This allows processes to directly use RDPMC
- * instructions to read their PMCs, without the overhead of a
- * system call.
- */
-
- case PMC_OP_PMCX86GETMSR:
- {
- int ri;
- struct pmc *pm;
- struct pmc_target *pt;
- struct pmc_op_x86_getmsr gm;
-
- PMC_DOWNGRADE_SX();
-
- /* CPU has no 'GETMSR' support */
- if (md->pmd_get_msr == NULL) {
- error = ENOSYS;
- break;
- }
-
- if ((error = copyin(arg, &gm, sizeof(gm))) != 0)
- break;
-
- if ((error = pmc_find_pmc(gm.pm_pmcid, &pm)) != 0)
+ if ((error = copyin(arg, &wl, sizeof(wl))) != 0)
break;
- /*
- * The allocated PMC has to be a process virtual PMC,
- * i.e., of type MODE_T[CS]. Global PMCs can only be
- * read using the PMCREAD operation since they may be
- * allocated on a different CPU than the one we could
- * be running on at the time of the RDPMC instruction.
- *
- * The GETMSR operation is not allowed for PMCs that
- * are inherited across processes.
- */
-
- if (!PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)) ||
- (pm->pm_flags & PMC_F_DESCENDANTS)) {
+ if ((po = pmc_find_owner_descriptor(td->td_proc)) == NULL) {
error = EINVAL;
break;
}
- /*
- * It only makes sense to use a RDPMC (or its
- * equivalent instruction on non-x86 architectures) on
- * a process that has allocated and attached a PMC to
- * itself. Conversely the PMC is only allowed to have
- * one process attached to it -- its owner.
- */
-
- if ((pt = LIST_FIRST(&pm->pm_targets)) == NULL ||
- LIST_NEXT(pt, pt_next) != NULL ||
- pt->pt_process->pp_proc != pm->pm_owner->po_owner) {
+ if ((po->po_flags & PMC_PO_OWNS_LOGFILE) == 0) {
error = EINVAL;
break;
}
- ri = PMC_TO_ROWINDEX(pm);
-
- if ((error = (*md->pmd_get_msr)(ri, &gm.pm_msr)) < 0)
- break;
-
- if ((error = copyout(&gm, arg, sizeof(gm))) < 0)
- break;
-
- /*
- * Mark our process as using MSRs. Update machine
- * state using a forced context switch.
- */
-
- pt->pt_process->pp_flags |= PMC_PP_ENABLE_MSR_ACCESS;
- pmc_force_context_switch();
-
+ error = pmclog_process_userlog(po, &wl);
}
break;
-#endif
+
default:
error = EINVAL;
@@ -3406,6 +3434,8 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
if (error)
atomic_add_int(&pmc_stats.pm_syscall_errors, 1);
+ PICKUP_GIANT();
+
return error;
}
@@ -3413,57 +3443,175 @@ pmc_syscall_handler(struct thread *td, void *syscall_args)
* Helper functions
*/
+
/*
- * Configure a log file.
+ * Interrupt processing.
+ *
+ * Find a free slot in the per-cpu array of PC samples and write the
+ * current (PMC,PID,PC) triple to it. If an event was successfully
+ * added, a bit is set in mask 'pmc_cpumask' denoting that the
+ * DO_SAMPLES hook needs to be invoked from the clock handler.
+ *
+ * This function is meant to be called from an NMI handler. It cannot
+ * use any of the locking primitives supplied by the OS.
*/
-static int
-pmc_configure_log(struct pmc_owner *po, int logfd)
+int
+pmc_process_interrupt(int cpu, struct pmc *pm, intfptr_t pc, int usermode)
{
- struct proc *p;
-
- return ENOSYS; /* for now */
+ int error, ri;
+ struct thread *td;
+ struct pmc_sample *ps;
+ struct pmc_samplebuffer *psb;
- p = po->po_owner;
+ error = 0;
+ ri = PMC_TO_ROWINDEX(pm);
- if (po->po_logfd < 0 && logfd < 0) /* nothing to do */
- return 0;
+ psb = pmc_pcpu[cpu]->pc_sb;
+
+ ps = psb->ps_write;
+ if (ps->ps_pc) { /* in use, reader hasn't caught up */
+ atomic_add_int(&pmc_stats.pm_intr_bufferfull, 1);
+ atomic_set_int(&pm->pm_flags, PMC_F_IS_STALLED);
+ PMCDBG(SAM,INT,1,"(spc) cpu=%d pm=%p pc=%jx um=%d wr=%d rd=%d",
+ cpu, pm, (int64_t) pc, usermode,
+ (int) (psb->ps_write - psb->ps_samples),
+ (int) (psb->ps_read - psb->ps_samples));
+ error = ENOMEM;
+ goto done;
+ }
- if (po->po_logfd >= 0 && logfd < 0) {
- /* deconfigure log */
- /* XXX */
- po->po_flags &= ~PMC_PO_OWNS_LOGFILE;
- pmc_maybe_remove_owner(po);
+ /* fill in entry */
+ PMCDBG(SAM,INT,1,"cpu=%d pm=%p pc=%jx um=%d wr=%d rd=%d", cpu, pm,
+ (int64_t) pc, usermode,
+ (int) (psb->ps_write - psb->ps_samples),
+ (int) (psb->ps_read - psb->ps_samples));
- } else if (po->po_logfd < 0 && logfd >= 0) {
- /* configure log file */
- /* XXX */
- po->po_flags |= PMC_PO_OWNS_LOGFILE;
+ atomic_add_rel_32(&pm->pm_runcount, 1); /* hold onto PMC */
+ ps->ps_pmc = pm;
+ if ((td = curthread) && td->td_proc)
+ ps->ps_pid = td->td_proc->p_pid;
+ else
+ ps->ps_pid = -1;
+ ps->ps_usermode = usermode;
+ ps->ps_pc = pc; /* mark entry as in use */
+
+ /* increment write pointer, modulo ring buffer size */
+ ps++;
+ if (ps == psb->ps_fence)
+ psb->ps_write = psb->ps_samples;
+ else
+ psb->ps_write = ps;
- /* mark process as using HWPMCs */
- PROC_LOCK(p);
- p->p_flag |= P_HWPMC;
- PROC_UNLOCK(p);
- } else
- return EBUSY;
+ done:
+ /* mark CPU as needing processing */
+ atomic_set_rel_int(&pmc_cpumask, (1 << cpu));
- return 0;
+ return error;
}
+
/*
- * Log an exit event to the PMC owner's log file.
+ * Process saved PC samples.
*/
static void
-pmc_log_process_exit(struct pmc *pm, struct pmc_process *pp)
+pmc_process_samples(int cpu)
{
- KASSERT(pm->pm_flags & PMC_F_LOG_TC_PROCEXIT,
- ("[pmc,%d] log-process-exit called gratuitously", __LINE__));
+ int n, ri;
+ struct pmc *pm;
+ struct thread *td;
+ struct pmc_owner *po;
+ struct pmc_sample *ps;
+ struct pmc_samplebuffer *psb;
- (void) pm;
- (void) pp;
+ KASSERT(PCPU_GET(cpuid) == cpu,
+ ("[pmc,%d] not on the correct CPU pcpu=%d cpu=%d", __LINE__,
+ PCPU_GET(cpuid), cpu));
+
+ psb = pmc_pcpu[cpu]->pc_sb;
+
+ for (n = 0; n < pmc_nsamples; n++) { /* bound on #iterations */
+
+ ps = psb->ps_read;
+ if (ps->ps_pc == (uintfptr_t) 0) /* no data */
+ break;
+
+ pm = ps->ps_pmc;
+ po = pm->pm_owner;
+
+ KASSERT(PMC_IS_SAMPLING_MODE(PMC_TO_MODE(pm)),
+ ("[pmc,%d] pmc=%p non-sampling mode=%d", __LINE__,
+ pm, PMC_TO_MODE(pm)));
+
+ /* Ignore PMCs that have been switched off */
+ if (pm->pm_state != PMC_STATE_RUNNING)
+ goto entrydone;
+
+ PMCDBG(SAM,OPS,1,"cpu=%d pm=%p pc=%jx um=%d wr=%d rd=%d", cpu,
+ pm, (int64_t) ps->ps_pc, ps->ps_usermode,
+ (int) (psb->ps_write - psb->ps_samples),
+ (int) (psb->ps_read - psb->ps_samples));
+
+ /*
+ * If this is a process-mode PMC that is attached to
+ * its owner, and if the PC is in user mode, update
+ * profiling statistics like timer-based profiling
+ * would have done.
+ */
+ if (pm->pm_flags & PMC_F_ATTACHED_TO_OWNER) {
+ if (ps->ps_usermode) {
+ td = FIRST_THREAD_IN_PROC(po->po_owner);
+ addupc_intr(td, ps->ps_pc, 1);
+ }
+ goto entrydone;
+ }
+
+ /*
+ * Otherwise, this is either a sampling mode PMC that
+ * is attached to a different process than its owner,
+ * or a system-wide sampling PMC. Dispatch a log
+ * entry to the PMC's owner process.
+ */
+
+ pmclog_process_pcsample(pm, ps);
+
+ entrydone:
+ ps->ps_pc = (uintfptr_t) 0; /* mark entry as free */
+ atomic_subtract_rel_32(&pm->pm_runcount, 1);
+
+ /* increment read pointer, modulo sample size */
+ if (++ps == psb->ps_fence)
+ psb->ps_read = psb->ps_samples;
+ else
+ psb->ps_read = ps;
+ }
+
+ atomic_add_int(&pmc_stats.pm_log_sweeps, 1);
+
+ /* Do not re-enable stalled PMCs if we failed to process any samples */
+ if (n == 0)
+ return;
+
+ /*
+ * Restart any stalled sampling PMCs on this CPU.
+ *
+ * If the NMI handler sets PMC_F_IS_STALLED on a PMC after the
+ * check below, we'll end up processing the stalled PMC at the
+ * next hardclock tick.
+ */
+ for (n = 0; n < md->pmd_npmc; n++) {
+ (void) (*md->pmd_get_config)(cpu,n,&pm);
+ if (pm == NULL || /* !cfg'ed */
+ pm->pm_state != PMC_STATE_RUNNING || /* !active */
+ !PMC_IS_SAMPLING_MODE(PMC_TO_MODE(pm)) || /* !sampling */
+ (pm->pm_flags & PMC_F_IS_STALLED) == 0) /* !stalled */
+ continue;
- return;
+ pm->pm_flags &= ~PMC_F_IS_STALLED;
+ ri = PMC_TO_ROWINDEX(pm);
+ (*md->pmd_start_pmc)(cpu, ri);
+ }
}
/*
@@ -3473,30 +3621,173 @@ pmc_log_process_exit(struct pmc *pm, struct pmc_process *pp)
/*
* Handle a process exit.
*
+ * Remove this process from all hash tables. If this process
+ * owned any PMCs, turn off those PMCs and deallocate them,
+ * removing any associations with target processes.
+ *
+ * This function will be called by the last 'thread' of a
+ * process.
+ *
* XXX This eventhandler gets called early in the exit process.
* Consider using a 'hook' invocation from thread_exit() or equivalent
* spot. Another negative is that kse_exit doesn't seem to call
* exit1() [??].
+ *
*/
static void
pmc_process_exit(void *arg __unused, struct proc *p)
{
int is_using_hwpmcs;
+ int cpu;
+ unsigned int ri;
+ struct pmc *pm;
+ struct pmc_process *pp;
+ struct pmc_owner *po;
+ pmc_value_t newvalue, tmp;
PROC_LOCK(p);
is_using_hwpmcs = p->p_flag & P_HWPMC;
PROC_UNLOCK(p);
- if (is_using_hwpmcs) {
- PMCDBG(PRC,EXT,1,"process-exit proc=%p (%d, %s)", p, p->p_pid,
- p->p_comm);
+ /*
+ * Log a sysexit event to all SS PMC owners.
+ */
+ LIST_FOREACH(po, &pmc_ss_owners, po_ssnext)
+ if (po->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_sysexit(po, p->p_pid);
+
+ if (!is_using_hwpmcs)
+ return;
+
+ PMC_GET_SX_XLOCK();
+ PMCDBG(PRC,EXT,1,"process-exit proc=%p (%d, %s)", p, p->p_pid,
+ p->p_comm);
- PMC_GET_SX_XLOCK();
- (void) pmc_hook_handler(curthread, PMC_FN_PROCESS_EXIT,
- (void *) p);
- sx_xunlock(&pmc_sx);
+ /*
+ * Since this code is invoked by the last thread in an exiting
+ * process, we would have context switched IN at some prior
+ * point. However, with PREEMPTION, kernel mode context
+ * switches may happen any time, so we want to disable a
+ * context switch OUT till we get any PMCs targetting this
+ * process off the hardware.
+ *
+ * We also need to atomically remove this process'
+ * entry from our target process hash table, using
+ * PMC_FLAG_REMOVE.
+ */
+ PMCDBG(PRC,EXT,1, "process-exit proc=%p (%d, %s)", p, p->p_pid,
+ p->p_comm);
+
+ critical_enter(); /* no preemption */
+
+ cpu = curthread->td_oncpu;
+
+ if ((pp = pmc_find_process_descriptor(p,
+ PMC_FLAG_REMOVE)) != NULL) {
+
+ PMCDBG(PRC,EXT,2,
+ "process-exit proc=%p pmc-process=%p", p, pp);
+
+ /*
+ * The exiting process could the target of
+ * some PMCs which will be running on
+ * currently executing CPU.
+ *
+ * We need to turn these PMCs off like we
+ * would do at context switch OUT time.
+ */
+ for (ri = 0; ri < md->pmd_npmc; ri++) {
+
+ /*
+ * Pick up the pmc pointer from hardware
+ * state similar to the CSW_OUT code.
+ */
+ pm = NULL;
+ (void) (*md->pmd_get_config)(cpu, ri, &pm);
+
+ PMCDBG(PRC,EXT,2, "ri=%d pm=%p", ri, pm);
+
+ if (pm == NULL ||
+ !PMC_IS_VIRTUAL_MODE(PMC_TO_MODE(pm)))
+ continue;
+
+ PMCDBG(PRC,EXT,2, "ppmcs[%d]=%p pm=%p "
+ "state=%d", ri, pp->pp_pmcs[ri].pp_pmc,
+ pm, pm->pm_state);
+
+ KASSERT(PMC_TO_ROWINDEX(pm) == ri,
+ ("[pmc,%d] ri mismatch pmc(%d) ri(%d)",
+ __LINE__, PMC_TO_ROWINDEX(pm), ri));
+
+ KASSERT(pm == pp->pp_pmcs[ri].pp_pmc,
+ ("[pmc,%d] pm %p != pp_pmcs[%d] %p",
+ __LINE__, pm, ri, pp->pp_pmcs[ri].pp_pmc));
+
+ (void) md->pmd_stop_pmc(cpu, ri);
+
+ KASSERT(pm->pm_runcount > 0,
+ ("[pmc,%d] bad runcount ri %d rc %d",
+ __LINE__, ri, pm->pm_runcount));
+
+ /* Stopped the hardware only if it is actually on */
+ if (pm->pm_state == PMC_STATE_RUNNING &&
+ (pm->pm_flags & PMC_F_IS_STALLED) == 0) {
+ md->pmd_read_pmc(cpu, ri, &newvalue);
+ tmp = newvalue -
+ PMC_PCPU_SAVED(cpu,ri);
+
+ mtx_pool_lock_spin(pmc_mtxpool, pm);
+ pm->pm_gv.pm_savedvalue += tmp;
+ pp->pp_pmcs[ri].pp_pmcval += tmp;
+ mtx_pool_unlock_spin(pmc_mtxpool, pm);
+ }
+
+ atomic_subtract_rel_32(&pm->pm_runcount,1);
+
+ KASSERT((int) pm->pm_runcount >= 0,
+ ("[pmc,%d] runcount is %d", __LINE__, ri));
+
+ (void) md->pmd_config_pmc(cpu, ri, NULL);
+ }
+
+ /*
+ * Inform the MD layer of this pseudo "context switch
+ * out"
+ */
+ (void) md->pmd_switch_out(pmc_pcpu[cpu], pp);
+
+ critical_exit(); /* ok to be pre-empted now */
+
+ /*
+ * Unlink this process from the PMCs that are
+ * targetting it. This will send a signal to
+ * all PMC owner's whose PMCs are orphaned.
+ *
+ * Log PMC value at exit time if requested.
+ */
+ for (ri = 0; ri < md->pmd_npmc; ri++)
+ if ((pm = pp->pp_pmcs[ri].pp_pmc) != NULL) {
+ if (pm->pm_flags & PMC_F_LOG_PROCEXIT)
+ pmclog_process_procexit(pm, pp);
+ pmc_unlink_target_process(pm, pp);
+ }
+ FREE(pp, M_PMC);
+
+ } else
+ critical_exit(); /* pp == NULL */
+
+
+ /*
+ * If the process owned PMCs, free them up and free up
+ * memory.
+ */
+ if ((po = pmc_find_owner_descriptor(p)) != NULL) {
+ pmc_remove_owner(po);
+ pmc_destroy_owner_descriptor(po);
}
+
+ sx_xunlock(&pmc_sx);
}
/*
@@ -3507,10 +3798,15 @@ pmc_process_exit(void *arg __unused, struct proc *p)
*/
static void
-pmc_process_fork(void *arg __unused, struct proc *p1, struct proc *p2,
+pmc_process_fork(void *arg __unused, struct proc *p1, struct proc *newproc,
int flags)
{
int is_using_hwpmcs;
+ unsigned int ri;
+ uint32_t do_descendants;
+ struct pmc *pm;
+ struct pmc_owner *po;
+ struct pmc_process *ppnew, *ppold;
(void) flags; /* unused parameter */
@@ -3518,14 +3814,72 @@ pmc_process_fork(void *arg __unused, struct proc *p1, struct proc *p2,
is_using_hwpmcs = p1->p_flag & P_HWPMC;
PROC_UNLOCK(p1);
- if (is_using_hwpmcs) {
- PMCDBG(PMC,FRK,1, "process-fork proc=%p (%d, %s)", p1,
- p1->p_pid, p1->p_comm);
- PMC_GET_SX_XLOCK();
- (void) pmc_hook_handler(curthread, PMC_FN_PROCESS_FORK,
- (void *) p2);
- sx_xunlock(&pmc_sx);
- }
+ /*
+ * If there are system-wide sampling PMCs active, we need to
+ * log all fork events to their owner's logs.
+ */
+
+ LIST_FOREACH(po, &pmc_ss_owners, po_ssnext)
+ if (po->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_procfork(po, p1->p_pid, newproc->p_pid);
+
+ if (!is_using_hwpmcs)
+ return;
+
+ PMC_GET_SX_XLOCK();
+ PMCDBG(PMC,FRK,1, "process-fork proc=%p (%d, %s) -> %p", p1,
+ p1->p_pid, p1->p_comm, newproc);
+
+ /*
+ * If the parent process (curthread->td_proc) is a
+ * target of any PMCs, look for PMCs that are to be
+ * inherited, and link these into the new process
+ * descriptor.
+ */
+ if ((ppold = pmc_find_process_descriptor(curthread->td_proc,
+ PMC_FLAG_NONE)) == NULL)
+ goto done; /* nothing to do */
+
+ do_descendants = 0;
+ for (ri = 0; ri < md->pmd_npmc; ri++)
+ if ((pm = ppold->pp_pmcs[ri].pp_pmc) != NULL)
+ do_descendants |= pm->pm_flags & PMC_F_DESCENDANTS;
+ if (do_descendants == 0) /* nothing to do */
+ goto done;
+
+ /* allocate a descriptor for the new process */
+ if ((ppnew = pmc_find_process_descriptor(newproc,
+ PMC_FLAG_ALLOCATE)) == NULL)
+ goto done;
+
+ /*
+ * Run through all PMCs that were targeting the old process
+ * and which specified F_DESCENDANTS and attach them to the
+ * new process.
+ *
+ * Log the fork event to all owners of PMCs attached to this
+ * process, if not already logged.
+ */
+ for (ri = 0; ri < md->pmd_npmc; ri++)
+ if ((pm = ppold->pp_pmcs[ri].pp_pmc) != NULL &&
+ (pm->pm_flags & PMC_F_DESCENDANTS)) {
+ pmc_link_target_process(pm, ppnew);
+ po = pm->pm_owner;
+ if (po->po_sscount == 0 &&
+ po->po_flags & PMC_PO_OWNS_LOGFILE)
+ pmclog_process_procfork(po, p1->p_pid,
+ newproc->p_pid);
+ }
+
+ /*
+ * Now mark the new process as being tracked by this driver.
+ */
+ PROC_LOCK(newproc);
+ newproc->p_flag |= P_HWPMC;
+ PROC_UNLOCK(newproc);
+
+ done:
+ sx_xunlock(&pmc_sx);
}
@@ -3542,8 +3896,9 @@ static const char *pmc_name_of_pmcclass[] = {
static int
pmc_initialize(void)
{
- int error, cpu, n;
+ int cpu, error, n;
struct pmc_binding pb;
+ struct pmc_samplebuffer *sb;
md = NULL;
error = 0;
@@ -3563,25 +3918,17 @@ pmc_initialize(void)
*/
if (pmc_hashsize <= 0) {
- (void) printf("pmc: sysctl variable \""
- PMC_SYSCTL_NAME_PREFIX "hashsize\" must be greater than "
- "zero\n");
+ (void) printf("hwpmc: tunable hashsize=%d must be greater "
+ "than zero.\n", pmc_hashsize);
pmc_hashsize = PMC_HASH_SIZE;
}
-#if defined(__i386__)
- /* determine the CPU kind. This is i386 specific */
- if (strcmp(cpu_vendor, "AuthenticAMD") == 0)
- md = pmc_amd_initialize();
- else if (strcmp(cpu_vendor, "GenuineIntel") == 0)
- md = pmc_intel_initialize();
- /* XXX: what about the other i386 CPU manufacturers? */
-#elif defined(__amd64__)
- if (strcmp(cpu_vendor, "AuthenticAMD") == 0)
- md = pmc_amd_initialize();
-#else /* other architectures */
- md = NULL;
-#endif
+ if (pmc_nsamples <= 0 || pmc_nsamples > 65535) {
+ (void) printf("hwpmc: tunable nsamples=%d out of range.\n", pmc_nsamples);
+ pmc_nsamples = PMC_NSAMPLES;
+ }
+
+ md = pmc_md_initialize();
if (md == NULL || md->pmd_init == NULL)
return ENOSYS;
@@ -3608,6 +3955,24 @@ pmc_initialize(void)
if (error != 0)
return error;
+ /* allocate space for the sample array */
+ for (cpu = 0; cpu < mp_ncpus; cpu++) {
+ if (pmc_cpu_is_disabled(cpu))
+ continue;
+ MALLOC(sb, struct pmc_samplebuffer *,
+ sizeof(struct pmc_samplebuffer) +
+ pmc_nsamples * sizeof(struct pmc_sample), M_PMC,
+ M_WAITOK|M_ZERO);
+
+ sb->ps_read = sb->ps_write = sb->ps_samples;
+ sb->ps_fence = sb->ps_samples + pmc_nsamples
+;
+ KASSERT(pmc_pcpu[cpu] != NULL,
+ ("[pmc,%d] cpu=%d Null per-cpu data", __LINE__, cpu));
+
+ pmc_pcpu[cpu]->pc_sb = sb;
+ }
+
/* allocate space for the row disposition array */
pmc_pmcdisp = malloc(sizeof(enum pmc_mode) * md->pmd_npmc,
M_PMC, M_WAITOK|M_ZERO);
@@ -3627,6 +3992,9 @@ pmc_initialize(void)
&pmc_processhashmask);
mtx_init(&pmc_processhash_mtx, "pmc-process-hash", "pmc", MTX_SPIN);
+ LIST_INIT(&pmc_ss_owners);
+ pmc_ss_count = 0;
+
/* allocate a pool of spin mutexes */
pmc_mtxpool = mtx_pool_create("pmc", pmc_mtxpool_size, MTX_SPIN);
@@ -3640,6 +4008,9 @@ pmc_initialize(void)
pmc_fork_tag = EVENTHANDLER_REGISTER(process_fork,
pmc_process_fork, NULL, EVENTHANDLER_PRI_ANY);
+ /* initialize logging */
+ pmclog_initialize();
+
/* set hook functions */
pmc_intr = md->pmd_intr;
pmc_hook = pmc_hook_handler;
@@ -3670,7 +4041,9 @@ pmc_cleanup(void)
PMCDBG(MOD,INI,0, "%s", "cleanup");
- pmc_intr = NULL; /* no more interrupts please */
+ /* switch off sampling */
+ atomic_store_rel_int(&pmc_cpumask, 0);
+ pmc_intr = NULL;
sx_xlock(&pmc_sx);
if (pmc_hook == NULL) { /* being unloaded already */
@@ -3701,7 +4074,8 @@ pmc_cleanup(void)
PROC_LOCK(po->po_owner);
psignal(po->po_owner, SIGBUS);
PROC_UNLOCK(po->po_owner);
- FREE(po, M_PMC);
+
+ pmc_destroy_owner_descriptor(po);
}
}
@@ -3732,6 +4106,11 @@ pmc_cleanup(void)
pmc_ownerhash = NULL;
}
+ KASSERT(LIST_EMPTY(&pmc_ss_owners),
+ ("[pmc,%d] Global SS owner list not empty", __LINE__));
+ KASSERT(pmc_ss_count == 0,
+ ("[pmc,%d] Global SS count not empty", __LINE__));
+
/* do processor dependent cleanup */
PMCDBG(MOD,INI,3, "%s", "md cleanup");
if (md) {
@@ -3762,6 +4141,8 @@ pmc_cleanup(void)
pmc_pmcdisp = NULL;
}
+ pmclog_shutdown();
+
sx_xunlock(&pmc_sx); /* we are done */
}