blob: cb5e99df81dfaf89c41cc1ab9da8ee233fece12f [file] [log] [blame]
/*
* Copyright (c) 1999-2006 Hewlett-Packard Development Company, L.P.
* Contributed by Stephane Eranian <eranian@hpl.hp.com>
*
* This file implements the new default sampling buffer format
* for the perfmon2 subsystem.
*
* 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/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/perfmon_kern.h>
#include <linux/perfmon_dfl_smpl.h>
MODULE_AUTHOR("Stephane Eranian <eranian@hpl.hp.com>");
MODULE_DESCRIPTION("new perfmon default sampling format");
MODULE_LICENSE("GPL");
static int pfm_dfl_fmt_validate(u32 ctx_flags, u16 npmds, void *data)
{
struct pfm_dfl_smpl_arg *arg = data;
u64 min_buf_size;
if (data == NULL) {
PFM_DBG("no argument passed");
return -EINVAL;
}
/*
* sanity check in case size_t is smaller then u64
*/
#if BITS_PER_LONG == 4
#define MAX_SIZE_T (1ULL<<(sizeof(size_t)<<3))
if (sizeof(size_t) < sizeof(arg->buf_size)) {
if (arg->buf_size >= MAX_SIZE_T)
return -ETOOBIG;
}
#endif
/*
* compute min buf size. npmds is the maximum number
* of implemented PMD registers.
*/
min_buf_size = sizeof(struct pfm_dfl_smpl_hdr)
+ (sizeof(struct pfm_dfl_smpl_entry) + (npmds*sizeof(u64)));
PFM_DBG("validate ctx_flags=0x%x flags=0x%x npmds=%u "
"min_buf_size=%llu buf_size=%llu\n",
ctx_flags,
arg->buf_flags,
npmds,
(unsigned long long)min_buf_size,
(unsigned long long)arg->buf_size);
/*
* must hold at least the buffer header + one minimally sized entry
*/
if (arg->buf_size < min_buf_size)
return -EINVAL;
return 0;
}
static int pfm_dfl_fmt_get_size(u32 flags, void *data, size_t *size)
{
struct pfm_dfl_smpl_arg *arg = data;
/*
* size has been validated in default_validate
* we can never loose bits from buf_size.
*/
*size = (size_t)arg->buf_size;
return 0;
}
static int pfm_dfl_fmt_init(struct pfm_context *ctx, void *buf, u32 ctx_flags,
u16 npmds, void *data)
{
struct pfm_dfl_smpl_hdr *hdr;
struct pfm_dfl_smpl_arg *arg = data;
hdr = buf;
hdr->hdr_version = PFM_DFL_SMPL_VERSION;
hdr->hdr_buf_size = arg->buf_size;
hdr->hdr_buf_flags = arg->buf_flags;
hdr->hdr_cur_offs = sizeof(*hdr);
hdr->hdr_overflows = 0;
hdr->hdr_count = 0;
hdr->hdr_min_buf_space = sizeof(struct pfm_dfl_smpl_entry) + (npmds*sizeof(u64));
/*
* due to cache aliasing, it may be necessary to flush the cache
* on certain architectures (e.g., MIPS)
*/
pfm_cacheflush(hdr, sizeof(*hdr));
PFM_DBG("buffer=%p buf_size=%llu hdr_size=%zu hdr_version=%u.%u "
"min_space=%llu npmds=%u",
buf,
(unsigned long long)hdr->hdr_buf_size,
sizeof(*hdr),
PFM_VERSION_MAJOR(hdr->hdr_version),
PFM_VERSION_MINOR(hdr->hdr_version),
(unsigned long long)hdr->hdr_min_buf_space,
npmds);
return 0;
}
/*
* called from pfm_overflow_handler() to record a new sample
*
* context is locked, interrupts are disabled (no preemption)
*/
static int pfm_dfl_fmt_handler(struct pfm_context *ctx,
unsigned long ip, u64 tstamp, void *data)
{
struct pfm_dfl_smpl_hdr *hdr;
struct pfm_dfl_smpl_entry *ent;
struct pfm_ovfl_arg *arg;
void *cur, *last;
u64 *e;
size_t entry_size, min_size;
u16 npmds, i;
u16 ovfl_pmd;
void *buf;
hdr = ctx->smpl_addr;
arg = &ctx->ovfl_arg;
buf = hdr;
cur = buf+hdr->hdr_cur_offs;
last = buf+hdr->hdr_buf_size;
ovfl_pmd = arg->ovfl_pmd;
min_size = hdr->hdr_min_buf_space;
/*
* precheck for sanity
*/
if ((last - cur) < min_size)
goto full;
npmds = arg->num_smpl_pmds;
ent = (struct pfm_dfl_smpl_entry *)cur;
entry_size = sizeof(*ent) + (npmds << 3);
/* position for first pmd */
e = (u64 *)(ent+1);
hdr->hdr_count++;
PFM_DBG_ovfl("count=%llu cur=%p last=%p free_bytes=%zu ovfl_pmd=%d "
"npmds=%u",
(unsigned long long)hdr->hdr_count,
cur, last,
(last-cur),
ovfl_pmd,
npmds);
/*
* current = task running at the time of the overflow.
*
* per-task mode:
* - this is usually the task being monitored.
* Under certain conditions, it might be a different task
*
* system-wide:
* - this is not necessarily the task controlling the session
*/
ent->pid = current->pid;
ent->ovfl_pmd = ovfl_pmd;
ent->last_reset_val = arg->pmd_last_reset;
/*
* where did the fault happen (includes slot number)
*/
ent->ip = ip;
ent->tstamp = tstamp;
ent->cpu = smp_processor_id();
ent->set = arg->active_set;
ent->tgid = current->tgid;
/*
* selectively store PMDs in increasing index number
*/
if (npmds) {
u64 *val = arg->smpl_pmds_values;
for (i = 0; i < npmds; i++)
*e++ = *val++;
}
/*
* update position for next entry
*/
hdr->hdr_cur_offs += entry_size;
cur += entry_size;
pfm_cacheflush(hdr, sizeof(*hdr));
pfm_cacheflush(ent, entry_size);
/*
* post check to avoid losing the last sample
*/
if ((last - cur) < min_size)
goto full;
/* reset before returning from interrupt handler */
arg->ovfl_ctrl = PFM_OVFL_CTRL_RESET;
return 0;
full:
PFM_DBG_ovfl("sampling buffer full free=%zu, count=%llu",
last-cur,
(unsigned long long)hdr->hdr_count);
/*
* increment number of buffer overflows.
* important to detect duplicate set of samples.
*/
hdr->hdr_overflows++;
/*
* request notification and masking of monitoring.
* Notification is still subject to the overflowed
* register having the FL_NOTIFY flag set.
*/
arg->ovfl_ctrl = PFM_OVFL_CTRL_NOTIFY | PFM_OVFL_CTRL_MASK;
return -ENOBUFS; /* we are full, sorry */
}
static int pfm_dfl_fmt_restart(struct pfm_context *ctx, u32 *ovfl_ctrl)
{
struct pfm_dfl_smpl_hdr *hdr;
hdr = ctx->smpl_addr;
hdr->hdr_count = 0;
hdr->hdr_cur_offs = sizeof(*hdr);
pfm_cacheflush(hdr, sizeof(*hdr));
*ovfl_ctrl = PFM_OVFL_CTRL_RESET;
return 0;
}
static int pfm_dfl_fmt_exit(void *buf)
{
return 0;
}
static struct pfm_smpl_fmt dfl_fmt = {
.fmt_name = "default",
.fmt_version = 0x10000,
.fmt_arg_size = sizeof(struct pfm_dfl_smpl_arg),
.fmt_validate = pfm_dfl_fmt_validate,
.fmt_getsize = pfm_dfl_fmt_get_size,
.fmt_init = pfm_dfl_fmt_init,
.fmt_handler = pfm_dfl_fmt_handler,
.fmt_restart = pfm_dfl_fmt_restart,
.fmt_exit = pfm_dfl_fmt_exit,
.fmt_flags = PFM_FMT_BUILTIN_FLAG,
.owner = THIS_MODULE
};
static int pfm_dfl_fmt_init_module(void)
{
return pfm_fmt_register(&dfl_fmt);
}
static void pfm_dfl_fmt_cleanup_module(void)
{
pfm_fmt_unregister(&dfl_fmt);
}
module_init(pfm_dfl_fmt_init_module);
module_exit(pfm_dfl_fmt_cleanup_module);