This patch contains the core read-write operations on the PMU registers

The patch contains the following functions:


__pfm_write_pmds(): implements write operations on a vector of PMD registers
__pfm_read_pmds() : implements read operations on a vector of PMD registers
__pfm_write_pmcs(): implements write operations on a vector of PMC registers


There is no read operation on PMC because they are always set by the users.
On some architectures, such as IA-64, Tsome PMCs may be set by hardware, but
they are never exposed to users.




--- linux-2.6.19.base/perfmon/perfmon_rw.c      1969-12-31 16:00:00.000000000 
-0800
+++ linux-2.6.19/perfmon/perfmon_rw.c   2006-12-03 14:15:48.000000000 -0800
@@ -0,0 +1,606 @@
+/*
+ * perfmon.c: perfmon2 PMC/PMD read/write system calls
+ *
+ * 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 <[EMAIL PROTECTED]>
+ *                David Mosberger-Tang <[EMAIL PROTECTED]>
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/perfmon.h>
+
+#define PFM_REGFL_PMC_ALL      (PFM_REGFL_NO_EMUL64|PFM_REG_RETFL_MASK)
+#define PFM_REGFL_PMD_ALL      (PFM_REGFL_RANDOM     | \
+                                PFM_REGFL_OVFL_NOTIFY| \
+                                PFM_REG_RETFL_MASK)
+/*
+ * function called from sys_pfm_write_pmds() to write the 
+ * requested PMD registers. The function succeeds whether the context is
+ * attached or not. When attached to another thread, that thread must be
+ * stopped.
+ *
+ * compat: is used only on IA-64 to maintain backward compatibility with v2.0
+ *
+ * The context is locked and interrupts are disabled.
+ */
+int __pfm_write_pmds(struct pfm_context *ctx, struct pfarg_pmd *req, int count,
+                    int compat)
+{
+       struct pfm_event_set *set, *active_set;
+       u64 value, hw_val, ovfl_mask;
+       u64 *smpl_pmds, *reset_pmds, *impl_pmds;
+       u32 req_flags, flags;
+       u16 cnum, pmd_type, max_pmd, max_pmc;
+       u16 set_id, prev_set_id;
+       int i, can_access_pmu;
+       int is_counter;
+       int ret, error_code;
+       pfm_pmd_check_t wr_func;
+
+       ovfl_mask = pfm_pmu_conf->ovfl_mask;
+       active_set = ctx->active_set;
+       max_pmd = pfm_pmu_conf->max_pmd;
+       max_pmc = pfm_pmu_conf->max_pmc;
+       impl_pmds = pfm_pmu_conf->impl_pmds;
+       wr_func = pfm_pmu_conf->pmd_write_check;
+       set = NULL;
+
+       prev_set_id = 0;
+       can_access_pmu = 0;
+
+       /*
+        * we cannot access the actual PMD registers when monitoring is masked
+        */
+       if (likely(ctx->state == PFM_CTX_LOADED))
+               can_access_pmu = __get_cpu_var(pmu_owner) == ctx->task
+                              || ctx->flags.system;
+
+       error_code = PFM_REG_RETFL_EINVAL;
+       ret = -EINVAL;
+
+       for (i = 0; i < count; i++, req++) {
+
+               cnum = req->reg_num;
+               set_id = req->reg_set;
+               req_flags = req->reg_flags;
+               smpl_pmds = req->reg_smpl_pmds;
+               reset_pmds = req->reg_reset_pmds;
+               flags = 0;
+
+               if (unlikely(cnum >= max_pmd || !pfm_bv_isset(impl_pmds, 
cnum))) {
+                       PFM_DBG("pmd%u is not implemented/unaccessible", cnum);
+                       error_code  = PFM_REG_RETFL_NOTAVAIL;
+                       goto error;
+               }
+
+               pmd_type = pfm_pmu_conf->pmd_desc[cnum].type;
+               is_counter = pmd_type & PFM_REG_C64;
+
+               if (likely(!compat && is_counter)) {
+                       /*
+                        * ensure only valid flags are set
+                        */
+                       if (req_flags & ~(PFM_REGFL_PMD_ALL)) {
+                               PFM_DBG("pmd%u: invalid flags=0x%x",
+                                               cnum, req_flags);
+                               goto error;
+                       }
+
+                       if (req_flags & PFM_REGFL_OVFL_NOTIFY)
+                               flags |= PFM_REGFL_OVFL_NOTIFY;
+                       if (req_flags & PFM_REGFL_RANDOM)
+                               flags |= PFM_REGFL_RANDOM;
+                       /*
+                        * verify validity of smpl_pmds
+                        */
+                       if (unlikely(!bitmap_subset(ulp(smpl_pmds),
+                                                       ulp(impl_pmds),
+                                                       max_pmd))) {
+                               PFM_DBG("invalid smpl_pmds=0x%llx "
+                                               "for pmd%u",
+                                               (unsigned long 
long)smpl_pmds[0],
+                                               cnum);
+                               goto error;
+                       }
+                       /*
+                        * verify validity of reset_pmds
+                        */
+                       if (unlikely(!bitmap_subset(ulp(reset_pmds),
+                                                       ulp(impl_pmds),
+                                                       max_pmd))) {
+                               PFM_DBG("invalid reset_pmds=0x%llx "
+                                               "for pmd%u",
+                                               (unsigned long 
long)reset_pmds[0],
+                                               cnum);
+                               goto error;
+                       }
+               }
+
+               /*
+                * locate event set
+                */
+               if (i == 0 || set_id != prev_set_id) {
+                       set = pfm_find_set(ctx, set_id, 0);
+                       if (set == NULL) {
+                               PFM_DBG("event set%u does not exist",
+                                       set_id);
+                               error_code = PFM_REG_RETFL_NOSET;
+                               goto error;
+                       }
+               }
+
+               /*
+                * execute write checker, if any
+                */
+               if (likely(wr_func && (pmd_type & PFM_REG_WC))) {
+                       ret = (*wr_func)(ctx, set, req);
+                       if (ret)
+                               goto error;
+
+               }
+               hw_val = value = req->reg_value;
+
+               /*
+                * now commit changes to software state
+                */
+               pfm_modview_begin(set);
+
+               if (likely(is_counter)) {
+                       if (likely(!compat)) {
+
+                               set->pmds[cnum].flags = flags;
+
+                               /*
+                                * copy reset and sampling bitvectors
+                                */
+                               bitmap_copy(ulp(set->pmds[cnum].reset_pmds),
+                                           ulp(reset_pmds),
+                                           max_pmd);
+
+                               bitmap_copy(ulp(set->pmds[cnum].smpl_pmds),
+                                           ulp(smpl_pmds),
+                                           max_pmd);
+
+                               set->pmds[cnum].eventid = req->reg_smpl_eventid;
+
+                               /*
+                                * Mark reset/smpl PMDS as used.
+                                *
+                                * We do not keep track of PMC because we have 
to
+                                * systematically restore ALL of them.
+                                */
+                               bitmap_or(ulp(set->used_pmds),
+                                         ulp(set->used_pmds),
+                                         ulp(reset_pmds), max_pmd);
+
+                               bitmap_or(ulp(set->used_pmds),
+                                         ulp(set->used_pmds),
+                                         ulp(smpl_pmds), max_pmd);
+
+                               /*
+                                * we reprogrammed the PMD hence, clear any 
pending
+                                * ovfl, switch based on the old value
+                                * for restart we have already established new 
values
+                                */
+                               if (pfm_bv_isset(set->povfl_pmds, cnum)) {
+                                       set->npend_ovfls--;
+                                       pfm_bv_clear(set->povfl_pmds, cnum);
+                               }
+                               pfm_bv_clear(set->ovfl_pmds, cnum);
+
+                               /*
+                                * update ovfl_notify
+                                */
+                               if (flags & PFM_REGFL_OVFL_NOTIFY)
+                                       pfm_bv_set(set->ovfl_notify, cnum);
+                               else
+                                       pfm_bv_clear(set->ovfl_notify, cnum);
+                       }
+                       /*
+                        * reset last value to new value
+                        */
+                       set->pmds[cnum].lval = value;
+
+                       hw_val = value & ovfl_mask;
+
+                       /*
+                        * establish new switch count
+                        */
+                       set->pmds[cnum].ovflsw_thres = req->reg_ovfl_switch_cnt;
+                       set->pmds[cnum].ovflsw_ref_thres = 
req->reg_ovfl_switch_cnt;
+               }
+
+               /*
+                * update reset values (not just for counters)
+                */
+               set->pmds[cnum].long_reset = req->reg_long_reset;
+               set->pmds[cnum].short_reset = req->reg_short_reset;
+
+               /*
+                * update randomization mask
+                */
+               set->pmds[cnum].mask = req->reg_random_mask;
+
+               /*
+                * update set values
+                */
+               set->view->set_pmds[cnum] = value;
+
+               pfm_modview_end(set);
+
+               pfm_bv_set(set->used_pmds, cnum);
+
+               if (set == active_set) {
+                       set->priv_flags |= PFM_SETFL_PRIV_MOD_PMDS;
+                       if (can_access_pmu)
+                               pfm_write_pmd(ctx, cnum, hw_val);
+               }
+
+               /*
+                * update number of used PMD registers
+                */
+               set->nused_pmds = bitmap_weight(ulp(set->used_pmds), max_pmd);
+
+               pfm_retflag_set(req->reg_flags, 0);
+
+               prev_set_id = set_id;
+
+               PFM_DBG("set%u pmd%u=0x%llx flags=0x%x a_pmu=%d "
+                       "hw_pmd=0x%llx ctx_pmd=0x%llx s_reset=0x%llx "
+                       "l_reset=0x%llx u_pmds=0x%llx nu_pmds=%u "
+                       "s_pmds=0x%llx r_pmds=0x%llx o_pmds=0x%llx "
+                       "o_thres=%llu compat=%d eventid=%llx",
+                       set->id,
+                       cnum,
+                       (unsigned long long)value,
+                       set->pmds[cnum].flags,
+                       can_access_pmu,
+                       (unsigned long long)hw_val,
+                       (unsigned long long)set->view->set_pmds[cnum],
+                       (unsigned long long)set->pmds[cnum].short_reset,
+                       (unsigned long long)set->pmds[cnum].long_reset,
+                       (unsigned long long)set->used_pmds[0],
+                       set->nused_pmds,
+                       (unsigned long long)set->pmds[cnum].smpl_pmds[0],
+                       (unsigned long long)set->pmds[cnum].reset_pmds[0],
+                       (unsigned long long)set->ovfl_pmds[0],
+                       (unsigned long long)set->pmds[cnum].ovflsw_thres,
+                       compat,
+                       (unsigned long long)set->pmds[cnum].eventid);
+       }
+
+       /*
+        * make changes visible
+        */
+       if (can_access_pmu)
+               pfm_arch_serialize();
+
+       return 0;
+
+error:
+       /*
+        * for now, we have only one possibility for error
+        */
+       pfm_retflag_set(req->reg_flags, error_code);
+       PFM_DBG("set%u pmd%u error=%d", set_id, cnum, error_code);
+       return ret;
+}
+
+/*
+ * function called from sys_pfm_write_pmcs() to write the 
+ * requested PMC registers. The function succeeds whether the context is
+ * attached or not. When attached to another thread, that thread must be
+ * stopped.
+ *
+ * The context is locked and interrupts are disabled.
+ */
+int __pfm_write_pmcs(struct pfm_context *ctx, struct pfarg_pmc *req, int count)
+{
+       struct pfm_event_set *set, *active_set;
+       u64 value, dfl_val, rsvd_msk;
+       u64 *impl_pmcs;
+       int i, can_access_pmu;
+       int ret, error_code;
+       u16 set_id, prev_set_id;
+       u16 cnum, pmc_type, max_pmc;
+       u32 flags;
+       pfm_pmc_check_t wr_func;
+
+       active_set = ctx->active_set;
+
+       wr_func = pfm_pmu_conf->pmc_write_check;
+       max_pmc = pfm_pmu_conf->max_pmc;
+       impl_pmcs = pfm_pmu_conf->impl_pmcs;
+
+       set = NULL;
+       prev_set_id = 0;
+       can_access_pmu = 0;
+
+       /*
+        * we cannot access the actual PMC registers when monitoring is masked
+        */
+       if (likely(ctx->state == PFM_CTX_LOADED))
+               can_access_pmu = __get_cpu_var(pmu_owner) == ctx->task
+                               || ctx->flags.system;
+
+       error_code  = PFM_REG_RETFL_EINVAL;
+
+       for (i = 0; i < count; i++, req++) {
+
+               ret = -EINVAL;
+               cnum = req->reg_num;
+               set_id = req->reg_set;
+               value = req->reg_value;
+               flags = req->reg_flags;
+
+               /*
+                * no access to unimplemented PMC register
+                */
+               if (unlikely(cnum >= max_pmc || !pfm_bv_isset(impl_pmcs, 
cnum))) {
+                       PFM_DBG("pmc%u is not implemented/unaccessible", cnum);
+                       error_code  = PFM_REG_RETFL_NOTAVAIL;
+                       goto error;
+               }
+
+               pmc_type = pfm_pmu_conf->pmc_desc[cnum].type;
+               dfl_val = pfm_pmu_conf->pmc_desc[cnum].dfl_val;
+               rsvd_msk = pfm_pmu_conf->pmc_desc[cnum].rsvd_msk;
+
+               /*
+                * ensure only valid flags are set
+                */
+               if (flags & ~PFM_REGFL_PMC_ALL) {
+                       PFM_DBG("pmc%u: invalid flags=0x%x", cnum, flags);
+                       goto error;
+               }
+
+               /*
+                * locate event set
+                */
+               if (i == 0 || set_id != prev_set_id) {
+                       set = pfm_find_set(ctx, set_id, 0);
+                       if (set == NULL) {
+                               PFM_DBG("event set%u does not exist",
+                                       set_id);
+                               error_code = PFM_REG_RETFL_NOSET;
+                               goto error;
+                       }
+               }
+
+               /*
+                * set reserved bits to default values
+                * (reserved bits must be 1 in rsvd_msk)
+                */
+               value = (value & ~rsvd_msk) | (dfl_val & rsvd_msk);
+
+               if (flags & PFM_REGFL_NO_EMUL64) {
+                       if (!(pmc_type & PFM_REG_NO64)) {
+                               PFM_DBG("pmc%u no support for "
+                                       "PFM_REGFL_NO_EMUL64", cnum);
+                               goto error;
+                       }
+                       value &= ~pfm_pmu_conf->pmc_desc[cnum].no_emul64_msk;
+               }
+
+               /*
+                * execute write checker, if any
+                */
+               if (likely(wr_func && (pmc_type & PFM_REG_WC))) {
+                       req->reg_value = value;
+                       ret = (*wr_func)(ctx, set, req);
+                       if (ret)
+                               goto error;
+                       value = req->reg_value;
+               }
+
+               /*
+                * Now we commit the changes
+                */
+
+               /*
+                * mark PMC register as used
+                * We do not track associated PMC register based on
+                * the fact that they will likely need to be written
+                * in order to become useful at which point the statement
+                * below will catch that.
+                *
+                * The used_pmcs bitmask is only useful on architectures where
+                * the PMC needs to be modified for particular bits, especially
+                * on overflow or to stop/start.
+                */
+               if (!pfm_bv_isset(set->used_pmcs, cnum)) {
+                       pfm_bv_set(set->used_pmcs, cnum);
+                       set->nused_pmcs++;
+               }
+
+               set->pmcs[cnum] = value;
+
+               if (set == active_set) {
+                       set->priv_flags |= PFM_SETFL_PRIV_MOD_PMCS;
+                       if (can_access_pmu)
+                               pfm_arch_write_pmc(ctx, cnum, value);
+               }
+
+               pfm_retflag_set(req->reg_flags, 0);
+
+               prev_set_id = set_id;
+
+               PFM_DBG("set%u pmc%u=0x%llx a_pmu=%d "
+                       "u_pmcs=0x%llx nu_pmcs=%u",
+                       set->id,
+                       cnum,
+                       (unsigned long long)value,
+                       can_access_pmu,
+                       (unsigned long long)set->used_pmcs[0],
+                       set->nused_pmcs);
+       }
+       /*
+        * make sure the changes are visible
+        */
+       if (can_access_pmu)
+               pfm_arch_serialize();
+
+       return 0;
+error:
+       pfm_retflag_set(req->reg_flags, error_code);
+       PFM_DBG("set%u pmc%u error=%d", set_id, cnum, error_code);
+       return ret;
+}
+
+/*
+ * function called from sys_pfm_read_pmds() to read the 64-bit value of
+ * requested PMD registers. The function succeeds whether the context is
+ * attached or not. When attached to another thread, that thread must be
+ * stopped.
+ *
+ * The context is locked and interrupts are disabled.
+ */
+int __pfm_read_pmds(struct pfm_context *ctx, struct pfarg_pmd *req, int count)
+{
+       u64 val = 0, lval, ovfl_mask, hw_val;
+       u64 sw_cnt;
+       u64 *impl_pmds;
+       struct pfm_event_set *set, *active_set;
+       int i, can_access_pmu = 0;
+       int error_code;
+       u16 cnum, pmd_type, set_id, prev_set_id, max_pmd;
+
+       ovfl_mask = pfm_pmu_conf->ovfl_mask;
+       impl_pmds = pfm_pmu_conf->impl_pmds;
+       max_pmd   = pfm_pmu_conf->max_pmd;
+       active_set = ctx->active_set;
+       set = NULL;
+       prev_set_id = 0;
+
+       if (likely(ctx->state == PFM_CTX_LOADED)) {
+               can_access_pmu = __get_cpu_var(pmu_owner) == ctx->task
+                              || ctx->flags.system;
+
+               if (can_access_pmu)
+                       pfm_arch_serialize();
+       }
+       error_code = PFM_REG_RETFL_EINVAL;
+
+       /*
+        * on both UP and SMP, we can only read the PMD from the hardware
+        * register when the task is the owner of the local PMU.
+        */
+       for (i = 0; i < count; i++, req++) {
+
+               cnum = req->reg_num;
+               set_id = req->reg_set;
+
+               if (unlikely(cnum >= max_pmd || !pfm_bv_isset(impl_pmds, 
cnum))) {
+                       PFM_DBG("pmd%u is not implemented/unaccessible", cnum);
+                       error_code  = PFM_REG_RETFL_NOTAVAIL;
+                       goto error;
+               }
+
+               pmd_type = pfm_pmu_conf->pmd_desc[cnum].type;
+
+               /*
+                * locate event set
+                */
+               if (i == 0 || set_id != prev_set_id) {
+                       set = pfm_find_set(ctx, set_id, 0);
+                       if (set == NULL) {
+                               PFM_DBG("event set%u does not exist",
+                                       set_id);
+                               error_code = PFM_REG_RETFL_NOSET;
+                               goto error;
+                       }
+               }
+               /*
+                * it is not possible to read a PMD which was not requested:
+                *      - explicitly written via pfm_write_pmds()
+                *      - provided as a reg_smpl_pmds[] to another PMD during
+                *        pfm_write_pmds()
+                *
+                * This is motivated by security and for optimizations purposes:
+                *      - on context switch restore, we can restore only what we
+                *        use (except when regs directly readable at user level,
+                *        e.g., IA-64 self-monitoring, I386 RDTSC).
+                *      - do not need to maintain PMC -> PMD dependencies
+                */
+               if (unlikely(!pfm_bv_isset(set->used_pmds, cnum))) {
+                       PFM_DBG("pmd%u cannot be read, because never "
+                               "requested", cnum);
+                       goto error;
+               }
+
+               /*
+                * it is possible to read PMD registers which have not
+                * explicitely been written by the application. In this case
+                * the default value is returned.
+                */
+               val = set->view->set_pmds[cnum];
+               lval = set->pmds[cnum].lval;
+
+               /*
+                * extract remaining ovfl to switch
+                */
+               sw_cnt = set->pmds[cnum].ovflsw_thres;
+
+               /*
+                * If the task is not the current one, then we check if the
+                * PMU state is still in the local live register due to lazy
+                * ctxsw. If true, then we read directly from the registers.
+                */
+               if (set == active_set && can_access_pmu) {
+                       hw_val = pfm_read_pmd(ctx, cnum);
+                       if (pmd_type & PFM_REG_C64)
+                               val = (val & ~ovfl_mask) | (hw_val & ovfl_mask);
+                       else
+                               val = hw_val;
+               }
+
+               PFM_DBG("set%u pmd%u=0x%llx switch_thres=%llu",
+                       set->id,
+                       cnum,
+                       (unsigned long long)val,
+                       (unsigned long long)sw_cnt);
+
+               pfm_retflag_set(req->reg_flags, 0);
+               req->reg_value = val;
+               req->reg_last_reset_val = lval;
+               req->reg_ovfl_switch_cnt = sw_cnt;
+
+               prev_set_id = set_id;
+       }
+       return 0;
+
+error:
+       pfm_retflag_set(req->reg_flags, error_code);
+       PFM_DBG("set%u pmd%u error=%d", set_id, cnum, error_code);
+       return -EINVAL;
+}
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to