9ns: mnt: Don't use a 'bogus' struct
[akaros.git] / kern / arch / x86 / perfmon.c
index 05e46a3..41173bc 100644 (file)
@@ -1,7 +1,34 @@
 /* Copyright (c) 2015 Google Inc
  * Davide Libenzi <dlibenzi@google.com>
+ * Barret Rhoden <brho@cs.berkeley.edu>
  * See LICENSE for details.
- */
+ *
+ * Manages the setting and reading of hardware perf counters across all cores,
+ * including generating samples in response to counter overflow interrupts.
+ *
+ * The hardware interface is pretty straightforward - it's mostly setting and
+ * unsetting fixed and unfixed events, sometimes with interrupts and trigger
+ * counts.
+ *
+ * The 'command' to the cores is a struct perfmon_alloc.  This tells the core
+ * which event to set up (this is the perfmon_event).  The cores respond in
+ * counters[], saying which of their counters it is using for that event.  If
+ * the cores are given different alloc requests, it is possible that they might
+ * choose different counters[] for the same event.
+ *
+ * These perfmon_allocs are collected in a perfmon_session.  The session is just
+ * a bunch of allocs, which are referred to by index (the 'ped').  Currently,
+ * the session is grabbed by whoever opens the perf FD in devarch, and closed
+ * when that FD is closed.  They are 1:1 with devarch's perf_contexts.
+ *
+ * The values for the counters are extracted with perfmon_get_event_status(),
+ * which uses a struct perfmon_status to collect the results.  We pass the
+ * perfmon_alloc as part of the perfmon_status_env, since we need to tell the
+ * core which counter we're talking about.
+ *
+ * You can have multiple sessions, but if you try to install the same counter in
+ * multiple, concurrent sessions, the hardware might complain (it definitely
+ * will if it is a fixed event). */
 
 #include <sys/types.h>
 #include <arch/ros/msr-index.h>
@@ -15,7 +42,6 @@
 #include <smp.h>
 #include <atomic.h>
 #include <core_set.h>
-#include <kref.h>
 #include <percpu.h>
 #include <kmalloc.h>
 #include <err.h>
@@ -41,6 +67,15 @@ static struct perfmon_cpu_caps cpu_caps;
 static DEFINE_PERCPU(struct perfmon_cpu_context, counters_env);
 DEFINE_PERCPU_INIT(perfmon_counters_env_init);
 
+#define PROFILER_BT_DEPTH 16
+
+struct sample_snapshot {
+       struct user_context                     ctx;
+       uintptr_t                                       pc_list[PROFILER_BT_DEPTH];
+       size_t                                          nr_pcs;
+};
+static DEFINE_PERCPU(struct sample_snapshot, sample_snapshots);
+
 static void perfmon_counters_env_init(void)
 {
        for (int i = 0; i < num_cores; i++) {
@@ -103,7 +138,7 @@ static uint64_t perfmon_apply_fixevent_mask(uint64_t event, int idx,
                m |= (1 << 0);
        if (PMEV_GET_USR(event))
                m |= (1 << 1);
-       if (PMEV_GET_ANYTH(event) && (cpu_caps.perfmon_version >= 3))
+       if (PMEV_GET_ANYTH(event))
                m |= (1 << 2);
        if (PMEV_GET_INTEN(event))
                m |= (1 << 3);
@@ -171,6 +206,29 @@ static void perfmon_set_unfixed_trigger(unsigned int idx, uint64_t count)
        write_msr(MSR_IA32_PERFCTR0 + idx, write_val);
 }
 
+/* Helper: sets errno/errstr based on the error code returned from the core.  We
+ * don't have a great way to get errors back from smp_do_in_cores() commands.
+ * We use negative counter values (e.g. i = -EBUSY) to signal an error of a
+ * certain type.  This converts that to something useful for userspace. */
+static void perfmon_convert_error(int err_code, int core_id)
+{
+       switch (err_code) {
+       case EBUSY:
+               set_error(err_code, "Fixed perf counter is busy on core %d", core_id);
+               break;
+       case ENOSPC:
+               set_error(err_code, "Perf counter idx out of range on core %d",
+                         core_id);
+               break;
+       case ENOENT:
+               set_error(err_code, "Perf counter not set on core %d", core_id);
+               break;
+       default:
+               set_error(err_code, "Unknown perf counter error on core %d", core_id);
+               break;
+       };
+}
+
 static void perfmon_do_cores_alloc(void *opaque)
 {
        struct perfmon_alloc *pa = (struct perfmon_alloc *) opaque;
@@ -184,7 +242,7 @@ static void perfmon_do_cores_alloc(void *opaque)
 
                i = PMEV_GET_EVENT(pa->ev.event);
                if (i >= (int) cpu_caps.fix_counters_x_proc) {
-                       i = -EINVAL;
+                       i = -ENOSPC;
                } else if (!perfmon_fix_event_available(i, fxctrl_value)) {
                        i = -EBUSY;
                } else {
@@ -257,6 +315,30 @@ static void perfmon_do_cores_free(void *opaque)
        pa->cores_counters[coreno] = (counter_t) err;
 }
 
+/* Helper: Reads a fixed counter's value.  Returns the max amount possible if
+ * the counter overflowed. */
+static uint64_t perfmon_read_fixed_counter(int ccno)
+{
+       uint64_t overflow_status = read_msr(MSR_CORE_PERF_GLOBAL_STATUS);
+
+       if (overflow_status & (1ULL << (32 + ccno)))
+               return (1ULL << cpu_caps.bits_x_fix_counter) - 1;
+       else
+               return read_msr(MSR_CORE_PERF_FIXED_CTR0 + ccno);
+}
+
+/* Helper: Reads an unfixed counter's value.  Returns the max amount possible if
+ * the counter overflowed. */
+static uint64_t perfmon_read_unfixed_counter(int ccno)
+{
+       uint64_t overflow_status = read_msr(MSR_CORE_PERF_GLOBAL_STATUS);
+
+       if (overflow_status & (1ULL << ccno))
+               return (1ULL << cpu_caps.bits_x_counter) - 1;
+       else
+               return read_msr(MSR_IA32_PERFCTR0 + ccno);
+}
+
 static void perfmon_do_cores_status(void *opaque)
 {
        struct perfmon_status_env *env = (struct perfmon_status_env *) opaque;
@@ -266,11 +348,9 @@ static void perfmon_do_cores_status(void *opaque)
 
        spin_lock_irqsave(&cctx->lock);
        if (perfmon_is_fixed_event(&env->pa->ev))
-               env->pef->cores_values[coreno] =
-                   read_msr(MSR_CORE_PERF_FIXED_CTR0 + ccno);
+               env->pef->cores_values[coreno] = perfmon_read_fixed_counter(ccno);
        else
-               env->pef->cores_values[coreno] =
-                   read_msr(MSR_IA32_PERFCTR0 + ccno);
+               env->pef->cores_values[coreno] = perfmon_read_unfixed_counter(ccno);
        spin_unlock_irqsave(&cctx->lock);
 }
 
@@ -301,17 +381,8 @@ static void perfmon_free_alloc(struct perfmon_alloc *pa)
 
 static void perfmon_destroy_alloc(struct perfmon_alloc *pa)
 {
-       if (pa) {
-               perfmon_cleanup_cores_alloc(pa);
-               perfmon_free_alloc(pa);
-       }
-}
-
-static void perfmon_release_alloc(struct kref *kref)
-{
-       struct perfmon_alloc *pa = container_of(kref, struct perfmon_alloc, ref);
-
-       perfmon_destroy_alloc(pa);
+       perfmon_cleanup_cores_alloc(pa);
+       perfmon_free_alloc(pa);
 }
 
 static struct perfmon_alloc *perfmon_create_alloc(const struct perfmon_event *pev)
@@ -321,7 +392,6 @@ static struct perfmon_alloc *perfmon_create_alloc(const struct perfmon_event *pe
                                                num_cores * sizeof(counter_t),
                                            MEM_WAIT);
 
-       kref_init(&pa->ref, perfmon_release_alloc, 1);
        pa->ev = *pev;
        for (i = 0; i < num_cores; i++)
                pa->cores_counters[i] = INVALID_COUNTER;
@@ -329,7 +399,7 @@ static struct perfmon_alloc *perfmon_create_alloc(const struct perfmon_event *pe
        return pa;
 }
 
-static struct perfmon_status *perfmon_alloc_status(void)
+static struct perfmon_status *perfmon_status_alloc(void)
 {
        struct perfmon_status *pef = kzmalloc(sizeof(struct perfmon_status) +
                                                  num_cores * sizeof(uint64_t),
@@ -340,7 +410,9 @@ static struct perfmon_status *perfmon_alloc_status(void)
 
 static void perfmon_arm_irq(void)
 {
-       apicrput(MSR_LAPIC_LVT_PERFMON, IdtLAPIC_PCINT);
+       /* Actually, the vector is ignored, I'm just adding T_NMI to avoid
+        * confusion.  The important part is the NMI-bits (0x4) */
+       apicrput(MSR_LAPIC_LVT_PERFMON, (0x4 << 8) | T_NMI);
 }
 
 bool perfmon_supported(void)
@@ -381,6 +453,66 @@ static uint64_t perfmon_make_sample_event(const struct perfmon_event *pev)
        return pev->user_data;
 }
 
+/* Called from NMI context! */
+void perfmon_snapshot_hwtf(struct hw_trapframe *hw_tf)
+{
+       struct sample_snapshot *sample = PERCPU_VARPTR(sample_snapshots);
+       uintptr_t pc = get_hwtf_pc(hw_tf);
+       uintptr_t fp = get_hwtf_fp(hw_tf);
+
+       sample->ctx.type = ROS_HW_CTX;
+       sample->ctx.tf.hw_tf = *hw_tf;
+       if (in_kernel(hw_tf)) {
+               sample->nr_pcs = backtrace_list(pc, fp, sample->pc_list,
+                                               PROFILER_BT_DEPTH);
+       } else {
+               sample->nr_pcs = backtrace_user_list(pc, fp, sample->pc_list,
+                                                    PROFILER_BT_DEPTH);
+       }
+}
+
+/* Called from NMI context, *and* this cannot fault (e.g. breakpoint tracing)!
+ * The latter restriction is due to the vmexit NMI handler not being
+ * interruptible.  Because of this, we just copy out the VM TF. */
+void perfmon_snapshot_vmtf(struct vm_trapframe *vm_tf)
+{
+       struct sample_snapshot *sample = PERCPU_VARPTR(sample_snapshots);
+
+       sample->ctx.type = ROS_VM_CTX;
+       sample->ctx.tf.vm_tf = *vm_tf;
+       sample->nr_pcs = 1;
+       sample->pc_list[0] = get_vmtf_pc(vm_tf);
+}
+
+static void profiler_add_sample(uint64_t info)
+{
+       struct sample_snapshot *sample = PERCPU_VARPTR(sample_snapshots);
+
+       /* We shouldn't need to worry about another NMI that concurrently mucks with
+        * the sample.  The PMU won't rearm the interrupt until we're done here.  In
+        * the event that we do get another NMI from another source, we may get a
+        * weird backtrace in the perf output. */
+       switch (sample->ctx.type) {
+       case ROS_HW_CTX:
+               if (in_kernel(&sample->ctx.tf.hw_tf)) {
+                       profiler_push_kernel_backtrace(sample->pc_list, sample->nr_pcs,
+                                                      info);
+               } else {
+                       profiler_push_user_backtrace(sample->pc_list, sample->nr_pcs, info);
+               }
+               break;
+       case ROS_VM_CTX:
+               /* TODO: add VM support to perf.  For now, just treat it like a user
+                * addr.  Note that the address is a guest-virtual address, not
+                * guest-physical (which would be host virtual), and our VM_CTXs don't
+                * make a distinction between user and kernel TFs (yet). */
+               profiler_push_user_backtrace(sample->pc_list, sample->nr_pcs, info);
+               break;
+       default:
+               warn("Bad perf sample type %d!", sample->ctx.type);
+       }
+}
+
 void perfmon_interrupt(struct hw_trapframe *hw_tf, void *data)
 {
        int i;
@@ -398,8 +530,8 @@ void perfmon_interrupt(struct hw_trapframe *hw_tf, void *data)
        for (i = 0; i < (int) cpu_caps.counters_x_proc; i++) {
                if (status & ((uint64_t) 1 << i)) {
                        if (cctx->counters[i].event) {
-                               profiler_add_hw_sample(
-                                   hw_tf, perfmon_make_sample_event(cctx->counters + i));
+                               profiler_add_sample(
+                                   perfmon_make_sample_event(cctx->counters + i));
                                perfmon_set_unfixed_trigger(i, cctx->counters[i].trigger_count);
                        }
                }
@@ -407,8 +539,8 @@ void perfmon_interrupt(struct hw_trapframe *hw_tf, void *data)
        for (i = 0; i < (int) cpu_caps.fix_counters_x_proc; i++) {
                if (status & ((uint64_t) 1 << (32 + i))) {
                        if (cctx->fixed_counters[i].event) {
-                               profiler_add_hw_sample(
-                                   hw_tf, perfmon_make_sample_event(cctx->fixed_counters + i));
+                               profiler_add_sample(
+                                   perfmon_make_sample_event(cctx->fixed_counters + i));
                                perfmon_set_fixed_trigger(i,
                                        cctx->fixed_counters[i].trigger_count);
                        }
@@ -433,20 +565,16 @@ void perfmon_get_cpu_caps(struct perfmon_cpu_caps *pcc)
 static int perfmon_install_session_alloc(struct perfmon_session *ps,
                                          struct perfmon_alloc *pa)
 {
-       int i;
-
-       spin_lock(&ps->lock);
-       for (i = 0; (i < ARRAY_SIZE(ps->allocs)) && (ps->allocs[i] != NULL); i++)
-               ;
-       if (likely(i < ARRAY_SIZE(ps->allocs)))
-               ps->allocs[i] = pa;
-       else
-               i = -ENFILE;
-       spin_unlock(&ps->lock);
-       if (unlikely(i < 0))
-               error(-i, ERROR_FIXME);
-
-       return i;
+       qlock(&ps->qlock);
+       for (int i = 0; i < ARRAY_SIZE(ps->allocs); i++) {
+               if (!ps->allocs[i]) {
+                       ps->allocs[i] = pa;
+                       qunlock(&ps->qlock);
+                       return i;
+               }
+       }
+       qunlock(&ps->qlock);
+       error(ENFILE, "Too many perf allocs in the session");
 }
 
 int perfmon_open_event(const struct core_set *cset, struct perfmon_session *ps,
@@ -460,6 +588,11 @@ int perfmon_open_event(const struct core_set *cset, struct perfmon_session *ps,
                perfmon_destroy_alloc(pa);
                nexterror();
        }
+       /* Ensure the user did not set reserved bits or otherwise give us a bad
+        * event.  pev (now pa->ev) must be a valid IA32_PERFEVTSEL MSR. */
+       pa->ev.event &= 0xffffffff;
+       if (cpu_caps.perfmon_version < 3)
+               PMEV_SET_ANYTH(pa->ev.event, 0);
        /* Ensure we're turning on the event.  The user could have forgotten to set
         * it.  Our tracking of whether or not a counter is in use depends on it
         * being enabled, or at least that some bit is set. */
@@ -472,7 +605,8 @@ int perfmon_open_event(const struct core_set *cset, struct perfmon_session *ps,
 
                        if (unlikely(ccno < 0)) {
                                perfmon_destroy_alloc(pa);
-                               return (int) ccno;
+                               perfmon_convert_error(-(int)ccno, i);
+                               return -1;
                        }
                }
        }
@@ -488,48 +622,61 @@ int perfmon_open_event(const struct core_set *cset, struct perfmon_session *ps,
        return i;
 }
 
-static void perfmon_alloc_get(struct perfmon_session *ps, int ped, bool reset,
-                              struct perfmon_alloc **ppa)
+/* Helper, looks up a pa, given ped.  Hold the qlock. */
+static struct perfmon_alloc *__lookup_pa(struct perfmon_session *ps, int ped)
 {
        struct perfmon_alloc *pa;
 
        if (unlikely((ped < 0) || (ped >= ARRAY_SIZE(ps->allocs))))
-               error(EBADFD, ERROR_FIXME);
-       spin_lock(&ps->lock);
+               error(EBADFD, "Perf event %d out of range", ped);
        pa = ps->allocs[ped];
-       if (likely(pa)) {
-               if (reset)
-                       ps->allocs[ped] = NULL;
-               else
-                       kref_get(&pa->ref, 1);
-       }
-       spin_unlock(&ps->lock);
-       if (unlikely(!pa))
-               error(ENOENT, ERROR_FIXME);
-       *ppa = pa;
+       if (!pa)
+               error(ENOENT, "No perf alloc for event %d", ped);
+       return pa;
 }
 
 void perfmon_close_event(struct perfmon_session *ps, int ped)
 {
+       ERRSTACK(1);
        struct perfmon_alloc *pa;
 
-       perfmon_alloc_get(ps, ped, TRUE, &pa);
-       kref_put(&pa->ref);
+       qlock(&ps->qlock);
+       if (waserror()) {
+               qunlock(&ps->qlock);
+               nexterror();
+       };
+       /* lookup does the error checking */
+       pa = __lookup_pa(ps, ped);
+       ps->allocs[ped] = NULL;
+       poperror();
+       qunlock(&ps->qlock);
+       perfmon_destroy_alloc(pa);
 }
 
+/* Fetches the status (i.e. PMU counters) of event ped from all applicable
+ * cores.  Returns a perfmon_status, which the caller should free. */
 struct perfmon_status *perfmon_get_event_status(struct perfmon_session *ps,
                                                 int ped)
 {
+       ERRSTACK(1);
        struct core_set cset;
        struct perfmon_status_env env;
 
-       perfmon_alloc_get(ps, ped, FALSE, &env.pa);
-       env.pef = perfmon_alloc_status();
-       perfmon_setup_alloc_core_set(env.pa, &cset);
+       /* qlock keeps the PA alive.  We don't want to spin, since the spinners
+        * might prevent the smp_do_in_cores(), resulting in a deadlock. */
+       qlock(&ps->qlock);
+       if (waserror()) {
+               qunlock(&ps->qlock);
+               nexterror();
+       };
+       env.pa = __lookup_pa(ps, ped);
+       env.pef = perfmon_status_alloc();
 
+       perfmon_setup_alloc_core_set(env.pa, &cset);
        smp_do_in_cores(&cset, perfmon_do_cores_status, &env);
 
-       kref_put(&env.pa->ref);
+       poperror();
+       qunlock(&ps->qlock);
 
        return env.pef;
 }
@@ -539,38 +686,23 @@ void perfmon_free_event_status(struct perfmon_status *pef)
        kfree(pef);
 }
 
-static void perfmon_release_session(struct kref *kref)
-{
-       struct perfmon_session *ps =
-           container_of(kref, struct perfmon_session, ref);
-
-       for (int i = 0; i < ARRAY_SIZE(ps->allocs); i++) {
-               struct perfmon_alloc *pa = ps->allocs[i];
-
-               if (pa)
-                       kref_put(&pa->ref);
-       }
-       kfree(ps);
-}
-
 struct perfmon_session *perfmon_create_session(void)
 {
        struct perfmon_session *ps = kzmalloc(sizeof(struct perfmon_session),
                                              MEM_WAIT);
 
-       kref_init(&ps->ref, perfmon_release_session, 1);
-       spinlock_init(&ps->lock);
-
+       qlock_init(&ps->qlock);
        return ps;
 }
 
-void perfmon_get_session(struct perfmon_session *ps)
-{
-       kref_get(&ps->ref, 1);
-}
-
 void perfmon_close_session(struct perfmon_session *ps)
 {
-       if (likely(ps))
-               kref_put(&ps->ref);
+       struct perfmon_alloc *pa;
+
+       for (int i = 0; i < ARRAY_SIZE(ps->allocs); i++) {
+               pa = ps->allocs[i];
+               if (pa)
+                       perfmon_destroy_alloc(pa);
+       }
+       kfree(ps);
 }