| /* |
| * perfmon_ctx.c: perfmon2 context functions |
| * |
| * 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/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/perfmon_kern.h> |
| #include "perfmon_priv.h" |
| |
| extern asmlinkage long sys_close(int fd); |
| /* |
| * context memory pool pointer |
| */ |
| static struct kmem_cache *pfm_ctx_cachep; |
| |
| /** |
| * pfm_free_context - de-allocate context and associated resources |
| * @ctx: context to free |
| */ |
| void pfm_free_context(struct pfm_context *ctx) |
| { |
| pfm_arch_context_free(ctx); |
| |
| pfm_free_sets(ctx); |
| |
| pfm_smpl_buf_free(ctx); |
| |
| PFM_DBG("free ctx @0x%p", ctx); |
| kmem_cache_free(pfm_ctx_cachep, ctx); |
| /* |
| * decrease refcount on: |
| * - PMU description table |
| * - sampling format |
| */ |
| pfm_pmu_conf_put(); |
| pfm_pmu_release(); |
| } |
| |
| /** |
| * pfm_ctx_flags_sane - check if context flags passed by user are okay |
| * @ctx_flags: flags passed user on pfm_create_context |
| * |
| * return: |
| * 0 if successful |
| * <0 and error code otherwise |
| */ |
| static inline int pfm_ctx_flags_sane(u32 ctx_flags) |
| { |
| if (ctx_flags & PFM_FL_SYSTEM_WIDE) { |
| if (ctx_flags & PFM_FL_NOTIFY_BLOCK) { |
| PFM_DBG("cannot use blocking mode in syswide mode"); |
| return -EINVAL; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * pfm_ctx_permissions - check authorization to create new context |
| * @ctx_flags: context flags passed by user |
| * |
| * check for permissions to create a context. |
| * |
| * A sysadmin may decide to restrict creation of per-thread |
| * and/or system-wide context to a group of users using the |
| * group id via /sys/kernel/perfmon/task_group and |
| * /sys/kernel/perfmon/sys_group. |
| * |
| * Once we identify a user level package which can be used |
| * to grant/revoke Linux capabilites at login via PAM, we will |
| * be able to use capabilities. We would also need to increase |
| * the size of cap_t to support more than 32 capabilities (it |
| * is currently defined as u32 and 32 capabilities are alrady |
| * defined). |
| */ |
| static inline int pfm_ctx_permissions(u32 ctx_flags) |
| { |
| if ((ctx_flags & PFM_FL_SYSTEM_WIDE) |
| && pfm_controls.sys_group != PFM_GROUP_PERM_ANY |
| && !in_group_p(pfm_controls.sys_group)) { |
| PFM_DBG("user group not allowed to create a syswide ctx"); |
| return -EPERM; |
| } else if (pfm_controls.task_group != PFM_GROUP_PERM_ANY |
| && !in_group_p(pfm_controls.task_group)) { |
| PFM_DBG("user group not allowed to create a task context"); |
| return -EPERM; |
| } |
| return 0; |
| } |
| |
| /** |
| * __pfm_create_context - allocate and initialize a perfmon context |
| * @req : pfarg_ctx from user |
| * @fmt : pointer sampling format, NULL if not used |
| * @fmt_arg: pointer to argument to sampling format, NULL if not used |
| * @mode: PFM_NORMAL or PFM_COMPAT(IA-64 v2.0 compatibility) |
| * @ctx : address of new context upon succesful return, undefined otherwise |
| * |
| * function used to allocate a new context. A context is allocated along |
| * with the default event set. If a sampling format is used, the buffer |
| * may be allocated and initialized. |
| * |
| * The file descriptor identifying the context is allocated and returned |
| * to caller. |
| * |
| * This function operates with no locks and interrupts are enabled. |
| * return: |
| * >=0: the file descriptor to identify the context |
| * <0 : the error code |
| */ |
| int __pfm_create_context(struct pfarg_ctx *req, |
| struct pfm_smpl_fmt *fmt, |
| void *fmt_arg, |
| int mode, |
| struct pfm_context **new_ctx) |
| { |
| struct pfm_context *ctx; |
| u32 ctx_flags; |
| int fd, ret; |
| |
| ctx_flags = req->ctx_flags; |
| |
| /* Increase refcount on PMU description */ |
| ret = pfm_pmu_conf_get(1); |
| if (ret < 0) |
| goto error_conf; |
| |
| ret = pfm_ctx_flags_sane(ctx_flags); |
| if (ret < 0) |
| goto error_smpl; |
| |
| ret = pfm_ctx_permissions(ctx_flags); |
| if (ret < 0) |
| goto error_smpl; |
| |
| /* |
| * we can use GFP_KERNEL and potentially sleep because we do |
| * not hold any lock at this point. |
| */ |
| might_sleep(); |
| ret = -ENOMEM; |
| ctx = kmem_cache_zalloc(pfm_ctx_cachep, GFP_KERNEL); |
| if (!ctx) |
| goto error_smpl; |
| |
| PFM_DBG("alloc ctx @0x%p", ctx); |
| |
| INIT_LIST_HEAD(&ctx->set_list); |
| spin_lock_init(&ctx->lock); |
| init_completion(&ctx->restart_complete); |
| init_waitqueue_head(&ctx->msgq_wait); |
| |
| /* |
| * context is unloaded |
| */ |
| ctx->state = PFM_CTX_UNLOADED; |
| |
| /* |
| * initialization of context's flags |
| * must be done before pfm_find_set() |
| */ |
| ctx->flags.block = (ctx_flags & PFM_FL_NOTIFY_BLOCK) ? 1 : 0; |
| ctx->flags.system = (ctx_flags & PFM_FL_SYSTEM_WIDE) ? 1: 0; |
| ctx->flags.no_msg = (ctx_flags & PFM_FL_OVFL_NO_MSG) ? 1: 0; |
| ctx->flags.ia64_v20_compat = mode == PFM_COMPAT ? 1 : 0; |
| |
| /* |
| * link to format, must be done first for correct |
| * error handling in pfm_free_context() |
| */ |
| ctx->smpl_fmt = fmt; |
| |
| ret = pfm_pmu_acquire(ctx); |
| if (ret) |
| goto error_alloc; |
| /* |
| * check if PMU is usable |
| */ |
| if (!(ctx->regs.num_pmcs && ctx->regs.num_pmds)) { |
| PFM_DBG("no usable PMU registers"); |
| ret = -EBUSY; |
| goto error_alloc; |
| } |
| |
| /* |
| * initialize arch-specific section |
| * must be done before fmt_init() |
| */ |
| ret = pfm_arch_context_create(ctx, ctx_flags); |
| if (ret) |
| goto error_alloc; |
| |
| /* |
| * add initial set |
| */ |
| ret = -ENOMEM; |
| if (pfm_create_initial_set(ctx)) |
| goto error_alloc; |
| |
| ret = fd = pfm_alloc_fd(ctx); |
| if (ret < 0) |
| goto error_alloc; |
| |
| /* |
| * does the user want to sample? |
| * must be done after pfm_pmu_acquire() because |
| * needs ctx->regs |
| */ |
| if (fmt) { |
| ret = pfm_setup_smpl_fmt(ctx, ctx_flags, fmt_arg, fd); |
| if (ret) |
| goto error_fd; |
| } |
| |
| ctx->last_act = PFM_INVALID_ACTIVATION; |
| ctx->last_cpu = -1; |
| |
| /* |
| * initialize notification message queue |
| */ |
| ctx->msgq_head = ctx->msgq_tail = 0; |
| |
| PFM_DBG("flags=0x%x sys=%d block=%d no_msg=%d" |
| " use_fmt=%d fd=%d mode=%d", |
| ctx_flags, |
| ctx->flags.system, |
| ctx->flags.block, |
| ctx->flags.no_msg, |
| !!fmt, |
| ret, mode); |
| |
| if (new_ctx) |
| *new_ctx = ctx; |
| |
| return fd; |
| error_fd: |
| sys_close(fd); |
| error_alloc: |
| /* |
| * free arch, sets, smpl_buffer |
| * conf, pmu |
| */ |
| pfm_free_context(ctx); |
| return ret; |
| error_smpl: |
| pfm_pmu_conf_put(); |
| error_conf: |
| pfm_smpl_fmt_put(fmt); |
| return ret; |
| } |
| |
| /** |
| * pfm_init_ctx -- initialize context SLAB |
| * |
| * called from pfm_init |
| */ |
| int __init pfm_init_ctx(void) |
| { |
| pfm_ctx_cachep = kmem_cache_create("pfm_context", |
| sizeof(struct pfm_context)+PFM_ARCH_CTX_SIZE, |
| SLAB_HWCACHE_ALIGN, 0, NULL); |
| if (!pfm_ctx_cachep) { |
| PFM_ERR("cannot initialize context slab"); |
| return -ENOMEM; |
| } |
| return 0; |
| } |