Messages in this thread Patch in this message | | | Subject | [patch 12/24] perfmon: read and write registers | From | eranian@googlema ... | Date | Tue, 25 Nov 2008 13:36:22 -0800 (PST) |
| |
This patch adds the functions to read and write PMU data and config registers.
Signed-off-by: Stephane Eranian <eranian@gmail.com> --
Index: o3/perfmon/perfmon_rw.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ o3/perfmon/perfmon_rw.c 2008-11-25 18:59:05.000000000 +0100 @@ -0,0 +1,449 @@ +/* + * 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 <eranian@hpl.hp.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/module.h> +#include <linux/kernel.h> +#include <linux/perfmon_kern.h> +#include "perfmon_priv.h" + +/** + * is_invalid -- check if register index is within limits + * @cnum: register index + * @impl: bitmask of implemented registers + * @max: highest implemented registers + 1 + * + * return: + * 0 is register index is valid + * 1 if invalid + */ +static inline int is_invalid(u16 cnum, u64 *impl, u16 max) +{ + return cnum >= max || !pfm_arch_bv_test_bit(cnum, impl); +} + +/** + * update_used_reg -- updated used_pmcs for a single PMD + * @set: set to update + * @cnum: new PMD to add + * + * This function adds the pmds and pmcs depending on PMD cnum + */ +static inline void update_used_reg(struct pfm_context *ctx, + struct pfm_event_set *set, u16 cnum) +{ + pfm_arch_bv_or(set->used_pmcs, + set->used_pmcs, + pfm_pmu_conf->pmd_desc[cnum].dep_pmcs, + ctx->regs.max_pmc); +} + +/** + * update_changes -- update nused_pmcs, nused_pmds, write newly touched pmcs + * @ctx: context to use + * @set: event set to use + * @old_used_pmcs: former used_pmc bitmask + * + * This function updates nused_pmcs and nused_pmds after the last modificiation + * to an event set. When new pmcs are used, then they must be initialized such + * that we do not pick up stale values from another session. + */ +static inline int update_changes(struct pfm_context *ctx, struct pfm_event_set *set, + u64 *old_used_pmcs) +{ + struct pfarg_pmr req; + u16 max_pmc, max_pmd; + int n, p, q, ret = 0; + + max_pmd = ctx->regs.max_pmd; + max_pmc = ctx->regs.max_pmc; + + /* + * update used counts + */ + set->nused_pmds = pfm_arch_bv_weight(set->used_pmds, max_pmd); + set->nused_pmcs = pfm_arch_bv_weight(set->used_pmcs, max_pmc); + + PFM_DBG("u_pmds=0x%llx nu_pmds=%u u_pmcs=0x%llx nu_pmcs=%u", + (unsigned long long)set->used_pmds[0], + set->nused_pmds, + (unsigned long long)set->used_pmcs[0], + set->nused_pmcs); + + memset(&req, 0, sizeof(req)); + + n = pfm_arch_bv_weight(set->used_pmcs, max_pmc); + for(p = 0; n; n--, p = q+1) { + q = pfm_arch_bv_find_next_bit(set->used_pmcs, max_pmc, p); + + if (pfm_arch_bv_test_bit(q, old_used_pmcs)) + continue; + + req.reg_num = q; + req.reg_value = set->pmcs[q]; + + ret = __pfm_write_pmcs(ctx, &req, 1); + if (ret) + break; + } + return ret; +} + +/** + * __pfm_write_pmds - modify data registers + * @ctx: context to operate on + * @req: pfarg_pmd_t request from user + * @count: number of element in the pfarg_pmd_t vector + * + * 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_pmds(struct pfm_context *ctx, struct pfarg_pmr *req, int count) +{ + struct pfm_event_set *set; + u64 old_used_pmcs[PFM_PMC_BV]; + u64 value, ovfl_mask; + u64 *impl_pmds; + u16 cnum, pmd_type, max_pmd; + int i, can_access_pmu; + int ret; + pfm_pmd_check_t wr_func; + + ovfl_mask = pfm_pmu_conf->ovfl_mask; + max_pmd = ctx->regs.max_pmd; + impl_pmds = ctx->regs.pmds; + wr_func = pfm_pmu_conf->pmd_write_check; + + can_access_pmu = 0; + + /* + * we cannot access the actual PMD registers when monitoring is masked + */ + if (unlikely(ctx->state == PFM_CTX_LOADED)) + can_access_pmu = __get_cpu_var(pmu_owner) == ctx->task; + + ret = -EINVAL; + set = ctx->active_set; + + pfm_arch_bv_copy(old_used_pmcs, set->used_pmcs, + ctx->regs.max_pmc); + + for (i = 0; i < count; i++, req++) { + + cnum = req->reg_num; + + /* + * cannot write to unexisting + * writes to read-only register are ignored + */ + if (unlikely(is_invalid(cnum, impl_pmds, max_pmd))) { + PFM_DBG("pmd%u is not available", cnum); + goto error; + } + + pmd_type = pfm_pmu_conf->pmd_desc[cnum].type; + + /* + * execute write checker, if any + */ + if (unlikely(wr_func && (pmd_type & PFM_REG_WC))) { + ret = (*wr_func)(ctx, set, req); + if (ret) + goto error; + + } + + value = req->reg_value; + + /* + * we reprogram the PMD hence, we clear any pending + * ovfl. Does affect ovfl switch on restart but new + * value has already been established here + */ + if (pfm_arch_bv_test_bit(cnum, set->povfl_pmds)) { + set->npend_ovfls--; + pfm_arch_bv_clear_bit(cnum, set->povfl_pmds); + } + + /* + * update value + */ + set->pmds[cnum] = value; + + pfm_arch_bv_set_bit(cnum, set->used_pmds); + update_used_reg(ctx, set, cnum); + + set->priv_flags |= PFM_SETFL_PRIV_MOD_PMDS; + if (can_access_pmu) + pfm_write_pmd(ctx, cnum, value); + + /* + * update number of used PMD registers + */ + set->nused_pmds = pfm_arch_bv_weight(set->used_pmds, + max_pmd); + + PFM_DBG("pmd%u=0x%llx a_pmu=%d " + "ctx_pmd=0x%llx " + " u_pmds=0x%llx nu_pmds=%u ", + cnum, + (unsigned long long)value, + can_access_pmu, + (unsigned long long)set->pmds[cnum], + (unsigned long long)set->used_pmds[0], + set->nused_pmds); + } + ret = 0; +error: + update_changes(ctx, set, old_used_pmcs); + /* + * make changes visible + */ + if (can_access_pmu) + pfm_arch_serialize(); + + return ret; +} + +/** + * __pfm_write_pmcs - modify config registers + * @ctx: context to operate on + * @req: pfarg_pmc_t request from user + * @count: number of element in the pfarg_pmc_t vector + * + * + * 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_pmr *req, int count) +{ + struct pfm_event_set *set; + u64 value, dfl_val, rsvd_msk; + u64 *impl_pmcs; + int i, can_access_pmu; + int ret; + u16 cnum, pmc_type, max_pmc; + pfm_pmc_check_t wr_func; + + wr_func = pfm_pmu_conf->pmc_write_check; + max_pmc = ctx->regs.max_pmc; + impl_pmcs = ctx->regs.pmcs; + + can_access_pmu = 0; + + /* + * we cannot access the actual PMC registers when monitoring is masked + */ + if (unlikely(ctx->state == PFM_CTX_LOADED)) + can_access_pmu = __get_cpu_var(pmu_owner) == ctx->task; + + ret = -EINVAL; + set = ctx->active_set; + + for (i = 0; i < count; i++, req++) { + + cnum = req->reg_num; + value = req->reg_value; + + /* + * no access to unavailable PMC register + */ + if (unlikely(is_invalid(cnum, impl_pmcs, max_pmc))) { + PFM_DBG("pmc%u is not available", cnum); + 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; + + /* + * set reserved bits to default values + * (reserved bits must be 1 in rsvd_msk) + */ + value = (value & ~rsvd_msk) | (dfl_val & rsvd_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_arch_bv_test_bit(cnum, set->used_pmcs)) { + pfm_arch_bv_set_bit(cnum, set->used_pmcs); + set->nused_pmcs++; + } + + set->pmcs[cnum] = value; + + set->priv_flags |= PFM_SETFL_PRIV_MOD_PMCS; + if (can_access_pmu) + pfm_arch_write_pmc(ctx, cnum, value); + + PFM_DBG("pmc%u=0x%llx a_pmu=%d " + "u_pmcs=0x%llx nu_pmcs=%u", + cnum, + (unsigned long long)value, + can_access_pmu, + (unsigned long long)set->used_pmcs[0], + set->nused_pmcs); + } + ret = 0; +error: + /* + * make sure the changes are visible + */ + if (can_access_pmu) + pfm_arch_serialize(); + + return ret; +} + +/** + * __pfm_read_pmds - read data registers + * @ctx: context to operate on + * @req: pfarg_pmd_t request from user + * @count: number of element in the pfarg_pmd_t vector + * + * + * 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_pmr *req, int count) +{ + u64 val = 0, ovfl_mask, hw_val; + u64 *impl_pmds; + struct pfm_event_set *set; + int i, ret, can_access_pmu = 0; + u16 cnum, pmd_type, max_pmd; + + ovfl_mask = pfm_pmu_conf->ovfl_mask; + impl_pmds = ctx->regs.pmds; + max_pmd = ctx->regs.max_pmd; + + if (likely(ctx->state == PFM_CTX_LOADED)) { + can_access_pmu = __get_cpu_var(pmu_owner) == ctx->task; + if (can_access_pmu) + pfm_arch_serialize(); + } + + /* + * 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. + */ + ret = -EINVAL; + set = ctx->active_set; + + for (i = 0; i < count; i++, req++) { + + cnum = req->reg_num; + + if (unlikely(is_invalid(cnum, impl_pmds, max_pmd))) { + PFM_DBG("pmd%u is not implemented/unaccessible", cnum); + goto error; + } + + pmd_type = pfm_pmu_conf->pmd_desc[cnum].type; + + /* + * 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 optimization 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 RDPMC). + * - do not need to maintain PMC -> PMD dependencies + */ + if (unlikely(!pfm_arch_bv_test_bit(cnum, set->used_pmds))) { + PFM_DBG("pmd%u cannot read, because not used", cnum); + goto error; + } + + val = set->pmds[cnum]; + + /* + * 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 (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("pmd%u=0x%llx ", + cnum, + (unsigned long long)val); + + req->reg_value = val; + } + ret = 0; +error: + return ret; +} Index: o3/include/linux/perfmon.h =================================================================== --- o3.orig/include/linux/perfmon.h 2008-11-25 18:29:29.000000000 +0100 +++ o3/include/linux/perfmon.h 2008-11-25 18:58:39.000000000 +0100 @@ -46,6 +46,22 @@ #define PFM_PMC_BV PFM_BVSIZE(PFM_MAX_PMCS) /* + * PMC and PMD generic register description + */ +struct pfarg_pmr { + __u16 reg_num; /* which register */ + __u16 reg_res1; /* reserved */ + __u32 reg_flags; /* REGFL flags */ + __u64 reg_value; /* 64-bit value */ +}; + +/* + * pfm_write, pfm_read type: + */ +#define PFM_RW_PMD 0x01 /* accessing PMD registers */ +#define PFM_RW_PMC 0x02 /* accessing PMC registers */ + +/* * default value for the user and group security parameters in * /proc/sys/kernel/perfmon/sys_group * /proc/sys/kernel/perfmon/task_group Index: o3/perfmon/perfmon_priv.h =================================================================== --- o3.orig/perfmon/perfmon_priv.h 2008-11-25 18:58:32.000000000 +0100 +++ o3/perfmon/perfmon_priv.h 2008-11-25 18:58:39.000000000 +0100 @@ -44,6 +44,11 @@ } int pfm_init_ctx(void); +int __pfm_write_pmcs(struct pfm_context *ctx, struct pfarg_pmr *req, + int count); +int __pfm_write_pmds(struct pfm_context *ctx, struct pfarg_pmr *req, + int count); +int __pfm_read_pmds(struct pfm_context *ctx, struct pfarg_pmr *req, int count); int pfm_session_acquire(void); void pfm_session_release(void); --
| |