summaryrefslogtreecommitdiff
path: root/perfmon/perfmon_ctxsw.c
diff options
context:
space:
mode:
Diffstat (limited to 'perfmon/perfmon_ctxsw.c')
-rw-r--r--perfmon/perfmon_ctxsw.c252
1 files changed, 252 insertions, 0 deletions
diff --git a/perfmon/perfmon_ctxsw.c b/perfmon/perfmon_ctxsw.c
new file mode 100644
index 000000000000..b1086f6dca31
--- /dev/null
+++ b/perfmon/perfmon_ctxsw.c
@@ -0,0 +1,252 @@
+/*
+ * perfmon_cxtsw.c: perfmon2 context switch code
+ *
+ * This file implements the perfmon2 interface which
+ * provides access to the hardware performance counters
+ * of the host processor.
+ *
+ * The initial version of perfmon.c was written by
+ * Ganesh Venkitachalam, IBM Corp.
+ *
+ * Then it was modified for perfmon-1.x by Stephane Eranian and
+ * David Mosberger, Hewlett Packard Co.
+ *
+ * Version Perfmon-2.x is a complete rewrite of perfmon-1.x
+ * by Stephane Eranian, Hewlett Packard Co.
+ *
+ * Copyright (c) 1999-2006 Hewlett-Packard Development Company, L.P.
+ * Contributed by Stephane Eranian <eranian@gmail.com>
+ * David Mosberger-Tang <davidm@hpl.hp.com>
+ *
+ * More information about perfmon available at:
+ * http://perfmon2.sf.net
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+#include <linux/kernel.h>
+#include <linux/perfmon_kern.h>
+#include "perfmon_priv.h"
+
+void pfm_save_pmds(struct pfm_context *ctx)
+{
+ struct pfm_event_set *set;
+ u64 val, ovfl_mask;
+ u64 *used_pmds, *cnt_pmds;
+ u16 i, num;
+
+ set = ctx->active_set;
+ ovfl_mask = pfm_pmu_conf->ovfl_mask;
+ num = set->nused_pmds;
+ cnt_pmds = ctx->regs.cnt_pmds;
+ used_pmds = set->used_pmds;
+
+ /*
+ * save HW PMD, for counters, reconstruct 64-bit value
+ */
+ for (i = 0; num; i++) {
+ if (pfm_arch_bv_test_bit(i, used_pmds)) {
+ val = pfm_read_pmd(ctx, i);
+ if (likely(pfm_arch_bv_test_bit(i, cnt_pmds)))
+ val = (set->pmds[i] & ~ovfl_mask) |
+ (val & ovfl_mask);
+ set->pmds[i] = val;
+ num--;
+ }
+ }
+}
+
+/*
+ * interrupts are disabled (no preemption)
+ */
+void __pfm_ctxswin_thread(struct task_struct *task,
+ struct pfm_context *ctx)
+{
+ u64 cur_act;
+ struct pfm_event_set *set;
+ int reload_pmcs, reload_pmds;
+ int mycpu, is_active;
+
+ mycpu = smp_processor_id();
+
+ cur_act = __get_cpu_var(pmu_activation_number);
+ /*
+ * we need to lock context because it could be accessed
+ * from another CPU. Normally the schedule() functions
+ * has masked interrupts which should be enough to
+ * protect against PMU interrupts.
+ */
+ spin_lock(&ctx->lock);
+
+ is_active = pfm_arch_is_active(ctx);
+
+ set = ctx->active_set;
+
+ /*
+ * in case fo zombie, we do not complete ctswin of the
+ * PMU, and we force a call to pfm_handle_work() to finish
+ * cleanup, i.e., free context + smpl_buff. The reason for
+ * deferring to pfm_handle_work() is that it is not possible
+ * to vfree() with interrupts disabled.
+ */
+ if (unlikely(ctx->state == PFM_CTX_ZOMBIE)) {
+ pfm_post_work(task, ctx, PFM_WORK_ZOMBIE);
+ goto done;
+ }
+
+ /*
+ * if we were the last user of the PMU on that CPU,
+ * then nothing to do except restore psr
+ */
+ if (ctx->last_cpu == mycpu && ctx->last_act == cur_act) {
+ /*
+ * check for forced reload conditions
+ */
+ reload_pmcs = set->priv_flags & PFM_SETFL_PRIV_MOD_PMCS;
+ reload_pmds = set->priv_flags & PFM_SETFL_PRIV_MOD_PMDS;
+ } else {
+#ifndef CONFIG_SMP
+ pfm_check_save_prev_ctx();
+#endif
+ reload_pmcs = 1;
+ reload_pmds = 1;
+ }
+ /* consumed */
+ set->priv_flags &= ~PFM_SETFL_PRIV_MOD_BOTH;
+
+ if (reload_pmds)
+ pfm_arch_restore_pmds(ctx, set);
+
+ /*
+ * need to check if had in-flight interrupt in
+ * pfm_ctxswout_thread(). If at least one bit set, then we must replay
+ * the interrupt to avoid losing some important performance data.
+ *
+ * npend_ovfls is cleared in interrupt handler
+ */
+ if (set->npend_ovfls)
+ pfm_arch_resend_irq(ctx);
+
+ if (reload_pmcs)
+ pfm_arch_restore_pmcs(ctx, set);
+
+ /*
+ * record current activation for this context
+ */
+ __get_cpu_var(pmu_activation_number)++;
+ ctx->last_cpu = mycpu;
+ ctx->last_act = __get_cpu_var(pmu_activation_number);
+
+ /*
+ * establish new ownership.
+ */
+ pfm_set_pmu_owner(task, ctx);
+
+ pfm_arch_ctxswin_thread(task, ctx);
+done:
+ spin_unlock(&ctx->lock);
+}
+
+/*
+ * interrupts are masked, runqueue lock is held.
+ *
+ * In UP. we simply stop monitoring and leave the state
+ * in place, i.e., lazy save
+ */
+void __pfm_ctxswout_thread(struct task_struct *task,
+ struct pfm_context *ctx)
+{
+ int need_save_pmds, is_active;
+
+ /*
+ * we need to lock context because it could be accessed
+ * from another CPU. Normally the schedule() functions
+ * has masked interrupts which should be enough to
+ * protect against PMU interrupts.
+ */
+
+ spin_lock(&ctx->lock);
+
+ is_active = pfm_arch_is_active(ctx);
+
+ /*
+ * stop monitoring and
+ * collect pending overflow information
+ * needed on ctxswin. We cannot afford to lose
+ * a PMU interrupt.
+ */
+ need_save_pmds = pfm_arch_ctxswout_thread(task, ctx);
+
+#ifdef CONFIG_SMP
+ /*
+ * in SMP, release ownership of this PMU.
+ * PMU interrupts are masked, so nothing
+ * can happen.
+ */
+ pfm_set_pmu_owner(NULL, NULL);
+
+ /*
+ * On some architectures, it is necessary to read the
+ * PMD registers to check for pending overflow in
+ * pfm_arch_ctxswout_thread(). In that case, saving of
+ * the PMDs may be done there and not here.
+ */
+ if (need_save_pmds)
+ pfm_save_pmds(ctx);
+#endif
+ spin_unlock(&ctx->lock);
+}
+
+/**
+ * pfm_ctxsw_out - save PMU state on context switch out
+ * @prev: thread being switched out
+ * @next: thread being switched in
+ *
+ * We pass the next thread as on some platforms it may be necessary to
+ * pass some settings from the current thread to the next
+ *
+ * Interrupts are masked
+ */
+void pfm_ctxsw_out(struct task_struct *prev,
+ struct task_struct *next)
+{
+ struct pfm_context *ctxp;
+
+ ctxp = prev->pfm_context;
+
+ if (ctxp)
+ __pfm_ctxswout_thread(prev, ctxp);
+}
+
+/**
+ * pfm_ctxsw_in - restore PMU state on context switch in
+ * @prev: thread being switched out
+ * @next: thread being switched in
+ *
+ * We pass the prev thread as on some platforms it may be necessary to
+ * pass some settings from the current thread to the next
+ *
+ * Interrupts are masked
+ */
+void pfm_ctxsw_in(struct task_struct *prev,
+ struct task_struct *next)
+{
+ struct pfm_context *ctxn;
+
+ ctxn = next->pfm_context;
+
+ if (ctxn)
+ __pfm_ctxswin_thread(next, ctxn);
+
+}